parent
148e0318f1
commit
2c8c6c2d87
15 changed files with 594 additions and 68 deletions
@ -0,0 +1,51 @@ |
|||||||
|
package cc.bnblogs.springdatajpa.config; |
||||||
|
|
||||||
|
import cc.bnblogs.springdatajpa.config.filter.JwtAuthenticationTokenFilter; |
||||||
|
import org.springframework.beans.factory.annotation.Autowired; |
||||||
|
import org.springframework.context.annotation.Bean; |
||||||
|
import org.springframework.context.annotation.Configuration; |
||||||
|
import org.springframework.http.HttpMethod; |
||||||
|
import org.springframework.security.authentication.AuthenticationManager; |
||||||
|
import org.springframework.security.config.annotation.web.builders.HttpSecurity; |
||||||
|
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; |
||||||
|
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; |
||||||
|
import org.springframework.security.config.http.SessionCreationPolicy; |
||||||
|
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; |
||||||
|
import org.springframework.security.crypto.password.PasswordEncoder; |
||||||
|
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; |
||||||
|
|
||||||
|
/** |
||||||
|
* @description: |
||||||
|
* @author: zfp@bnblogs.cc |
||||||
|
* @date: 2023/3/8 17:06 |
||||||
|
*/ |
||||||
|
@Configuration |
||||||
|
@EnableWebSecurity |
||||||
|
public class SecurityConfig extends WebSecurityConfigurerAdapter { |
||||||
|
@Autowired |
||||||
|
private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter; |
||||||
|
|
||||||
|
@Bean |
||||||
|
public PasswordEncoder passwordEncoder() { |
||||||
|
return new BCryptPasswordEncoder(); |
||||||
|
} |
||||||
|
|
||||||
|
@Bean |
||||||
|
@Override |
||||||
|
public AuthenticationManager authenticationManagerBean() throws Exception { |
||||||
|
return super.authenticationManagerBean(); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
protected void configure(HttpSecurity http) throws Exception { |
||||||
|
http.csrf().disable() |
||||||
|
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) |
||||||
|
.and() |
||||||
|
.authorizeRequests() |
||||||
|
.antMatchers("/user/account/token/", "/user/account/register/").permitAll() |
||||||
|
.antMatchers(HttpMethod.OPTIONS).permitAll() |
||||||
|
.anyRequest().authenticated(); |
||||||
|
|
||||||
|
http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,61 @@ |
|||||||
|
package cc.bnblogs.springdatajpa.config.filter; |
||||||
|
|
||||||
|
import cc.bnblogs.springdatajpa.mapper.UserMapper; |
||||||
|
import cc.bnblogs.springdatajpa.pojo.User; |
||||||
|
import cc.bnblogs.springdatajpa.service.impl.security.UserDetailsImpl; |
||||||
|
import cc.bnblogs.springdatajpa.utils.JwtUtil; |
||||||
|
import com.sun.istack.internal.NotNull; |
||||||
|
import io.jsonwebtoken.Claims; |
||||||
|
import org.springframework.beans.factory.annotation.Autowired; |
||||||
|
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; |
||||||
|
import org.springframework.security.core.context.SecurityContextHolder; |
||||||
|
import org.springframework.stereotype.Component; |
||||||
|
import org.springframework.util.StringUtils; |
||||||
|
import org.springframework.web.filter.OncePerRequestFilter; |
||||||
|
|
||||||
|
import javax.servlet.FilterChain; |
||||||
|
import javax.servlet.ServletException; |
||||||
|
import javax.servlet.http.HttpServletRequest; |
||||||
|
import javax.servlet.http.HttpServletResponse; |
||||||
|
import java.io.IOException; |
||||||
|
|
||||||
|
@Component |
||||||
|
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter { |
||||||
|
@Autowired |
||||||
|
private UserMapper userMapper; |
||||||
|
|
||||||
|
@Override |
||||||
|
protected void doFilterInternal(HttpServletRequest request, @NotNull HttpServletResponse response, @NotNull FilterChain filterChain) throws ServletException, IOException { |
||||||
|
String token = request.getHeader("Authorization"); |
||||||
|
|
||||||
|
if (!StringUtils.hasText(token) || !token.startsWith("Bearer ")) { |
||||||
|
filterChain.doFilter(request, response); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
token = token.substring(7); |
||||||
|
|
||||||
|
String userid; |
||||||
|
try { |
||||||
|
Claims claims = JwtUtil.parseJWT(token); |
||||||
|
userid = claims.getSubject(); |
||||||
|
} catch (Exception e) { |
||||||
|
throw new RuntimeException(e); |
||||||
|
} |
||||||
|
// 这里不同的ORM实现可能不同,这里用的是JPA
|
||||||
|
// 就是根据用户id获取user
|
||||||
|
User user = userMapper.findUserById(Integer.parseInt(userid)); |
||||||
|
|
||||||
|
if (user == null) { |
||||||
|
throw new RuntimeException("用户名未登录"); |
||||||
|
} |
||||||
|
|
||||||
|
UserDetailsImpl loginUser = new UserDetailsImpl(user); |
||||||
|
UsernamePasswordAuthenticationToken authenticationToken = |
||||||
|
new UsernamePasswordAuthenticationToken(loginUser, null, null); |
||||||
|
|
||||||
|
SecurityContextHolder.getContext().setAuthentication(authenticationToken); |
||||||
|
|
||||||
|
filterChain.doFilter(request, response); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,30 @@ |
|||||||
|
package cc.bnblogs.springdatajpa.service.impl.security; |
||||||
|
|
||||||
|
import cc.bnblogs.springdatajpa.mapper.UserMapper; |
||||||
|
import cc.bnblogs.springdatajpa.pojo.User; |
||||||
|
import org.springframework.beans.factory.annotation.Autowired; |
||||||
|
import org.springframework.security.core.userdetails.UserDetails; |
||||||
|
import org.springframework.security.core.userdetails.UserDetailsService; |
||||||
|
import org.springframework.security.core.userdetails.UsernameNotFoundException; |
||||||
|
import org.springframework.stereotype.Service; |
||||||
|
|
||||||
|
/** |
||||||
|
* @description: |
||||||
|
* @author: zfp@bnblogs.cc |
||||||
|
* @date: 2023/3/8 17:42 |
||||||
|
*/ |
||||||
|
|
||||||
|
@Service |
||||||
|
public class UserDetailServiceImpl implements UserDetailsService { |
||||||
|
|
||||||
|
@Autowired |
||||||
|
private UserMapper userMapper; |
||||||
|
@Override |
||||||
|
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { |
||||||
|
User user = userMapper.getUserByName(username); |
||||||
|
if (user==null) { |
||||||
|
throw new RuntimeException("用户不存在"); |
||||||
|
} |
||||||
|
return new UserDetailsImpl(user); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,56 @@ |
|||||||
|
package cc.bnblogs.springdatajpa.service.impl.security; |
||||||
|
|
||||||
|
import cc.bnblogs.springdatajpa.pojo.User; |
||||||
|
import lombok.AllArgsConstructor; |
||||||
|
import lombok.Data; |
||||||
|
import org.springframework.security.core.GrantedAuthority; |
||||||
|
import org.springframework.security.core.userdetails.UserDetails; |
||||||
|
|
||||||
|
import java.util.Collection; |
||||||
|
|
||||||
|
/** |
||||||
|
* @description: |
||||||
|
* @author: zfp@bnblogs.cc |
||||||
|
* @date: 2023/3/8 17:51 |
||||||
|
*/ |
||||||
|
@Data |
||||||
|
@AllArgsConstructor |
||||||
|
public class UserDetailsImpl implements UserDetails { |
||||||
|
|
||||||
|
private User user; |
||||||
|
@Override |
||||||
|
public Collection<? extends GrantedAuthority> getAuthorities() { |
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public String getPassword() { |
||||||
|
return user.getPassword(); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public String getUsername() { |
||||||
|
return user.getName(); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public boolean isAccountNonExpired() { |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public boolean isAccountNonLocked() { |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public boolean isCredentialsNonExpired() { |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public boolean isEnabled() { |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,39 @@ |
|||||||
|
package cc.bnblogs.springdatajpa.service.impl.user; |
||||||
|
|
||||||
|
import cc.bnblogs.springdatajpa.pojo.User; |
||||||
|
import cc.bnblogs.springdatajpa.service.impl.security.UserDetailsImpl; |
||||||
|
import cc.bnblogs.springdatajpa.utils.JwtUtil; |
||||||
|
import org.springframework.beans.factory.annotation.Autowired; |
||||||
|
import org.springframework.security.authentication.AuthenticationManager; |
||||||
|
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; |
||||||
|
import org.springframework.security.core.Authentication; |
||||||
|
import org.springframework.stereotype.Service; |
||||||
|
|
||||||
|
import java.util.HashMap; |
||||||
|
import java.util.Map; |
||||||
|
|
||||||
|
/** |
||||||
|
* @description: 根据用户名和密码返回一个jwt_token |
||||||
|
* @author: zfp@bnblogs.cc |
||||||
|
* @date: 2023/3/8 21:34 |
||||||
|
*/ |
||||||
|
@Service |
||||||
|
public class LoginServiceImpl { |
||||||
|
@Autowired |
||||||
|
private AuthenticationManager authenticationManager; |
||||||
|
|
||||||
|
public Map<String, String> getToken(String username, String password) { |
||||||
|
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username, password); |
||||||
|
|
||||||
|
Authentication authenticate = authenticationManager.authenticate(authenticationToken); |
||||||
|
UserDetailsImpl loginUser = (UserDetailsImpl) authenticate.getPrincipal(); |
||||||
|
User user = loginUser.getUser(); |
||||||
|
String jwt = JwtUtil.createJWT(user.getId().toString()); |
||||||
|
|
||||||
|
Map<String, String> map = new HashMap<>(); |
||||||
|
map.put("error_message", "success"); |
||||||
|
map.put("token", jwt); |
||||||
|
|
||||||
|
return map; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,87 @@ |
|||||||
|
package cc.bnblogs.springdatajpa.service.impl.user; |
||||||
|
|
||||||
|
import cc.bnblogs.springdatajpa.mapper.UserMapper; |
||||||
|
import cc.bnblogs.springdatajpa.pojo.User; |
||||||
|
import org.springframework.beans.factory.annotation.Autowired; |
||||||
|
import org.springframework.security.crypto.password.PasswordEncoder; |
||||||
|
import org.springframework.stereotype.Service; |
||||||
|
|
||||||
|
import java.util.HashMap; |
||||||
|
import java.util.Map; |
||||||
|
|
||||||
|
/** |
||||||
|
* @description: 用户注册 |
||||||
|
* @author: zfp@bnblogs.cc |
||||||
|
* @date: 2023/3/8 22:12 |
||||||
|
*/ |
||||||
|
@Service |
||||||
|
public class RegisterServiceImpl { |
||||||
|
|
||||||
|
@Autowired |
||||||
|
private UserMapper userMapper; |
||||||
|
|
||||||
|
@Autowired |
||||||
|
private PasswordEncoder passwordEncoder; |
||||||
|
|
||||||
|
public Map<String, String> register(String username, String password, String confirmedPassword) { |
||||||
|
Map<String, String> map = new HashMap<>(); |
||||||
|
if (username == null) { |
||||||
|
map.put("error_message", "用户名不能为空"); |
||||||
|
return map; |
||||||
|
} |
||||||
|
|
||||||
|
if (password == null || confirmedPassword == null) { |
||||||
|
map.put("error_message", "密码不能为空"); |
||||||
|
return map; |
||||||
|
} |
||||||
|
//删除首尾的空白字符
|
||||||
|
username = username.trim(); |
||||||
|
if (username.length() == 0) { |
||||||
|
map.put("error_message", "用户名不能为空"); |
||||||
|
return map; |
||||||
|
} |
||||||
|
|
||||||
|
if (password.length() == 0 || confirmedPassword.length() == 0) { |
||||||
|
map.put("error_message", "密码不能为空"); |
||||||
|
return map; |
||||||
|
} |
||||||
|
|
||||||
|
if (username.length() > 100) { |
||||||
|
map.put("error_message", "用户名长度不能大于100"); |
||||||
|
return map; |
||||||
|
} |
||||||
|
|
||||||
|
if (password.length() > 100 || confirmedPassword.length() > 100) { |
||||||
|
map.put("error_message", "密码不能大于100"); |
||||||
|
return map; |
||||||
|
} |
||||||
|
|
||||||
|
if (!password.equals(confirmedPassword)) { |
||||||
|
map.put("error_message", "两次输入的密码不一致"); |
||||||
|
return map; |
||||||
|
} |
||||||
|
|
||||||
|
//查询用户名是否重复
|
||||||
|
|
||||||
|
User user = userMapper.getUserByName(username); |
||||||
|
|
||||||
|
if (user != null) { |
||||||
|
map.put("error_message", "用户名已存在"); |
||||||
|
return map; |
||||||
|
} |
||||||
|
|
||||||
|
// 添加一个新用户
|
||||||
|
String encodedPassword = passwordEncoder.encode(password); |
||||||
|
|
||||||
|
User u = new User(); |
||||||
|
u.setName(username); |
||||||
|
u.setAge(18); |
||||||
|
u.setEmail("xxxxx"); |
||||||
|
u.setPassword(encodedPassword); |
||||||
|
userMapper.save(u); |
||||||
|
|
||||||
|
map.put("error_message", "success"); |
||||||
|
return map; |
||||||
|
} |
||||||
|
} |
||||||
|
|
@ -0,0 +1,36 @@ |
|||||||
|
package cc.bnblogs.springdatajpa.service.impl.user; |
||||||
|
|
||||||
|
import cc.bnblogs.springdatajpa.pojo.User; |
||||||
|
import cc.bnblogs.springdatajpa.service.impl.security.UserDetailsImpl; |
||||||
|
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; |
||||||
|
import org.springframework.security.core.context.SecurityContextHolder; |
||||||
|
import org.springframework.stereotype.Service; |
||||||
|
|
||||||
|
import java.util.HashMap; |
||||||
|
import java.util.Map; |
||||||
|
|
||||||
|
/** |
||||||
|
* @description: 用户信息 |
||||||
|
* @author: zfp@bnblogs.cc |
||||||
|
* @date: 2023/3/8 22:12 |
||||||
|
*/ |
||||||
|
@Service |
||||||
|
public class UserInfoServiceImpl { |
||||||
|
|
||||||
|
/** |
||||||
|
* 根据token返回用户信息 |
||||||
|
* @return map存储的信息 |
||||||
|
*/ |
||||||
|
public Map<String, String> getInfo() { |
||||||
|
UsernamePasswordAuthenticationToken authentication = (UsernamePasswordAuthenticationToken) SecurityContextHolder.getContext().getAuthentication(); |
||||||
|
|
||||||
|
UserDetailsImpl loginUser = (UserDetailsImpl) authentication.getPrincipal(); |
||||||
|
User user = loginUser.getUser(); |
||||||
|
|
||||||
|
Map<String, String> map = new HashMap<>(); |
||||||
|
map.put("error_message", "success"); |
||||||
|
map.put("id", user.getId().toString()); |
||||||
|
map.put("name", user.getName()); |
||||||
|
return map; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,68 @@ |
|||||||
|
package cc.bnblogs.springdatajpa.utils; |
||||||
|
|
||||||
|
/** |
||||||
|
* @description: |
||||||
|
* @author: zfp@bnblogs.cc |
||||||
|
* @date: 2023/3/8 20:33 |
||||||
|
*/ |
||||||
|
|
||||||
|
import io.jsonwebtoken.Claims; |
||||||
|
import io.jsonwebtoken.JwtBuilder; |
||||||
|
import io.jsonwebtoken.Jwts; |
||||||
|
import io.jsonwebtoken.SignatureAlgorithm; |
||||||
|
import org.springframework.stereotype.Component; |
||||||
|
|
||||||
|
import javax.crypto.SecretKey; |
||||||
|
import javax.crypto.spec.SecretKeySpec; |
||||||
|
import java.util.Base64; |
||||||
|
import java.util.Date; |
||||||
|
import java.util.UUID; |
||||||
|
|
||||||
|
@Component |
||||||
|
public class JwtUtil { |
||||||
|
public static final long JWT_TTL = 60 * 60 * 1000L * 24 * 14; // 有效期14天
|
||||||
|
public static final String JWT_KEY = "SDFGjhdsfalshdfHFdsjkdsfds121232131afasdfac"; |
||||||
|
|
||||||
|
public static String getUUID() { |
||||||
|
return UUID.randomUUID().toString().replaceAll("-", ""); |
||||||
|
} |
||||||
|
|
||||||
|
public static String createJWT(String subject) { |
||||||
|
JwtBuilder builder = getJwtBuilder(subject, null, getUUID()); |
||||||
|
return builder.compact(); |
||||||
|
} |
||||||
|
|
||||||
|
private static JwtBuilder getJwtBuilder(String subject, Long ttlMillis, String uuid) { |
||||||
|
SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256; |
||||||
|
SecretKey secretKey = generalKey(); |
||||||
|
long nowMillis = System.currentTimeMillis(); |
||||||
|
Date now = new Date(nowMillis); |
||||||
|
if (ttlMillis == null) { |
||||||
|
ttlMillis = JwtUtil.JWT_TTL; |
||||||
|
} |
||||||
|
|
||||||
|
long expMillis = nowMillis + ttlMillis; |
||||||
|
Date expDate = new Date(expMillis); |
||||||
|
return Jwts.builder() |
||||||
|
.setId(uuid) |
||||||
|
.setSubject(subject) |
||||||
|
.setIssuer("sg") |
||||||
|
.setIssuedAt(now) |
||||||
|
.signWith(signatureAlgorithm, secretKey) |
||||||
|
.setExpiration(expDate); |
||||||
|
} |
||||||
|
|
||||||
|
public static SecretKey generalKey() { |
||||||
|
byte[] encodeKey = Base64.getDecoder().decode(JwtUtil.JWT_KEY); |
||||||
|
return new SecretKeySpec(encodeKey, 0, encodeKey.length, "HmacSHA256"); |
||||||
|
} |
||||||
|
|
||||||
|
public static Claims parseJWT(String jwt) throws Exception { |
||||||
|
SecretKey secretKey = generalKey(); |
||||||
|
return Jwts.parserBuilder() |
||||||
|
.setSigningKey(secretKey) |
||||||
|
.build() |
||||||
|
.parseClaimsJws(jwt) |
||||||
|
.getBody(); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,35 @@ |
|||||||
|
package cc.bnblogs.springdatajpa.config; |
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test; |
||||||
|
import org.springframework.boot.test.context.SpringBootTest; |
||||||
|
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; |
||||||
|
import org.springframework.security.crypto.password.PasswordEncoder; |
||||||
|
import sun.security.util.Password; |
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*; |
||||||
|
|
||||||
|
/** |
||||||
|
* @description: |
||||||
|
* @author: zfp@bnblogs.cc |
||||||
|
* @date: 2023/3/8 17:13 |
||||||
|
*/ |
||||||
|
@SpringBootTest |
||||||
|
class SecurityConfigTest { |
||||||
|
|
||||||
|
@Test |
||||||
|
void passwordEncoder() { |
||||||
|
PasswordEncoder passwordEncoder = new BCryptPasswordEncoder(); |
||||||
|
// 对明文加密
|
||||||
|
System.out.println(passwordEncoder.encode("123456")); |
||||||
|
System.out.println(passwordEncoder.encode("123456")); |
||||||
|
System.out.println(passwordEncoder.encode("123456")); |
||||||
|
System.out.println(passwordEncoder.encode("123456")); |
||||||
|
System.out.println(passwordEncoder.encode("123456")); |
||||||
|
|
||||||
|
// 明文和密文匹配
|
||||||
|
// true
|
||||||
|
System.out.println(passwordEncoder.matches("123456","$2a$10$8ew9orusgSjo4fRBODHgBug6aOkJwaq3ikAOVRAtwPkCqVMVLJI.O")); |
||||||
|
// false
|
||||||
|
System.out.println(passwordEncoder.matches("1234567","$2a$10$8ew9orusgSjo4fRBODHgBug6aOkJwaq3ikAOVRAtwPkCqVMVLJI.O")); |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue