Pular para o conteúdo principal

Adicionar autenticação ao seu aplicativo Java Spring Boot (Add authentication to your Java Spring Boot application)

Este guia mostrará como integrar o Logto ao seu aplicativo Java Spring Boot.

dica:
  • Você pode encontrar o código de exemplo para este guia em nosso repositório do github spring-boot-sample.
  • Não é necessário um SDK oficial para integrar o Logto ao seu aplicativo Java Spring Boot. Usaremos as bibliotecas Spring Security e Spring Security OAuth2 para lidar com o fluxo de autenticação OIDC com o Logto.

Pré-requisitos

Configure seu aplicativo Java Spring Boot

Adicionando dependências

Para usuários do gradle, adicione as seguintes dependências ao seu arquivo 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 usuários do maven, adicione as seguintes dependências ao seu arquivo 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>

Configuração do Cliente OAuth2

Registre um novo aplicativo Java Spring Boot no Logto Console e obtenha as credenciais do cliente e configurações do IdP para seu aplicativo web.

Adicione a seguinte configuração ao seu arquivo 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

Implementação

Antes de entrarmos nos detalhes, aqui está uma visão geral rápida da experiência do usuário final. O processo de login pode ser simplificado da seguinte forma:

  1. Seu app invoca o método de login.
  2. O usuário é redirecionado para a página de login do Logto. Para aplicativos nativos, o navegador do sistema é aberto.
  3. O usuário faz login e é redirecionado de volta para seu app (configurado como o URI de redirecionamento).

Sobre o login baseado em redirecionamento

  1. Este processo de autenticação segue o protocolo OpenID Connect (OIDC), e o Logto aplica medidas de segurança rigorosas para proteger o login do usuário.
  2. Se você tiver vários aplicativos, pode usar o mesmo provedor de identidade (Logto). Uma vez que o usuário faz login em um aplicativo, o Logto completará automaticamente o processo de login quando o usuário acessar outro aplicativo.

Para saber mais sobre a lógica e os benefícios do login baseado em redirecionamento, veja Experiência de login do Logto explicada.


Para redirecionar os usuários de volta ao seu aplicativo após o login, você precisa definir o redirect URI usando a propriedade client.registration.logto.redirect-uri no passo anterior.

Configurar URIs de redirecionamento

Vá para a página de detalhes do aplicativo no Logto Console. Adicione um URI de redirecionamento http://localhost:8080/login/oauth2/code/logto.

URI de redirecionamento no Logto Console

Assim como no login, os usuários devem ser redirecionados para o Logto para sair da sessão compartilhada. Uma vez concluído, seria ótimo redirecionar o usuário de volta para o seu site. Por exemplo, adicione http://localhost:3000/ como a seção de URI de redirecionamento pós logout.

Em seguida, clique em "Salvar" para salvar as alterações.

Implemente o WebSecurityConfig

Crie uma nova classe WebSecurityConfig em seu projeto

A classe WebSecurityConfig será usada para configurar as definições de segurança do seu aplicativo. É a classe chave que irá lidar com o fluxo de autenticação e autorização. Consulte a documentação do Spring Security para mais detalhes.

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 {
// ...
}

Crie um bean idTokenDecoderFactory

Isso é necessário porque o Logto usa ES384 como algoritmo padrão, precisamos sobrescrever o OidcIdTokenDecoderFactory padrão para usar o mesmo 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;
}
}

Crie uma classe LoginSuccessHandler para lidar com o evento de sucesso no login

Vamos redirecionar o usuário para a página /user após um login bem-sucedido.

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");
}
}

Crie uma classe LogoutSuccessHandler para lidar com o evento de sucesso no logout

Limpe a sessão e redirecione o usuário para a página inicial.

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");
}
}

Atualize a classe WebSecurityConfig com um securityFilterChain

O securityFilterChain é uma cadeia de filtros responsável por processar as requisições e respostas recebidas.

