配置类:
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
// 已登录的用户的token验证
@Autowired
private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;
// 登录失败
@Autowired
private AuthenticationEntryPointImpl authenticationEntryPoint;
// 认证失败
@Autowired
private AccessDeniedHandlerImpl accessDeniedHandler;
// 前端用户登录的用户信息验证
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
// 密码加密解密
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
// super.configure(http);
http
// 关闭csrf
.csrf().disable()
// 不通过session获取SecurityContext
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
// 放行
.antMatchers("/login").anonymous()
// 这个接口需要认证才能访问
.antMatchers("/link/getAllLink").authenticated()
.antMatchers("/logout").authenticated()
// 其余的接口也放行
.anyRequest().permitAll();
// 关闭security的默认关闭接口
http.logout().disable();
// 配置过滤器
http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
// 配置异常处理器
http.exceptionHandling()
.authenticationEntryPoint(authenticationEntryPoint)
.accessDeniedHandler(accessDeniedHandler);
// 允许跨域
http.cors();
}
}
用户登录流程:
@Service
public class BlogLoginServiceImpl implements BlogLoginService {
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private RedisTemplate<Object, Object> redisTemplate;
@Override
public ResponseResult login(User user) {
// 放入用户名和密码
UsernamePasswordAuthenticationToken authentication =
new UsernamePasswordAuthenticationToken(user.getUserName(), user.getPassword());
// authenticate 会调用 UserDetailsServiceImpl 登录判断
Authentication authenticate = authenticationManager.authenticate(authentication);
// 判断是否认证通过
if (authenticate == null) throw new RuntimeException("用户名或密码错误");
// 获取userId 生成token
LoginUser loginUser = (LoginUser) authenticate.getPrincipal();
Long userId = loginUser.getUser().getId();
String token = JwtUtil.createJWT(String.valueOf(userId));
// 用户信息存入redis
redisTemplate.opsForValue().set("bloglogin:" + userId, loginUser,1, TimeUnit.DAYS);
// token和userinfo封装 返回
BlogUserLoginVo vo = new BlogUserLoginVo();
vo.setToken(token);
vo.setUserInfo(BeanCopyUtils.copyBean(loginUser.getUser(), UserInfoVo.class));
return ResponseResult.success(vo);
}
}
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired
private UserMapper userMapper;
// 登录判断
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
if (StringUtils.isEmpty(username)) throw new RuntimeException("用户名不能为空");
// 条件
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(User::getUserName, username);
// 查询
User user = userMapper.selectOne(wrapper);
if (user == null) throw new RuntimeException("用户不存在");
return new LoginUser(user);
}
}
@Data
@NoArgsConstructor
@AllArgsConstructor
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;
}
}
用户登陆后,访问权限接口,token认证拦截器:
@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
// 用户访问需要登录的接口,验证token
@Autowired
private ObjectMapper om;
@Autowired
private RedisTemplate redisTemplate;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
// 获取token
String token = request.getHeader("token");
if (!StringUtils.hasText(token)) {
// 后面登录的security还会进行校验
filterChain.doFilter(request, response);
return;
}
// 解析token 获取userId
Claims claims = null;
try {
claims = JwtUtil.parseJWT(token);
} catch (Exception e) {
e.printStackTrace();
// 过期
// 非法token
// 响应前端重新登录
ResponseResult result = new ResponseResult(401, "用户未登录", null);
WebUtils.renderString(response, om.writeValueAsString(result));
return;
}
String userId = claims.getSubject();
// 从redis中获取用户信息
LoginUser loginUser = (LoginUser) redisTemplate.opsForValue().get("bloglogin:" + userId);
if (loginUser == null) {
// redis里没有
ResponseResult result = new ResponseResult(402, "登录已过期", null);
WebUtils.renderString(response, om.writeValueAsString(result));
return;
}
// 存入SecurityContextHolder
// 未认证的是2个参数,已认证的是3个参数
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(loginUser, null, null);
SecurityContextHolder.getContext().setAuthentication(authentication);
// 放行
filterChain.doFilter(request, response);
return;
}
}
以上完成后,拦截后不会返回前端json结果
自定义认证失败处理:
@Component
public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint {
@Autowired
private ObjectMapper om;
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
// 登录失败失败响应给前端
authException.printStackTrace();
// BadCredentialsException 用户名或密码错误
// InsufficientAuthenticationException 需要登录
ResponseResult result = null;
if (authException instanceof BadCredentialsException)
result = new ResponseResult(401, authException.getMessage(), null);
else if (authException instanceof InsufficientAuthenticationException)
result = new ResponseResult(402, "用户未登录", null);
else
result = new ResponseResult(403, authException.getMessage(), null);
WebUtils.renderString(response, om.writeValueAsString(result));
}
}
下面这个是鉴权的响应,目前还用不到:
@Component
public class AccessDeniedHandlerImpl implements AccessDeniedHandler {
@Autowired
private ObjectMapper om;
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
// 鉴权失败的响应
accessDeniedException.printStackTrace();
ResponseResult result = new ResponseResult(401, "无权限操作", null);
WebUtils.renderString(response, om.writeValueAsString(result));
}
}
统一异常处理:
@Data
@NoArgsConstructor
public class SystemException extends RuntimeException {
private Integer code;
private String msg;
public SystemException(Integer code, String msg) {
super(msg);
this.code = code;
this.msg = msg;
}
}
// 捕获Controller的异常
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
// 可控的异常
@ExceptionHandler(SystemException.class)
public ResponseResult SystemExceptionHandler(SystemException e) {
// 打印日志
log.error("出现了异常! {}", e);
// 响应前端
return new ResponseResult(e.getCode(), e.getMsg(), null);
}
// 不可控的异常
@ExceptionHandler(Exception.class)
public ResponseResult exceptionHandler(Exception e) {
// 打印日志
log.error("出现了异常! {}", e);
// 响应前端
return new ResponseResult(500, e.getMessage(), null);
}
}
参考视频: