Spring boot 3:使用密钥时钟资源自动管理来保护 RestController api

问题描述 投票:0回答:1

我正在使用 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 令牌中提取授权 ---> 权限:{资源和范围}

spring-security authorization keycloak user-permissions
1个回答
0
投票

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")
    可能会好得多。
  • JWT 大小没有指定限制,但它以
    Bearer
    标头形式提供,并且大多数服务器将标头设置为 8kB 的限制。这意味着
    $.authorization.permissions
    中的条目数量实际上是有限的。
  • 在创建 Keycloak 与资源服务器上的资源的一致性之前,您应该三思而后行。对我来说,这是一种代码味道,会让迁移到另一个 OpenID 提供商成为一场噩梦。在这种情况下,我更喜欢以下其中一项:
    • 如果我确信权限的数量始终适合
      Bearer
      标头,我会编写一个资源服务器来管理和公开权限。然后,我会添加一个 Keycloak“映射器”来在构建令牌时调用此资源服务器,并将响应添加为私有声明。
    • 如果数字权限有可能变高,那么我在令牌中就没有任何内容,
      PermissionEvaluator
      实现将从专用数据库中读取。当然,为了获得不错的性能,您必须使用一些缓存,如果您有多个资源服务器,您可能会在专用服务器中隔离权限。
© www.soinside.com 2019 - 2024. All rights reserved.