当前位置: 首页 > 知识库问答 >
问题:

带有oauth2的Spring Security 5导致“principalName不能为空”错误

强才捷
2023-03-14

我试图用SpringSecurity5实现一个oauth2安全的SpringBootAPI。我希望API是一个oauth2资源服务器,并且能够使用WebClient访问外部oauth2资源服务器,并授予客户端凭据。

我可以将API配置为oauth2资源服务器或oauth2客户机,但不能同时配置两者。

下面是将API配置为具有Spring Security性5的资源服务器的最小设置。我使用不透明令牌,所以服务器配置为。

应用性质

spring.security.oauth2.resourceserver.opaquetoken.introspection-uri=http://localhost:8086/auth/oauth/check_token
spring.security.oauth2.resourceserver.opaquetoken.client-id=test-api
spring.security.oauth2.resourceserver.opaquetoken.client-secret=e61aa5d6-074d-4216-b15f-1bf3fc71b833

WebSecurity配置类

@EnableWebSecurity
public class WebSecurity extends WebSecurityConfigurerAdapter {
    
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests(authorizeRequests ->
                    authorizeRequests
                        .anyRequest().authenticated()
            )
            .oauth2ResourceServer(OAuth2ResourceServerConfigurer::opaqueToken);

    }
}

通过此设置,我可以使用有效令牌访问此apiendpoint以访问其受保护的资源。因此,资源服务器配置本身工作正常。

下面是最低限度的SpringSecurity5设置,配置为使用WebClient使用客户端凭据授权访问外部受保护的资源。

应用性质

spring.security.oauth2.client.provider.my-oauth-provider.token-uri=http://127.0.0.1:8086/auth/oauth/token
spring.security.oauth2.client.registration.test-api.client-id=test-store
spring.security.oauth2.client.registration.test-api.client-secret=password
spring.security.oauth2.client.registration.test-api.provider=my-oauth-provider
spring.security.oauth2.client.registration.test-api.scope=read,write
spring.security.oauth2.client.registration.test-api.authorization-grant-type=client_credentials

WebSecurity配置类

@EnableWebSecurity
public class WebSecurity extends WebSecurityConfigurerAdapter {
    
    @Autowired
    ClientRegistrationRepository clientRegistrationRepository;
    
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .oauth2Client(oauth2 -> oauth2
                .clientRegistrationRepository(clientRegistrationRepository)
            );
    }
}

Oauth2Client配置类

@Configuration
public class Oauth2ClientConfig {
    
    @Bean
    public OAuth2AuthorizedClientManager authorizedClientManager(
            ClientRegistrationRepository clientRegistrationRepository,
            OAuth2AuthorizedClientRepository authorizedClientRepository) {

        OAuth2AuthorizedClientProvider authorizedClientProvider =
                OAuth2AuthorizedClientProviderBuilder.builder()
                        .clientCredentials()
                        .build();

        DefaultOAuth2AuthorizedClientManager authorizedClientManager =
                new DefaultOAuth2AuthorizedClientManager(
                        clientRegistrationRepository, authorizedClientRepository);
        authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);

        return authorizedClientManager;
    }

    
    @Bean
    WebClient webClient(OAuth2AuthorizedClientManager oAuth2AuthorizedClientManager) {
        ServletOAuth2AuthorizedClientExchangeFilterFunction oauth2Client =
                new ServletOAuth2AuthorizedClientExchangeFilterFunction(
                        oAuth2AuthorizedClientManager);
        
        // default registrationId
       oauth2Client.setDefaultClientRegistrationId("test-api");
        
        // set client to use oauth2 by default globally
       oauth2Client.setDefaultOAuth2AuthorizedClient(true);
        
        return WebClient.builder()
          .apply(oauth2Client.oauth2Configuration())
          .build();
    }
}

使用此设置,我可以使用客户端凭据授权访问外部受保护终结点以访问受保护资源。

但是,当我将这两种配置放在一起时,当我尝试访问apiendpoint,而apiendpoint又尝试使用WebClient访问外部资源时,它就不起作用了。

    @Autowired
    WebClient webClient;

    @GetMapping("test")
    public String test() {
        String message = webClient
                .get()
                .uri("http://localhost:8084/external/api/endpoint")
                .retrieve()
                .bodyToMono(String.class)
                .block();
        return message;
    }

Spring security抛出principalName不能为空错误。


java.lang.IllegalArgumentException: principalName cannot be empty
    at org.springframework.util.Assert.hasText(Assert.java:287) ~[spring-core-5.2.8.RELEASE.jar:5.2.8.RELEASE]
    Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException: 
Error has been observed at the following site(s):
    |_ checkpoint ⇢ Request to GET http://localhost:8084/ocr/document/test [DefaultWebClient]
