Search code examples
spring-securityoauth-2.0spring-security-oauth2

401 Unauthorized from POST http://127.0.0.1:8081/postArticles of grant-type urn:ietf:params:oauth:grant-type:jwt-bearer with spring security 5 oauth2


Recently,i wanna to try the grant-type of "urn:ietf:params:oauth:grant-type:jwt-bearer" with spring security5 oauth2,but got some errors and i don't know why. Could anyone please help me? Thanks very much!

I have 3 projects:spring-security-oauth2-authorization_server(8080),spring-security-oauth2-resource_server(8081),spring-security-oauth2-client(8082). My code snippet is shown bellow.

1.spring-security-oauth2-client

ArticlesController.java:

   @GetMapping(value = "/postArticles")
    public String postArticles(
      @RegisteredOAuth2AuthorizedClient("articles-client-jwt-bearer") OAuth2AuthorizedClient authorizedClient
        ,HttpServletRequest request
        ,HttpServletResponse response
      ) 
    {    
        return webClient
          .post()
          .uri("http://127.0.0.1:8081/postArticles")
          .attributes(oauth2AuthorizedClient(authorizedClient))
          .bodyValue("new articles" + new Date())
          .retrieve()
          .bodyToMono(String.class)
          .block();
        
    }

WebClientConfig.java:

    @Bean(name = "wc")
    WebClient webClient(
    OAuth2AuthorizedClientManager authorizedClientManager) {
        ServletOAuth2AuthorizedClientExchangeFilterFunction oauth2Client = new ServletOAuth2AuthorizedClientExchangeFilterFunction(
                authorizedClientManager);
        return WebClient.builder().apply(oauth2Client.oauth2Configuration()).build();

    }
    @Bean
    OAuth2AuthorizedClientManager authorizedClientManager(ClientRegistrationRepository clientRegistrationRepository,
            OAuth2AuthorizedClientRepository authorizedClientRepository) {

        OAuth2AuthorizedClientProvider authorizedClientProvider = OAuth2AuthorizedClientProviderBuilder.builder()
                .authorizationCode()
                .refreshToken()
                .clientCredentials()
                .provider(new JwtBearerOAuth2AuthorizedClientProvider())
                .build();

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

SecurityConfig.java:

    @Bean
    SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
//          .authorizeRequests(authorizeRequests ->
//            authorizeRequests.anyRequest().authenticated()
//          )
          .headers().frameOptions().sameOrigin() //for h2
          .and()
          .csrf().ignoringAntMatchers("/h2-console/**")
          .and()
          .oauth2Client(withDefaults())
          ;
        return http.build();
    }

application-oauth2.properties:

spring.security.oauth2.client.registration.articles-client-jwt-bearer.provider: spring
spring.security.oauth2.client.registration.articles-client-jwt-bearer.client-id: articles-client
spring.security.oauth2.client.registration.articles-client-jwt-bearer.client-secret: secret
spring.security.oauth2.client.registration.articles-client-jwt-bearer.authorization-grant-type: urn:ietf:params:oauth:grant-type:jwt-bearer
spring.security.oauth2.client.registration.articles-client-jwt-bearer.scope: articles.read,articles.write
spring.security.oauth2.client.registration.articles-client-jwt-bearer.client-name: articles-client-jwt-bearer


spring.security.oauth2.client.provider.spring.issuer-uri: http://auth-server:8080

2.spring-security-oauth2-authorization_server

AuthorizationServerConfig.java:


@Configuration(proxyBeanMethods = false)
public class AuthorizationServerConfig {

    


    /**
     * A Spring Security filter chain for the Protocol Endpoints.
     * 
     * @param http
     * @return
     * @throws Exception
     */
    @Bean
    @Order(Ordered.HIGHEST_PRECEDENCE)
    public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
        OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
        http
                // Redirect to the login page when not authenticated from the
                // authorization endpoint
                .exceptionHandling((exceptions) -> exceptions
                        .authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/login")))
                .oauth2ResourceServer().jwt()
                .and().and()

                ;
        return http.build();
    }

