7

I want to migrate this Spring security configuration to latest Spring Cloud:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.security.oauth2.resource.ResourceServerProperties;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.error.OAuth2AuthenticationEntryPoint;
import org.springframework.security.oauth2.provider.token.DefaultAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;

public class DefaultSecurityConfig extends ResourceServerConfigurerAdapter {

  @Autowired
  private ResourceServerProperties resource;

  @Autowired
  private CustomUserDataAuthenticationConverter customUserDataAuthenticationConverter;

  public DefaultSecurityConfig() {
  }

  @Override
  public void configure(final HttpSecurity http) throws Exception {

    http.authorizeRequests()
        .antMatchers("/configuration/**",)
        .permitAll();

    http.authorizeRequests().antMatchers("/**").authenticated();

    final OAuth2AuthenticationEntryPoint oAuth2AuthenticationEntryPoint =
        new CustomOAuth2AuthenticationEntryPoint();

    http.exceptionHandling().authenticationEntryPoint(oAuth2AuthenticationEntryPoint);
  }

  @Override
  public void configure(final ResourceServerSecurityConfigurer resources) {
    resources.tokenServices(tokenServices());
    resources.resourceId(resource.getResourceId());
  }

  private TokenStore customTokenStore() {
    return new JwtTokenStore(accessTokenConverter());
  }

  private JwtAccessTokenConverter accessTokenConverter() {
    JwtAccessTokenConverter converter = new NonValidatingAccessTokenConverter();
    converter.setAccessTokenConverter(customAccessTokenConverter());
    return converter;
  }

  private DefaultTokenServices tokenServices() {
    DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
    defaultTokenServices.setTokenStore(customTokenStore());
    return defaultTokenServices;
  }

  private DefaultAccessTokenConverter customAccessTokenConverter() {
    DefaultAccessTokenConverter tokenConverter = new DefaultAccessTokenConverter();
    tokenConverter.setUserTokenConverter(customUserDataAuthenticationConverter);
    return tokenConverter;
  }
}

As you can see I migrate parts of the code but several issues are not clear:

  1. implementation 'org.springframework.security.oauth.boot:spring-security-oauth2-autoconfigure' Is removed from spring.dependency.management package. What package should replace it?

  2. ResourceServerConfigurerAdapter is deprecated. How to replace it?

  3. OAuth2AuthenticationEntryPoint is deprecated. How to replace it?

What should be the proper way to migrate the code accordingly?

Olivier
  • 13,283
  • 1
  • 8
  • 24
Peter Penzov
  • 1,126
  • 134
  • 430
  • 808

2 Answers2

2

How in official documentation was descried that spring-security-oauth-reaches-end-of-life

And also how we discussed in comments, you will have an authorization-server Keycloak as Identity Provider with oAuth2.0 and Oidc connect.

Your migration code will have next steps:

Replace current one with Resource-server in your case with JWT as Bearer Token

Gradle/Maven configuration:

Gradle:

  implementation group: 'org.springframework.boot', name: 'spring-boot-starter-security', version: '3.1.2'

  implementation group: 'org.springframework.boot', name: 'spring-boot-starter-oauth2-resource-server', version: '3.1.2'

Maven:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
    <version>3.1.2</version>
</dependency>

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
    <version>3.1.2</version>
</dependency>

In application.yml file you should provide next ones paths:

spring:
  security:
    oauth2:
      resourceserver:
        jwt:
          issuer-uri: 
          jwk-set-uri: 

This values you will find out to : /.well-known/openid-configuration path on Keycloak side.

Where you security config layer will look so:

@Configuration
@EnableWebSecurity
public class WebSecurityConfig {

     
  @Bean
  public AuthenticationManager authenticationManager(
      AuthenticationConfiguration authenticationConfiguration) throws Exception {
    return authenticationConfiguration.getAuthenticationManager();
  }

  @Bean
  public SecurityFilterChain securityFilterChain(HttpSecurity request) throws Exception {

    request.cors(corsConfigurer -> corsConfigurer.configurationSource(yourCustomCorsConfiguration))
        .csrf(AbstractHttpConfigurer::disable);

    request.headers(http -> http.frameOptions(FrameOptionsConfig::sameOrigin));
    request.sessionManagement(sessionAuthenticationStrategy ->
        sessionAuthenticationStrategy.sessionCreationPolicy(SessionCreationPolicy.STATELESS));

    request.authorizeHttpRequests(requestMatcherRegistry -> {

      requestMatcherRegistry.anyRequest().authenticated();
    });

    request.oauth2ResourceServer(httpSecurityOAuth2ResourceServerConfigurer ->
            httpSecurityOAuth2ResourceServerConfigurer.jwt(
                token -> token.jwtAuthenticationConverter(myConverter())));

    return request.build();
  }

@Bean
public JwtDecoder jwtDecoder() {
    return NimbusJwtDecoder.withJwkSetUri(jwkSetUri).build();
}

}

