新版Spring Security中OAuth2SecurityExpressionMethods有何替代方案?
旧版Spring OAuth2生态中org.springframework.security.oauth2.provider.expression.OAuth2SecurityExpressionMethods类下的isClient()、isUser()方法,核心判断逻辑非常明确:
isClient():当前请求持有客户端凭证模式签发的token,访问身份为OAuth2客户端本身,无关联终端用户isUser():当前请求持有带用户身份的OAuth2 token,访问身份为授权关联的终端用户
新版Spring Security将OAuth2能力整合到主安全包后,可通过两种方案实现完全等效的控制效果:
方案一:直接使用内置SpEL表达式(零额外代码)
新版Spring Security的OAuth2 Resource Server模块会在认证通过后,将token解析后的所有属性注入认证上下文,不管是JWT格式还是不透明令牌格式,都可以直接通过principal属性判断身份,直接替换原有注解即可:
// 等效旧版 @PreAuthorize("#oauth2.isClient()") @PreAuthorize("principal.getAttribute('sub') == principal.getAttribute('client_id')") // 等效旧版 @PreAuthorize("#oauth2.isUser()") @PreAuthorize("principal.getAttribute('sub') != principal.getAttribute('client_id')")
这个判断逻辑的依据是:客户端凭证模式下不存在独立用户主体,token的sub声明值就是客户端ID;其他带用户身份的授权模式下,sub声明为用户唯一标识,和client_id(客户端标识)值不相等。
方案二:自定义安全表达式(存量代码零修改迁移)
如果项目中大量使用了旧版#oauth2.isClient()、#oauth2.isUser()写法,不想逐个修改注解,可以通过自定义安全表达式的方式,完全对齐旧版的变量名和方法逻辑,迁移成本为0。
- 新建自定义安全表达式逻辑类,实现和旧版完全一致的判断规则
public class CustomOAuth2SecurityExpression { private final Authentication authentication; public CustomOAuth2SecurityExpression(Authentication authentication) { this.authentication = authentication; } public boolean isClient() { if (!(authentication.getPrincipal() instanceof OAuth2AuthenticatedPrincipal principal)) { return false; } Object sub = principal.getAttribute("sub"); Object clientId = principal.getAttribute("client_id"); return sub != null && sub.equals(clientId); } public boolean isUser() { return !isClient(); } } - 自定义方法安全表达式处理器,将上述类的实例注册为SpEL上下文中的
oauth2变量,和旧版变量名完全一致@Component public class CustomOAuth2MethodSecurityExpressionHandler extends DefaultMethodSecurityExpressionHandler { @Override protected StandardEvaluationContext createEvaluationContextInternal(Authentication auth, MethodInvocation mi) { StandardEvaluationContext context = super.createEvaluationContextInternal(auth, mi); context.setVariable("oauth2", new CustomOAuth2SecurityExpression(auth)); return context; } } - 在Spring Security配置类中注册自定义的表达式处理器
@Configuration @EnableMethodSecurity(prePostEnabled = true) public class SecurityConfig { @Bean public MethodSecurityExpressionHandler methodSecurityExpressionHandler() { return new CustomOAuth2MethodSecurityExpressionHandler(); } }
配置完成后,原有代码中所有@PreAuthorize("#oauth2.isClient()")、@PreAuthorize("#oauth2.isUser()")的写法不需要做任何修改,就能实现和旧版完全一致的权限拦截效果。
注意:如果对接的第三方身份提供商(IDP)在客户端凭证模式下签发的token不满足
sub等于client_id的规则,只需要调整CustomOAuth2SecurityExpression类中isClient()方法的判断逻辑即可,比如可以通过判断授权类型声明grant_type是否为client_credentials实现适配。
内容的提问来源于stack exchange,提问作者IHaveHandedInMyResignation

