实现步骤
1.实现JWT过滤器
在首次登录成功后,LoginSuccessHandler将生成JWT,并返回给前端。在之后的所有请求中(包括再次登录请求),都会携带此JWT信息。我们需要写一个JWT过滤器JwtAuthenticationFilter,当前端发来的请求有JWT信息时,该过滤器将检验JWT是否正确以及是否过期,若检验成功,则获取JWT中的用户名信息,检索数据库获得用户实体类,并将用户信息告知Spring Security,后续我们就能调用security的接口获取到当前登录的用户信息。
若前端发的请求不含JWT,我们也不能拦截该请求,因为一般的项目都是允许匿名访问的,有的接口允许不登录就能访问,没有JWT也放行是安全的,因为我们可以通过Spring Security进行权限管理,设置一些接口需要权限才能访问,不允许匿名访问
public class JwtAuthenticationFilter extends BasicAuthenticationFilter {
@Autowired
JwtUtils jwtUtils;
@Autowired
UserDetailServiceImpl userDetailService;
@Autowired
SysUserService sysUserService;
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());
// 这里如果没有jwt,继续往后走,因为后面还有鉴权管理器等去判断是否拥有身份凭证,所以是可以放行的
// 没有jwt相当于匿名访问,若有一些接口是需要权限的,则不能访问这些接口
if (StrUtil.isBlankOrUndefined(jwt)) {
chain.doFilter(request, response);
return;
}
Claims claim = jwtUtils.getClaimsByToken(jwt);
if (claim == null) {
throw new JwtException("token 异常");
}
if (jwtUtils.isTokenExpired(claim)) {
throw new JwtException("token 已过期");
}
String username = claim.getSubject();
// 获取用户的权限等信息
SysUser sysUser = sysUserService.getByUsername(username);
// 构建UsernamePasswordAuthenticationToken,这里密码为null,是因为提供了正确的JWT,实现自动登录
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(username, null, userDetailService.getUserAuthority(sysUser.getId()));
SecurityContextHolder.getContext().setAuthentication(token);
chain.doFilter(request, response);
}
}
当前端通过axios发送的hears头带有token令牌,进行验证token的有效性。
若JWT验证成功,我们构建了一个UsernamePasswordAuthenticationToken对象,用于保存用户信息,之后将该对象交给SecurityContextHolder,set进它的context中,这样后续我们就能通过调用SecurityContextHolder.getContext().getAuthentication().getPrincipal()等方法获取到当前登录的用户信息了。
2.JWT异常处理器
JWT认证失败处理器JwtAuthenticationEntryPoint
当BasicAuthenticationFilter认证失败的时候会进入AuthenticationEntryPoint,我们定义JWT认证失败处理器JwtAuthenticationEntryPoint,使其实现AuthenticationEntryPoint接口,该接口只有一个commence方法,表示认证失败的处理,我们重写该方法,向前端返回错误信息,不论是什么原因,JWT认证失败,我们就要求重新登录,所以返回的错误信息为请先登录
@Component
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
httpServletResponse.setContentType("application/json;charset=UTF-8");
httpServletResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
ServletOutputStream outputStream = httpServletResponse.getOutputStream();
Result result = Result.fail("请先登录");
outputStream.write(JSONUtil.toJsonStr(result).getBytes(StandardCharsets.UTF_8));
outputStream.flush();
outputStream.close();
}
}