Adicionar autenticação ao seu aplicativo Java Spring Boot
Este guia mostrará como integrar o Logto ao seu aplicativo Java Spring Boot.
- Você pode encontrar o código de exemplo para este guia em nosso repositório spring-boot-sample no GitHub.
- Nenhum SDK oficial é necessário 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
- Uma conta Logto Cloud ou um Logto auto-hospedado.
- Nosso código de exemplo foi criado usando o securing web starter do Spring Boot. Siga as instruções para iniciar um novo aplicativo web se você não tiver um.
- Neste guia, usaremos as bibliotecas Spring Security e Spring Security OAuth2 para lidar com o fluxo de autenticação OIDC com o Logto. Certifique-se de passar pela documentação oficial para entender os conceitos.
Configurar seu aplicativo Java Spring Boot
Adicionando dependências
Para usuários do gradle, adicione as seguintes dependências ao seu arquivo 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
:
<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 as configurações do IdP para seu aplicativo web.
Adicione a seguinte configuração ao seu arquivo 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 mergulharmos 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:
- Seu aplicativo invoca o método de login.
- O usuário é redirecionado para a página de login do Logto. Para aplicativos nativos, o navegador do sistema é aberto.
- O usuário faz login e é redirecionado de volta para o seu aplicativo (configurado como o URI de redirecionamento).
Sobre o login baseado em redirecionamento
- 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.
- 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 URI de redirecionamento usando a propriedade client.registration.logto.redirect-uri
na etapa anterior.
Configurar URIs de redirecionamento
Vá para a página de detalhes do aplicativo no Logto Console. Adicione um URI de redirecionamento http://localhost:3000/callback
.
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.
Implementar o WebSecurityConfig
Crie uma nova classe WebSecurityConfig
em seu projeto
A classe WebSecurityConfig
será usada para configurar as configurações de segurança do seu aplicativo. É a classe chave que lidará com o fluxo de autenticação e autorização. Por favor, consulte a documentação do Spring Security para mais detalhes.
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 o algoritmo padrão, precisamos sobrescrever o OidcIdTokenDecoderFactory
padrão para usar o mesmo algoritmo.
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 de login
Redirecionaremos o usuário para a página /user
após um login bem-sucedido.
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 de logout
Limpe a sessão e redirecione o usuário para a página inicial.
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
securityFilterChain é uma cadeia de filtros responsáveis por processar as solicitações e respostas recebidas.
Configuraremos o securityFilterChain
para permitir o acesso à página inicial e exigir autenticação para todas as outras solicitações. Use o CustomSuccessHandler
e o CustomLogoutHandler
para lidar com os eventos de login e logout.
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 solicitaçõ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)
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 redirecionará o usuário para a página do usuário se o usuário estiver autenticado, caso contrário, mostrará a página inicial. Adicione um link de login à página inicial.
<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 controlador para lidar com a página do usuário:
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, recuperaremos os dados do OAuth2User
do objeto principal autenticado. Por favor, consulte OAuth2AuthenticationToken e OAuth2User para mais detalhes.
Leia os dados do usuário e passe-os para o template 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.
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:
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
:
spring.security.oauth2.client.registration.logto.scope=openid,profile,offline_access,email,phone,urn:logto:scope:organizations
Então você pode acessar as reivindicações adicionais no objeto OAuth2User
.
Execute e teste o aplicativo
Execute o aplicativo e navegue para 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 do usuário com seus detalhes de usuário.
- Clique no botão de logout para sair. Você será redirecionado de volta para a página inicial.
Escopos e Reivindicações
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 (Scopes) suportados e as Reivindicações (Claims) correspondentes:
openid
Nome da Reivindicação | Tipo | Descrição | Precisa de userinfo? |
---|---|---|---|
sub | string | O identificador único do usuário | Não |
profile
Nome da Reivindicação | Tipo | Descrição | Precisa de userinfo? |
---|---|---|---|
name | string | O nome completo do usuário | Não |
username | string | O nome de usuário do usuário | Não |
picture | string | URL da foto de perfil do Usuário Final. Este URL DEVE referir-se 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. Note que este URL DEVE referenciar especificamente uma foto de perfil do Usuário Final adequada para exibição ao descrever o Usuário Final, em vez de uma foto arbitrária tirada pelo Usuário Final. | Não |
created_at | number | Hora 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). | Não |
updated_at | number | Hora 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). | Não |
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 estiverem vazios, enquanto as reivindicações acima retornarão null
se os valores estiverem vazios.
Ao contrário das reivindicações padrão, as reivindicações created_at
e updated_at
estão usando milissegundos em vez de segundos.
email
Nome da Reivindicação | Tipo | Descrição | Precisa de userinfo? |
---|---|---|---|
string | O endereço de email do usuário | Não | |
email_verified | boolean | Se o endereço de email foi verificado | Não |
phone
Nome da Reivindicação | Tipo | Descrição | Precisa de userinfo? |
---|---|---|---|
phone_number | string | O número de telefone do usuário | Não |
phone_number_verified | boolean | Se o número de telefone foi verificado | Não |
address
Por favor, consulte o OpenID Connect Core 1.0 para os detalhes da reivindicação de endereço.
custom_data
Nome da Reivindicação | Tipo | Descrição | Precisa de userinfo? |
---|---|---|---|
custom_data | object | Os dados personalizados do usuário | Sim |
identities
Nome da Reivindicação | Tipo | Descrição | Precisa de userinfo? |
---|---|---|---|
identities | object | As identidades vinculadas do usuário | Sim |
sso_identities | array | As identidades SSO vinculadas do usuário | Sim |
urn:logto:scope:organizations
Nome da Reivindicação | Tipo | Descrição | Precisa de userinfo? |
---|---|---|---|
organizations | string[] | Os IDs das organizações às quais o usuário pertence | Não |
organization_data | object[] | Os dados das organizações às quais o usuário pertence | Sim |
urn:logto:scope:organization_roles
Nome da Reivindicação | Tipo | Descrição | Precisa de userinfo? |
---|---|---|---|
organization_roles | string[] | Os papéis da organização aos quais o usuário pertence com o formato <organization_id>:<role_name> | Não |
Considerando o desempenho e o tamanho dos dados, se "Precisa de userinfo?" for "Sim", isso significa que a reivindicação não aparecerá no Token de ID, mas será retornada na resposta do endpoint userinfo.
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
:
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.