🔑 Spring Authorization Server Cheatsheet Completo 🔑

Spring Authorization Server (SAS) es un proyecto de Spring Security que implementa la especificación OAuth 2.1 y OpenID Connect 1.0. Permite a las aplicaciones actuar como un Servidor de Autorización, emitiendo tokens de acceso a clientes autenticados después de obtener la autorización del propietario del recurso.


1. 🌟 Conceptos Clave (OAuth 2.1 y OIDC)


2. 🛠️ Configuración Inicial (Spring Boot 3+)

  1. Añadir dependencias en pom.xml (Maven):

    <dependencies>
        <parent>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-parent</artifactId>
            <version>3.2.0</version> &lt;!-- Usar la versión estable actual -->
            <relativePath/>
        </parent>
    
        &lt;!-- Spring Boot Starter Security (fundamental) -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        &lt;!-- Spring Boot Starter Web (para endpoints HTTP) -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        &lt;!-- Spring Authorization Server -->
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-oauth2-authorization-server</artifactId>
            <version>1.2.0</version> &lt;!-- Usar la versión compatible con tu Spring Boot -->
        </dependency>
        &lt;!-- Para el almacenamiento persistente de clientes y autorizaciones (ej. JPA para H2) -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
            <scope>runtime</scope>
        </dependency>
        &lt;!-- Para pruebas -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
  2. Configurar application.yml:

    # src/main/resources/application.yml
    server:
      port: 9000 # Puerto del Authorization Server (ej. 9000)
    
    spring:
      application:
        name: authorization-server
      security:
        oauth2:
          authorizationserver:
            issuer-uri: http://localhost:9000 # ¡CRÍTICO! URL base de tu servidor de autorización
            # Más configuraciones aquí (ej. JWT, etc.)
      datasource: # Para persistencia de clientes y autorizaciones (ej. H2)
        url: jdbc:h2:mem:authdb
        username: sa
        password: password
      jpa:
        hibernate:
          ddl-auto: update # Crear tablas automáticamente (solo para desarrollo)

3. 📝 Configuración de Seguridad y Autorización (Clase @Configuration)

Esta es la parte central donde se configura el servidor de autorización.

import com.nimbusds.jose.jwk.JWKSet;
import com.nimbusds.jose.jwk.RSAKey;
import com.nimbusds.jose.jwk.source.ImmutableJWKSet;
import com.nimbusds.jose.jwk.source.JWKSource;
import com.nimbusds.jose.proc.SecurityContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.http.MediaType;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
import org.springframework.security.oauth2.core.oidc.OidcScopes;
import org.springframework.security.oauth2.server.authorization.client.InMemoryRegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration;
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2AuthorizationServerConfigurer;
import org.springframework.security.oauth2.server.authorization.settings.ClientSettings;
import org.springframework.security.oauth2.server.authorization.settings.TokenSettings;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint;

import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.time.Duration;
import java.util.UUID;

@Configuration
@EnableWebSecurity // Habilita la seguridad web
public class AuthorizationServerConfig {

