我正在使用 spring boot 3 java 17 和 keyclock 21.1.1。 我需要通过密钥时钟资源自动化来保护我的其余 api。 为此我添加了一个关键的时钟客户端
政策
以及我的许可
我的RPT代币
我正在使用 oauth2 依赖项
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
</dependency>
使用 @PreAuthorize("hasPermission('my_ressource', 'add')") 给我 403 禁止
我的安全配置是
@Configuration
@EnableWebSecurity
@EnableMethodSecurity
class SecurityConfiguration {
@Autowired
private CorsConfigurationSource corsConfigurationSource;
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {
httpSecurity
.authorizeHttpRequests(registry -> registry.anyRequest().authenticated())
.oauth2ResourceServer(oauth2Configurer -> oauth2Configurer.jwt(jwtConfigurer -> jwtConfigurer.jwtAuthenticationConverter(jwt -> {
Map<String, Collection<String>> realmAccess = jwt.getClaim("realm_access");
Collection<String> roles = realmAccess.get("roles");
var grantedAuthorities = roles.stream()
.map(role -> new SimpleGrantedAuthority("ROLE_" + role))
.collect(Collectors.toList());
return new JwtAuthenticationToken(jwt,grantedAuthorities , "");
})))
;
return httpSecurity.build();
}
}
我需要调整我的安全配置以使用 hasPermission。
应用日志
2023-09-12T17:39:18.444+01:00 DEBUG 1948 --- [nio-9000-exec-2] horizationManagerBeforeMethodInterceptor : Authorizing method invocation ReflectiveMethodInvocation: public org.springframework.http.ResponseEntity com.mypartner.tm.authentification.controller.UserBranchController.addKeyClock(); target is of class [com.mypartner.tm.authentification.controller.UserBranchController] 2023-09-12T17:39:18.452+01:00 WARN 1948 --- [nio-9000-exec-2] o.s.s.a.e.DenyAllPermissionEvaluator : Denying user permission 'add' on object my_ressource 2023-09-12T17:39:18.459+01:00 DEBUG 1948
--- [nio-9000-exec-2] horizationManagerBeforeMethodInterceptor : Failed to authorize ReflectiveMethodInvocation: public org.springframework.http.ResponseEntity com.mypartner.tm.authentification.controller.UserBranchController.addKeyClock(); target is of class [com.mypartner.tm.authentification.controller.UserBranchController] with authorization manager org.springframework.security.config.annotation.method.configuration.DeferringObservationAuthorizationManager@181c058b and decision ExpressionAuthorizationDecision [granted=false, expressionAttribute=hasPermission('my_ressource', 'add')] 2023-09-12T17:39:18.470+01:00 TRACE 1948 --- [nio-9000-exec-2] o.s.s.w.a.ExceptionTranslationFilter : Sending JwtAuthenticationToken [Principal=org.springframework.security.oauth2.jwt.Jwt@7e539329, Credentials=[PROTECTED], Authenticated=true, Details=WebAuthenticationDetails [RemoteIpAddress=0:0:0:0:0:0:0:1, SessionId=null], Granted Authorities=[ROLE_default-roles-test, ROLE_offline_access, ROLE_test_role, ROLE_uma_authorization]] to access denied handler since access is denied
org.springframework.security.access.AccessDeniedException: Access Denied at org.springframework.security.authorization.method.AuthorizationManagerBeforeMethodInterceptor.attemptAuthorization(AuthorizationManagerBeforeMethodInterceptor.java:257) at org.springframework.security.authorization.method.AuthorizationManagerBeforeMethodInterceptor.invoke(AuthorizationManagerBeforeMethodInterceptor.java:198) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:750) at net.bull.javamelody.MonitoringSpringInterceptor.invoke(MonitoringSpringInterceptor.java:76) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:750) at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:702) at com.mypartner.tm.authentification.controller.UserBranchController$$SpringCGLIB$$0.addKeyClock(<generated>) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.base/java.lang.reflect.Method.invoke(Method.java:568) at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:207) at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:152) at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:118) at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:884) at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:797) at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87) at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1081) at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:974) at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1011) at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:903) at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:537) at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:885) at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:631) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:205) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149) at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149)
PS: @PreAuthorize("hasRole('test_role')") 正在为我工作。 我认为我应该从 jwt 令牌中提取授权 ---> 权限:{资源和范围}
hasPermission
由 PermissionEvaluator
评价。 Spring Security 提供的唯一实现是 DenyAllPermissionEvaluator
,它将...拒绝所有(结果为 403)。
您应该从访问令牌中的
PermissionEvaluator
声明中提供您自己的 $.authorization.permissions
bean 读数。类似于:
@Component
public class KeycloakAuthorizationPermissionEvaluator implements PermissionEvaluator {
@Override
public boolean hasPermission(Authentication authentication, Object targetDomainObject, Object permission) {
final var permissions = getAuthenticationPermissions(authentication);
// FIXME: implement permission evaluation
return false;
}
@Override
public boolean hasPermission(Authentication authentication, Serializable targetId, String targetType, Object permission) {
final var permissions = getAuthenticationPermissions(authentication);
// FIXME: implement permission evaluation
return false;
}
@SuppressWarnings("unchecked")
static List<Map<String, Object>> getAuthenticationPermissions(Authentication authentication) {
if(authentication instanceof JwtAuthenticationToken jwtAuth) {
final var authorization = (Map<String, Object>) jwtAuth.getToken().getClaims().getOrDefault("authorization", Map.of());
return (List<Map<String, Object>>) authorization.getOrDefault("permissions", Map.of());
}
return List.of();
}
}
一些注意事项:
JwtAuthenticationToken
时设置空字符串作为用户名似乎是一个非常糟糕的主意。使用 jwt.getClaim("preferred_username")
或 jwt.getClaim("sub")
可能会好得多。Bearer
标头形式提供,并且大多数服务器将标头设置为 8kB 的限制。这意味着$.authorization.permissions
中的条目数量实际上是有限的。Bearer
标头,我会编写一个资源服务器来管理和公开权限。然后,我会添加一个 Keycloak“映射器”来在构建令牌时调用此资源服务器,并将响应添加为私有声明。PermissionEvaluator
实现将从专用数据库中读取。当然,为了获得不错的性能,您必须使用一些缓存,如果您有多个资源服务器,您可能会在专用服务器中隔离权限。