首先我们要先明白springsecurity过滤器链的大致执行流程:
即当用户发起一个请求,那么他将进入 Security 过滤器链。
LogoutFilter - 登出过滤器
logoutSuccessHandler - 登出成功之后的操作类
UsernamePasswordAuthenticationFilter - from提交用户名密码登录认证过滤器
AuthenticationFailureHandler - 登录失败操作类
AuthenticationSuccessHandler - 登录成功操作类
BasicAuthenticationFilter - Basic身份认证过滤器
SecurityContextHolder - 安全上下文静态工具类
AuthenticationEntryPoint - 认证失败入口
ExceptionTranslationFilter - 异常处理过滤器
AccessDeniedHandler - 权限不足操作类
FilterSecurityInterceptor - 权限判断拦截器、出口
了解大致流程后,我们第一步自然是导入他们的jar包。
@Configuration public class KaptchaConfig { @Bean public DefaultKaptcha producer() { Properties properties = new Properties(); properties.put("kaptcha.border", "no"); properties.put("kaptcha.textproducer.font.color", "black"); properties.put("kaptcha.textproducer.char.space", "4"); properties.put("kaptcha.image.height", "40"); properties.put("kaptcha.image.width", "120"); properties.put("kaptcha.textproducer.font.size", "30"); Config config = new Config(properties); DefaultKaptcha defaultKaptcha = new DefaultKaptcha(); defaultKaptcha.setConfig(config); return defaultKaptcha; } }
@Slf4j @RestController public class AuthController extends BaseController{ @Autowired private Producer producer; /** * 图片验证码 */ @GetMapping("/captcha") public Result captcha(HttpServletRequest request, HttpServletResponse response) throws IOException { String code = producer.createText(); String key = UUID.randomUUID().toString(); BufferedImage image = producer.createImage(code); ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); ImageIO.write(image, "jpg", outputStream); BASE64Encoder encoder = new BASE64Encoder(); String str = "data:image/jpeg;base64,"; String base64Img = str + encoder.encode(outputStream.toByteArray()); // 存储到redis中 redisUtil.hset(Const.captcha_KEY, key, code, 120); log.info("验证码 -- {} - {}", key, code); return Result.succ( MapUtil.builder() .put("token", key) .put("base64Img", base64Img) .build() ); } }
由于springsecurity的配置,我们需要在原先的登录过滤器之前设置一个前置过滤器先验证验证码是否正确:
/** * 图片验证码校验过滤器,在登录过滤器前 */ @Slf4j @Component public class CaptchaFilter extends OncePerRequestFilter { private final String loginUrl = "/login"; @Autowired RedisUtil redisUtil; @Autowired LoginFailureHandler loginFailureHandler; @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { String url = request.getRequestURI(); if (loginUrl.equals(url) && request.getMethod().equals("POST")) { log.info("获取到login链接,正在校验验证码 -- " + url); try { validate(request); } catch (CaptchaException e) { log.info(e.getMessage()); // 交给登录失败处理器处理 loginFailureHandler.onAuthenticationFailure(request, response, e); } } filterChain.doFilter(request, response); } private void validate(HttpServletRequest request) { String code = request.getParameter("code"); String token = request.getParameter("token"); if (StringUtils.isBlank(code) || StringUtils.isBlank(token)) { throw new CaptchaException("验证码不能为空"); } if(!code.equals(redisUtil.hget(Const.captcha_KEY, token))) { throw new CaptchaException("验证码不正确"); } // 一次性使用 redisUtil.hdel(Const.captcha_KEY, token); } }
@Component public class LoginFailureHandler implements AuthenticationFailureHandler { @Override public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException { response.setContentType("application/json;charset=UTF-8"); ServletOutputStream outputStream = response.getOutputStream(); outputStream.write(JSONUtil.toJsonStr(new ApiResponse<>().setReMsg(exception.getMessage())).getBytes("UTF-8")); outputStream.flush(); outputStream.close(); } }
@Component public class LoginSuccessHandler implements AuthenticationSuccessHandler { @Autowired JwtUtils jwtUtils; @Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { response.setContentType("application/json;charset=UTF-8"); ServletOutputStream outputStream = response.getOutputStream(); // 生成jwt,并放置到请求头中 String jwt = jwtUtils.generateToken(authentication.getName()); response.setHeader(jwtUtils.getHeader(), jwt); outputStream.write(JSONUtil.toJsonStr(new ApiResponse<>().setReMsg("登录成功")).getBytes("UTF-8")); outputStream.flush(); outputStream.close(); } }
@Data @Component @ConfigurationProperties(prefix = "matrix.jwt") public class JwtUtils { private long expire; private String secret; private String header; // 生成jwt public String generateToken(String username) { Date nowDate = new Date(); Date expireDate = new Date(nowDate.getTime() + 1000 * expire); return Jwts.builder() .setHeaderParam("typ", "JWT") .setSubject(username) .setIssuedAt(nowDate) .setExpiration(expireDate)// 7天過期 .signWith(SignatureAlgorithm.HS512, secret) .compact(); } // 解析jwt public Claims getClaimByToken(String jwt) { try { return Jwts.parser() .setSigningKey(secret) .parseClaimsJws(jwt) .getBody(); } catch (Exception e) { return null; } } // jwt是否过期 public boolean isTokenExpired(Claims claims) { return claims.getExpiration().before(new Date()); } }
matrix: jwt: header: authorization expire: 604800 secret: ji8n3439n439n43ld9ne9343fdfer49h
public class JwtAuthenticationFilter extends BasicAuthenticationFilter { @Autowired UserService userService; @Autowired JwtUtils jwtUtils; @Autowired UserDetailServiceImpl userDetailService; public JwtAuthenticationFilter(AuthenticationManager authenticationManager) { super(authenticationManager); } @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException { String jwt = request.getHeader(jwtUtils.getHeader()); if (StrUtil.isBlankOrUndefined(jwt)) { // 没有jwt直接放行 chain.doFilter(request, response); return; } System.out.println(12312); Claims claim = jwtUtils.getClaimByToken(jwt); if (claim == null) { throw new JwtException("token 异常"); } if (jwtUtils.isTokenExpired(claim)) { // 会注入进验证失败的异常中 throw new JwtException("token已过期"); } String username = claim.getSubject(); // 获取用户的权限等信息 User user = userService.getByUsername(username); // 将token信息存放 UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(username, null, userDetailService.getUserAuthority(user.getId())); SecurityContextHolder.getContext().setAuthentication(token); // 放行 chain.doFilter(request, response); } }
@Component public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint { @Override public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException { response.setContentType("application/json;charset=UTF-8"); response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); ServletOutputStream outputStream = response.getOutputStream(); outputStream.write(JSONUtil.toJsonStr(new ApiResponse<>().setReMsg("请先登录")).getBytes("UTF-8")); outputStream.flush(); outputStream.close(); } }
@Bean BCryptPasswordEncoder bCryptPasswordEncoder() { return new BCryptPasswordEncoder(); }