Stack trace:
        at org.springframework.util.Assert.hasText(Assert.java:287) ~[spring-core-5.2.8.RELEASE.jar:5.2.8.RELEASE]
        at org.springframework.security.oauth2.client.InMemoryOAuth2AuthorizedClientService.loadAuthorizedClient(InMemoryOAuth2AuthorizedClientService.java:73) ~[spring-security-oauth2-client-5.3.3.RELEASE.jar:5.3.3.RELEASE]
        at org.springframework.security.oauth2.client.web.AuthenticatedPrincipalOAuth2AuthorizedClientRepository.loadAuthorizedClient(AuthenticatedPrincipalOAuth2AuthorizedClientRepository.java:73) ~[spring-security-oauth2-client-5.3.3.RELEASE.jar:5.3.3.RELEASE]
        at org.springframework.security.oauth2.client.web.DefaultOAuth2AuthorizedClientManager.authorize(DefaultOAuth2AuthorizedClientManager.java:144) ~[spring-security-oauth2-client-5.3.3.RELEASE.jar:5.3.3.RELEASE]
        at org.springframework.security.oauth2.client.web.reactive.function.client.ServletOAuth2AuthorizedClientExchangeFilterFunction.lambda$authorizeClient$24(ServletOAuth2AuthorizedClientExchangeFilterFunction.java:534) ~[spring-security-oauth2-client-5.3.3.RELEASE.jar:5.3.3.RELEASE]
...

任何关于解决这个问题的建议都将受到高度赞赏。当API也是资源服务器时,是否有其他方式配置WebClient以使其工作?

我在maven项目中有这些依赖项。Spring Security版本是5.3.3。释放聚甲醛。xml

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.2.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    ...

        <!-- Spring Boot Web starter -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!-- Security Starter -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <!-- Oauth2 resource server -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
        </dependency>
        <dependency>
            <groupId>com.nimbusds</groupId>
            <artifactId>oauth2-oidc-sdk</artifactId>
        </dependency>
        <!-- Oauth2 client -->
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-oauth2-client</artifactId>
        </dependency>
        <!-- WebClient -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-webflux</artifactId>
        </dependency>

共有2个答案

公羊渝
2023-03-14

问题是,您有一个同时是resourceServeroAuth2Client的应用程序,尽管这听起来很奇怪。一旦您将. oAuth2ResourceServer(OAuth2ResourceServerConfigrer::opaqueToken)配置包含到您的http pSecurity中,所有身份验证现在都将是BearerToken身份验证的实例。这些将没有与它们关联的名称,因为您的不透明令牌没有name字段。如果您排除了. oAuth2ResourceServer(OAuth2ResourceServerConfigrer::opaqueToken),所有身份验证都将是匿名身份验证令牌的实例,默认主体为匿名用户

这个问题的解决方案是为不透明令牌实现自己的内窥镜器,它将为身份验证设置主体名称。我通过扩展NimbusReactiveOpaqueTokenNeplesttor并复制具有默认主体名称的Oau2身份验证来快速而肮脏地完成此操作。

内省者:

class ExtendedNimbusReactiveOpaqueTokenIntrospector(introspectionUri: String, clientId: String, clientSecret: String) : NimbusReactiveOpaqueTokenIntrospector(introspectionUri, clientId, clientSecret) {
    override fun introspect(token: String?): Mono<OAuth2AuthenticatedPrincipal> {
        return super.introspect(token).map { auth ->
            val name = SecurityContextHolder.getContext().authentication?.name ?: "defaultPrincipal"
            DefaultOAuth2AuthenticatedPrincipal(name, auth.attributes, auth.authorities)
    }
}

}

Web安全配置:

@Configuration
@EnableWebFluxSecurity
class OAuthConfig
@Autowired constructor(@Value("\${spring.security.oauth2.resourceserver.opaquetoken.introspection-uri}") private val introspectionUri: String,
                   @Value("\${spring.security.oauth2.resourceserver.opaquetoken.client-id}") private val clientId: String,
                   @Value("\${spring.security.oauth2.resourceserver.opaquetoken.client-secret}") private val clientSecret: String){

    @Bean
    fun securityWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
        return http
            .oauth2Client {

            }
            .authorizeExchange { authExchange ->
                authExchange.pathMatchers(
                        "/apidoc",
                        "/swagger-ui.html",
                        "/webjars/**",
                        "/swagger-resources/**",
                        "/v2/api-docs")
                        .permitAll()
                        .anyExchange()
                        .permitAll()
            }
            .oauth2Client {}
            .oauth2ResourceServer {
                it.opaqueToken {opaqueTokenSpec ->
                    opaqueTokenSpec.introspector(ReweNimbusReactiveOpaqueTokenIntrospector(introspectionUri, clientId, clientSecret))
                }
            }
            .build()
    }

}

WebClientConfig:

@Configuration
class WebClientConfig @Autowired constructor() {

