当前位置: 首页 > article >正文

项目集成Spring Security授权部分

一、需求分析

  • 业务背景
    当前项目采用前后端分离架构,后端需要对接口访问进行严格控制,防止未授权访问。鉴于系统需要支持高并发与分布式部署,采用无状态认证方式显得尤为重要。

  • 核心需求

    • 无状态认证:使用 JWT 作为令牌,前后端分离项目不依赖 session,保障系统的横向扩展能力。
    • 动态权限校验:每个用户的可访问接口列表存储在 Redis 缓存中,通过令牌解析得到用户信息后,再根据缓存中的数据判断是否具备访问权限。
    • 白名单与忽略路径:配置公开接口与忽略路径,允许无需认证的请求(例如登录接口、静态资源等)直接放行。

设计方案

  1. 安全过滤链

    • 使用 Spring Security 提供的 SecurityFilterChain 进行整体安全配置。
    • 从配置文件中读取忽略路径列表,利用 antMatchers 放行这些请求。
    • 对其他请求,委托自定义的 JwtTokenAuthorizationManager 进行权限校验。
  2. JWT 授权管理

    • 通过请求头获取 JWT 令牌,利用工具类解析出 JWT 中包含的用户信息(例如当前用户对象)。
    • 从 Redis 缓存中获取该用户拥有访问权限的 URL 列表,并使用 AntPathMatcher 进行路径匹配,判断当前请求是否在允许列表中。
  3. 其他安全策略

    • 禁用 session 管理(设置为无状态)。
    • 关闭 CSRF 保护(前后端分离场景下通常不需要)。
    • 配置密码加密器(使用 BCrypt 加密)。

二、代码解读

2.1 SecurityConfig 配置类

@Configuration
public class SecurityConfig {

    @Autowired
    private JwtTokenAuthorizationManager jwtTokenAuthorizationManager;

    @Autowired
    private SecurityConfigProperties securityConfigProperties;

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {

        // 获取忽略列表,从配置文件加载 ignoreUrl 列表
        List<String> ignoreUrl = securityConfigProperties.getIgnoreUrl();

        // 配置放行忽略路径(如登录、注册或其他公共接口)
        http.authorizeHttpRequests()
            .antMatchers(ignoreUrl.toArray(new String[ignoreUrl.size()])).permitAll()
            // 其他请求交由自定义 JwtTokenAuthorizationManager 校验权限
            .anyRequest().access(jwtTokenAuthorizationManager);

        // 设置无状态会话管理,关闭 session(适用于 RESTful 接口)
        http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
        // 关闭缓存,确保安全头信息不被浏览器缓存
        http.headers().cacheControl().disable();
        // 关闭 CSRF(前后端分离项目一般不需要 CSRF 防护)
        http.csrf().disable();

        return http.build();
    }

    /**
     * 创建认证管理器 Bean
     */
    @Bean
    public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
        return authenticationConfiguration.getAuthenticationManager();
    }

    /**
     * 定义密码加密器,采用 BCrypt 算法
     */
    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }
}
解析说明
  • 忽略路径的配置
    SecurityConfigProperties 中读取忽略 URL 列表,通过 antMatchers(...).permitAll() 放行,不需要进行认证与授权。

  • 授权管理器
    对除忽略路径外的其他请求,调用自定义的 JwtTokenAuthorizationManager 进行权限校验,确保只有拥有权限的用户才能访问相应资源。

  • 无状态策略与安全设置
    配置无状态的 session 管理(STATELESS),同时关闭缓存和 CSRF,适应前后端分离的 RESTful 接口场景。

  • 认证管理器和密码加密器
    分别配置了认证管理器和基于 BCrypt 的密码加密器,保障用户密码的安全存储。

2.2 JwtTokenAuthorizationManager 自定义授权管理器

@Component
public class JwtTokenAuthorizationManager implements AuthorizationManager<RequestAuthorizationContext> {

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Autowired
    private JwtTokenManagerProperties jwtTokenManagerProperties;

    private AntPathMatcher antPathMatcher = new AntPathMatcher();

