前面我们实现了给客户端下发token,虽然客户端拿到了token,但我们还没处理客户端下一次携带token请求时如何验证,我们想要实现拿得到token之后,只需要验证token,不需要用户再携带用户名和密码了。
1. 禁用 UsernamePasswordAuthenticationFilter
由上图可以看出,当用户访问了相关路径之后,Spring内部会自己创建一个UsernamePasswordAuthenticationFilter
给我们,现在我们不想通过username和password进行认证了,我们想通过token进行验证,那就需要把内部创建的UsernamePasswordAuthenticationFilter
给禁用了。
添加formLogin().disable到过滤链中:
为什么添加了这行代码就禁用了那个东西了呢?这是因为如果不禁用formLogin,它就会创建一个UsernamePasswordAuthenticationFilter
2. 创建自己的认证过滤器
禁用了UsernamePasswordAuthenticationFilter
之后,我们就需要创建自己的过滤器,并把它添加到过滤链中,我们的验证过程就是在我们自己编写的过滤器中进行。
@Slf4j
@Component
public class JWTAuthenticationFilter extends OncePerRequestFilter {
@Resource
RedisTemplate redisTemplate;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
//如果是要登录,放行到登录页面
if("/login2".equals(request.getRequestURI())){
filterChain.doFilter(request, response);
return;
}
//不是进行登录,说明前端会携带token
String token = request.getHeader("Authorization");
if(StrUtil.isEmpty(token))
throw new RuntimeException("token 为空");
//token类型:postman前端传来的token会在前面加一个类型和一个空格
if(!StrUtil.startWith(token, "Bearer"))
throw new RuntimeException("token 类型错误");
//1. 验证token
token = token.substring(7);
JWTSigner jwtSigner = JWTSignerUtil.hs512("testttttt".getBytes(StandardCharsets.UTF_8));
if (!JWTUtil.verify(token, jwtSigner)) {
throw new RuntimeException("token 无效");
}
//2. 从token中取出用户名
String username = (String) JWTUtil.parseToken(token).getPayload().getClaim("username");
//3. 根据用户名从redis中取出password(登录时可以将UserDetails存储到redis)
String password = (String) redisTemplate.opsForHash().get("user-details", username);
if(password == null) throw new RemoteException("token 过期");
log.info("--------> get password= {}", password);
//4. new一个UserDetails,这个UserDetails会被存储到SecurityContextHolder中
MyUserDetails userDetails = new MyUserDetails(new MyUser(1, username, password));
//5. 将UserDetails存储到SecurityContextHolder中
SecurityContextHolder.getContext().setAuthentication(UsernamePasswordAuthenticationToken.authenticated(userDetails
, null, null));
filterChain.doFilter(request, response);
}
}
注意,代码中的第5步决定是否放行请求,只有将UserDetails
存储到SecurityContextHolder
中,过滤器才会放行。
将编写好的过滤器添加到过滤器链中:
.addFilterAt(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
3. 测试
可以看到,用户首次登录之后,后端成功返回一个token
接下来携带token请求另一个页面。
成功返回结果:
EX++++++++++++++
经过测验发现,放行和不放行完全取决于这行代码:
SecurityContextHolder.getContext().setAuthentication(UsernamePasswordAuthenticationToken.authenticated(userDetails, null, null));
我甚至可以直接写成
SecurityContextHolder.getContext().setAuthentication(new UsernamePasswordAuthenticationToken(userDetails, null, null));
因为UsernamePasswordAuthenticationToken.authenticated(userDetails, null, null)
这个函数在底层就是会直接new一个UsernamePasswordAuthenticationToken