登录校验
更多SpringSecurity见
:https://hedsay.github.io/rui-notes/pages/e3c9bf/#%E7%99%BB%E5%BD%95%E6%A0%A1%E9%AA%8C%E6%B5%81%E7%A8%8B
# Security配置类
主要管理了密码解析Bean和AuthenticationManager的Bean;
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
//关闭csrf
.csrf().disable()
//不通过Session获取SecurityContext
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
// 对于登录接口 允许匿名访问
.antMatchers("/login").anonymous()
// 除上面外的所有请求全部不需要认证即可访问
.anyRequest().permitAll();
http.logout().disable();
//允许跨域
http.cors();
}
@Override
@Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# Security工具类
获取LoginUser和Authentication对象
public class SecurityUtils {
public static LoginUser getLoginUser() {
return (LoginUser) getAuthentication().getPrincipal();
}
public static Authentication getAuthentication() {
return SecurityContextHolder.getContext().getAuthentication();
}
public static Boolean isAdmin() {
Long id = getLoginUser().getUser().getId();
return id != null && id.equals(1L);
}
public static Long getUserId() {
return getLoginUser().getUser().getId();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 用户注册
@Service("userService")
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
@Autowired
private PasswordEncoder passwordEncoder;
@Override
public ResponseResult register(User user) {
//对数据进行非空判断
if(!StringUtils.hasText(user.getUserName())){
throw new SystemException(AppHttpCodeEnum.USERNAME_NOT_NULL);
}
if(!StringUtils.hasText(user.getPassword())){
throw new SystemException(AppHttpCodeEnum.PASSWORD_NOT_NULL);
}
if(!StringUtils.hasText(user.getEmail())){
throw new SystemException(AppHttpCodeEnum.EMAIL_NOT_NULL);
}
if(!StringUtils.hasText(user.getNickName())){
throw new SystemException(AppHttpCodeEnum.NICKNAME_NOT_NULL);
}
//对数据进行是否存在的判断
if(userNameExist(user.getUserName())){
throw new SystemException(AppHttpCodeEnum.USERNAME_EXIST);
}
if(nickNameExist(user.getNickName())){
throw new SystemException(AppHttpCodeEnum.NICKNAME_EXIST);
}
//对密码进行加密
String encodePassword = passwordEncoder.encode(user.getPassword());
user.setPassword(encodePassword);
//存入数据库
save(user);
return ResponseResult.okResult();
}
private boolean nickNameExist(String nickName) {
LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(User::getNickName,nickName);
return count(queryWrapper)>0;
}
private boolean userNameExist(String userName) {
LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(User::getNickName,userName);
return count(queryWrapper)>0;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
测试数据:
{
"email":"1141955640@qq.com",
"nickName":"Nreal",
"password":"1234",
"userName":"Nreal"
}
1
2
3
4
5
6
2
3
4
5
6
# 登录
自定义登录接口
- 调用ProviderManager的方法进行认证 如果认证通过生成jwt;
- 把用户信息存入redis中;
自定义UserDetailsService
- 在这个实现类中去查询数据库
# 封装UserDetails
SpringSecurity会将用户的权限信息封装进UserDetails对象
@Data
@AllArgsConstructor
@NoArgsConstructor
public class LoginUser 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.getUserName();
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
UserDetailsService中loadUserByUsername方法能验证UserDetails中的密码与Authentication的密码是否正确,正确把UserDetails中信息设置到Authentication对象中:
@Service
public class UserDetailServiceImpl implements UserDetailsService {
@Autowired
private UserMapper userMapper;
@Override
public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {
LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(User::getUserName,userName);
User user = userMapper.selectOne(queryWrapper);
if(Objects.isNull(user)){
throw new RuntimeException("用户不存在");
}
return new LoginUser(user);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 封装Vo
@Data
@Accessors(chain = true)
public class UserInfoVo {
private Long id;
private String nickName;
private String avatar;
private String sex;
private String email;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
@Data
@NoArgsConstructor
@AllArgsConstructor
public class BlogUserLoginVo {
private String token;
private UserInfoVo userInfo;
}
1
2
3
4
5
6
7
2
3
4
5
6
7
根据user的用户名密码生成token
AuthenticationManager的authenticate方法认证返回Authentication对象,调用其getPrincipal拿到认证过的对象UserDetails;
从UserDetails对象拿到userId生成jwt,存入redis;
封装BlogUserLoginVo;
@Service
public class BlogLoginServiceImpl implements BlogLoginService {
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private RedisCache redisCache;
@Override
public ResponseResult login(User user) {
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(user.getUserName(),user.getPassword());
Authentication authenticate = authenticationManager.authenticate(authenticationToken);
if(Objects.isNull(authenticate)){
throw new RuntimeException("用户名密码错误");
}
LoginUser loginUser = (LoginUser)authenticate.getPrincipal();
String userId = loginUser.getUser().getId().toString();
String jwt = JwtUtil.createJWT(userId);
redisCache.setCacheObject("blogLogin:"+userId,loginUser);
UserInfoVo userInfoVo = BeanCopyUtils.copyBean(loginUser.getUser(), UserInfoVo.class);
BlogUserLoginVo blogUserLoginVo = new BlogUserLoginVo(jwt, userInfoVo);
return ResponseResult.okResult(blogUserLoginVo);
}
@Override
public ResponseResult logout() {
return null;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# 接口
@Api(tags = "登录接口")
@RestController
public class BlogLoginController {
@Autowired
private BlogLoginService blogLoginService;
@PostMapping("/login")
@ApiOperation(value = "登录")
public ResponseResult login(@RequestBody User user){
if(!StringUtils.hasText(user.getUserName())){
throw new SystemException(AppHttpCodeEnum.REQUIRE_USERNAME);
}
return blogLoginService.login(user);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 校验
在请求头中获取 token ,解析出userId,从redis中获取 LoginUser对象,存入ContextHolder中;
@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
@Autowired
private RedisCache redisCache;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
String token = request.getHeader("token");
if(!StringUtils.hasText(token)) {
// 不需要token,直接放行
filterChain.doFilter(request,response);
return ;
}
// 解析token获取userId
Claims claims = null;
try {
claims = JwtUtil.parseJWT(token);
} catch (Exception e) {
e.printStackTrace();
// token超时
ResponseResult responseResult = ResponseResult.errorResult(AppHttpCodeEnum.NEED_LOGIN);
WebUtils.renderString(response, JSON.toJSONString(responseResult));
return ;
}
String userId = claims.getSubject();
// redis获取用户信息
LoginUser loginUser = redisCache.getCacheObject("blogLogin:"+userId);
// 无缓存重新登录
if(Objects.isNull(loginUser)){
ResponseResult responseResult = ResponseResult.errorResult(AppHttpCodeEnum.NEED_LOGIN);
WebUtils.renderString(response, JSON.toJSONString(responseResult));
return ;
}
// 存入ContextHolder
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUser, null, null);
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
filterChain.doFilter(request,response);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
在SecurityConfig中添加此过滤器:
@Autowired
private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;
//把jwtAuthenticationTokenFilter添加到SpringSecurity的过滤器链中
http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
1
2
3
4
5
6
2
3
4
5
6
# 认证授权失败处理
AuthenticationEntryPoint 认证失败处理器
@Component public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint { @Override public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException { authException.printStackTrace(); //InsufficientAuthenticationException //BadCredentialsException ResponseResult result = null; if(authException instanceof BadCredentialsException){ result = ResponseResult.errorResult(AppHttpCodeEnum.LOGIN_ERROR.getCode(),authException.getMessage()); }else if(authException instanceof InsufficientAuthenticationException){ result = ResponseResult.errorResult(AppHttpCodeEnum.NEED_LOGIN); }else{ result = ResponseResult.errorResult(AppHttpCodeEnum.SYSTEM_ERROR.getCode(),"认证或授权失败"); } //响应给前端 WebUtils.renderString(response, JSON.toJSONString(result)); } }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19AccessDeniedHandler 授权失败处理器
@Component public class AccessDeniedHandlerImpl implements AccessDeniedHandler { @Override public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException { accessDeniedException.printStackTrace(); ResponseResult result = ResponseResult.errorResult(AppHttpCodeEnum.NO_OPERATOR_AUTH); //响应给前端 WebUtils.renderString(response, JSON.toJSONString(result)); } }
1
2
3
4
5
6
7
8
9
10
SecurityConfig中添加此两个处理器:
@Autowired
AuthenticationEntryPoint authenticationEntryPoint;
@Autowired
AccessDeniedHandler accessDeniedHandler;
http.exceptionHandling()
.authenticationEntryPoint(authenticationEntryPoint)
.accessDeniedHandler(accessDeniedHandler);
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
# 退出登录
删除 redis中缓存;
@Override
public ResponseResult logout() {
// 解析token
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
LoginUser loginUser = (LoginUser) authentication.getPrincipal();
Long userId = loginUser.getUser().getId();
// 删除redis
redisCache.deleteObject("blogLogin:"+userId);
return ResponseResult.okResult();
}
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10