spring 自定义转换器在jwt oauth2 [关闭]

pcww981p  于 2023-11-16  发布在  Spring
关注(0)|答案(2)|浏览(115)

**已关闭。**此问题需要debugging details。目前不接受回答。

编辑问题以包括desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem。这将帮助其他人回答问题。
8天前关闭。
Improve this question
我尝试用Sping Boot 和Keycloak实现oauth2 authorization_code flow。我的Sping Boot 应用程序充当客户端和资源服务器,Keycloak是授权提供者。
我通过浏览器请求资源,然后Keycloak的登录页面出现,我输入我的用户的凭据,我得到资源。我想做的是基于角色检索资源。所以,我创建了一个自定义转换器来添加角色值(添加前缀ROLE_),我想进入授权。问题是我的转换器似乎从来没有被调用,导致403响应
SecurityConfig.java

@RequiredArgsConstructor
@EnableWebSecurity
@EnableMethodSecurity
@Configuration
public class SecurityConfig {
    
    private final JwtAuthConverter jwtAuthConverter;
    
    @Value(value = "${jwt.auth.converter.issuer-uri}")
    private String issuerUri;
    
    
    @Bean   
    public SecurityFilterChain clientFilterChain(HttpSecurity http) throws Exception {  
        return http.authorizeHttpRequests(req-> req
                 .anyRequest()
                .authenticated())
        .oauth2Login(Customizer.withDefaults())
        .oauth2ResourceServer(oauth2ResourceServer -> oauth2ResourceServer
                .jwt(jwt -> 
                        // here I set my custom converter, but it's never called
                    jwt.jwtAuthenticationConverter(jwtAuthConverter)         
                )
         )
        .build();
     }
        
 
    @Bean
    public JwtDecoder jwtDecoder() {
        return NimbusJwtDecoder.withIssuerLocation(issuerUri).build();
    } 
}

字符串
application.yml

jwt:
  auth:
    converter:
      resource-id: john-rest-api
      principal-attribute: preferred_username
      issuer-uri: http://localhost:8180/realms/john
spring:
  security:
    oauth2:
      client:
        registration:
          john-rest-api:
            client-id: john-rest-api
            authorization-grant-type: authorization_code
            redirect-uri: http://localhost:8080/login/oauth2/code/john-rest-api
            scope: openid, profile, roles
        provider:
           john-rest-api:
            issuer-uri: http://localhost:8180/realms/john
        resourceserver:
          jwt:
            issuer-uri: http://localhost:8180/realms/john
            jwk-set-uri: ${spring.security.oauth2.resourceserver.jwt.issuer-uri}/protocol/openid-connect/certs


JwtAuthConverter.java自定义转换器)

@Component
public class JwtAuthConverter implements Converter<Jwt, AbstractAuthenticationToken>{
    
    private final JwtGrantedAuthoritiesConverter jwtGrantedAuthoritiesConverter =
            new JwtGrantedAuthoritiesConverter();
    
    @Value(value = "${jwt.auth.converter.principal-attribute}")
    private String principalAttribute;
    
    @Value(value = "${jwt.auth.converter.resource-id}")
    private String resourceId;

    @Override
    public AbstractAuthenticationToken convert(Jwt jwt) {
              // ...
              // never been here !!!
              // code to add roles that i want to grant authorities, adding to them the prefix "ROLE_"
        }
}


DemoController.java

