🔑 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.
- Roles:
- 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+)
-
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> <!-- Usar la versión estable actual --> <relativePath/> </parent> <!-- Spring Boot Starter Security (fundamental) --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <!-- Spring Boot Starter Web (para endpoints HTTP) --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- Spring Authorization Server --> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-oauth2-authorization-server</artifactId> <version>1.2.0</version> <!-- Usar la versión compatible con tu Spring Boot --> </dependency> <!-- 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> <!-- Para pruebas --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> -
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 -> exceptions
.authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/login"))
)
// Para permitir la configuración de recursos de Resource Server que acepte JWT (Access Tokens)
.oauth2ResourceServer(oauth2ResourceServer -> 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 -> 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<>(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.
-
Dependencias: Asegúrate de tener
spring-boot-starter-data-jpay el driver de tu base de datos (ej.mysql-connector-java). -
Esquemas de Base de Datos: Spring Authorization Server proporciona scripts SQL para crear las tablas necesarias (
oauth2_registered_client,oauth2_authorization,oauth2_authorization_consent). Buscaoauth2-authorization-server-schema.sqlen el JAR de la librería. -
Configurar Beans de JDBC:
JdbcRegisteredClientRepository(paraRegisteredClientRepository)JdbcOAuth2AuthorizationService(paraOAuth2AuthorizationService)JdbcOAuth2AuthorizationConsentService(paraOAuth2AuthorizationConsentService)
// 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.
- 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> - 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 - 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 -> authorize .anyRequest().authenticated() ) .oauth2Login(Customizer.withDefaults()) // Habilita OAuth2 Login .logout(logout -> 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-uriCorrecto: Asegúrate de que elissuer-urien 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
UserDetailsServicedebe 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.