代码编织梦想

概述

以前使用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 下跨域及认证授权异常处理。

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/tergou/article/details/131026058