前后分离项目自定义token认证
2022年12月22日大约 2 分钟
注意
网上很难精准找到一个前后端分离项目自定义Token认证的教程,找了很久终于找到,特此记录
1、Security配置注意事项
1. 我在很多教程中都看到他们有讲解自定义登录页面,但是我想说的是,都21世纪了,早都前后分离了,
所以在Security配置中不要配置formLogin了,前后分离项目会直接在Controller自定义登录逻辑,
一旦配置这个万一Security会自动生成表单登录那几个过滤器。这里重点强调一下,前后分离项目和前
后不分离在Security中是大大的不一样!!大家学习的时候关键词检索要记得加上前后分离!!
2. 配置自定义过滤器,自定义过滤器用来处理请求,识别请求中是否有我们自定义的合法token,当然合不合法就要在过滤器的doFilter中自己写了。
@Bean
@Order(2)
public SecurityFilterChain webSiteSecurityFilterChain(HttpSecurity http) throws Exception {
return http
.addFilterBefore(tokenAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)
.authorizeRequests()
.mvcMatchers("/login","/login/phone")
.permitAll()
.anyRequest()//剩下所有的请求
.authenticated() // 所有请求都必须要认证才可以访问
.and()
// 禁用csrf
.csrf()
.disable()
// 启用表单登录
// .formLogin()
// .permitAll()
// .and()
// 异常处理
.exceptionHandling()
.authenticationEntryPoint(customAuthenticationEntryPoint)
.and()
// .authenticationProvider(customMobileAuthenticationProvider)
// .authenticationProvider(new DaoAuthenticationProvider())
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS) //禁止生成session,也不会向客户端返回session
.and()
.build();
}
~~~
## 2、自定义登录逻辑
~~~java
//Controller
@PostMapping
public ResponseEntity login(@RequestBody SysUser sysUser){
return loginService.login(sysUser);
}
//Service
@Override
public ResponseEntity login(SysUser sysUser) {
//TODO 重复登录逻辑
//构造一个未认证的对象
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(sysUser.getUserName(), sysUser.getPassword());
//1. 使用AuthenticationManager认证用户
Authentication authenticate = null;
try {
authenticate = authenticationManager.authenticate(usernamePasswordAuthenticationToken);
} catch (Exception e) {
//2. 认证失败
log.error("认证失败,{}",e.getMessage());
throw e;
}
//3. 认证通过,生成token,key->token,value->username
String token = UUID.fastUUID().toString(true);
CustomSecurityUser customSecurityUser = (CustomSecurityUser) authenticate.getPrincipal();
//4. token存入redis
redisTemplate.set(CacheConst.TOKEN_PREFIX + StrPool.COLON + token, customSecurityUser, expiration);
Map<String, Object> data = new HashMap<>(4);
data.put("access_token", token);
data.put("authorities", authenticate.getAuthorities());
return ResponseEntity.ok(data);
}
3、自定义过滤器
提示
在过滤器中把token转换为Authentication并且设置到SecurityContext,设置到上下文后,后续的过滤器中就可以直接使用,
要注意过滤器的设置位置,在Security的配置类中把自定义过滤器放在UsernamePasswordAuthenticationFilter之前
@Component
public class TokenAuthenticationFilter extends OncePerRequestFilter {
@Autowired
private IGlobalCache redisTemplate;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
//1. 解析token
String token = request.getHeader("X-token");
//2. token不存在直接放行,后续的FilterInterceptor会校验权限,没有权限依然无法访问接口
if (!StringUtils.hasText(token)) {
filterChain.doFilter(request, response);
return;
}
//3. 根据token查询用户信息,目标是设置SecurityContext
CustomSecurityUser customSecurityUser = redisTemplate.get(CacheConst.TOKEN_PREFIX + StrPool.COLON + token);
if (Objects.nonNull(customSecurityUser)) {
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(customSecurityUser.getUsername(), customSecurityUser.getPassword(), customSecurityUser.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
}
filterChain.doFilter(request, response);
}
}