    @Bean
    fun authorizedClientManager(
            clientRegistrationRepository: ReactiveClientRegistrationRepository?,
            authorizedClientService: ReactiveOAuth2AuthorizedClientService?
    ): ReactiveOAuth2AuthorizedClientManager? {
        val authorizedClientProvider = ReactiveOAuth2AuthorizedClientProviderBuilder.builder()
            .clientCredentials()
            .build()
        val authorizedClientManager = AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager(clientRegistrationRepository, authorizedClientService)
        authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider)
        return authorizedClientManager
    }

    @Bean
    fun webClientBuilder(authorizedClientManager: ReactiveOAuth2AuthorizedClientManager?): WebClient.Builder? {
        val oauth = ServerOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager)
    oauth.setDefaultClientRegistrationId("rewe")
        return WebClient.builder()
            .filter(oauth)
    }

    @Bean
    fun authorizedWebClient(webClientBuilder: WebClient.Builder, @Value("\${host}") host: String): WebClient {
        return webClientBuilder.baseUrl(host).build()
    }
}
潘银龙
2023-03-14

从这里开始:https://www.gitmemory.com/issue/spring-projects/spring-security/8398/614615036

这是预期的行为-

OAuth2AuthorizedClient需要principalName,因为访问令牌始终与主体关联。reactiveOAuth2AuthorizedClient服务验证principalName,如果principalName不可用,则预期将失败。

principalName不可用的原因是JwtAuthenticationToken。getName()将默认为子声明,根据上面的令牌示例,该子声明在Jwt的声明集中不可用。您需要做的是指定用户名声明用作principalName。

看看这个示例,它向您展示了如何将JwtDecoder配置为JwtAuthenticationToken的默认用户名。getName()。

链接的示例无法正常工作,因为convertedClaims中没有“user_name”值,所以我只创建了自己的用户名。这是我添加到配置中的内容:

http.authorizeExchange(exchangeSpec -> exchangeSpec.anyExchange().authenticated())
                .oauth2ResourceServer(oAuth2 -> oAuth2.jwt(jwt -> jwt.jwtDecoder(jwtDecoder())));
http.oauth2Login();

public class UsernameSubClaimAdapter implements Converter<Map<String, Object>, Map<String, Object>> {
    private final MappedJwtClaimSetConverter delegate =
            MappedJwtClaimSetConverter.withDefaults(Collections.emptyMap());

    public Map<String, Object> convert(Map<String, Object> claims) {
        Map<String, Object> convertedClaims = this.delegate.convert(claims);

        String username = "anonymous_user";
        convertedClaims.put("sub", username);

        return convertedClaims;
    }
}

@Bean
public NimbusReactiveJwtDecoder jwtDecoder() {
    NimbusReactiveJwtDecoder jwtDecoder = new NimbusReactiveJwtDecoder(processor());
    jwtDecoder.setJwtValidator(JwtValidators.createDefaultWithIssuer(_jwtIssuerUrl));
    jwtDecoder.setClaimSetConverter(new UsernameSubClaimAdapter());
    return jwtDecoder;
}
 类似资料:
  • 我正在尝试将PSK与mbedtls库与SGX结合使用。没有PSK,连接工作正常。 以下是相关的客户端代码: 我有openssl测试服务器运行: 服务器接收连接并交换PSK消息,但是在解密点我收到以下错误: 我还尝试将更改为不同的密码,但仍然是相同的错误。当完全省略密码时,连接正常,但没有执行PSK!?

  • 我的应用程序中使用了Google的oauth2,其中id_token是由句点分隔的字符串。我在这里读到,id_token被划分为三部分,第二部分包含实际有效负载。如果我将字符串拆分并解码第二个值,我将使用我的帐户得到我期望的结果。但我不明白的是,当我尝试使用我妻子的帐户时,如果我将第二部分解析为json,就会出现“意外字符”错误。 我尝试过从控制台获取字符串并通过在线Base64解码器运行它,但确

  • 我正在尝试在我的android应用中使用Firebase发布的新Firestore。不幸的是,当我试图写入数据库时,我一直得到这个错误。 这就是我一直得到的错误。我已经通过助手将这个项目添加到了我的android应用程序中。所以不应该有什么问题。

  • 本文向大家介绍sqlserver登陆后报不能为空不能为null的错误,包括了sqlserver登陆后报不能为空不能为null的错误的使用技巧和注意事项,需要的朋友参考一下 sql server 2012 值不能为null。参数名:viewinfo (microsoft.sqlserver.managemenmen) 是因为在C:\Users\你的用户名\AppData\Local\Temp\中,缺

  • 我们为源和目标定义了一个mapstruct映射器,该映射器也使用Lombok。 null 对于选项3和4,我找不到mapstruct是否支持它的答案。

  • 方法定义是一个无效方法,但它的定义头部没有。为什么它不会导致错误?我尝试了另一个方法定义,而另一个确实会导致错误:。