Springboot JWT 通过 OIDC 使用 Auth0 签名无效

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

我正在尝试学习 spring 安全性并创建了一个示例项目,其中使用 OIDC 和 PKCE 通过 springboot 对基于角度的 UI 进行身份验证。我按照 Spring 的本教程 https://docs.spring.io/spring-authorization-server/reference/guides/how-to-social-login.html 使用 Auth0 对用户进行身份验证。我能够正确登录,但生成的访问令牌和 ID 令牌始终无法通过签名验证。我的配置文件中是否缺少任何可能导致签名失败的内容?

application.yaml

server:
  port: 9000
auth:
    client: <insert auth0 address here>
spring:
  security:
    oauth2:
      resourceserver:
        jwt:
          issuer-uri: ${auth.client}
          jwk-set-uri: ${auth.client}/.well-known/jwks.json
      client:
        registration:
          my-client:
            provider: test
            authorization-grant-type: authorization_code
            redirect-uri: http://localhost:9000/login/oauth2/code/my-client
            client-authentication-method: none
            client-id: xxxx
            client-secret: yyyyyy
            scope:
              - openid
              - profile
              - email
              - read.data
        provider:
          test:
            issuer-uri: ${auth.client}/
            authorization-uri: ${auth.client}/authorize
            token-uri: ${auth.client}/oauth/token
            user-info-uri: ${auth.client}/userinfo
            user-name-attribute: sub
            jwk-set-uri: ${auth.client}/.well-known/jwks.json
      authorizationserver:
        client:
          - my-client:
            registration:
              client-id: "public-client"
              client-authentication-methods:
                - "none"
              authorization-grant-types:
                - "authorization_code"
              redirect-uris:
                - "http://localhost:4200"
              scopes:
                - "openid"
                - "profile"
                - "offline_access"
            require-authorization-consent: true
            require-proof-key: true
            token:
                access-token-time-to-live: 600s

安全配置.java

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    @Order(1)
    public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http)
            throws Exception {
        OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
        http.getConfigurer(OAuth2AuthorizationServerConfigurer.class)
            .oidc(Customizer.withDefaults());   // Enable OpenID Connect 1.0
        
                http
            // Redirect to the OAuth 2.0 Login endpoint when not authenticated
            // from the authorization endpoint
            .exceptionHandling((exceptions) -> exceptions
                .defaultAuthenticationEntryPointFor( 
                    new LoginUrlAuthenticationEntryPoint("/oauth2/authorization/my-client"),
                    new MediaTypeRequestMatcher(MediaType.TEXT_HTML)
                )
            )
            // Accept access tokens for User Info and/or Client Registration
            .oauth2ResourceServer((oauth2) -> oauth2.jwt(Customizer.withDefaults()));

        return http.cors(Customizer.withDefaults()).build();
    }

    @Bean
    @Order(2)
    public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http)
            throws Exception {
        http
            .authorizeHttpRequests((authorize) -> authorize
                .anyRequest().authenticated()
            )
                        .oauth2Login(Customizer.withDefaults()); 
        return http.cors(Customizer.withDefaults()).build();
    }

    @Bean
    public CorsConfigurationSource corsConfigurationSource() {
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        CorsConfiguration config = new CorsConfiguration();
        config.addAllowedHeader("*");
        config.addAllowedMethod("*");
        config.addAllowedOrigin("http://127.0.0.1:4200");
                config.addAllowedOrigin("http://localhost:4200");
        config.setAllowCredentials(true);
        source.registerCorsConfiguration("/**", config);
        return source;
    }
              
}

这是一个访问令牌示例

eyJraWQiOiJmOGI2OWFmNi1jYmE1LTQ1YzAtOGY4NC1kNWMzMDI3ZjQzZDUiLCJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJhdXRoMHw2MjA1YzJjYzY5NDVmOTAwNmI1Yzg1YWMiLCJhdWQiOiJwdWJsaWMtY2xpZW50IiwibmJmIjoxNzA1MTU3ODIwLCJzY29wZSI6WyJvcGVuaWQiLCJwcm9maWxlIiwib2ZmbGluZV9hY2Nlc3MiXSwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo5MDAwIiwiZXhwIjoxNzA1MTU4NDIwLCJpYXQiOjE3MDUxNTc4MjAsImp0aSI6ImNjMzRlZTRkLTNmYzQtNGRiZi05YTI2LTBlNmNiNjg5YzMwZiJ9.TUNQLWUYnKjn8XCfyQRtkDLXbt4PBKheP9u60t8by_5C6318NRUkfxJlAh2ye5Gv2ZMWQDjXbTOznB0W5IsMA8x67oq49QRpmHYW_Gd23968DGo00XHAt4GEQaR7COh-Tm7yRwp3ihIwGPWDffYT0bTUrCwzEH8OpYu26CSXzyTa7Cc7VcuTnf1qlRysI2j-jwyIkTM-V8JppffIcuTz-r0dRbxsi4tYGNEvmRghY-QNuApc_BekVr0pOq9rMblaUOMz2zOcoCN0O1ty1XXb-6hLWUemOO3Q7Zdj4N-s6cx42EbFv6L_fsj7oYCYq-Pzt-hzLCDRQfTpYf4-TD9ccw

