项目集成Spring Security授权部分
一、需求分析
-
业务背景
当前项目采用前后端分离架构,后端需要对接口访问进行严格控制,防止未授权访问。鉴于系统需要支持高并发与分布式部署,采用无状态认证方式显得尤为重要。 -
核心需求
- 无状态认证:使用 JWT 作为令牌,前后端分离项目不依赖 session,保障系统的横向扩展能力。
- 动态权限校验:每个用户的可访问接口列表存储在 Redis 缓存中,通过令牌解析得到用户信息后,再根据缓存中的数据判断是否具备访问权限。
- 白名单与忽略路径:配置公开接口与忽略路径,允许无需认证的请求(例如登录接口、静态资源等)直接放行。
设计方案
-
安全过滤链
- 使用 Spring Security 提供的
SecurityFilterChain
进行整体安全配置。 - 从配置文件中读取忽略路径列表,利用
antMatchers
放行这些请求。 - 对其他请求,委托自定义的
JwtTokenAuthorizationManager
进行权限校验。
- 使用 Spring Security 提供的
-
JWT 授权管理
- 通过请求头获取 JWT 令牌,利用工具类解析出 JWT 中包含的用户信息(例如当前用户对象)。
- 从 Redis 缓存中获取该用户拥有访问权限的 URL 列表,并使用 AntPathMatcher 进行路径匹配,判断当前请求是否在允许列表中。
-
其他安全策略
- 禁用 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
:项目中预设的默认密码(可用于测试或初始化)。publicAccessUrls
与ignoreUrl
:分别定义了公开访问与无需认证的接口列表,保证不同接口的访问策略灵活配置。
三、总结
学习收获
-
无状态认证思路
通过 JWT 结合 Redis 实现了无状态的权限校验,既避免了 session 的管理压力,也便于集群部署下的权限动态更新。 -
灵活的权限控制
自定义JwtTokenAuthorizationManager
根据用户缓存的权限列表动态校验请求,利用 AntPathMatcher 实现了路径模式匹配,提升了系统的灵活性与可扩展性。 -
配置与解耦
将忽略路径和其他安全配置集中管理(通过SecurityConfigProperties
),使得代码更具可维护性,方便在不同环境下进行调整。