Ken DeLong
Ken DeLong

Reputation: 961

Spring Authentication Server is Not Adding OAuth Authentication Providers

I am trying to configure Spring Authorization Server (version 1.1.0, Boot 3.1.0), but it's not working the way I think the docs suggest. When making a request to the authorization endpoint, the OAuth2AuthorizationEndpointFilter is denying authorization because it does not have a provider for the OAuth2AuthorizationCodeRequestAuthenticationToken. The token does have an authenticated UsernamePasswordAuthenticationToken inside of it with the correct GrantedAuthorities, so the upstream part of authentication seems to be working. But the OAuth2AuthorizationCodeRequestAuthenticationConverter turns it into the OAuth specific token.

The AuthenticationManager being used is the main one for the rest of the app, which has only Dao and RememberMe providers. I try to add the OAuth2AuthorizationCodeRequestAuthenticationProvider via the configurer, as seen in the code below, but it's not there when I set a breakpoint.

Here is my security config (the comments are the XML configuration for the previous Spring Security OAuth 2 which was working fine - but not in Boot 3.1.0).

    /*
    <http pattern="/oauth/**" auto-config="true" entry-point-ref="oauthLoginEntryPoint" use-expressions="true" create-session="always">
        <csrf disabled="false"/>
        <intercept-url pattern="/oauth/authorize" access="hasRole('ROLE_MEMBER')" />
        <intercept-url pattern="/oauth/**" access="permitAll"/>

        <!-- UsernamePasswordAuthenticationFilter -->
        <form-login default-target-url="/oauth/authorize"  
                    login-processing-url="/oauth/do_login"
                    authentication-failure-url="/oauth/login?failure=true"
                    authentication-success-handler-ref="oauthAuthenticationSuccessHandler"
                    username-parameter="username"
                    password-parameter="password" />
    </http>
     */
    @Bean
    @Order(103)
    public SecurityFilterChain oauthFilterChain(HttpSecurity http, 
                                                AuthenticationManager authenticationManager,
                                                JdbcRegisteredClientRepository registeredClientRepository,
                                                OAuth2TokenGenerator<?> tokenGenerator,
                                                OAuth2AuthorizationCodeRequestAuthenticationProvider authorizationCodeRequestAuthenticationProvider) 
                                throws Exception
    {
        http
            .securityMatcher("/oauth/**")
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/oauth/authorize").hasRole("MEMBER")
                .requestMatchers("/oauth/**").permitAll()
            )
            .sessionManagement(session -> session
                .sessionCreationPolicy(SessionCreationPolicy.ALWAYS)
                .sessionFixation(c -> c.none())  // FIXME turn it back on 
            )
            .authenticationManager(authenticationManager)
            .formLogin(form -> form
                .successForwardUrl("/oauth/authorize")
                .loginProcessingUrl("/oauth/do_login")
                .failureUrl("/oauth/login?failure=true")
                .successHandler(oauthAuthenticationSuccessHandler())
                .usernameParameter("username")
                .passwordParameter("password")
                .loginPage(OAUTH_LOGIN_VIEW_NAME)
            )
            .exceptionHandling(exception -> exception.authenticationEntryPoint(oauthLoginEntryPoint()))
            .csrf(withDefaults())
            ;
        
        //OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
        OAuth2AuthorizationServerConfigurer authorizationServerConfigurer = new OAuth2AuthorizationServerConfigurer();
        http.apply(authorizationServerConfigurer);
        authorizationServerConfigurer
            .registeredClientRepository(registeredClientRepository) 
            .tokenGenerator(tokenGenerator) 
            .clientAuthentication(clientAuthentication -> clientAuthentication
                    .authenticationProvider(authorizationCodeRequestAuthenticationProvider)
            )  
            .authorizationEndpoint(authorizationEndpoint -> authorizationEndpoint
                    .authenticationProvider(authorizationCodeRequestAuthenticationProvider)
            )    
            .tokenEndpoint(tokenEndpoint -> { })    
        ;
       
        return http.build();
        
    }
    
    /*
    <!--  this forces the redirect back to /oauth/authorize to be https -->
    <beans:bean id="oauthAuthenticationSuccessHandler" class="org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler">
        <beans:property name="redirectStrategy">
            <beans:bean class="com.hatchbaby.service.security.ForceHttpsRedirectStrategy">
                <beans:property name="forceHttps" value="${oauth.https.login}"/>
            </beans:bean>
        </beans:property>
    </beans:bean>
     */
    @Bean
    public SavedRequestAwareAuthenticationSuccessHandler  oauthAuthenticationSuccessHandler()
    {
        SavedRequestAwareAuthenticationSuccessHandler ash = new SavedRequestAwareAuthenticationSuccessHandler();
        ForceHttpsRedirectStrategy rs = new ForceHttpsRedirectStrategy();
        rs.setForceHttps(forceHttps);
        ash.setRedirectStrategy(rs);
        return ash;
    }
    
    @Bean
    public LoginUrlAuthenticationEntryPoint oauthLoginEntryPoint()
    {
        LoginUrlAuthenticationEntryPoint ep = new LoginUrlAuthenticationEntryPoint(OAUTH_LOGIN_VIEW_NAME);
        ep.setForceHttps(forceHttps);
        return ep;
    }

    @Bean
    public JdbcRegisteredClientRepository registeredClientRepository(JdbcOperations jdbcOperations)
    {
        JdbcRegisteredClientRepository repo = new JdbcRegisteredClientRepository(jdbcOperations);
        return repo;
    }
    
    @Bean
    public OAuth2TokenGenerator<?> tokenGenerator(JWKSource<SecurityContext> jwkSource) 
    {
        JwtEncoder jwtEncoder = new NimbusJwtEncoder(jwkSource);
        JwtGenerator jwtGenerator = new JwtGenerator(jwtEncoder);
        OAuth2AccessTokenGenerator accessTokenGenerator = new OAuth2AccessTokenGenerator();
        OAuth2RefreshTokenGenerator refreshTokenGenerator = new OAuth2RefreshTokenGenerator();
        return new DelegatingOAuth2TokenGenerator(
                jwtGenerator, accessTokenGenerator, refreshTokenGenerator);
    }
    
    @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);
    }

    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;
    }

    @Bean
    public AuthorizationServerSettings authorizationServerSettings()
    {
        AuthorizationServerSettings.Builder builder = AuthorizationServerSettings.builder()
                .authorizationEndpoint("/oauth/authorize")
                .tokenEndpoint("/oauth/token")
        ;
        
        return builder.build();
    }
    
    @Bean
    public OAuth2AuthorizationCodeRequestAuthenticationProvider oAuth2AuthorizationCodeRequestAuthenticationProvider(
            RegisteredClientRepository registeredClientRepository,
            OAuth2AuthorizationService authorizationService, 
            OAuth2AuthorizationConsentService authorizationConsentService)
    {
        return new OAuth2AuthorizationCodeRequestAuthenticationProvider(registeredClientRepository, authorizationService, authorizationConsentService);
    }
    
    @Bean
    public OAuth2AuthorizationService oauth2AuthorizationService()
    {
        // FIXME use jdbc
        //JdbcOAuth2AuthorizationService auth =  new JdbcOAuth2AuthorizationService();
        var auth = new InMemoryOAuth2AuthorizationService();
        return auth;
    }
    
    @Bean
    public OAuth2AuthorizationConsentService oauth2AuthorizationConsentService()
    {
        // FIXME not in memory
        var auth = new InMemoryOAuth2AuthorizationConsentService();
        return auth;
    }