这是来自本地主机 jwk 端点的结果

{
    "keys": [
        {
            "kty": "RSA",
            "e": "AQAB",
            "kid": "f8b69af6-cba5-45c0-8f84-d5c3027f43d5",
            "n": "s6z7mFu60eiD5zAgpiz6rL2ZsdayRf5xM95BHloWD_xz9n56l6oBX82gXpkF6x4rY9f6OKcCXTFEM37Bu_xvVxrkyAfkZ0XGvJYQv6glrbJWkV7Qfl3H7dWmjO57wWWLn7_Hr9imx-T-y9VuPAKKkK_c2L-yzd8zj1dYf1hmzKnOwK46mIDZ86Q28udNz-pHjcj3RxTjd4f1u4YKh4a66A8EJRTDC4iJ6wjutbggl3Aywb1RNksp1HlmEl2ifHm51Jw4guPEUSo2KnZajN8qBqUBmLMq1rcHOIM6IMsvFsrto-ZESwvgKJobcGIVFystPbEljA0pEbtK2UZTVWCi0Q"
        }
    ]
}

这是来自 auth0 的 jwk 端点

{
    "keys": [
        {
            "kty": "RSA",
            "use": "sig",
            "n": "nGXHKrVaXqj1jOgZ37FD4BIVYlhvL-CGflccHEczP7vtYfArkNOVjW4V4DY1zQNHd9da5WKdVuuEzjsAC_E74u7u6zTJVhLC4reYCPqClkK-fxLRlRSeGTRbEjXxk4hJZ2bdEnzfM_RK8mcxLgSDF4jar45atWHvebsaI8LdzxnaOLqA2aQbKQbhPnGnngdR6kdZTWSBq-Kkmh73r4hZ0Nm9cKgA7q9v06Uq1XGiEqgDlCn3_dSDaUaZKJvrDSOMtftv71O15mDrC8bJLufF_oROOTrSqk-9LMBnjO5vUSK4A4i-JjR4hQnAZvryVW6RyqUiEkaU6lWR_miwAqZytw",
            "e": "AQAB",
            "kid": "MMBwle23wTQWIvx-CW55l",
            "x5t": "OmYvzIheoHg5Ik2y2c7iXbxxpwU",
            "x5c": [
                "MIIDDTCCAfWgAwIBAgIJC0XZSRME3+GKMA0GCSqGSIb3DQEBCwUAMCQxIjAgBgNVBAMTGWRldi03d2EwaHptcC51cy5hdXRoMC5jb20wHhcNMjIwMjA4MjI0NzE3WhcNMzUxMDE4MjI0NzE3WjAkMSIwIAYDVQQDExlkZXYtN3dhMGh6bXAudXMuYXV0aDAuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnGXHKrVaXqj1jOgZ37FD4BIVYlhvL+CGflccHEczP7vtYfArkNOVjW4V4DY1zQNHd9da5WKdVuuEzjsAC/E74u7u6zTJVhLC4reYCPqClkK+fxLRlRSeGTRbEjXxk4hJZ2bdEnzfM/RK8mcxLgSDF4jar45atWHvebsaI8LdzxnaOLqA2aQbKQbhPnGnngdR6kdZTWSBq+Kkmh73r4hZ0Nm9cKgA7q9v06Uq1XGiEqgDlCn3/dSDaUaZKJvrDSOMtftv71O15mDrC8bJLufF/oROOTrSqk+9LMBnjO5vUSK4A4i+JjR4hQnAZvryVW6RyqUiEkaU6lWR/miwAqZytwIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTUixf7gM1eEMvzY05IVpq33NpOMDAOBgNVHQ8BAf8EBAMCAoQwDQYJKoZIhvcNAQELBQADggEBABxW2jufvaRzLMFqkRZloAGTiEqpeIDbuoBtd2rzyirsiD2z6z7e4x5ERwBxRVki1AQsqxsezCrqc0EeE4mDxqWvxEJd9wAQKRSgptmMiCC+3ACtHKm6sAGvzpCJt5dAFvqQc1RiPV9nOTHG4VbWbx3EK6jCocMQu/oJfDmGe+V5A/Uz43bLRVJxXG/QqiF6T6paRM+l0j6nxP3WU30vX5NyCILEYabnUHOENMmEaVoarGogwqIv/mb5Rd+RQ14Ab1PYBtvjFF4sSX1oxmAJR+NpZx646JabUg3OmFRUdZKPaS/Sqn7YwGuBtlyMTA1ek/SqLxSoDN91rFlXpFITupI="
            ],
            "alg": "RS256"
        },
        {
            "kty": "RSA",
            "use": "sig",
            "n": "yad3J942bXm02rmBBQ5zuekX7j7cxUugPCnfJukUxQurzpkmxAcfCaXhzzwNjqlRdzd8BVyTwzGfBxdBKJCcor-_6a_D2B8p9B_5-3aCIzrf0zWer9gs_K1E-FXynHnGxXtx3RuddsQXyc423bIFKbq3TJcpVZrubMQPAkJfnIxaVx9_iB4obZYxUW70Gd8dUHcChExBgZZWipy0Tn5FIQUpNxT2_oR7PGqWG2DQW3f08Om21dQha-RiCVMSSQ_8nXjEsYR6r7R8EjCQhukv7B-PsX4vgpjVo8tOCzvfuTeLlwWTP07zGUw0ZTTWm4_v1GTIoYY-5YqU7iuu_AjAOw",
            "e": "AQAB",
            "kid": "hmQrSAlxu9ngNAlLkPeqY",
            "x5t": "gUenOFsCzw4z9VJVIrjuqI4T_UM",
            "x5c": [
                "MIIDDTCCAfWgAwIBAgIJbHLvlGpDKY0SMA0GCSqGSIb3DQEBCwUAMCQxIjAgBgNVBAMTGWRldi03d2EwaHptcC51cy5hdXRoMC5jb20wHhcNMjIwMjA4MjI0NzE3WhcNMzUxMDE4MjI0NzE3WjAkMSIwIAYDVQQDExlkZXYtN3dhMGh6bXAudXMuYXV0aDAuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyad3J942bXm02rmBBQ5zuekX7j7cxUugPCnfJukUxQurzpkmxAcfCaXhzzwNjqlRdzd8BVyTwzGfBxdBKJCcor+/6a/D2B8p9B/5+3aCIzrf0zWer9gs/K1E+FXynHnGxXtx3RuddsQXyc423bIFKbq3TJcpVZrubMQPAkJfnIxaVx9/iB4obZYxUW70Gd8dUHcChExBgZZWipy0Tn5FIQUpNxT2/oR7PGqWG2DQW3f08Om21dQha+RiCVMSSQ/8nXjEsYR6r7R8EjCQhukv7B+PsX4vgpjVo8tOCzvfuTeLlwWTP07zGUw0ZTTWm4/v1GTIoYY+5YqU7iuu/AjAOwIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRSZQM0uQyFnmwU3W+DYUc7z4nQyjAOBgNVHQ8BAf8EBAMCAoQwDQYJKoZIhvcNAQELBQADggEBAF3Psiqk32Dq10WEtT5T2UIxczim6pKGv+edH5yPxDLlfJjnFNK7PMMQS7whyYrMzMZoJv4VUlNvQ3hkg1ICvYzwGDM4kNgRP7TTOQqgomCslpUnqsLNjVwBJBqC0XpMcmldcUXbYaXwUN73PiEkjFDNjjezQnJlO7o3CFySDqaahKevHXoTbex5RjwnrjziC/pVyjlruaEFZUhrAsi+jTQ8NUkPWj2APtU3WhMAYGAvccd9CXl3CSQXRUVtd8yTV7z8YNdG3MAx/JSrpB/m2ghrRUhML7Jn8io4HY0iP4fbQ46FAjLAdWDDDf+IupqEysOegdnuok6dY61qv750C4A="
            ],
            "alg": "RS256"
        }
    ]
}
spring-boot jwt openid-connect auth0 signature
1个回答
0
投票

