Saltar al contenido principal

Añade autenticación a tu aplicación Java Spring Boot

Esta guía te mostrará cómo integrar Logto en tu aplicación Java Spring Boot.

tip
  • Puedes encontrar el código de ejemplo para esta guía en nuestro repositorio de github spring-boot-sample.
  • No se requiere un SDK oficial para integrar Logto con tu aplicación Java Spring Boot. Usaremos las bibliotecas Spring Security y Spring Security OAuth2 para manejar el flujo de autenticación OIDC con Logto.

Prerrequisitos

  • Una cuenta de Logto Cloud o un Logto autoalojado.
  • Nuestro código de ejemplo fue creado usando el securing web starter de Spring Boot. Sigue las instrucciones para iniciar una nueva aplicación web si no tienes una.
  • En esta guía, usaremos las bibliotecas Spring Security y Spring Security OAuth2 para manejar el flujo de autenticación OIDC con Logto. Asegúrate de revisar la documentación oficial para entender los conceptos.

Configura tu aplicación Java Spring Boot

Añadir dependencias

Para los usuarios de gradle, añade las siguientes dependencias a tu archivo build.gradle:

build.gradle
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.boot:spring-boot-starter-oauth2-client'
}

Para los usuarios de maven, añade las siguientes dependencias a tu archivo pom.xml:

pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</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>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>

Configuración del Cliente OAuth2

Registra una nueva aplicación Java Spring Boot en Logto Console y obtén las credenciales del cliente y las configuraciones de IdP para tu aplicación web.

Añade la siguiente configuración a tu archivo application.properties:

application.properties
spring.security.oauth2.client.registration.logto.client-name=logto
spring.security.oauth2.client.registration.logto.client-id={{YOUR_CLIENT_ID}}
spring.security.oauth2.client.registration.logto.client-secret={{YOUR_CLIENT_ID}}
spring.security.oauth2.client.registration.logto.redirect-uri={baseUrl}/login/oauth2/code/{registrationId}
spring.security.oauth2.client.registration.logto.authorization-grant-type=authorization_code
spring.security.oauth2.client.registration.logto.scope=openid,profile,offline_access
spring.security.oauth2.client.registration.logto.provider=logto

spring.security.oauth2.client.provider.logto.issuer-uri={{LOGTO_ENDPOINT}}/oidc
spring.security.oauth2.client.provider.logto.authorization-uri={{LOGTO_ENDPOINT}}/oidc/auth
spring.security.oauth2.client.provider.logto.jwk-set-uri={{LOGTO_ENDPOINT}}/oidc/jwks

Implementación

Antes de profundizar en los detalles, aquí tienes una visión general rápida de la experiencia del usuario final. El proceso de inicio de sesión se puede simplificar de la siguiente manera:

  1. Tu aplicación invoca el método de inicio de sesión.
  2. El usuario es redirigido a la página de inicio de sesión de Logto. Para aplicaciones nativas, se abre el navegador del sistema.
  3. El usuario inicia sesión y es redirigido de vuelta a tu aplicación (configurada como el URI de redirección).
Sobre el inicio de sesión basado en redirección
  1. Este proceso de autenticación sigue el protocolo OpenID Connect (OIDC), y Logto aplica medidas de seguridad estrictas para proteger el inicio de sesión del usuario.
  2. Si tienes múltiples aplicaciones, puedes usar el mismo proveedor de identidad (Logto). Una vez que el usuario inicia sesión en una aplicación, Logto completará automáticamente el proceso de inicio de sesión cuando el usuario acceda a otra aplicación.

Para aprender más sobre la lógica y los beneficios del inicio de sesión basado en redirección, consulta Experiencia de inicio de sesión de Logto explicada.


Para redirigir a los usuarios de vuelta a tu aplicación después de que inicien sesión, necesitas establecer el URI de redirección usando la propiedad client.registration.logto.redirect-uri en el paso anterior.

Configurar URIs de redirección

Cambia a la página de detalles de la aplicación en Logto Console. Añade una URI de redirección http://localhost:3000/callback.

URI de redirección en Logto Console

