AI SYNTHESIZED • 150 SHEETS
v1.0.0


🔑 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)

  • OAuth 2.1: Un framework de autorización que permite a una aplicación (Cliente) acceder a recursos protegidos de un Propietario del Recurso en otro servidor (Servidor de Recursos), con la aprobación de un Servidor de Autorización (SAS). Es para autorización.
    • Roles:
      • Resource Owner (Propietario del Recurso): El usuario final que posee los datos protegidos.
      • Client (Cliente): La aplicación que quiere acceder a los recursos del Propietario del Recurso (ej. una aplicación web, móvil, de escritorio).
      • Authorization Server (Servidor de Autorización - SAS): Autentica al Propietario del Recurso, obtiene su autorización y emite tokens de acceso al Cliente.
      • Resource Server (Servidor de Recursos): Aloja los recursos protegidos y acepta tokens de acceso para validar que el Cliente está autorizado a acceder.
    • Grant Types (Tipos de Concesión): Métodos para que un Cliente obtenga un token de acceso.
      • Authorization Code (Código de Autorización): El flujo más seguro y común para clientes web o móviles. Implica una redirección del navegador.
      • Client Credentials (Credenciales de Cliente): Para que un Cliente (servidor a servidor) acceda a sus propios recursos o a recursos que posee, sin un Propietario del Recurso.
      • Refresh Token (Token de Refresco): Un token de larga duración utilizado para obtener nuevos tokens de acceso cuando el actual expira, sin que el Propietario del Recurso tenga que autenticarse de nuevo.
    • Tokens:
      • Access Token (Token de Acceso): Un token de corta duración utilizado por el Cliente para acceder a los recursos en el Servidor de Recursos.
      • Refresh Token (Token de Refresco): Un token de larga duración utilizado para obtener nuevos Access Tokens.
  • OpenID Connect (OIDC): Una capa de identidad construida sobre OAuth 2.1. Permite a los clientes verificar la identidad del Propietario del Recurso basándose en la autenticación realizada por el Servidor de Autorización, y obtener información básica del perfil del Propietario del Recurso. Es para autenticación (además de autorización).
    • ID Token (Token de ID): Un token JWT emitido por el Servidor de Autorización que contiene información sobre la identidad del Propietario del Recurso.

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:

  • Discovery Endpoint: /.well-known/openid-configuration (para OIDC)
    • Proporciona metadatos del servidor de autorización (URLs de endpoints, métodos soportados, etc.).
  • Authorization Endpoint: /oauth2/authorize
    • El Propietario del Recurso es redirigido aquí para autenticarse y dar su consentimiento.
  • Token Endpoint: /oauth2/token
    • El Cliente intercambia el código de autorización por tokens de acceso/refresco.
  • JWK Set Endpoint: /oauth2/jwks
    • Expone las claves públicas del servidor de autorización, utilizadas por los Servidores de Recursos para verificar la firma de los JWT.
  • User Info Endpoint: /userinfo (para OIDC)
    • El Cliente (con un Access Token válido) puede obtener información básica del perfil del Propietario del Recurso.
  • End Session Endpoint: /connect/logout (para OIDC)
    • Para cerrar la sesión del usuario.
  • Token Revocation Endpoint: /oauth2/revoke
    • Para que los clientes puedan revocar tokens de acceso o de refresco.
  • Token Introspection Endpoint: /oauth2/introspect
    • Para que los Servidores de Recursos puedan inspeccionar un token de acceso y obtener su estado activo y metadatos.

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

  • HTTPS en Producción: Siempre usa HTTPS para el Servidor de Autorización y para toda la comunicación OAuth2/OIDC.
  • Secretos de Cliente Fuertes: Para clientes confidenciales, usa secretos fuertes y que no estén hardcodeados (ej. inyectados como variables de entorno).
  • Rotación de Claves JWK: Implementa una estrategia de rotación regular para tus claves de firma JWT.
  • Manejo de Consentimiento de Usuario: Para el flujo de Código de Autorización, es crucial que el servidor de autorización tenga una página de consentimiento de usuario clara.
  • Scopes Granulares: Define scopes pequeños y específicos para los permisos que un cliente puede solicitar.
  • issuer-uri Correcto: Asegúrate de que el issuer-uri en la configuración del servidor y del cliente sea exactamente el mismo. Este es un identificador clave en OIDC.
  • Persistencia de Datos: Para entornos de producción, usa las implementaciones de repositorio JDBC para almacenar clientes, autorizaciones y consentimientos de forma persistente.
  • Auditoría: Habilita la auditoría para registrar eventos importantes en el servidor de autorización.
  • Logging: Configura un logging detallado para el servidor de autorización para depurar problemas y monitorear la actividad.
  • Rate Limiting / Circuit Breaker: Aplica patrones de resiliencia al servidor de autorización para protegerlo de ataques de fuerza bruta o sobrecarga.
  • Roles y Autoridades de Usuario: El UserDetailsService debe proporcionar roles y autoridades detalladas para la autorización a nivel de aplicación.
  • Validación de Tokens: En tu Resource Server, valida los tokens JWT (firma, expiración, emisor, audiencia, etc.) usando spring-boot-starter-oauth2-resource-server.

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.

Descarga completada