    /**
     * An instance of RegisteredClientRepository for managing clients. Registered
     * information.
     * 
     * @return
     */
    @Bean
    public RegisteredClientRepository registeredClientRepository(JdbcOperations jdbcOperations) {
        RegisteredClient registeredClient = RegisteredClient.withId(UUID.randomUUID().toString())
                .clientId("articles-client")
                .clientSecret("{noop}secret")
//              .clientName("client1")
                .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
                .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
                .authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
                .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
                .authorizationGrantType(AuthorizationGrantType.JWT_BEARER)
                .redirectUri("http://127.0.0.1:8080/login/oauth2/code/articles-client-oidc")
                .redirectUri("http://127.0.0.1:8080/authorized")
                .scope(OidcScopes.OPENID)
                .scope(OidcScopes.PROFILE)
                .scope("articles.read")
                .scope("articles.write")
                .tokenSettings(TokenSettings.builder().accessTokenTimeToLive(Duration.ofDays(2))
                        .refreshTokenTimeToLive(Duration.ofDays(3)).build())
                .clientSettings(ClientSettings.builder().requireAuthorizationConsent(false) 
                        .build())
                .build();

        return new InMemoryRegisteredClientRepository(registeredClient); 
    }

    @Bean
    public OAuth2AuthorizationService authorizationService(JdbcOperations jdbcOperations,
            RegisteredClientRepository registeredClientRepository) {
        return new InMemoryOAuth2AuthorizationService();
    }

    @Bean
    public OAuth2AuthorizationConsentService authorizationConsentService(JdbcOperations jdbcOperations,
            RegisteredClientRepository registeredClientRepository) {
        return new InMemoryOAuth2AuthorizationConsentService();
    }

    /**
     * An instance of com.nimbusds.jose.jwk.source.JWKSource for signing access
     * tokens.
     * 
     * @return
     */
    @Bean
    public JWKSource<SecurityContext> jwkSource() {
        KeyPair keyPair = generateRsaKey();
        RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
        RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
        RSAKey rsaKey = new RSAKey.Builder(publicKey).privateKey(privateKey).keyID(UUID.randomUUID().toString())
                .build();
        JWKSet jwkSet = new JWKSet(rsaKey);
        return new ImmutableJWKSet<>(jwkSet);
    }

    /**
     * An instance of java.security.KeyPair with keys generated on startup used to
     * create the JWKSource above.
     * 
     * @return
     */
    private static KeyPair generateRsaKey() {
        KeyPair keyPair;
        try {
            KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
            keyPairGenerator.initialize(2048);
            keyPair = keyPairGenerator.generateKeyPair();
        } catch (Exception ex) {
            throw new IllegalStateException(ex);
        }
        return keyPair;
    }

    /**
     * An instance of ProviderSettings to configure Spring Authorization Server.
     * 
     * @return
     */
    @Bean
    public ProviderSettings providerSettings() {
        return ProviderSettings.builder().issuer("http://auth-server:8080").build();
    }

    @Bean
    public JwtDecoder jwtDecoder(JWKSource<SecurityContext> jwkSource) {
        return OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource);
    }

}

3.spring-security-oauth2-resource_server

ResourceServerConfig.java

    @Bean
    @Order(2)
    public SecurityFilterChain resourceServerFilterChain(HttpSecurity http) throws Exception {
        http  
            .authorizeHttpRequests(authorize -> authorize
                .mvcMatchers(HttpMethod.POST, "/postArticles").hasAuthority("SCOPE_articles.write")
                .anyRequest().authenticated()
            )
            .csrf().disable() 
            .oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt)
            ;
        return http.build();
    }

ResourceController.java

    @ResponseStatus(HttpStatus.CREATED)
    @PostMapping("/postArticles")
    public String postArticles(@RequestBody String newArticle) {
        return newArticle;
    }

application-oauth2.properties:

spring.security.oauth2.resourceserver.jwt.issuer-uri: http://auth-server:8080

Solution

  • The JWT Bearer grant has not been implemented yet in Spring Authorization Server. See #546 and the feature list.