Al igual que al iniciar sesión, los usuarios deben ser redirigidos a Logto para cerrar la sesión de la sesión compartida. Una vez terminado, sería ideal redirigir al usuario de vuelta a tu sitio web. Por ejemplo, añade http://localhost:3000/ como la sección de URI de redirección posterior al cierre de sesión.

Luego haz clic en "Guardar" para guardar los cambios.

Implementa el WebSecurityConfig

Crea una nueva clase WebSecurityConfig en tu proyecto

La clase WebSecurityConfig se usará para configurar los ajustes de seguridad de tu aplicación. Es la clase clave que manejará el flujo de autenticación y autorización. Por favor, consulta la documentación de Spring Security para más detalles.

WebSecurityConfig.java
package com.example.securingweb;

import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;

@Configuration
@EnableWebSecurity

public class WebSecurityConfig {
// ...
}

Crea un bean idTokenDecoderFactory

Esto es necesario porque Logto usa ES384 como el algoritmo predeterminado, necesitamos sobrescribir el OidcIdTokenDecoderFactory predeterminado para usar el mismo algoritmo.

WebSecurityConfig.java
import org.springframework.context.annotation.Bean;
import org.springframework.security.oauth2.client.oidc.authentication.OidcIdTokenDecoderFactory;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;
import org.springframework.security.oauth2.jwt.JwtDecoderFactory;

public class WebSecurityConfig {
// ...

@Bean
public JwtDecoderFactory<ClientRegistration> idTokenDecoderFactory() {
OidcIdTokenDecoderFactory idTokenDecoderFactory = new OidcIdTokenDecoderFactory();
idTokenDecoderFactory.setJwsAlgorithmResolver(clientRegistration -> SignatureAlgorithm.ES384);
return idTokenDecoderFactory;
}
}

Crea una clase LoginSuccessHandler para manejar el evento de éxito de inicio de sesión

Redirigiremos al usuario a la página /user después de un inicio de sesión exitoso.

CustomSuccessHandler.java
package com.example.securingweb;

import java.io.IOException;

import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;

import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

public class CustomSuccessHandler implements AuthenticationSuccessHandler {
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws IOException, ServletException {
response.sendRedirect("/user");
}
}

Crea una clase LogoutSuccessHandler para manejar el evento de éxito de cierre de sesión

Limpia la sesión y redirige al usuario a la página de inicio.

CustomLogoutHandler.java
package com.example.securingweb;

import java.io.IOException;

import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;

import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;

public class CustomLogoutHandler implements LogoutSuccessHandler {
@Override
public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication)
throws IOException, ServletException {
HttpSession session = request.getSession();

if (session != null) {
session.invalidate();
}

response.sendRedirect("/home");
}
}

Actualiza la clase WebSecurityConfig con un securityFilterChain

securityFilterChain es una cadena de filtros que son responsables de procesar las solicitudes y respuestas entrantes.

Configuraremos el securityFilterChain para permitir el acceso a la página de inicio y requerir autenticación para todas las demás solicitudes. Usa el CustomSuccessHandler y el CustomLogoutHandler para manejar los eventos de inicio y cierre de sesión.

WebSecurityConfig.java
import org.springframework.context.annotation.Bean;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.DefaultSecurityFilterChain;

public class WebSecurityConfig {
// ...

@Bean
public DefaultSecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeRequests(authorizeRequests ->
authorizeRequests
.antMatchers("/", "/home").permitAll() // Permitir acceso a la página de inicio
.anyRequest().authenticated() // Todas las demás solicitudes requieren autenticación
)
.oauth2Login(oauth2Login ->
oauth2Login
.successHandler(new CustomSuccessHandler())
)
.logout(logout ->
logout
.logoutSuccessHandler(new CustomLogoutHandler())
);
return http.build();
}
}

Crea una página de inicio

(Puedes omitir este paso si ya tienes una página de inicio en tu proyecto)

HomeController.java
package com.example.securingweb;

import java.security.Principal;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class HomeController {
@GetMapping({ "/", "/home" })
public String home(Principal principal) {
return principal != null ? "redirect:/user" : "home";
}
}

