Spring Boot 3集成Keycloak资源授权:hasPermission验证403问题解决
解决Keycloak资源授权中hasPermission注解403问题
针对Spring Boot 3 + Java 17 + Keycloak 21.1.1环境下,@PreAuthorize("hasPermission('my_ressource', 'add')")返回403的问题,需要修改Security配置,从JWT的authorization.permissions字段提取权限信息,具体步骤如下:
1. 自定义JWT权限转换器
实现JwtGrantedAuthoritiesConverter,同时保留原有角色解析逻辑,从RPT令牌的权限节点提取资源和操作:
import org.springframework.core.convert.converter.Converter; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.oauth2.jwt.Jwt; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Map; public class KeycloakPermissionConverter implements Converter<Jwt, Collection<GrantedAuthority>> { @Override public Collection<GrantedAuthority> convert(Jwt jwt) { Collection<GrantedAuthority> authorities = new ArrayList<>(); // 解析authorization.permissions中的资源权限 Map<String, Object> authorization = jwt.getClaim("authorization"); if (authorization != null && authorization.containsKey("permissions")) { List<Map<String, String>> permissions = (List<Map<String, String>>) authorization.get("permissions"); for (Map<String, String> perm : permissions) { String resource = perm.get("resource"); String action = perm.get("action"); if (resource != null && action != null) { authorities.add(new SimpleGrantedAuthority("PERMISSION_" + resource + ":" + action)); } } } // 保留原realm角色解析,兼容已有的hasRole逻辑 Map<String, Object> realmAccess = jwt.getClaim("realm_access"); if (realmAccess != null && realmAccess.containsKey("roles")) { List<String> roles = (List<String>) realmAccess.get("roles"); roles.forEach(role -> authorities.add(new SimpleGrantedAuthority("ROLE_" + role))); } return authorities; } }
2. 更新Security配置
在资源服务器配置中替换默认的JWT转换器,并开启方法级安全:
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter; import org.springframework.security.web.SecurityFilterChain; @Configuration @EnableWebSecurity @EnableMethodSecurity(prePostEnabled = true) public class SecurityConfig { @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http .authorizeHttpRequests(auth -> auth.anyRequest().authenticated()) .oauth2ResourceServer(oauth2 -> oauth2 .jwt(jwt -> jwt.jwtAuthenticationConverter(jwtAuthenticationConverter())) ); return http.build(); } @Bean public JwtAuthenticationConverter jwtAuthenticationConverter() { JwtAuthenticationConverter converter = new JwtAuthenticationConverter(); converter.setJwtGrantedAuthoritiesConverter(new KeycloakPermissionConverter()); return converter; } }
3. 适配hasPermission注解使用
方案一:直接使用hasAuthority
如果不想额外配置,可以直接用格式化后的权限字符串:
@PreAuthorize("hasAuthority('PERMISSION_my_ressource:add')")
方案二:自定义PermissionEvaluator(保留原有注解写法)
如果想继续使用hasPermission('my_ressource', 'add'),需要自定义权限评估器:
import org.springframework.security.access.PermissionEvaluator; import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; import org.springframework.stereotype.Component; import java.io.Serializable; @Component public class KeycloakPermissionEvaluator implements PermissionEvaluator { @Override public boolean hasPermission(Authentication auth, Object resource, Object action) { String requiredAuth = "PERMISSION_" + resource + ":" + action; return auth.getAuthorities().stream() .map(GrantedAuthority::getAuthority) .anyMatch(requiredAuth::equals); } @Override public boolean hasPermission(Authentication auth, Serializable targetId, String targetType, Object action) { return hasPermission(auth, targetType, action); } }
然后配置方法级安全表达式处理器:
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.GlobalMethodSecurityConfiguration; @Configuration public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration { private final KeycloakPermissionEvaluator permissionEvaluator; public MethodSecurityConfig(KeycloakPermissionEvaluator permissionEvaluator) { this.permissionEvaluator = permissionEvaluator; } @Override protected MethodSecurityExpressionHandler createExpressionHandler() { DefaultMethodSecurityExpressionHandler handler = new DefaultMethodSecurityExpressionHandler(); handler.setPermissionEvaluator(permissionEvaluator); return handler; } }
完成以上配置后,@PreAuthorize("hasPermission('my_ressource', 'add')")即可正常生效。
关键注意点
- 确认Keycloak颁发的RPT令牌中存在
authorization.permissions字段,且每个权限条目包含resource和action属性 - 转换器中保留了原角色解析逻辑,不会影响已有的
hasRole功能 - 权限前缀
PERMISSION_可根据需求调整,只需保证转换器和评估器中的前缀一致即可
内容的提问来源于stack exchange,提问作者nihel turki
相关产品推荐
相关产品推荐

