自定义认证授权体系
- 概述
- 自定义认证
- 定义登录接口
- 配置 Security 放行策略
- 定义通用登录过滤器并将其配置到 Security 过滤器链上
- 定义资源接口
- 在 Security 授权设置中放行
- 启动项目
- 结尾
概述
以前使用Spring Security 时,基本都是按部就班参考文档开发。
基本是从 User,UserDetails,UserDetailsService以及参考 UsernamePasswordAuthenticationFilter 开始。
现在看来,这种方式略显僵化,不够灵活。
今天以一种全新的方式来实现自定义认证授权。
先说明一点,考虑到现在前端的多样性以及cookie使用的局限性等,现在采用header+token方式认证授权,摒弃cookie+session的方式
自定义认证
定义登录接口
/**
* 自定义登录接口,Security 对其放行
* @return
*/
@PostMapping("login")
public String login(){
log.info("login....");
// TODO: 2023/6/3 验证登录参数,通过后直接下发token,这里的token可以是无状态jwt 也可以是存储在服务端的有状态token
return "token";
}
配置 Security 放行策略
明确不用的过滤器给禁了
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.authorizeHttpRequests(httpRequest ->
httpRequest
.antMatchers("/login").permitAll()
.anyRequest().denyAll()
)
// 不走默认form表单以及默认登录页
.formLogin().disable()
// 禁用csrf,不走cookie相关认证
.csrf().disable()
// 禁用session,不用session,采用header认证
.sessionManagement().disable()
);
return http.build();
}
定义通用登录过滤器并将其配置到 Security 过滤器链上
默认,Security使用的是 用户名密码显式 + cookie(session)隐式登录方式。
我们这里将其改造成 自定义显式登录 + header或者其他任意方式 隐式登录
/**
* 通用登录过滤器
* 替代传统 cookie -> session 的登录方式
*/
public class CustomAuthenticationFilter extends OncePerRequestFilter {
private final Logger logger = LoggerFactory.getLogger(CustomAuthenticationFilter.class);
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
// 这里支持 header 和 查询参数的方式传递token
String token = request.getHeader("token");
if (!StringUtils.hasText(token)) {
token = request.getParameter("token");
}
if (!StringUtils.hasText(token)) {
logger.info("请求头没有 token,跳过当前登录器");
filterChain.doFilter(request, response);
return;
}
logger.info("调用通用登录过滤器, token = {}", token);
try {
// 这里做简单校验,实际开发中,做正常校验,通过后,提取认证信息,进行认证
if (Objects.equals(token, "token")) {
UsernamePasswordAuthenticationToken authenticationToken
= UsernamePasswordAuthenticationToken.authenticated("zhangsan", "zs", Collections.emptyList());
SecurityContext context = SecurityContextHolder.createEmptyContext();
context.setAuthentication(authenticationToken);
SecurityContextHolder.setContext(context);
}
filterChain.doFilter(request, response);
} finally {
SecurityContextHolder.clearContext();
}
}
@Override
protected boolean shouldNotFilterErrorDispatch() {
return true;
}
}
将通用登录过滤器配置到 SecurityFilterChain 上
httpRequest.addFilterBefore(new CustomAuthenticationFilter(), AnonymousAuthenticationFilter.class)
定义资源接口
分别定义公共接口,需登录的接口,需特定权限的接口
@RestController
@Slf4j
public class HellController {
@GetMapping("common")
public String common(){
log.info("common....");
return "common";
}
@GetMapping("auth")
public String auth(){
log.info("auth....");
return "auth";
}
@GetMapping("admin")
public String admin(){
log.info("admin....");
return "admin";
}
}
在 Security 授权设置中放行
httpRequest
.antMatchers("/common").permitAll()
.antMatchers("/auth").authenticated()
.antMatchers("/admin").hasAuthority("admin")
.antMatchers("/login").permitAll()
.anyRequest().denyAll()
启动项目
- 首先访问公共接口,可以直接访问
- 然后访问需要认证的接口
可以看到这里的响应码为 403,表示无权访问。这里没有提示信息,是由于禁用了 默认的Form表单相关的配置,还没有配置异常处理器的缘故,这个后面再说。
继续,这里模拟登录,拿到的token为"token",然后在参数或者header上面进行传递。
可以看到这里成功访问 auth 接口 - 然后我们去访问 需要admin权限的接口
我们可以看到,即使带了token,依然是403,因为当前这个用户没有admin权限。我们修改修改通用登录过滤器,再给一个带有admin权限的token进行测试。
if (Objects.equals(token, "token") || Objects.equals(token, "admin")) {
UsernamePasswordAuthenticationToken authenticationToken
= UsernamePasswordAuthenticationToken.authenticated("zhangsan", "zs"
, Objects.equals(token, "admin") ? AuthorityUtils.createAuthorityList("admin") : Collections.emptyList());
SecurityContext context = SecurityContextHolder.createEmptyContext();
context.setAuthentication(authenticationToken);
SecurityContextHolder.setContext(context);
}
调整部分,如果token是 admin ,那么就赋予 admin 权限,普通token依然只有 普通认证权限,再次启动项目。
我们将token 换成 admin
可以看到,成功获取到数据。
结尾
Security 里面东西其实很多,但是在日常开发中常用的就那些。最主要的是要理解其设计思想和基础架构,不太清楚的可以看看 上一篇文章
下一节,将学习 Security 下跨域及认证授权异常处理。