Este controlador redirigirá al usuario a la página de usuario si el usuario está autenticado, de lo contrario, mostrará la página de inicio. Añade un enlace de inicio de sesión a la página de inicio.

resources/templates/home.html
<body>
<h1>¡Bienvenido!</h1>

<p><a th:href="@{/oauth2/authorization/logto}">Iniciar sesión con Logto</a></p>
</body>

Crea una página de usuario

Crea un nuevo controlador para manejar la página de usuario:

UserController.java
package com.example.securingweb;

import java.security.Principal;
import java.util.Map;

import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
@RequestMapping("/user")
public class UserController {

@GetMapping
public String user(Model model, Principal principal) {
if (principal instanceof OAuth2AuthenticationToken) {
OAuth2AuthenticationToken token = (OAuth2AuthenticationToken) principal;
OAuth2User oauth2User = token.getPrincipal();
Map<String, Object> attributes = oauth2User.getAttributes();

model.addAttribute("username", attributes.get("username"));
model.addAttribute("email", attributes.get("email"));
model.addAttribute("sub", attributes.get("sub"));
}

return "user";
}
}

Una vez que el usuario está autenticado, recuperaremos los datos del OAuth2User del objeto principal autenticado. Por favor, consulta OAuth2AuthenticationToken y OAuth2User para más detalles.

Lee los datos del usuario y pásalos a la plantilla user.html.

resources/templates/user.html
<body>
<h1>Detalles del Usuario</h1>
<div>
<p>
<div><strong>nombre:</strong> <span th:text="${username}"></span></div>
<div><strong>correo electrónico:</strong> <span th:text="${email}"></span></div>
<div><strong>id:</strong> <span th:text="${sub}"></span></div>
</p>
</div>

<form th:action="@{/logout}" method="post">
<input type="submit" value="Cerrar sesión" />
</form>
</body>

Solicitar reclamos adicionales

Es posible que encuentres que falta alguna información del usuario en el objeto devuelto desde principal (OAuth2AuthenticationToken). Esto se debe a que OAuth 2.0 y OpenID Connect (OIDC) están diseñados para seguir el principio de privilegio mínimo (PoLP), y Logto está construido sobre estos estándares.

De forma predeterminada, se devuelven reclamos limitados. Si necesitas más información, puedes solicitar alcances adicionales para acceder a más reclamos.

info

Un "reclamo (Claim)" es una afirmación hecha sobre un sujeto; un "alcance (Scope)" es un grupo de reclamos. En el caso actual, un reclamo es una pieza de información sobre el usuario.

Aquí tienes un ejemplo no normativo de la relación alcance - reclamo:

tip

El reclamo "sub" significa "sujeto", que es el identificador único del usuario (es decir, el ID del usuario).

El SDK de Logto siempre solicitará tres alcances: openid, profile y offline_access.

Para recuperar información adicional del usuario, puedes añadir alcances extra al archivo application.properties. Por ejemplo, para solicitar el alcance email, phone y urn:logto:scope:organizations, añade la siguiente línea al archivo application.properties:

application.properties
  spring.security.oauth2.client.registration.logto.scope=openid,profile,offline_access,email,phone,urn:logto:scope:organizations

Luego puedes acceder a los reclamos adicionales en el objeto OAuth2User.

Ejecuta y prueba la aplicación

Ejecuta la aplicación y navega a http://localhost:8080.

  • Verás la página de inicio con un enlace de inicio de sesión.
  • Haz clic en el enlace para iniciar sesión con Logto.
  • Después de una autenticación exitosa, serás redirigido a la página de usuario con los detalles de tu usuario.
  • Haz clic en el botón de cierre de sesión para salir. Serás redirigido de vuelta a la página de inicio.

Alcances y Reclamos

Logto utiliza las convenciones de alcances y reclamos (scopes and claims) de OIDC para definir los alcances y reclamos para obtener información del usuario del Token de ID y del endpoint de userinfo de OIDC. Tanto "alcance" como "reclamo" son términos de las especificaciones de OAuth 2.0 y OpenID Connect (OIDC).