@GetMapping("/hello-2")
@PreAuthorize("hasRole('client_admin')")    
public String hello() { 
  return "hello";


任何关于这方面的想法都会很有帮助。另外,如果有关于如何使用Keycloak和Sping Boot 实现authorization_code flow的教程(我找不到),也会很有帮助。
日志

2023-11-06T14:38:22.468+02:00 DEBUG 7100 --- [nio-8080-exec-7] o.s.security.web.FilterChainProxy        : Securing GET /login/oauth2/code/john-rest-api?state=482b447Sm8xpm__xVJzRgLzLIaEl1CwUBi79b7C2MZ8%3D&session_state=06bb0831-88be-4580-8717-e08985098f2c&code=c4800a89-9de1-4f6e-8c80-f74b30825286.06bb0831-88be-4580-8717-e08985098f2c.06b90e8a-c3a3-40fe-877a-41d5f3131066
2023-11-06T14:38:22.666+02:00 DEBUG 7100 --- [nio-8080-exec-7] .s.ChangeSessionIdAuthenticationStrategy : Changed session id from F4F280CA99D66DCFD5C20757B8D61BEA
2023-11-06T14:38:22.667+02:00 DEBUG 7100 --- [nio-8080-exec-7] w.c.HttpSessionSecurityContextRepository : Stored SecurityContextImpl [Authentication=OAuth2AuthenticationToken [Principal=Name: [f865bdf4-8438-4ac5-a587-917ed06fb82e], Granted Authorities: [[OIDC_USER, SCOPE_email, SCOPE_openid, SCOPE_profile]], User Attributes: [{at_hash=hdODzgXtXVwmIIC8I0_-Lg, sub=f865bdf4-8438-4ac5-a587-917ed06fb82e, email_verified=false, iss=http://localhost:8180/realms/john, typ=ID, preferred_username=john, given_name=, nonce=BoS9yhBYY4d-aY0yHdbX-3IK_LF6uBl3uFEQ1vp6L6c, sid=06bb0831-88be-4580-8717-e08985098f2c, aud=[john-rest-api], acr=1, azp=john-rest-api, auth_time=2023-11-06T12:38:22Z, exp=2023-11-06T12:43:22Z, session_state=06bb0831-88be-4580-8717-e08985098f2c, family_name=, iat=2023-11-06T12:38:22Z, jti=3951b6f2-fda1-4374-8e81-556881d7c9a6}], Credentials=[PROTECTED], Authenticated=true, Details=WebAuthenticationDetails [RemoteIpAddress=127.0.0.1, SessionId=F4F280CA99D66DCFD5C20757B8D61BEA], Granted Authorities=[OIDC_USER, SCOPE_email, SCOPE_openid, SCOPE_profile]]] to HttpSession [org.apache.catalina.session.StandardSessionFacade@4f0bbf2a]
2023-11-06T14:38:22.668+02:00 DEBUG 7100 --- [nio-8080-exec-7] .s.o.c.w.OAuth2LoginAuthenticationFilter : Set SecurityContextHolder to OAuth2AuthenticationToken [Principal=Name: [f865bdf4-8438-4ac5-a587-917ed06fb82e], Granted Authorities: [[OIDC_USER, SCOPE_email, SCOPE_openid, SCOPE_profile]], User Attributes: [{at_hash=hdODzgXtXVwmIIC8I0_-Lg, sub=f865bdf4-8438-4ac5-a587-917ed06fb82e, email_verified=false, iss=http://localhost:8180/realms/john, typ=ID, preferred_username=john, given_name=, nonce=BoS9yhBYY4d-aY0yHdbX-3IK_LF6uBl3uFEQ1vp6L6c, sid=06bb0831-88be-4580-8717-e08985098f2c, aud=[john-rest-api], acr=1, azp=john-rest-api, auth_time=2023-11-06T12:38:22Z, exp=2023-11-06T12:43:22Z, session_state=06bb0831-88be-4580-8717-e08985098f2c, family_name=, iat=2023-11-06T12:38:22Z, jti=3951b6f2-fda1-4374-8e81-556881d7c9a6}], Credentials=[PROTECTED], Authenticated=true, Details=WebAuthenticationDetails [RemoteIpAddress=127.0.0.1, SessionId=F4F280CA99D66DCFD5C20757B8D61BEA], Granted Authorities=[OIDC_USER, SCOPE_email, SCOPE_openid, SCOPE_profile]]
2023-11-06T14:38:22.669+02:00 DEBUG 7100 --- [nio-8080-exec-7] o.s.s.web.DefaultRedirectStrategy        : Redirecting to http://localhost:8080/api/v1/demo/hello-2?continue
2023-11-06T14:38:22.699+02:00 DEBUG 7100 --- [nio-8080-exec-7] o.s.security.web.FilterChainProxy        : Securing GET /api/v1/demo/hello-2?continue
2023-11-06T14:38:22.700+02:00 DEBUG 7100 --- [nio-8080-exec-7] o.s.s.w.s.HttpSessionRequestCache        : Loaded matching saved request http://localhost:8080/api/v1/demo/hello-2?continue
2023-11-06T14:38:22.702+02:00 DEBUG 7100 --- [nio-8080-exec-7] w.c.HttpSessionSecurityContextRepository : Retrieved SecurityContextImpl [Authentication=OAuth2AuthenticationToken [Principal=Name: [f865bdf4-8438-4ac5-a587-917ed06fb82e], Granted Authorities: [[OIDC_USER, SCOPE_email, SCOPE_openid, SCOPE_profile]], User Attributes: [{at_hash=hdODzgXtXVwmIIC8I0_-Lg, sub=f865bdf4-8438-4ac5-a587-917ed06fb82e, email_verified=false, iss=http://localhost:8180/realms/john, typ=ID, preferred_username=john, given_name=, nonce=BoS9yhBYY4d-aY0yHdbX-3IK_LF6uBl3uFEQ1vp6L6c, sid=06bb0831-88be-4580-8717-e08985098f2c, aud=[john-rest-api], acr=1, azp=john-rest-api, auth_time=2023-11-06T12:38:22Z, exp=2023-11-06T12:43:22Z, session_state=06bb0831-88be-4580-8717-e08985098f2c, family_name=, iat=2023-11-06T12:38:22Z, jti=3951b6f2-fda1-4374-8e81-556881d7c9a6}], Credentials=[PROTECTED], Authenticated=true, Details=WebAuthenticationDetails [RemoteIpAddress=127.0.0.1, SessionId=F4F280CA99D66DCFD5C20757B8D61BEA], Granted Authorities=[OIDC_USER, SCOPE_email, SCOPE_openid, SCOPE_profile]]]
2023-11-06T14:38:22.702+02:00 DEBUG 7100 --- [nio-8080-exec-7] o.s.security.web.FilterChainProxy        : Secured GET /api/v1/demo/hello-2?continue
2023-11-06T14:38:22.704+02:00 DEBUG 7100 --- [nio-8080-exec-7] horizationManagerBeforeMethodInterceptor : Authorizing method invocation ReflectiveMethodInvocation: public java.util.Map com.example.keycloak.DemoController.hello2(); target is of class [com.example.keycloak.DemoController]
2023-11-06T14:38:22.705+02:00 DEBUG 7100 --- [nio-8080-exec-7] horizationManagerBeforeMethodInterceptor : Failed to authorize ReflectiveMethodInvocation: public java.util.Map com.example.keycloak.DemoController.hello2(); target is of class [com.example.keycloak.DemoController] with authorization manager org.springframework.security.config.annotation.method.configuration.DeferringObservationAuthorizationManager@53e70692 and decision ExpressionAuthorizationDecision [granted=false, expressionAttribute=hasRole('client_admin')]

5tmbdcev

5tmbdcev1#

对于具有登录的OAuth2客户端和OAuth2资源服务器,权限Map的执行方式不同。
使用Converter<Jwt, AbstractAuthenticationToken>(如JwtAuthenticationConverter)仅适用于带有JWT解码器的OAuth2资源服务器(但既不适用于带有内省的OAuth2资源服务器,也不适用于带有oauth2Login的OAuth2客户端)。
对于使用oauth2Login的OAuth2客户端,可以使用GrantedAuthoritiesMapperOAuth2UserService(在大多数情况下,第一种更简单,也足够了)。
作为一个旁注,你不应该在同一个安全过滤器链中混合使用oauth2LoginresourceServer。配置要求太不同了(安全性是基于登录的客户端的会话,这使得CSRF保护成为必要,而安全性是基于资源服务器的令牌,这使得会话和CSRF保护成为不必要)。
由于您使用的是oauth2Login,我猜您实际上没有使用令牌(检查您的浏览器调试工具,你会发现会话cookie,但没有任何包含Bearer令牌的Authorization头)。resourceServer配置只有在OAuth2客户端使用访问令牌发送请求时才有意义。浏览器本身不能成为OAuth2客户端。它需要一个JavaScript框架为此配置,它需要将此框架配置为 public OAuth2客户端,现在不鼓励这样做。
如果你的UI是一个带有Thymeleaf(或任何其他服务器端渲染框架)的Spring应用程序,有两个选项:

  • 如果MVC控制器使用REST客户端,(WebClientRestClientRestTemplate@FeignClient等)对暴露给其他OAuth2客户端的API进行内部REST调用,然后在两个不同的SecurityFilterChain bean中分离安全性:一个用于OAuth2客户端登录(MVC控制器端点呈现模板),另一个用于REST API的OAuth2资源服务器配置。
  • 如果REST API没有公开(仅由您的UI使用会话),只需将x1m17 n1x配置沿着您的x1m18 n1x一起删除。

如果你的UI是SPA,那么也有两个选择:

  • 从REST API中删除resourceServer配置(仅将其用作具有登录和会话的OAuth2客户端),但这对于可伸缩性和容错性来说并不理想
  • 删除x1m20 n1x(保留REST API单独作为无状态资源服务器),并在前端和此API之间插入Backend For Frontend。我使用x1m21 n1x作为OAuth2客户端创建了一个x1e1 f1x,其中包含x1m22 n1x和x1m23 n1x过滤器。
epfja78i

epfja78i2#

上面的代码完整吗?我看到private final JwtAuthConverter jwtAuthConverter;,但没有代码给出它的初始值。
另一种方法是创建org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter类型的Bean并对其进行自定义:

@Bean
public JwtAuthenticationConverter oauth2JwtAuthenticationConverter() {
    JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter();
    jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(new CustomJwtGrantedAuthoritiesConverter());
    return jwtAuthenticationConverter;
}

字符串
其中CustomJwtGrantedAuthoritiesConverter是一个自定义类,用于处理授予的权限、ROLE_ etc

相关问题