Vamos configurar o securityFilterChain para permitir acesso à página inicial e exigir autenticação para todas as outras requisições. Use o CustomSuccessHandler e o CustomLogoutHandler para lidar com os eventos de login e logout.

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 acesso à página inicial
.anyRequest().authenticated() // Todas as outras requisições exigem autenticação
)
.oauth2Login(oauth2Login ->
oauth2Login
.successHandler(new CustomSuccessHandler())
)
.logout(logout ->
logout
.logoutSuccessHandler(new CustomLogoutHandler())
);
return http.build();
}
}

Crie uma página inicial

(Você pode pular esta etapa se já tiver uma página inicial em seu projeto)

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 controller irá redirecionar o usuário para a página de usuário se ele estiver autenticado, caso contrário, mostrará a página inicial. Adicione um link de login à página inicial.

resources/templates/home.html
<body>
<h1>Bem-vindo!</h1>

<p><a th:href="@{/oauth2/authorization/logto}">Login com Logto</a></p>
</body>

Crie uma página de usuário

Crie um novo controller para lidar com a página de usuário:

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";
}
}

Uma vez que o usuário está autenticado, vamos recuperar os dados do OAuth2User a partir do objeto principal autenticado. Consulte OAuth2AuthenticationToken e OAuth2User para mais detalhes.

Leia os dados do usuário e passe-os para o template user.html.

resources/templates/user.html
<body>
<h1>Detalhes do Usuário</h1>
<div>
<p>
<div><strong>nome:</strong> <span th:text="${username}"></span></div>
<div><strong>email:</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="Logout" />
</form>
</body>

Solicitar reivindicações adicionais

Você pode perceber que algumas informações do usuário estão faltando no objeto retornado de principal (OAuth2AuthenticationToken). Isso ocorre porque OAuth 2.0 e OpenID Connect (OIDC) são projetados para seguir o princípio do menor privilégio (PoLP), e o Logto é construído com base nesses padrões.

Por padrão, reivindicações limitadas são retornadas. Se você precisar de mais informações, pode solicitar escopos adicionais para acessar mais reivindicações.

info:

Uma "reivindicação (Claim)" é uma afirmação feita sobre um sujeito; um "escopo (Scope)" é um grupo de reivindicações. No caso atual, uma reivindicação é uma informação sobre o usuário.

Aqui está um exemplo não normativo da relação escopo - reivindicação:

dica:

A reivindicação "sub" significa "sujeito (Subject)", que é o identificador único do usuário (ou seja, ID do usuário).

O Logto SDK sempre solicitará três escopos: openid, profile e offline_access.

Para recuperar informações adicionais do usuário, você pode adicionar escopos extras ao arquivo application.properties. Por exemplo, para solicitar os escopos email, phone e urn:logto:scope:organizations, adicione a seguinte linha ao arquivo application.properties:

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

Depois, você poderá acessar as reivindicações adicionais no objeto OAuth2User.

Execute e teste o aplicativo

Execute o aplicativo e acesse http://localhost:8080.

  • Você verá a página inicial com um link de login.
  • Clique no link para fazer login com o Logto.
  • Após a autenticação bem-sucedida, você será redirecionado para a página de usuário com seus detalhes.
  • Clique no botão de logout para sair. Você será redirecionado de volta para a página inicial.

Escopos e Reivindicações (Scopes and Claims)

Logto usa as convenções de escopos e reivindicações (scopes and claims) do OIDC para definir os escopos e reivindicações para recuperar informações do usuário do Token de ID (ID token) e do endpoint userinfo do OIDC. Tanto o "escopo" quanto a "reivindicação" são termos das especificações OAuth 2.0 e OpenID Connect (OIDC).

Em resumo, quando você solicita um escopo, você obterá as reivindicações correspondentes nas informações do usuário. Por exemplo, se você solicitar o escopo email, você obterá os dados email e email_verified do usuário.

Aqui está a lista de escopos suportados e as reivindicações correspondentes:

Escopos OIDC padrão

openid (padrão)

Nome da reivindicaçãoTipoDescrição
substringO identificador único do usuário

profile (padrão)