En resumen, cuando solicitas un alcance, obtendrás los reclamos correspondientes en la información del usuario. Por ejemplo, si solicitas el alcance email, obtendrás los datos email y email_verified del usuario.

Aquí está la lista de alcances (Scopes) soportados y los reclamos (Claims) correspondientes:

openid

Nombre del reclamoTipoDescripción¿Necesita userinfo?
substringEl identificador único del usuarioNo

profile

Nombre del reclamoTipoDescripción¿Necesita userinfo?
namestringEl nombre completo del usuarioNo
usernamestringEl nombre de usuario del usuarioNo
picturestringURL de la foto de perfil del usuario final. Esta URL DEBE referirse a un archivo de imagen (por ejemplo, un archivo de imagen PNG, JPEG o GIF), en lugar de a una página web que contenga una imagen. Ten en cuenta que esta URL DEBERÍA referirse específicamente a una foto de perfil del usuario final adecuada para mostrar al describir al usuario final, en lugar de una foto arbitraria tomada por el usuario final.No
created_atnumberMomento en que se creó el usuario final. El tiempo se representa como el número de milisegundos desde la época Unix (1970-01-01T00:00:00Z).No
updated_atnumberMomento en que se actualizó por última vez la información del usuario final. El tiempo se representa como el número de milisegundos desde la época Unix (1970-01-01T00:00:00Z).No

Otros reclamos estándar incluyen family_name, given_name, middle_name, nickname, preferred_username, profile, website, gender, birthdate, zoneinfo y locale también se incluirán en el alcance profile sin necesidad de solicitar el endpoint de userinfo. Una diferencia en comparación con los reclamos anteriores es que estos reclamos solo se devolverán cuando sus valores no estén vacíos, mientras que los reclamos anteriores devolverán null si los valores están vacíos.

nota

A diferencia de los reclamos estándar, los reclamos created_at y updated_at utilizan milisegundos en lugar de segundos.

email

Nombre del reclamoTipoDescripción¿Necesita userinfo?
emailstringLa dirección de correo electrónico del usuarioNo
email_verifiedbooleanSi la dirección de correo electrónico ha sido verificadaNo

phone

Nombre del reclamoTipoDescripción¿Necesita userinfo?
phone_numberstringEl número de teléfono del usuarioNo
phone_number_verifiedbooleanSi el número de teléfono ha sido verificadoNo

address

Por favor, consulta el OpenID Connect Core 1.0 para los detalles del reclamo de dirección.

custom_data

Nombre del reclamoTipoDescripción¿Necesita userinfo?
custom_dataobjectLos datos personalizados del usuario

identities

Nombre del reclamoTipoDescripción¿Necesita userinfo?
identitiesobjectLas identidades vinculadas del usuario
sso_identitiesarrayLas identidades SSO vinculadas del usuario

urn:logto:scope:organizations

Nombre del reclamoTipoDescripción¿Necesita userinfo?
organizationsstring[]Los IDs de las organizaciones a las que pertenece el usuarioNo
organization_dataobject[]Los datos de las organizaciones a las que pertenece el usuario

urn:logto:scope:organization_roles

Nombre del reclamoTipoDescripción¿Necesita userinfo?
organization_rolesstring[]Los roles de organización a los que pertenece el usuario con el formato <organization_id>:<role_name>No

Considerando el rendimiento y el tamaño de los datos, si "¿Necesita userinfo?" es "Sí", significa que el reclamo no aparecerá en el Token de ID, pero se devolverá en la respuesta del endpoint de userinfo.

Agrega alcances y reclamos adicionales en el archivo application.properties para solicitar más información del usuario. Por ejemplo, para solicitar el alcance urn:logto:scope:organizations, añade la siguiente línea al archivo application.properties:

application.properties
spring.security.oauth2.client.registration.logto.scope=openid,profile,offline_access,urn:logto:scope:organizations

Los reclamos de la organización del usuario se incluirán en el token de autorización.

Lecturas adicionales

Flujos de usuario final: flujos de autenticación, flujos de cuenta y flujos de organización Configurar conectores Protege tu API