Spring Boot OAuth2 OIDC - um Authorization Server mínimo in-memory

    Foto do autor Nelio Alves
    Nelio Alves
    Compartilhar
    Compartilhar no LinkedInCompartilhar no FacebookCompartilhar no XCompartilhar no WhatsApp
    Imagem banner do post

    Este post faz parte de uma série sobre a construção de um sistema de referência para autenticação e autorização com OAuth2 e OpenID Connect (OIDC) usando Spring Boot e Next.js. O objetivo da série é cobrir as principais funcionalidades de uma aplicação web típica: recursos públicos, recursos protegidos por login, controle de acesso por perfil, e login social via Google (SSO). Neste post vamos abordar o ponto de partida: a construção de um Authorization Server mínimo, com recursos in-memory.

    Visão geral - o que você vai aprender

    Neste primeiro post, vamos construir o Authorization Server (AS) — o componente central do ecossistema OAuth2, responsável por autenticar usuários e emitir os tokens que controlam o acesso ao sistema.

    Vamos utilizar, por enquanto, recursos in-memory para simplificar a implementação, tais como usuários, aplicações clientes e chave RSA. Em estudos futuros, vamos adicionar os devidos repositórios para lidar com esses recursos.

    Ao final deste post, teremos um AS mínimo e funcional com:

    • Servidor rodando na porta 9000
    • Tela de login padrão do Spring Security
    • Dois usuários in-memory (maria e alex)
    • Um cliente OAuth2 registrado (o futuro frontend Next.js)
    • Fluxo Authorization Code com PKCE
    • Suporte a OpenID Connect (OIDC)
    • Discovery document (/.well-known/openid-configuration)
    • Emissão de JWTs assinados com RSA

    Contexto: o papel do Authorization Server no ecossistema

    Antes de começar a codar, vale entender o lugar do AS na arquitetura. O sistema completo será composto por quatro partes:

    Copiar
    ┌─────────────┐     Authorization Code + PKCE      ┌─────────────────────┐
    │  Next.js    │ ─────────────────────────────────► │  Authorization      │
    │  (Cliente)  │ ◄───────────────────────────────── │  Server             │
    └─────────────┘         access_token + id_token    └─────────────────────┘
           │                                                      ▲
           │  Bearer access_token                                  │ federa com
           ▼                                                      ▼
    ┌─────────────┐                                    ┌─────────────────────┐
    │  Resource   │                                    │  Google (OIDC)      │
    │  Server     │                                    │                     │
    └─────────────┘                                    └─────────────────────┘

    O AS é o único componente que conhece os usuários e suas senhas. O Resource Server nunca vê senhas — ele só valida o JWT emitido pelo AS. O Next.js nunca fala diretamente com o Google — ele sempre passa pelo AS.


    Estrutura da implementação do Authorization Server com Spring Security

    A estrutura de implementação possui as seguintes partes:

    1. Filtros do Spring Security
      • SecurityFilterChain - para endpoints do Authorization Server
      • SecurityFilterChain - para demais rotas
    2. Infraestrutura de usuários
      • UserDetailsService - para acessar usuários
      • PasswordEncoder - para encodar as senhas
    3. Registro de aplicações clientes
      • RegisteredClientRepository - para acessar apps clientes
    4. Infraestrutura JWT
      • JWKSource - para acessar o par de chaves RSA
      • JwtDecoder - para validar o acceess token
    5. Configuração do servidor
      • AuthorizationServerSettings

    Começando o projeto

    Criando o projeto no Spring Initializr

    Acesse start.spring.io e configure:

    CampoValor
    ProjectMaven
    LanguageJava
    Spring Boot4.x.x
    Groupcom.devsuperior
    Artifactauthserver
    PackagingJar
    Java25

    Adicione as seguintes dependências:

    • Spring Web (spring-boot-starter-webmvc)
    • Spring Security (spring-boot-starter-security)
    • OAuth2 Authorization Server (spring-boot-starter-security-oauth2-authorization-server)

    Clique em Generate, descompacte o arquivo e abra o projeto na IDE.

    A estrutura inicial do pom.xml gerado ficará assim (dependências de test omitidas):

    Copiar
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-webmvc</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-security-oauth2-authorization-server</artifactId>
        </dependency>
    </dependencies>

    Configurando os profiles e propriedades

    O AS precisa rodar na porta 9000 para não colidir com a porta padrão 8080 (que será usada pelo Resource Server).

    Vamos usar o padrão de profiles do Spring Boot para separar configurações de ambiente. Edite o application.properties gerado pelo Initializr:

    Copiar
    spring.profiles.active=${APP_PROFILE:dev}
    
    server.port=9000

    A expressão ${APP_PROFILE:dev} significa: use a variável de ambiente APP_PROFILE se ela existir; caso contrário, use dev como padrão. Isso permite que em produção você defina APP_PROFILE=prod sem alterar o código.


    Criando a classe de configuração do Authorization Server

    Toda a configuração do AS ficará em uma única classe: AuthorizationServerConfig. Crie-a no pacote com.devsuperior.authserver.config.

    Vamos construir essa classe parte por parte.

    Copiar
    @Configuration
    @EnableWebSecurity
    public class AuthorizationServerConfig {
        // ...
    }

    @EnableWebSecurity é necessária para que o Spring Security processe os beans SecurityFilterChain que vamos definir.

    1. Filtros do Spring Security

    O Spring Security usa uma pilha de filter chains. Cada chain tem um securityMatcher que define para quais URLs ela se aplica. O AS precisa de dois:

    Filter chain 1 — endpoints do Authorization Server

    Este filter chain intercepta todas as URLs padrão do protocolo OAuth2 e OIDC: /oauth2/authorize, /oauth2/token, /oauth2/jwks, /userinfo, /.well-known/openid-configuration, etc.

    Copiar
    @Bean
    @Order(1)
    public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
    
        OAuth2AuthorizationServerConfigurer authorizationServerConfigurer =
                new OAuth2AuthorizationServerConfigurer();
    
        http
            .securityMatcher(authorizationServerConfigurer.getEndpointsMatcher())
            .with(authorizationServerConfigurer, as -> as
                // Habilita suporte a OpenID Connect (ID token, /userinfo, discovery)
                .oidc(Customizer.withDefaults())
            )
            .authorizeHttpRequests(authorize -> authorize
                .anyRequest().authenticated()
            )
            // Redireciona para /login quando o browser acessa um endpoint protegido
            .exceptionHandling(ex -> ex
                .defaultAuthenticationEntryPointFor(
                    new LoginUrlAuthenticationEntryPoint("/login"),
                    new MediaTypeRequestMatcher(MediaType.TEXT_HTML)
                )
            );
    
        return http.build();
    }

    Alguns pontos importantes:

    • getEndpointsMatcher() retorna um RequestMatcher que cobre exatamente os endpoints registrados pelo protocolo — não é necessário listá-los manualmente.
    • .oidc(Customizer.withDefaults()) habilita o suporte a OpenID Connect. Sem isso, o AS funciona apenas como OAuth2 puro, sem emitir ID tokens nem expor o discovery document.
    • O LoginUrlAuthenticationEntryPoint garante que quando um browser (que envia Accept: text/html) acessar um endpoint protegido, ele seja redirecionado para /login em vez de receber um 401 JSON.

    Filter chain 2 — demais rotas

    Este filter chain captura tudo que não foi interceptado pela chain 1: a tela de login e qualquer outra rota que possamos criar no futuro.

    Copiar
    @Bean
    @Order(2)
    public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests(authorize -> authorize
                .anyRequest().authenticated()
            )
            .formLogin(Customizer.withDefaults());
    
        return http.build();
    }
    • .formLogin(Customizer.withDefaults()) ativa a tela de login padrão do Spring Security em /login.

    2. Infraestrutura de usuários

    PasswordEncoder

    Copiar
    @Bean
    public PasswordEncoder passwordEncoder() {
        return PasswordEncoderFactories.createDelegatingPasswordEncoder();
    }

    O DelegatingPasswordEncoder é o encoder padrão recomendado pelo Spring Security. Ele funciona com prefixos no hash armazenado: {bcrypt}$2a$..., {noop}senha, etc. Isso é fundamental porque o Spring Authorization Server usa este mesmo bean para verificar o client secret do cliente registrado — um BCryptPasswordEncoder puro não consegue processar o prefixo {noop} e causaria erro de autenticação do cliente.

    UserDetailsService

    Vamos criar o componente UserDetailsService (padrão do Spring Security) para acessar os usuários. Para este primeiro estágio, os usuários são armazenados em memória. Em um post futuro, migraremos para banco de dados.

    Copiar
    @Bean
    public UserDetailsService userDetailsService(PasswordEncoder passwordEncoder) {
    
        UserDetails maria = User.builder()
            .username("maria@example.com")
            .password(passwordEncoder.encode("12345678"))
            .authorities(List.of())
            .build();
    
        UserDetails alex = User.builder()
            .username("alex@example.com")
            .password(passwordEncoder.encode("12345678"))
            .authorities(List.of())
            .build();
    
        return new InMemoryUserDetailsManager(maria, alex);
    }

    O método passwordEncoder.encode("12345678") produz um hash no formato {bcrypt}$2a$10$..., que o DelegatingPasswordEncoder sabe verificar no momento do login.

    3. Registro de aplicações clientes

    No OAuth2, um cliente é uma aplicação que solicita acesso a recursos em nome do usuário. No nosso caso, o cliente é o frontend Next.js.

    Copiar
    @Bean
    public RegisteredClientRepository registeredClientRepository() {
    
        RegisteredClient nextjsClient = RegisteredClient.withId(UUID.randomUUID().toString())
            .clientId("nextjs-client")
            // {noop} = sem encoding de senha, apenas para desenvolvimento
            .clientSecret("{noop}nextjs-secret")
            .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
            .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_POST)
            .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
            .authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
            // URL de callback do Next.js (Auth.js)
            .redirectUri("http://localhost:3000/api/auth/callback/spring")
            // URL de callback para testes manuais (browser)
            .redirectUri("http://localhost:9000/authorized")
            .postLogoutRedirectUri("http://localhost:3000")
            // Scopes disponíveis para este cliente
            .scope(OidcScopes.OPENID)       // obrigatório para OIDC / ID token
            .clientSettings(ClientSettings.builder()
                .requireAuthorizationConsent(false)
                .build())
            .build();
    
        return new InMemoryRegisteredClientRepository(nextjsClient);
    }

    Alguns pontos a destacar:

    • CLIENT_SECRET_BASIC envia as credenciais do cliente no header Authorization: Basic. CLIENT_SECRET_POST envia no corpo do form. Ambos são suportados para flexibilidade de teste.
    • AUTHORIZATION_CODE é o único grant type seguro para aplicações com interface de usuário. O REFRESH_TOKEN permite renovar o access token sem novo login.
    • O scope openid é obrigatório para o fluxo OIDC — ele instrui o AS a emitir um ID token além do access token. Scopes de negócio (como products:read) são responsabilidade dos Resource Servers, não do AS.
    • requireAuthorizationConsent(false) desativa a tela de consentimento — o fluxo Authorization Code redireciona direto para o callback sem pedir aprovação de scopes ao usuário.

    4. Infraestrutura JWT

    JWKSource - para acessar o par de chaves RSA

    Os JWTs emitidos pelo AS são assinados com uma chave privada RSA e verificados com a chave pública correspondente. O Resource Server busca a chave pública via endpoint /oauth2/jwks e valida os tokens localmente — sem precisar chamar o AS a cada requisição.

    Copiar
    @Bean
    public JWKSource<SecurityContext> jwkSource() {
        KeyPair keyPair = generateRsaKey();
        RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
        RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
    
        RSAKey rsaKey = new RSAKey.Builder(publicKey)
            .privateKey(privateKey)
            .keyID(UUID.randomUUID().toString())
            .build();
    
        return new ImmutableJWKSet<>(new JWKSet(rsaKey));
    }
    
    private static KeyPair generateRsaKey() {
        try {
            KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA");
            generator.initialize(2048);
            return generator.generateKeyPair();
        } catch (Exception ex) {
            throw new IllegalStateException(ex);
        }
    }

    Atenção: O par de chaves gerado aqui é efêmero — é criado em memória a cada inicialização do servidor. Isso significa que todos os tokens emitidos anteriormente se tornam inválidos após um restart. Em produção, as chaves devem ser persistidas externamente (ex: AWS KMS, HashiCorp Vault, ou um keystore em disco). Isso será abordado em um post futuro.

    JwtDecoder - para validar o acceess token

    Copiar
    @Bean
    public JwtDecoder jwtDecoder(JWKSource<SecurityContext> jwkSource) {
        return OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource);
    }

    Este JwtDecoder é usado internamente pelo próprio AS — por exemplo, para validar o access token quando o cliente chama o endpoint /userinfo ou /oauth2/introspect. Ele não é o mesmo JwtDecoder que será configurado no Resource Server.

    5. Configuração do servidor

    Copiar
    @Bean
    public AuthorizationServerSettings authorizationServerSettings() {
        return AuthorizationServerSettings.builder()
            .issuer("http://localhost:9000")
            .build();
    }

    O issuer é a URL base do AS. Ela aparece na claim iss de todos os tokens emitidos e no discovery document. O Resource Server usará essa URL para descobrir automaticamente os endpoints do AS via /.well-known/openid-configuration.


    Fluxo Authorization Code em detalhes

    Suponha um App frontend (por exemplo uma aplicação Next) executando o fluxo authorization code. A sequência é esta:

    • Em algum lugar do App vai haver um botão "Entrar".
    • Quando o usuário clicar no botão "Entrar", será chamada uma rota do App que inicia o processo de login, por exemplo {App}/api/auth/login.
    • O App prepara todos parâmetros (client_id, response_type, redirect_uri, scope, state, code_challenge, code_challenge_method) e redireciona para o Authorization Server em GET {AS}/oauth2/authorize?....
    • O Authorization Server apresenta sua tela de login ao usuário, que informa suas credenciais.
    • O Authorization Server valida tudo e redireciona para a callback do App passando code e state, por exemplo {App}/api/auth/callback/spring?code=aaa&state=bbb.
    • O App valida tudo e, via servidor, envia a requisição ao Authorization Server para trocar o code pelos tokens: POST {AS}/oauth2/token.
    • O App recebe a resposta da requisição (contendo os tokens).

    A seguir eu mostro dois diagramas de sequência, para ilustrar de forma didática como este fluxo ocorre. Cole o código no Visualizador do Mermaid.

    Fluxo com algoritmo detalhado

    Copiar
    sequenceDiagram
        actor U as Usuário
        participant B as Browser
        participant N as Next.js<br/>(localhost:3000)
        participant AS as Authorization Server<br/>(localhost:9000)
    
        U->>B: Clica em "Entrar"
        B->>N: GET /api/auth/login
    
        Note over N: Gera code_verifier (32 bytes aleatórios)<br/>Calcula code_challenge = SHA-256(verifier)<br/>Gera state (16 bytes aleatórios)
        N->>B: Set-Cookie: pkce_verifier, oauth_state (HttpOnly)
        N->>B: 302 → /oauth2/authorize?response_type=code<br/>&client_id=nextjs-client<br/>&redirect_uri=.../callback/spring<br/>&scope=openid<br/>&state=...&code_challenge=...&code_challenge_method=S256
    
        B->>AS: GET /oauth2/authorize?...
        AS->>B: 302 → /login (tela de login do AS)
        B->>U: Exibe formulário de login
    
        U->>B: Preenche e-mail + senha e submete
        B->>AS: POST /login (username, password)
    
        Note over AS: Autentica o usuário via<br/>InMemoryUserDetailsManager
        Note over AS: Valida code_challenge<br/>Gera authorization code
    
        AS->>B: 302 → /api/auth/callback/spring?code=...&state=...
    
        B->>N: GET /api/auth/callback/spring?code=...&state=...
        Note over N: Lê pkce_verifier e oauth_state dos cookies<br/>Valida state recebido === state salvo
    
        N->>AS: POST /oauth2/token<br/>Authorization: Basic (client_id:secret)<br/>grant_type=authorization_code<br/>code=...&code_verifier=...&redirect_uri=...
    
        Note over AS: Valida authorization code<br/>Verifica code_verifier contra code_challenge
        AS->>N: { access_token, id_token, ... }
    
        Note over N: Decodifica id_token (JWT)<br/>Extrai claim "sub" (e-mail do usuário)<br/>Apaga cookies pkce_verifier e oauth_state<br/>Define cookie user_email (HttpOnly)
        N->>B: Delete-Cookie: pkce_verifier, oauth_state<br/>Set-Cookie: user_email (HttpOnly)<br/>302 → /
    
        B->>N: GET /
        Note over N: Lê cookie user_email
        N->>B: Página "Logado com maria@example.com"
        B->>U: Exibe "Logado com maria@example.com"

    Fluxo somente com requisições e valores reais

    Copiar
    sequenceDiagram
        actor U as Usuário
        participant B as Browser
        participant N as Next.js<br/>(localhost:3000)
        participant AS as Authorization Server<br/>(localhost:9000)
    
        U->>B: Clica em "Entrar"
    
        B->>N: GET http://localhost:3000/api/auth/login
    
        N->>B: HTTP/1.1 302 Found<br/>Set-Cookie: pkce_verifier=dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk#59; HttpOnly#59; SameSite=Lax<br/>Set-Cookie: oauth_state=rT2hJ9kLmN4pQwXv#59; HttpOnly#59; SameSite=Lax<br/>Location: http://localhost:9000/oauth2/authorize?response_type=code<br/>&client_id=nextjs-client<br/>&redirect_uri=http://localhost:3000/api/auth/callback/spring<br/>&scope=openid<br/>&state=rT2hJ9kLmN4pQwXv<br/>&code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM<br/>&code_challenge_method=S256
    
        B->>AS: GET http://localhost:9000/oauth2/authorize?response_type=code<br/>&client_id=nextjs-client<br/>&redirect_uri=http://localhost:3000/api/auth/callback/spring<br/>&scope=openid<br/>&state=rT2hJ9kLmN4pQwXv<br/>&code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM<br/>&code_challenge_method=S256
    
        AS->>B: HTTP/1.1 302 Found<br/>Location: http://localhost:9000/login
    
        B->>AS: GET http://localhost:9000/login
        AS->>B: HTTP/1.1 200 OK (HTML — formulário de login)
        B->>U: Exibe formulário de login
    
        U->>B: Preenche e-mail + senha e submete
    
        B->>AS: POST http://localhost:9000/login<br/>Content-Type: application/x-www-form-urlencoded<br/><br/>username=maria%40example.com&password=12345678
    
        AS->>B: HTTP/1.1 302 Found<br/>Location: http://localhost:3000/api/auth/callback/spring?code=jx8KpLmN2qRtUvWx&state=rT2hJ9kLmN4pQwXv
    
        B->>N: GET http://localhost:3000/api/auth/callback/spring?code=jx8KpLmN2qRtUvWx&state=rT2hJ9kLmN4pQwXv<br/>Cookie: pkce_verifier=dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk#59; oauth_state=rT2hJ9kLmN4pQwXv
    
        N->>AS: POST http://localhost:9000/oauth2/token<br/>Authorization: Basic bmV4dGpzLWNsaWVudDpuZXh0anMtc2VjcmV0<br/>Content-Type: application/x-www-form-urlencoded<br/><br/>grant_type=authorization_code<br/>&code=jx8KpLmN2qRtUvWx<br/>&redirect_uri=http://localhost:3000/api/auth/callback/spring<br/>&code_verifier=dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk
    
        AS->>N: HTTP/1.1 200 OK<br/>Content-Type: application/json<br/><br/>{"access_token":"eyJhbGci...","id_token":"eyJhbGci...eyJzdWIiOiJtYXJpYUBleGFtcGxlLmNvbSJ9...","token_type":"Bearer","expires_in":300}
    
        N->>B: HTTP/1.1 302 Found<br/>Set-Cookie: pkce_verifier=#59; Max-Age=0<br/>Set-Cookie: oauth_state=#59; Max-Age=0<br/>Set-Cookie: user_email=maria@example.com#59; HttpOnly#59; SameSite=Lax<br/>Location: http://localhost:3000/
    
        B->>N: GET http://localhost:3000/<br/>Cookie: user_email=maria@example.com
    
        N->>B: HTTP/1.1 200 OK (HTML — "Logado com maria@example.com")
        B->>U: Exibe "Logado com maria@example.com"

    Testando o servidor

    Execute o projeto e verifique os seguintes endpoints no browser:

    Discovery document OIDChttp://localhost:9000/.well-known/openid-configuration

    Retorna um JSON com todos os endpoints do AS, algoritmos suportados, scopes, etc. Este documento é consumido automaticamente pelo Resource Server e pelo cliente Next.js para auto-configuração.

    Copiar
    {
      "issuer": "http://localhost:9000",
      "authorization_endpoint": "http://localhost:9000/oauth2/authorize",
      "token_endpoint": "http://localhost:9000/oauth2/token",
      "jwks_uri": "http://localhost:9000/oauth2/jwks",
      "userinfo_endpoint": "http://localhost:9000/userinfo",
      ...
    }

    JWK Sethttp://localhost:9000/oauth2/jwks

    Expõe as chaves públicas RSA em formato JSON. O Resource Server busca este endpoint uma vez e faz cache da chave para validar os JWTs localmente.

    Tela de loginhttp://localhost:9000/login

    A tela de login padrão do Spring Security. Futuramente será substituída por uma tela customizada.

    Testando no Postman

    Por favor acesse a pasta do projeto no Github e baixe a collection Postman, que é o arquivo authserver.postman_collection.json localizado na pasta acima.

    Importe a collection no seu Postman. Depois de importar a collection no seu Postman, navegue nela para a pasta 1. Discovery & JWK e teste os endpoints:

    • OIDC Discovery Document
    • OAuth2 AS Metadata
    • JWK Set (chave pública RSA)

    Testando o fluxo Authorization Code

    Cole esta URL no browser para iniciar o fluxo completo:

    Copiar
    http://localhost:9000/oauth2/authorize?response_type=code&client_id=nextjs-client&redirect_uri=http://localhost:9000/authorized&scope=openid&state=xyz&code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM&code_challenge_method=S256

    O par PKCE utilizado neste exemplo de teste é fixo para conveniência:

    • code_verifier: dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk
    • code_challenge: E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM (= BASE64URL(SHA256(verifier)))

    Após o login, o browser redirecionará para http://localhost:9000/authorized?code=XXXX&state=xyz. O endpoint /authorized não existe, então a página dará 404 — isso é esperado. Copie o code da URL.

    Em seguida, copie o valor do argumento o code no resultado acima na barra de URL do seu navegador, depois acesse a collection -> Variables, e copie o valor para o parâmetro authorization_code.

    Depois de copiar o valor de authorization_code, acesse a pasta 2. Fluxo Authorization Code da collection e execute a requisição:

    • [2] Trocar code por tokens

    A resposta conterá access_token, refresh_token e id_token. Cole o access_token em jwt.io para inspecionar o payload.


    Código completo para baixar

    O código completo deste post está disponível no Github do blog.

    Conclusão

    Construímos um Authorization Server mínimo e funcional com Spring Boot 4 e Spring Authorization Server 7. Com apenas uma classe de configuração e dois arquivos de properties, temos:

    • Um servidor OAuth2/OIDC completo rodando na porta 9000
    • Suporte a Authorization Code com PKCE (o único fluxo recomendado pelo OAuth 2.1 para aplicações com usuário)
    • Emissão de JWTs assinados com RS256
    • Discovery document OIDC totalmente funcional

    A implementação in-memory é intencional neste ponto — ela mantém o código mínimo e focado no que importa: entender a estrutura e o fluxo do protocolo.

    Em estudos futuros, faremos incrementos na direção de se construir uma aplicação completa de referência para os principais recursos para autenticação e autorização com OAuth2 e OpenID Connect (OIDC).

    Foto do autor Nelio Alves
    Nelio Alves
    Desenvolvedor e Professor
    Olá, meu nome é Nelio Alves. Sou graduado em Ciência da Computação e possuo mestrado e doutorado em Engenharia de Software pela Universidade Federal de Uberlândia. Trabalho como desenvolvedor e professor de programação há mais de 20 anos, e sou um dos educadores de tecnologia mais influentes da Internet com mais de 500 mil alunos online.