    // 1. Configuración de la cadena de filtros para el servidor de autorización (Prioridad Alta)
    @Bean
    @Order(1) // Asegura que esta cadena de filtros se aplique primero
    public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
        OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http); // Aplica la configuración por defecto de SAS

        http.getConfigurer(OAuth2AuthorizationServerConfigurer.class)
            .oidc(Customizer.withDefaults()); // Habilita OpenID Connect 1.0 (para /oauth2/jwks, /.well-known/openid-configuration, /userinfo, /connect/logout)

        http
            // Redirige a la página de login si la autenticación falla en los endpoints de OAuth 2.0/OIDC
            .exceptionHandling(exceptions -&gt; exceptions
                .authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/login"))
            )
            // Para permitir la configuración de recursos de Resource Server que acepte JWT (Access Tokens)
            .oauth2ResourceServer(oauth2ResourceServer -&gt; oauth2ResourceServer.jwt(Customizer.withDefaults()));

        return http.build();
    }

    // 2. Configuración de la cadena de filtros para la autenticación de la aplicación (Menos Prioridad)
    @Bean
    @Order(2) // Se aplica después de la cadena de SAS
    public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests(authorize -&gt; authorize
                .anyRequest().authenticated() // Todas las demás solicitudes requieren autenticación
            )
            .formLogin(Customizer.withDefaults()); // Habilita el formulario de login por defecto

        return http.build();
    }

    // 3. Gestión de Clientes Registrados (RegisteredClientRepository)
    // Define los clientes que pueden solicitar tokens al servidor de autorización
    @Bean
    public RegisteredClientRepository registeredClientRepository() {
        // Cliente Público (sin secreto, para SPA o aplicaciones móviles)
        RegisteredClient publicClient = RegisteredClient.withId(UUID.randomUUID().toString())
            .clientId("public-client")
            .clientAuthenticationMethod(ClientAuthenticationMethod.NONE) // No requiere secreto
            .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE) // Flujo de Código de Autorización
            .redirectUri("http://127.0.0.1:8080/login/oauth2/code/public-client") // URI de redirección después de autorización
            .postLogoutRedirectUri("http://127.0.0.1:8080/") // URI de post-logout para OIDC
            .scope(OidcScopes.OPENID) // Alcance OpenID Connect
            .scope(OidcScopes.PROFILE) // Alcance de perfil
            .scope("message.read") // Alcances personalizados
            .scope("message.write")
            .clientSettings(ClientSettings.builder().requireAuthorizationConsent(true).build()) // Requiere consentimiento del usuario
            .tokenSettings(TokenSettings.builder()
                .accessTokenTimeToLive(Duration.ofMinutes(5)) // Validez del Access Token
                .refreshTokenTimeToLive(Duration.ofHours(24)) // Validez del Refresh Token
                .reuseRefreshTokens(false) // No reusar Refresh Tokens (más seguro)
                .build())
            .build();

        // Cliente Confidencial (con secreto, para aplicaciones de servidor a servidor o web apps tradicionales)
        RegisteredClient confidentialClient = RegisteredClient.withId(UUID.randomUUID().toString())
            .clientId("confidential-client")
            .clientSecret("{noop}secret") // Secreto (usar PasswordEncoder en producción)
            .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC) // Método de autenticación
            .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
            .authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN) // También soporta Refresh Token
            .redirectUri("http://127.0.0.1:8080/authorized") // Diferente URI de redirección
            .scope(OidcScopes.OPENID)
            .scope("api.read")
            .clientSettings(ClientSettings.builder().requireAuthorizationConsent(true).build())
            .build();

        // Cliente para el flujo Client Credentials (servidor a servidor)
        RegisteredClient clientCredentialsClient = RegisteredClient.withId(UUID.randomUUID().toString())
            .clientId("api-client")
            .clientSecret("{noop}api-secret")
            .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
            .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
            .scope("api.internal") // Alcance para uso interno/API
            .clientSettings(ClientSettings.builder().requireAuthorizationConsent(false).build()) // No requiere consentimiento
            .build();

        return new InMemoryRegisteredClientRepository(publicClient, confidentialClient, clientCredentialsClient);
        // Para producción, usar JdbcRegisteredClientRepository (requiere la tabla 'oauth2_registered_client')
    }

    // 4. Gestión de Usuarios (UserDetailsService)
    // Define cómo el servidor de autorización autentica a los usuarios (propietarios del recurso)
    @Bean
    public UserDetailsService userDetailsService() {
        UserDetails user = User.withDefaultPasswordEncoder() // ¡Solo para demo! Usar BCryptPasswordEncoder en prod.
            .username("user")
            .password("password")
            .roles("USER")
            .build();
        UserDetails admin = User.withDefaultPasswordEncoder()
            .username("admin")
            .password("adminpass")
            .roles("ADMIN", "USER")
            .build();
        return new InMemoryUserDetailsManager(user, admin);
        // Para producción, usar un UserDetailsService que cargue usuarios de una base de datos.
    }

    // 5. JWK Source (JSON Web Key Source) para la firma de JWT (ID Tokens, Access Tokens)
    // Genera y gestiona las claves RSA para firmar los tokens JWT
    @Bean
    public JWKSource<SecurityContext> jwkSource() {
        KeyPair keyPair = generateRsaKey(); // Genera un par de claves RSA
        RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
        RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
        RSAKey rsaKey = new RSAKey.Builder(publicKey)
            .privateKey(privateKey)
            .keyID(UUID.randomUUID().toString()) // ID único para la clave
            .build();
        JWKSet jwkSet = new JWKSet(rsaKey);
        return new ImmutableJWKSet&lt;>(jwkSet);
    }

    private static KeyPair generateRsaKey() {
        try {
            KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
            keyPairGenerator.initialize(2048); // Tamaño de clave
            return keyPairGenerator.generateKeyPair();
        } catch (Exception ex) {
            throw new IllegalStateException(ex);
        }
    }
}

4. 🔗 Endpoints del Authorization Server

Spring Authorization Server expone automáticamente los siguientes endpoints:


