Spring Security provides first-class support for OAuth 2.0 and OpenID Connect, covering both client (login with Google/GitHub) and resource server (protect APIs with JWT) scenarios.

OAuth2 Roles

Role Description
Authorization Server Issues tokens (Keycloak, Auth0, Okta)
Resource Server Protects APIs, validates tokens
Client Redirects users to login, receives tokens

OAuth2 Client (Login with Provider)

  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>
  
  spring:
  security:
    oauth2:
      client:
        registration:
          google:
            client-id: ${GOOGLE_CLIENT_ID}
            client-secret: ${GOOGLE_CLIENT_SECRET}
            scope: openid, profile, email
          github:
            client-id: ${GITHUB_CLIENT_ID}
            client-secret: ${GITHUB_CLIENT_SECRET}
            scope: read:user, user:email
        provider:
          google:
            issuer-uri: https://accounts.google.com
  
  http.oauth2Login(oauth2 -> oauth2
    .loginPage("/login")
    .defaultSuccessUrl("/dashboard")
    .userInfoEndpoint(userInfo -> userInfo
        .userService(oauth2UserService())
    )
);
  

Custom OAuth2 User Service

  @Service
public class CustomOAuth2UserService extends DefaultOAuth2UserService {
    @Override
    public OAuth2User loadUser(OAuth2UserRequest request) throws OAuth2AuthenticationException {
        OAuth2User oauth2User = super.loadUser(request);
        String email = oauth2User.getAttribute("email");
        User user = userRepository.findByEmail(email)
            .orElseGet(() -> createUserFromOAuth2(oauth2User));
        return new CustomOAuth2User(oauth2User, user);
    }
}
  

OAuth2 Resource Server (JWT)

Protect REST APIs with bearer tokens:

  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
</dependency>
  
  spring:
  security:
    oauth2:
      resourceserver:
        jwt:
          issuer-uri: https://auth.example.com/realms/myapp
          # or jwk-set-uri: https://auth.example.com/.well-known/jwks.json
  
  http
    .authorizeHttpRequests(auth -> auth
        .requestMatchers("/api/public/**").permitAll()
        .requestMatchers("/api/admin/**").hasRole("ADMIN")
        .anyRequest().authenticated()
    )
    .oauth2ResourceServer(oauth2 -> oauth2.jwt(Customizer.withDefaults()));
  

Extract Claims from JWT

  @GetMapping("/api/me")
public Map<String, Object> me(@AuthenticationPrincipal Jwt jwt) {
    return Map.of(
        "subject", jwt.getSubject(),
        "email", jwt.getClaim("email"),
        "roles", jwt.getClaim("roles")
    );
}
  

Map JWT Roles to Authorities

  @Bean
public JwtAuthenticationConverter jwtAuthenticationConverter() {
    JwtGrantedAuthoritiesConverter granted = new JwtGrantedAuthoritiesConverter();
    granted.setAuthoritiesClaimName("roles");
    granted.setAuthorityPrefix("ROLE_");

    JwtAuthenticationConverter converter = new JwtAuthenticationConverter();
    converter.setJwtGrantedAuthoritiesConverter(granted);
    return converter;
}
  

Authorization Code Flow

  Browser → /oauth2/authorization/google
       → Google login page
       → Redirect to /login/oauth2/code/google?code=...
       → Spring exchanges code for tokens
       → User authenticated in SecurityContext
  

Best Practices

  • Use a dedicated identity provider (Keycloak, Auth0) rather than rolling your own
  • Validate JWT issuer and audience in resource servers
  • Store client secrets in environment variables or secret managers
  • Use PKCE for public clients (SPAs, mobile apps)
  • Configure token refresh for long-lived sessions