如果您不将

audience
参数传递给
authorize
端点,Auth0 将返回不透明的访问令牌而不是 JWT。您可以在受众参数中配置 Spring Security pass,如下所示:

package com.example.apigateway;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
import org.springframework.security.oauth2.client.web.DefaultOAuth2AuthorizationRequestResolver;
import org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestResolver;
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
import org.springframework.security.web.SecurityFilterChain;

import java.util.function.Consumer;

@Configuration
public class SecurityConfiguration {

    @Value("${okta.oauth2.audience}")
    private String audience;

    private final ClientRegistrationRepository clientRegistrationRepository;

    public SecurityConfiguration(ClientRegistrationRepository clientRegistrationRepository) {
        this.clientRegistrationRepository = clientRegistrationRepository;
    }

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests(authorize -> authorize
                .anyRequest().authenticated()
            )
            .oauth2Login(oauth2 -> oauth2
                .authorizationEndpoint(authorization -> authorization
                    .authorizationRequestResolver(
                        authorizationRequestResolver(this.clientRegistrationRepository)
                    )
                )
            );
        return http.build();
    }

    private OAuth2AuthorizationRequestResolver authorizationRequestResolver(
        ClientRegistrationRepository clientRegistrationRepository) {

        DefaultOAuth2AuthorizationRequestResolver authorizationRequestResolver =
            new DefaultOAuth2AuthorizationRequestResolver(
                clientRegistrationRepository, "/oauth2/authorization");
        authorizationRequestResolver.setAuthorizationRequestCustomizer(
            authorizationRequestCustomizer());

        return authorizationRequestResolver;
    }

    private Consumer<OAuth2AuthorizationRequest.Builder> authorizationRequestCustomizer() {
        return customizer -> customizer
            .additionalParameters(params -> params.put("audience", audience));
    }
}

如果您需要 WebFlux 版本,请参阅此问题

© www.soinside.com 2019 - 2024. All rights reserved.