5. 💾 Persistencia de Clientes y Autorizaciones

Para producción, InMemoryRegisteredClientRepository y InMemoryUserDetailsManager no son suficientes. SAS proporciona implementaciones basadas en JDBC.

  1. Dependencias: Asegúrate de tener spring-boot-starter-data-jpa y el driver de tu base de datos (ej. mysql-connector-java).

  2. Esquemas de Base de Datos: Spring Authorization Server proporciona scripts SQL para crear las tablas necesarias (oauth2_registered_client, oauth2_authorization, oauth2_authorization_consent). Busca oauth2-authorization-server-schema.sql en el JAR de la librería.

  3. Configurar Beans de JDBC:

    • JdbcRegisteredClientRepository (para RegisteredClientRepository)
    • JdbcOAuth2AuthorizationService (para OAuth2AuthorizationService)
    • JdbcOAuth2AuthorizationConsentService (para OAuth2AuthorizationConsentService)
    // En AuthorizationServerConfig, reemplaza los beans InMemory por estos
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.jdbc.core.JdbcTemplate;
    import org.springframework.security.oauth2.server.authorization.JdbcOAuth2AuthorizationConsentService;
    import org.springframework.security.oauth2.server.authorization.JdbcOAuth2AuthorizationService;
    import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository;
    import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
    import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsentService;
    import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
    
    // Asume que tienes un DataSource configurado en application.yml
    
    @Configuration
    public class JdbcPersistenceConfig {
    
        @Bean
        public RegisteredClientRepository registeredClientRepository(JdbcTemplate jdbcTemplate) {
            return new JdbcRegisteredClientRepository(jdbcTemplate);
            // Si necesitas inicializar clientes, hazlo en un CommandLineRunner o ApplicationRunner
            // Ejemplo: if (repo.findByClientId("public-client") == null) repo.save(publicClient);
        }
    
        @Bean
        public OAuth2AuthorizationService authorizationService(JdbcTemplate jdbcTemplate, RegisteredClientRepository registeredClientRepository) {
            return new JdbcOAuth2AuthorizationService(jdbcTemplate, registeredClientRepository);
        }
    
        @Bean
        public OAuth2AuthorizationConsentService authorizationConsentService(JdbcTemplate jdbcTemplate, RegisteredClientRepository registeredClientRepository) {
            return new JdbcOAuth2AuthorizationConsentService(jdbcTemplate, registeredClientRepository);
        }
    }

6. 🌐 Configuración de Cliente OAuth2 (Ejemplo Spring Boot Cliente)

Para que otra aplicación Spring Boot actúe como Cliente de OAuth2 e inicie sesión vía SAS.

  1. Dependencias en pom.xml (del Cliente):
    <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>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
  2. Configurar application.yml (del Cliente):
    # src/main/resources/application.yml (del cliente)
    server:
      port: 8080 # Puerto del cliente
    
    spring:
      security:
        oauth2:
          client:
            registration:
              public-client: # ID de registro del cliente (debe coincidir con SAS)
                client-id: public-client
                client-authentication-method: none # Mismo método que en SAS
                authorization-grant-type: authorization_code
                redirect-uri: "{baseUrl}/login/oauth2/code/{registrationId}" # URL de redirección estándar
                scope: openid, profile, message.read # Scopes solicitados
            provider:
              public-client: # ID del proveedor (debe coincidir con el registro)
                issuer-uri: http://localhost:9000 # ¡CRÍTICO! URL del servidor de autorización
  3. Configuración de Seguridad (del Cliente):
    // src/main/java/com/example/oauth2client/SecurityConfig.java (del Cliente)
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.security.config.Customizer;
    import org.springframework.security.config.annotation.web.builders.HttpSecurity;
    import org.springframework.security.web.SecurityFilterChain;
    
    @Configuration
    public class SecurityConfig {
        @Bean
        public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
            http
                .authorizeHttpRequests(authorize -&gt; authorize
                    .anyRequest().authenticated()
                )
                .oauth2Login(Customizer.withDefaults()) // Habilita OAuth2 Login
                .logout(logout -&gt; logout.logoutSuccessUrl("/")); // Configura logout
            return http.build();
        }
    }

7. 💡 Buenas Prácticas y Consejos


Este cheatsheet te proporciona una referencia completa de Spring Authorization Server, cubriendo sus conceptos esenciales de OAuth 2.1 y OIDC, cómo configurarlo, los endpoints que expone, la persistencia, la configuración de clientes y las mejores prácticas para construir un servidor de autorización seguro y conforme a los estándares.