Spring Boot控制器中如何利用自定义JWT声明实现授权?
针对你的需求,有几种实用的方案可以直接在@PreAuthorize表达式中对比Keycloak JWT的自定义声明与路径参数,下面逐一说明:
方案一:自定义SpEL函数(最直观易用)
通过扩展Spring Security的SpEL表达式,添加一个直接获取custom_claim的函数,这样可以在@PreAuthorize中像调用普通方法一样使用。
1. 创建自定义表达式根类
继承SecurityExpressionRoot并实现MethodSecurityExpressionOperations,添加获取自定义声明的方法:
import org.springframework.security.access.expression.SecurityExpressionRoot; import org.springframework.security.access.expression.method.MethodSecurityExpressionOperations; import org.springframework.security.core.Authentication; import org.keycloak.adapters.springsecurity.token.KeycloakAuthenticationToken; import org.keycloak.representations.AccessToken; public class CustomMethodSecurityExpressionRoot extends SecurityExpressionRoot implements MethodSecurityExpressionOperations { private Object filterObject; private Object returnObject; private Object target; public CustomMethodSecurityExpressionRoot(Authentication authentication) { super(authentication); } // 自定义获取custom_claim的方法 public Object getCustomClaim() { if (getAuthentication() instanceof KeycloakAuthenticationToken token) { AccessToken accessToken = token.getAccount().getKeycloakSecurityContext().getToken(); // 根据你的实际结构获取声明,原问题中是otherclaims[0],这里按需调整 return accessToken.getOtherClaims().get("custom_claim"); } return null; } @Override public void setFilterObject(Object filterObject) { this.filterObject = filterObject; } @Override public Object getFilterObject() { return filterObject; } @Override public void setReturnObject(Object returnObject) { this.returnObject = returnObject; } @Override public Object getReturnObject() { return returnObject; } @Override public Object getThis() { return target; } public void setThis(Object target) { this.target = target; } }
2. 配置自定义表达式处理器
注册一个自定义的MethodSecurityExpressionHandler,让Spring Security使用我们的表达式根:
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler; import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler; import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; @Configuration @EnableMethodSecurity public class MethodSecurityConfig { @Bean public MethodSecurityExpressionHandler methodSecurityExpressionHandler() { DefaultMethodSecurityExpressionHandler handler = new DefaultMethodSecurityExpressionHandler() { @Override protected SecurityExpressionRoot createSecurityExpressionRoot(Authentication authentication) { CustomMethodSecurityExpressionRoot root = new CustomMethodSecurityExpressionRoot(authentication); root.setPermissionEvaluator(getPermissionEvaluator()); root.setTrustResolver(getTrustResolver()); root.setRoleHierarchy(getRoleHierarchy()); return root; } }; return handler; } }
3. 在控制器中使用
直接在@PreAuthorize中调用getCustomClaim()与路径参数对比:
@RestController @RequestMapping("/api") public class ResourceController { @GetMapping("/items/{itemId}") @PreAuthorize("getCustomClaim() == #itemId") public String getItem(@PathVariable("itemId") Integer itemId) { return "Access granted to item: " + itemId; } }
注意:如果custom_claim和路径参数类型不同(比如一个是String一个是Integer),需要做类型转换,例如getCustomClaim().toString() == #itemId或者T(Integer).valueOf(#itemId) == getCustomClaim()
方案二:将自定义声明注入Authentication详情
通过自定义Keycloak认证转换器,把custom_claim提取出来放到Authentication的details属性中,这样可以直接在SpEL中访问。
1. 自定义Keycloak认证转换器
import org.keycloak.adapters.springsecurity.authentication.KeycloakAuthenticationConverter; import org.keycloak.adapters.springsecurity.token.KeycloakAuthenticationToken; import org.keycloak.representations.AccessToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; import java.util.Collection; public class CustomKeycloakAuthConverter implements KeycloakAuthenticationConverter { private final KeycloakAuthenticationConverter delegate = new KeycloakAuthenticationConverter() {}; @Override public Authentication convert(Authentication authentication) { KeycloakAuthenticationToken token = (KeycloakAuthenticationToken) delegate.convert(authentication); if (token != null) { AccessToken accessToken = token.getAccount().getKeycloakSecurityContext().getToken(); Object customClaim = accessToken.getOtherClaims().get("custom_claim"); // 将自定义声明存入自定义Details类 token.setDetails(new CustomAuthDetails(customClaim)); } return token; } @Override public Collection<GrantedAuthority> extractAuthorities(AccessToken accessToken) { return delegate.extractAuthorities(accessToken); } // 自定义Details类存储声明 public static class CustomAuthDetails { private final Object customClaim; public CustomAuthDetails(Object customClaim) { this.customClaim = customClaim; } public Object getCustomClaim() { return customClaim; } } }
2. 配置Keycloak认证提供者
import org.keycloak.adapters.springsecurity.authentication.KeycloakAuthenticationProvider; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.authentication.AuthenticationProvider; @Configuration public class KeycloakAuthConfig { @Bean public AuthenticationProvider keycloakAuthenticationProvider() { KeycloakAuthenticationProvider provider = new KeycloakAuthenticationProvider(); provider.setKeycloakAuthenticationConverter(new CustomKeycloakAuthConverter()); return provider; } }
3. 控制器中使用
@RestController @RequestMapping("/api") public class ResourceController { @GetMapping("/items/{itemId}") @PreAuthorize("authentication.details.customClaim == #itemId") public String getItem(@PathVariable("itemId") Integer itemId) { return "Access granted to item: " + itemId; } }
方案三:使用静态工具类(最简实现)
编写一个静态工具类获取当前认证的自定义声明,然后在SpEL中直接调用。
1. 静态工具类
import org.keycloak.adapters.springsecurity.token.KeycloakAuthenticationToken; import org.keycloak.representations.AccessToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; public class ClaimHelper { public static Object getCustomClaim() { Authentication auth = SecurityContextHolder.getContext().getAuthentication(); if (auth instanceof KeycloakAuthenticationToken token) { AccessToken accessToken = token.getAccount().getKeycloakSecurityContext().getToken(); return accessToken.getOtherClaims().get("custom_claim"); } return null; } }
2. 控制器中使用
@RestController @RequestMapping("/api") public class ResourceController { @GetMapping("/items/{itemId}") @PreAuthorize("T(com.yourpackage.ClaimHelper).getCustomClaim() == #itemId") public String getItem(@PathVariable("itemId") Integer itemId) { return "Access granted to item: " + itemId; } }
内容的提问来源于stack exchange,提问作者George
相关产品推荐
相关产品推荐

