Search code examples
spring-securityoauth-2.0access-tokenspring-authorization-server

Spring authorization server redirect to login page when token is requested


We currently have 2 web applications that authenticate against a CAS. The communication between them is done via Basic Auth.

enter image description here

For security reasons we want to switch to OAuth2 to get rid of the Basic Auth. At the same time we want to get rid of the CAS because it does not meet our requirements.

So the goal is something along those lines:

enter image description here

I am at the point that I can log in to the user management and manipulate data. The 2nd application also correctly redirects me to the 1st application to authenticate me. Authentication also works. And the 1st application responds correctly to the 2nd application. The 2nd application then executes the expected POST to the url "oauth2/token". But the 1st application replies with a 302 to "/login" instead of a token.

Which point am I missing or do I still have to configure?

I have orientated myself on the following documentation: https://docs.spring.io/spring-authorization-server/docs/current/reference/html/getting-started.html

My AuthorizationServerConfig looks like this:

@Configuration
public class AuthorizationServerConfig {

  private final SecurityApp app;

  public AuthorizationServerConfig(SecurityApp app) {
    this.app = app;
  }

  @Bean
  @Order(1)
  public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http)
      throws Exception {

    OAuth2AuthorizationServerConfigurer authorizationServerConfigurer =
        new OAuth2AuthorizationServerConfigurer();
    authorizationServerConfigurer
        .authorizationEndpoint(authorizationEndpoint ->
            authorizationEndpoint.consentPage("/oauth2/authorize"))
        .oidc(Customizer.withDefaults());   // Enable OpenID Connect 1.0

    RequestMatcher endpointsMatcher = authorizationServerConfigurer
        .getEndpointsMatcher();

    http
        .securityMatcher(endpointsMatcher)
        .authorizeHttpRequests(authorize ->
            authorize.anyRequest().authenticated()
        )
        .csrf(csrf -> csrf.ignoringRequestMatchers(endpointsMatcher))
        .exceptionHandling(exceptions ->
            exceptions.authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/login"))
        )
        .oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt)
        .apply(authorizationServerConfigurer);
    return http.build();
  }

  @Bean
  @Order(2)
  public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http)
      throws Exception {
    http
        .authorizeHttpRequests((authorize) -> authorize
            .anyRequest().authenticated()
        )
        // Form login handles the redirect to the login page from the
        // authorization server filter chain
        .formLogin(Customizer.withDefaults());

    return http.build();
  }

  @Bean
  UserDetailsService users() {
    return app::findUserByLogin;
  }

  // OVERWATCH

  @Bean
  public RegisteredClientRepository registeredClientRepository() {

    return new RegisteredClientRepository() {
      @Override
      public void save(RegisteredClient registeredClient) {
        throw new NotImplementedException();
      }

      @Override
      public RegisteredClient findById(String id) {
        return app.findByClientId(id);
      }

      @Override
      public RegisteredClient findByClientId(String clientId) {
        return app.findByClientId(clientId);
      }
    };
  }

  @Bean
  public JWKSource<SecurityContext> jwkSource() {
    RSAPublicKey publicKey = app.getPublicKey();
    RSAPrivateKey privateKey = (RSAPrivateKey) app.getPrivateKey();
    RSAKey rsaKey = new RSAKey.Builder(publicKey)
        .privateKey(privateKey)
        .keyID(UUID.randomUUID().toString())
        .build();
    JWKSet jwkSet = new JWKSet(rsaKey);
    return new ImmutableJWKSet<>(jwkSet);
  }

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

  @Bean
  public AuthorizationServerSettings authorizationServerSettings() {
    return AuthorizationServerSettings.builder().build();
  }


  // region Password Authenticator

  @Bean
  public PasswordEncoder passwordEncoder() {
    return app.passwordEncoder();
  }

  // endregion
}

And the Configuration from App 2 looks like this:

@Configuration
@EnableWebSecurity
public class AceSecurityConfiguration extends WebSecurityConfigurerAdapter {

  @Override
  protected void configure(HttpSecurity http) throws Exception {
    http
        .antMatcher("/**")
        .authorizeRequests()
        .antMatchers("/oauth/authorize**", "/login**", "/error**")
        .permitAll()
        .and()
        .authorizeRequests()
        .anyRequest().authenticated()
        .and()
        .oauth2Login( oauth2Login -> oauth2Login.defaultSuccessUrl("/index.html") );
  }
}

application.properties from App2 is:

spring.security.oauth2.client.registration.<provider-name>.client-id=${app.uuid}
spring.security.oauth2.client.registration.<provider-name>.client-secret=${app.secret}
spring.security.oauth2.client.registration.<provider-name>.scope=openid
spring.security.oauth2.client.registration.<provider-name>.redirect-uri=http://127.0.0.1:8088/login/oauth2/code/<provider-name>
spring.security.oauth2.client.registration.<provider-name>.client-name=${app.name}
spring.security.oauth2.client.registration.<provider-name>.provider=${provider.name}
spring.security.oauth2.client.registration.<provider-name>.client-authentication-method=code
spring.security.oauth2.client.registration.<provider-name>.authorization-grant type=authorization_code

spring.security.oauth2.client.provider.<provider-name>.authorization-uri=http://localhost:8086/oauth2/authorize
spring.security.oauth2.client.provider.<provider-name>.token-uri=http://localhost:8086/oauth2/token
spring.security.oauth2.client.provider.<provider-name>.user-info-uri=http://localhost:8086/oauth2/userinfo?schema=openid
spring.security.oauth2.client.provider.<provider-name>.user-name-attribute=name
spring.security.oauth2.client.provider.<provider-name>.user-info-authentication-method=header
spring.security.oauth2.client.provider.<provider-name>.jwk-set-uri=http://localhost:8086/jwks

The request from App2 to App 1 that causes the redirect is:

POST http://localhost:8086/oauth2/token

Body:
grant_type=authorization_code, 
code=Zls0ppjnS_RXyMVPB8fg_eQQgoiUAxRguOMsdyVYQpgd8eDkUDzgz813L0ybovTL7sNj0TDRUHibPfek9NzwULND1mty5WPW2DOtQjTAaEROL3qP7RvyTWXTEzzYe-o,
redirect_uri=[http://127.0.0.1:8088/login/oauth2/code/<provider-name>],
client_id=<app.uuid>

Header:
Accept:"application/json;charset=UTF-8", 
Content-Type:"application/x-www-form-urlencoded;charset=UTF-8"

Solution

  • For people running into the same or a similar problem. The actual problem was:

    spring.security.oauth2.client.registration.<provider-name>.client-authentication-method=code.
    

    A different authentication method was declared in the database.

    Another problem was the declaration of the PasswordEncoder. Passwords in the database were stored BCrypt encrypted. The shared secret, however, was stored unencrypted in the database (in order to be able to display it in the frontend). So there was a problem comparing these two secrets.