Nome da reivindicaçãoTipoDescrição
namestringO nome completo do usuário
usernamestringO nome de usuário do usuário
picturestringURL da foto de perfil do usuário final. Esta URL DEVE se referir a um arquivo de imagem (por exemplo, um arquivo de imagem PNG, JPEG ou GIF), em vez de uma página da Web contendo uma imagem. Observe que esta URL DEVE referenciar especificamente uma foto de perfil do usuário final adequada para exibição, em vez de uma foto arbitrária tirada pelo usuário final.
created_atnumberMomento em que o usuário final foi criado. O tempo é representado como o número de milissegundos desde a época Unix (1970-01-01T00:00:00Z).
updated_atnumberMomento em que as informações do usuário final foram atualizadas pela última vez. O tempo é representado como o número de milissegundos desde a época Unix (1970-01-01T00:00:00Z).

Outras reivindicações padrão incluem family_name, given_name, middle_name, nickname, preferred_username, profile, website, gender, birthdate, zoneinfo e locale também serão incluídas no escopo profile sem a necessidade de solicitar o endpoint userinfo. Uma diferença em relação às reivindicações acima é que essas reivindicações só serão retornadas quando seus valores não forem vazios, enquanto as reivindicações acima retornarão null se os valores estiverem vazios.

nota:

Diferente das reivindicações padrão, as reivindicações created_at e updated_at usam milissegundos em vez de segundos.

email

Nome da reivindicaçãoTipoDescrição
emailstringO endereço de email do usuário
email_verifiedbooleanSe o endereço de email foi verificado

phone

Nome da reivindicaçãoTipoDescrição
phone_numberstringO número de telefone do usuário
phone_number_verifiedbooleanSe o número de telefone foi verificado

address

Consulte o OpenID Connect Core 1.0 para detalhes sobre a reivindicação de endereço.

info:

Escopos marcados como (padrão) são sempre solicitados pelo SDK do Logto. As reivindicações sob escopos OIDC padrão são sempre incluídas no token de ID quando o escopo correspondente é solicitado — elas não podem ser desativadas.

Escopos estendidos

Os seguintes escopos são estendidos pelo Logto e retornarão reivindicações através do endpoint userinfo. Essas reivindicações também podem ser configuradas para serem incluídas diretamente no token de ID através de Console > Custom JWT. Veja Token de ID personalizado para mais detalhes.

custom_data

Nome da reivindicaçãoTipoDescriçãoIncluído no token de ID por padrão
custom_dataobjectOs dados personalizados do usuário

identities

Nome da reivindicaçãoTipoDescriçãoIncluído no token de ID por padrão
identitiesobjectAs identidades vinculadas do usuário
sso_identitiesarrayAs identidades SSO vinculadas do usuário

roles

Nome da reivindicaçãoTipoDescriçãoIncluído no token de ID por padrão
rolesstring[]Os papéis do usuário

urn:logto:scope:organizations

Nome da reivindicaçãoTipoDescriçãoIncluído no token de ID por padrão
organizationsstring[]Os IDs das organizações às quais o usuário pertence
organization_dataobject[]Os dados das organizações às quais o usuário pertence
nota:

Essas reivindicações de organização também podem ser recuperadas via endpoint userinfo ao usar um token opaco. No entanto, tokens opacos não podem ser usados como tokens de organização para acessar recursos específicos da organização. Veja Token opaco e organizações para mais detalhes.

urn:logto:scope:organization_roles

Nome da reivindicaçãoTipoDescriçãoIncluído no token de ID por padrão
organization_rolesstring[]Os papéis da organização aos quais o usuário pertence no formato <organization_id>:<role_name>

Adicione escopos e reivindicações extras no arquivo application.properties para solicitar mais informações do usuário. Por exemplo, para solicitar o escopo urn:logto:scope:organizations, adicione a seguinte linha ao arquivo application.properties:

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

As reivindicações de organização do usuário serão incluídas no token de autorização.

Leituras adicionais

Fluxos do usuário final: fluxos de autenticação, fluxos de conta e fluxos de organização Configurar conectores Autorização (Authorization)