Any help on getting that authorizationCodeRequestAuthenticationProvider to show up in the AuthenicationManager?

Upvotes: 0

Views: 403

Answers (1)

Ken DeLong
Ken DeLong

Reputation: 961

In the end I was unable to get the DSL to add the authentication providers that I wanted. I kept adding them to the DSL because I felt like I "should".

    @Bean
    @Order(103)
    public SecurityFilterChain oauthFilterChain(HttpSecurity http, 
                                                AuthenticationManager authenticationManager,
                                                JdbcRegisteredClientRepository registeredClientRepository,
                                                OAuth2AuthorizationCodeRequestAuthenticationProvider authorizationCodeRequestAuthenticationProvider,
                                                OAuth2AuthorizationCodeAuthenticationProvider oauth2AuthorizationCodeAuthenticationProvider,
                                                OAuth2RefreshTokenAuthenticationProvider oauth2RefreshTokenAuthenticationProvider,
                                                OAuth2ClientCredentialsAuthenticationProvider oauth2ClientCredentialsAuthenticationProvider,
                                                ClientSecretAuthenticationProvider clientSecretAuthenticationProvider
                    )   throws Exception
    {
        http
            .securityMatcher("/oauth/**")
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/oauth/authorize").hasRole("MEMBER")
                .requestMatchers("/oauth/**").permitAll()
            )
            .sessionManagement(session -> session
                .sessionCreationPolicy(SessionCreationPolicy.ALWAYS)
                .sessionFixation(c -> c.none())  // FIXME turn it back on 
            )
            .authenticationManager(authenticationManager)
            .formLogin(form -> form
                .successForwardUrl("/oauth/authorize")
                .loginProcessingUrl("/oauth/do_login")
                .failureUrl("/oauth/login?failure=true")
                .successHandler(oauthAuthenticationSuccessHandler())
                .usernameParameter("username")
                .passwordParameter("password")
                .loginPage(OAUTH_LOGIN_VIEW_NAME)
            )
            .exceptionHandling(exception -> exception.authenticationEntryPoint(oauthLoginEntryPoint()))
            .csrf(c -> c.ignoringRequestMatchers("/oauth/token"))
            ;
        
        OAuth2AuthorizationServerConfigurer authorizationServerConfigurer = new OAuth2AuthorizationServerConfigurer();
        http.apply(authorizationServerConfigurer);
        authorizationServerConfigurer
            .registeredClientRepository(registeredClientRepository) 
            .tokenGenerator(tokenGenerator()) 
            .clientAuthentication(clientAuthentication -> clientAuthentication
                    .authenticationProvider(authorizationCodeRequestAuthenticationProvider)
            )  
            .authorizationEndpoint(authorizationEndpoint -> authorizationEndpoint
                    .authenticationProvider(authorizationCodeRequestAuthenticationProvider)
            )    
            .tokenEndpoint(tokenEndpoint -> tokenEndpoint
                    .authenticationProvider(oauth2AuthorizationCodeAuthenticationProvider)
                    .authenticationProvider(oauth2RefreshTokenAuthenticationProvider)
                    .authenticationProvider(oauth2ClientCredentialsAuthenticationProvider)
                    .authenticationProvider(clientSecretAuthenticationProvider)
            )    
        ;
       
        return http.build();
        
    }