And also you should to have a look to oauth2-client, spring-authorization-server

Andrei Lisa
  • 1,361
  • 3
  • 11
  • There are some inconsistencies in this answer: the resource server is configured with an Authentication converter bean that is not defined and an authentication manager bean is defined but not injected in the resource server config... Note that none of the two is required if you use the starter I linked in my answer. Also, a JWT decoder is auto-configured by Spring Boot already. Last, this applies to servlet applications only (not reactive ones like spring-cloud-gateway is for instance) – ch4mp Aug 24 '23 at 07:24
2

Reactive or Servlet?

First thing to check is if the application to secure is reactive or a servlet. Spring Security implementations to use are not the same (SecurityWebFilterChain in reactive applications VS SecurityFilterChain in servlets and each depend on different components).

spring-cloud-gateway for instance is a reactive application and requires reactive Security beans. So, you should check your project(s) dependencies (and transient dependencies) to see if you have spring-boot-starter-web (servlet) or spring-boot-starter-webflux (reactive apps) on your classpath.

Configuring a resource server

The easiest way to configure a resource server is using spring-addons-starter-oidc, a starter I wrote, in addition to spring-boot-starter-resource-server:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
</dependency>
<dependency>
    <groupId>com.c4-soft.springaddons</groupId>
    <artifactId>spring-addons-starter-oidc</artifactId>
    <version>7.1.3</version>
</dependency>
// @EnableMethodSecurity is optional, required only if you use `@PreAuthorize`, `@PostFilter` and alike
// In a reactive application, use @EnableReactiveMethodSecurity instead
@EnableMethodSecurity
@Configuration
public class SecurityConfig {
}
scheme: http
origins: ${scheme}://localhost:4200
keycloak-port: 8442
keycloak-issuer: ${scheme}://localhost:${keycloak-port}/realms/master

com:
  c4-soft:
    springaddons:
      oidc:
        ops:
        # You can add as many OpenID providers as you trust (Keycloak realms or instances, or even other providers like Auth0, Cognito, etc.)
        - iss: ${keycloak-issuer}
          # this is optional: use "preferred_username" claim (rather than "sub") to set Authentication "name"
          username-claim: preferred_username
          # you can add as any many JSON path as you want to use as Spring Authorities source
          # you may also define a prefix to add or transformation to uppercase for each
          authorities:
          - path: $.realm_access.roles
          - path: $.resource_access.*.roles
        resourceserver:
          # the path matchers below determine what is accessible to anonymous requests
          # requests not matched must be authorized
          permit-all:
          - "/public/**"
          - "/actuator/health/readiness"
          - "/actuator/health/liveness"
          - "/v3/api-docs/**"
          # Fine grained CORS configuration from properties (you can change allowed origins for each environment by just setting a configuration property)
          cors:
          - path: /**
            allowed-origin-patterns: ${origins}

If you don't want to use "my" starter, then you'll have much more Java code to write, and this Java code will depend on your app being a servlet or a reactive application. More on that in my tutorials and in an answer I posted there.

Consider configuring gateway as an OAuth2 client

We have little to no information about the "cloud apps" you want to configure, but if you are configuring a spring-cloud-gateway instance in front of Javascript based applications (Angular, React, Vue, etc.), you should consider:

  • configuring the gateway as an OAuth2 client (with oauth2Login) and with TokenRelay= filter
  • configuring downstream REST APIs as resource servers
  • removing anything related to OAuth2 from upstream Javascript based applications (it will be secured with sessions)

This is called the BFF pattern and is safer than using using public clients (a client in a browser or mobile application can't keep a secret) where tokens are exposed to Javascript code (session cookie can be flagged with same-site, http-only, and be exchanged with SSL only).

You might configure some routes without security at all for upstream applications which are OAuth2 clients. I have a tutorial for spring-cloud-gateway configured as BFF on some routes (for an Angular app) and with permitAll (and no token relay) there.

Last, configuring a gateway as a resource server to centralize security rules is a bad pattern to me:

  • the service itself is not secured (or access rules are doubled which brings the usual code duplication issues)
  • it requires tight coupling with downstream services implementation (need to know a minimum of routes details and maybe resources structures when access rule involves the resource itself)
  • it makes it pretty hard to unit test access rules (it's an easy task on the REST API itself when it is configured as a resource server)
ch4mp
  • 6,622
  • 6
  • 29
  • 49