    @Override
    public AuthorizationDecision check(Supplier<Authentication> authentication, RequestAuthorizationContext requestAuthorizationContext) {

        // 获取 HttpServletRequest,提取请求头中的 token
        HttpServletRequest request = requestAuthorizationContext.getRequest();
        String token = request.getHeader(Constants.USER_TOKEN);
        if(ObjectUtil.isEmpty(token)){
            return new AuthorizationDecision(false);
        }

        // 使用 JWT 工具类解析 token,获取 Claims
        Claims claims = JwtUtil.parseJWT(jwtTokenManagerProperties.getBase64EncodedSecretKey(), token);
        if(ObjectUtil.isEmpty(claims)){
            return new AuthorizationDecision(false);
        }

        // 从 token 中解析出当前用户信息,并转为 UserVo 对象
        UserVo userVo = JSONUtil.toBean(String.valueOf(claims.get("currentUser")), UserVo.class);

        // 从 Redis 缓存中获取该用户的访问权限 URL 列表
        String accessUrlsCacheKey = CacheConstants.ACCESS_URLS_CACHE + userVo.getId();
        String accessUrlsJson = redisTemplate.opsForValue().get(accessUrlsCacheKey);
        List<String> accessUrls = null;
        if(StringUtils.isNotEmpty(accessUrlsJson)){
            accessUrls = JSONUtil.toList(accessUrlsJson, String.class);
        }

        // 构造当前请求的标识,例如 "GET/nursing_project/**"
        String targetUrl = request.getMethod() + request.getRequestURI();

        // 遍历缓存中保存的权限 URL,使用 AntPathMatcher 判断当前请求是否匹配
        for (String url : accessUrls) {
            if(antPathMatcher.match(url, targetUrl)){
                return new AuthorizationDecision(true);
            }
        }
        // 不匹配则返回拒绝访问,系统将自动返回 403 状态码
        return new AuthorizationDecision(false);
    }
}
解析说明
  • Token 提取与解析
    从请求头中获取 JWT token,利用 JwtUtil.parseJWT 方法进行解析,获取 Claims。如果 token 无效或解析失败,直接拒绝访问。

  • 用户信息与权限获取
    从 Claims 中提取包含的用户信息,并借助 Redis 缓存,根据用户 ID 获取其允许访问的 URL 列表。这样做的好处是可以动态更新用户权限,无需重启服务。

  • 权限校验
    构造当前请求的标识(HTTP 方法 + 请求 URI),利用 AntPathMatcher 与缓存中的 URL 模式进行匹配,若有任一匹配成功,则返回授权通过。

2.3 SecurityConfigProperties 配置类

@Slf4j
@Data
@ConfigurationProperties(prefix = "zzyl.framework.security")
@Configuration
public class SecurityConfigProperties {

    /**
     * 默认密码
     */
    String defaulePassword;

    /**
     * 白名单列表(公开接口)
     */
    private List<String> publicAccessUrls;
    
    /**
     * 忽略列表(无需认证的接口)
     */
    private List<String> ignoreUrl;
}
解析说明
  • 配置属性注入
    通过 @ConfigurationProperties 注解,将配置文件中以 zzyl.framework.security 为前缀的配置映射到该类属性上,方便在 Security 配置中动态读取忽略的 URL 列表和其他安全相关的配置项。

  • 属性用途

    • defaulePassword:项目中预设的默认密码(可用于测试或初始化)。
    • publicAccessUrlsignoreUrl:分别定义了公开访问与无需认证的接口列表,保证不同接口的访问策略灵活配置。

三、总结

学习收获

  • 无状态认证思路
    通过 JWT 结合 Redis 实现了无状态的权限校验,既避免了 session 的管理压力,也便于集群部署下的权限动态更新。

  • 灵活的权限控制
    自定义 JwtTokenAuthorizationManager 根据用户缓存的权限列表动态校验请求,利用 AntPathMatcher 实现了路径模式匹配,提升了系统的灵活性与可扩展性。

  • 配置与解耦
    将忽略路径和其他安全配置集中管理(通过 SecurityConfigProperties),使得代码更具可维护性,方便在不同环境下进行调整。


http://www.kler.cn/a/530495.html

相关文章:

  • day6手机摄影社区,可以去苹果摄影社区学习拍摄技巧
  • springboot 启动原理
  • 【Proteus】NE555纯硬件实现LED呼吸灯效果,附源文件,效果展示
  • Ollama+OpenWebUI部署本地大模型
  • PyTorch框架——基于深度学习YOLOv8神经网络学生课堂行为检测识别系统
  • 【教程】在CMT上注册账号并声明Conflicts
  • 2025年2月2日(range()函数的参数及含义)
  • 「全网最细 + 实战源码案例」设计模式——享元模式
  • 【C++面试题】malloc和new delete和delete[]
  • 在AWS上使用Flume搜集分布在不同EC2实例上的应用程序日志具体流程和代码
  • Golang 并发机制-4:用Mutex管理共享资源
  • 毕业设计:基于卷积神经网络的鲜花花卉种类检测算法研究
  • 51单片机 02 独立按键
  • 享元模式——C++实现
  • Java基础知识总结(四十)--Java.util.Properties
  • 浅析服务器虚拟化技术
  • unity学习26:用Input接口去监测: 鼠标,键盘,虚拟轴,虚拟按键
  • Leetcode:598
  • 深入核心:一步步手撕Tomcat搭建自己的Web服务器
  • Ubuntu 下 nginx-1.24.0 源码分析 ngx_debug_init();
  • 构建一个文档助手Agent:提升知识管理效率的实践
  • CUDA内存模型
  • 力扣经典题目之3无重复字符的最长子串
  • STL之初识string
  • 浅谈 JSON 对象和 FormData 相互转换,打通前端与后端的通信血脉_json转formdata
  • Baklib推动内容中台与人工智能技术的智能化升级与行业变革