But in order to make it work, I had to go back to the main AuthenticationManager and update it:

    @Bean(name = {BeanIds.AUTHENTICATION_MANAGER, "authenticationManager"})
    @Primary
    public AuthenticationManager authenticationManager(
            OAuth2AuthorizationCodeRequestAuthenticationProvider oauth2AuthorizationCodeRequestAuthenticationProvider,
            OAuth2AuthorizationCodeAuthenticationProvider oauth2AuthorizationCodeAuthenticationProvider,
            OAuth2RefreshTokenAuthenticationProvider oauth2RefreshTokenAuthenticationProvider,
            OAuth2ClientCredentialsAuthenticationProvider oauth2ClientCredentialsAuthenticationProvider,
            ClientSecretAuthenticationProvider clientSecretAuthenticationProvider,
            OauthSecretKeySource keySource,
            LegacyOauthAuthenticationProvider legacyOauthAuthenticationProvider
        )
    {
        // FIXME I don't like having to add the whole Oauth guys here
        List<AuthenticationProvider> aps = List.of(
                authProvider(),
                rememberMeAuthenticationProvider(),
                oauth2AuthorizationCodeRequestAuthenticationProvider,
                oauth2AuthorizationCodeAuthenticationProvider,
                oauth2RefreshTokenAuthenticationProvider,
                oauth2ClientCredentialsAuthenticationProvider,
                clientSecretAuthenticationProvider,
                legacyOauthAuthenticationProvider,
                jwtAuthenticationProvider(keySource)
        );
        ProviderManager pm = new ProviderManager(aps);
        return pm;
    }

So the Oauth guys are added to the AuthenticationManager for everyone, while I was hoping they'd be limited to that SecurityFilterChain. Not ideal, but it's working.

Upvotes: 0

Related Questions