Spring Security avec OAuth 2 et OpenID Connect

Application Web

Pour une application à l’ancienne, on utilise le flux Authorization Code Grant, qui implique un User Agent, un Client et l'Authorization Server.

Dépendances

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

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

WebSecurityConfigurerAdapter

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .authorizeRequests()
                .antMatchers("/echo/**").authenticated()
                .antMatchers("/").permitAll()
            .and()
                .oauth2Login();
    }

}

Configuration YAML

4 providers prédéfinis

  • Google

  • Github

  • Facebook

  • Okta

Provider maison (exemple avec Keycloak)

spring:
  security:
    oauth2:
      client:
        registration:
          keycloak:
            client-id: example-client
            client-secret: example-secret
            authorization-grant-type: authorization_code
            redirect-uri: '{baseUrl}/login/oauth2/code/{registrationId}'
            client-authentication-method: basic
            client-name: Keycloak
            scope:
              - openid
              - profile
              - email
              - address
              - phone
        provider:
          keycloak:
            authorization-uri: http://localhost:8888/auth/realms/example-realm/protocol/openid-connect/auth
            token-uri: http://localhost:8888/auth/realms/example-realm/protocol/openid-connect/token
            jwk-set-uri: http://localhost:8888/auth/realms/example-realm/protocol/openid-connect/certs

Configuration par code

Un peu plus de code, et un peu moins de YAML, en remplacement de gros application.yml précédent.

example:
  keycloak:
    client-id: example-client
    client-secret: example-secret
    base-url: http://localhost:8888
    realm: example-realm
@Configuration
public class KeycloakConfig {

    @Value("${example.keycloak.client-id}")
    private String clientId;

    @Value("${example.keycloak.client-secret}")
    private String clientSecret;

    @Value("${example.keycloak.base-url}")
    private String baseUrl;

    @Value("${example.keycloak.realm}")
    private String realm;

    @Bean
    public ClientRegistrationRepository clientRegistrationRepository() {
        return new InMemoryClientRegistrationRepository(this.keycloakClientRegistration());
    }

    private ClientRegistration keycloakClientRegistration() {
        return ClientRegistration.withRegistrationId("keycloak")
                .clientId(clientId)
                .clientSecret(clientSecret)
                .clientAuthenticationMethod(ClientAuthenticationMethod.BASIC)
                .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
                .redirectUri("{baseUrl}/login/oauth2/code/{registrationId}")
                .scope("openid", "profile", "email", "address", "phone")
                .clientName("Keycloak")
                .authorizationUri(baseUrl + "/auth/realms/" + realm + "/protocol/openid-connect/auth")
                .tokenUri(baseUrl + "/auth/realms/" + realm + "/protocol/openid-connect/token")
                .jwkSetUri(baseUrl + "/auth/realms/" + realm + "/protocol/openid-connect/certs")
                .build();
    }

}

Username

  private String getUsername() {
      Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
      return ((DefaultOidcUser)principal).getPreferredUsername();
  }

Application SPA

Pour une application plus moderne en Single Page Application, le serveur Spring joue le rôle de Resource Server.

Dépendances

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

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

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

WebSecurityConfigurerAdapter

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .authorizeRequests()
                .antMatchers("/echo/**").authenticated()
                .antMatchers("/").permitAll()
            .and()
                .oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt);
    }

}

Configuration YAML

spring:
  security:
    oauth2:
      resourceserver:
        jwt:
          issuer-uri: http://localhost:8888/auth/realms/example-realm

Username

  private String getUsername() {
      Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
      return ((Jwt)principal).getClaimAsString("preferred_username");
  }

Tests

Cette configuration peut être testée avec Keycloak et Postman.