Spring boot: Garantindo a segurança de APIs REST com Keycloak e Spring boot

O Keycloak é uma solução de gerenciamento de acesso e identidade de código aberto que facilita a segurança de aplicativos e serviços modernos com pouco ou nenhum código. O Keycloak vem com seus próprios adapters para plataformas selecionadas, mas também é possível usar as bibliotecas genéricas OpenID Connect Relying Party e SAML Service Provider. Porém, o uso dos adapters de cliente do Keycloak seria muito mais simples, fácil de usar e requer menos código padrão do que é normalmente exigido por uma biblioteca.

O foco principal deste artigo é proteger APIs REST do Spring Boot com o Keycloak Spring Boot Adapter.

Conheci o Keycloak através de um projeto grande na empresa que precisava que o gerenciamento de segurança fosse centralizado em um único lugar, tanto para os parceiros quanto para os clientes dos parceiros (caso viessem a vender a solução). Para esse tipo de configuração vou explicar melhor em um próximo artigo, por enquanto vamos focar na configuração básica do Keycloak com Spring boot.

Vamos lá! Para seguir melhor seguir este artigo, você precisa ter uma instância Keycloak em execução. Se você não tiver, poderá fazer o download no próprio site do Keycloak. Depois de realizar o download, se você estiver utilizando Linux ou Mac, é só entrar na pasta /bin e executar o comando:

sh standalone.sh

Configurando o Keycloak

Criando um realm

A primeira coisa que precisamos fazer é criar um novo realm no Keycloak.

Vou dar aqui uma breve explicação de como funciona esse conceito de realms. Um realm gerencia um conjunto de usuários, credenciais, funções e grupos. Um usuário que pertence a ele efetua login nesse realm. Realms são isolados uns dos outros e só podem gerenciar e autenticar os usuários que pertencem a ele. Dito isso, vamos ao que interessa.


Vá para http: //localhost:8080/auth/admin/ e faça login no Keycloak Admin Console usando as credenciais de administrador (Geralmente são admin:admin).


No menu suspenso, clique em Add realm. Quando você está conectado ao realm master, este menu suspenso lista todos os realms existentes. Digite demo-realm no campo Name e clique em Create.

Criando novo realm

Quando o realm é criado, a página principal do console de administrador é aberta. Observe que o realm atual está agora definido como demo-realm. Alterne entre o gerenciamento do realm principal e o realm que acabamos de criar clicando nas entradas no menu suspenso Select realm. Verifique se demo-realm está selecionado para as configurações abaixo. Evite usar o realm master. Você não precisa criar o realm todas as vezes. É um processo único.

Criando um client

Clients são entidades que podem solicitar o Keycloak para autenticar um usuário. Na maioria das vezes, os clients são aplicativos e serviços que desejam usar o Keycloak para se proteger e fornecer uma solução de logon único. Os clients também podem ser entidades que desejam apenas solicitar informações de identidade ou um token de acesso, para que possam invocar com segurança outros serviços na rede protegidos pelo Keycloak.

Clique no menu Clients no painel esquerdo. Todos os clients disponíveis para o realm selecionado serão listados aqui.

Gerenciamento de clients no Keycloak Admin Console

Para criar um novo client, clique em Create. Você será solicitado a fornecer um client ID, um Client Protocol e uma Root URL. Uma boa opção para o client ID é o nome da sua aplicação (gateway-microservice), o client protocol deve ser definido como openid-connect e a Root URL deve ser definido como a URL da aplicação.

Criando novo client no Keycloak Admin Console

Após salvar, você irá para a tela de configuração do client onde é possível atribuir um nome e uma descrição ao client, se desejar.

Deixe Access Type como confidential, Authorization Enabled para OnService Account Enabled para On e clique em Save.

Configuração do client

A aba Credentials mostrará o client secret necessário para as configurações do Keycloak com o Spring Boot.

Aba client credentials

Vá para a aba Client Role para criar as definições da Role do gateway-microservice. Imagine que a aplicação que você está construindo tem diferentes tipos de usuários e com permissões de usuário diferentes. Ex: users e admins. Ficaria mais ou menos assim:

Algumas APIs seriam acessíveis apenas aos users.

Algumas APIs seriam acessíveis apenas para admins.

Algumas APIs estariam acessíveis para users e admins.

Conforme o exemplo, vamos criar duas Roles: user e admin, clicando no botão Add Role.

Adicionando uma nova Role
Adicionando a Role user
Adicionando a Role admin
Lista das Roles criadas

Criando Realm Role

As aplicações geralmente atribuem acesso e permissões a funções específicas, em vez de usuários individuais, pois lidar com usuários pode ser muito difícil de gerenciar. Vamos criar app-user e app-admin roles realm, atribuindo as roles correspondente ao gateway-microservice (user, admin).

Clique no menu Roles no painel esquerdo. Todas as roles disponíveis para o Realm selecionado serão listadas.

Lista de Roles do Realm selecionado

Para criar o app-user realm role, clique em Add role. Você será solicitado a fornecer um nome da role e uma descrição. Forneça os detalhes como abaixo e salve.

Adicionando a realm role app-user

Depois de Salvar, habilite o Composite Role e Procure o gateway-microservice no campo Client roles. Selecione a user role do gateway-microservice e clique em Add Selected.

Atribuir user role a app-user Realm Role

Essa configuração atribuirá a user client role para o app-user realm role do gateway-microservice. Se você tiver vários clients com várias roles, escolha as roles necessárias de cada client para criar realm role com base na necessidade. Siga as mesmas etapas para criar o user app-admin, mas atribua a client role admin em vez da role user.

Criando usuários

Usuários são entidades capazes de efetuar login no seu sistema. Eles podem ter atributos associados a eles mesmos, como email, nome de usuário, endereço, número de telefone e dia do nascimento. Eles podem ser associados ao grupo e ter roles específicas atribuídas a eles.

Vamos criar os seguintes usuários e conceder a eles funções de app-user e de app-admin para fins de teste.

  • funcionario1 com app-user realm role.
  • funcionario2 com app-admin realm role.
  • funcionario3 com app-user e app-admin realm roles.

No menu, clique em Users para abrir a página da lista de usuários. No lado direito da lista de usuários vazia, clique em Add user para abrir a página de adicionar usuário. Digite um nome no campo username, este é o único campo obrigatório. Coloque a opção Email Verified de Off para On e clique em save para salvar os dados e abrir a página de gerenciamento para o novo usuário.

adicionando usuario

Clique na aba Credentials para definir uma senha temporária para o novo usuário. Digite uma nova senha e confirme-a. Mude o botão de Temporary de On para Off e clique em Reset Password para definir a senha do usuário para a nova que você especificou. Para simplificar, vamos definir a mesma senha para todos os usuários.

Definindo senha para o novo usuário

Clique na aba Role Mappings para atribuir o realm roles para o usuário. A lista de Realm roles estará disponível em Available Roles. Selecione uma role e clique em Add Selected para atribuir ao usuário.

Após a atribuição das roles, as roles atribuídas estarão disponíveis na lista Assigned Roles. Façam a mesma coisa para o usuários (funcionario2, funcionario3). Feito as atribuições as telas ficarão mais ou menos assim:

funcionario1
funcionario2
funcionario3

Geração dos tokens

Vá para Realm Settings do Demo-Realm no menu esquerdo e clique em OpenID Endpoint Configuration para ver os detalhes do endpoint.

Configurações do realm
Detalhes do endpoint

Copie o token_endpoint do OpenID Endpoint Configuration. A URL deve ser mais ou menos assim:

<KEYCLOAK_SERVER_URL>/auth/realms/<REALM_NAME>/protocol/openid-connect/token
Ex: http://localhost:8080/auth/realms/Demo-Realm/protocol/openid-connect/token

Use o comando CURL para gerar as credenciais do usuário. Mude para os valores corretos.

curl -X POST '<KEYCLOAK_SERVER_URL>/auth/realms/<REALM_NAME>/protocol/openid-connect/token' \
 --header 'Content-Type: application/x-www-form-urlencoded' \
 --data-urlencode 'grant_type=password' \
 --data-urlencode 'client_id=<CLIENT_ID>' \
 --data-urlencode 'client_secret=<CLIENT_SECRET>' \
 --data-urlencode 'username=<USERNAME>' \
 --data-urlencode 'password=<PASSWORD>'

ou pelo postman:

Geração do token no postman

Caso você queira sabe o que vem nesse token, é só ir no site do jwt.io

Site jwt.io para decodificar o token

Configuração Spring boot

Bom, chegou a melhor parte!! Suponho que já tenha levantado um projeto básico do Spring boot com as seguintes dependências: Spring boot starter web e Spring boot starter security.

Agora vamos adicionar a dependência do Keycloak no maven:

<dependencies>
   <dependency>
      <groupId>org.keycloak</groupId>
      <artifactId>keycloak-spring-boot-starter</artifactId>
      <version>${keycloak.version}</version>
   </dependency>
   ...
</dependencies>


Onde tem keycloak.version pode colocar a versão mais recente. Pode procurar no repositório do maven.

Depois adicionamos também o gerenciamento de dependências no maven para o Keycloak:

<dependencyManagement>
   <dependencies>
      <dependency>
         <groupId>org.keycloak.bom</groupId>
         <artifactId>keycloak-adapter-bom</artifactId>
         <version>${keycloak.version}</version>
         <type>pom</type>
         <scope>import</scope>
      </dependency>
   </dependencies>
</dependencyManagement>

No application.properties do Spring boot vamos adicionar a seguinte configuração:

server.port                         = 8000
keycloak.realm                      = Demo-Realm
keycloak.auth-server-url            = http://localhost:8080/auth
keycloak.ssl-required               = external
keycloak.resource                   = gateway-microservice
keycloak.credentials.secret         = XXXXXXXXXXXXXXXXXXXXXXXXX
keycloak.use-resource-role-mappings = true
keycloak.bearer-only                = true

O Keycloak fornece um KeycloakWebSecurityConfigurerAdapter como uma classe base conveniente para criar uma instância do WebSecurityConfigurer. A implementação permite a personalização substituindo métodos. Embora seu uso não seja necessário, ele simplifica bastante a configuração do contexto de segurança.

Vamos criar uma classe chamada KeycloakSecurityConfig que estende o adapter.

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(jsr250Enabled = true)
public class KeycloakSecurityConfig extends KeycloakWebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        super.configure(http);
        http.authorizeRequests()
            .anyRequest()
            .permitAll();
        http.csrf().disable();
    }
    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        KeycloakAuthenticationProvider keycloakAuthenticationProvider = keycloakAuthenticationProvider();
        keycloakAuthenticationProvider.setGrantedAuthoritiesMapper(new SimpleAuthorityMapper());
        auth.authenticationProvider(keycloakAuthenticationProvider);
    }
    @Bean
    @Override
    protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
        return new RegisterSessionAuthenticationStrategy(new SessionRegistryImpl());
    }
    @Bean
    public KeycloakConfigResolver KeycloakConfigResolver() {
        return new KeycloakSpringBootConfigResolver();
    }
}

configureGlobal: registra o KeycloakAuthenticationProvider com o gerenciador de autenticação.

sessionAuthenticationStrategy: define a estratégia de autenticação da sessão.

KeycloakConfigResolver: por padrão, o Spring Security Adapter procura um arquivo de configuração keycloak.json. Você pode verificar se a configuração fornecida pelo Spring boot Adapter inclui este bean.

@EnableGlobalMethodSecurity: a propriedade jsr250Enabled nos permite usar a anotação

@RoleAllowed. Exploraremos mais sobre esta anotação daqui a pouco.

Vamos criar um rest controller muito simples:

@RestController
@RequestMapping("/test")
public class TestController {
    @RequestMapping(value = "/anonymous", method = RequestMethod.GET)
    public ResponseEntity<String> getAnonymous() {
        return ResponseEntity.ok("Hello Anonymous");
    }
    @RequestMapping(value = "/user", method = RequestMethod.GET)
    public ResponseEntity<String> getUser() {
        return ResponseEntity.ok("Hello User");
    }
    @RequestMapping(value = "/admin", method = RequestMethod.GET)
    public ResponseEntity<String> getAdmin() {
        return ResponseEntity.ok("Hello Admin");
    }
    @RequestMapping(value = "/all-user", method = RequestMethod.GET)
    public ResponseEntity<String> getAllUser() {
        return ResponseEntity.ok("Hello All User");
    }
}

Vamos utilizar a annotation @RolesAllowed com as roles que criamos no Keycloak:

@RolesAllowed("user")
@RequestMapping(value = "/user", method = RequestMethod.GET)
public ResponseEntity<String> getUser(@RequestHeader String Authorization) {
    return ResponseEntity.ok("Hello User");
}

Faça isso para as outras roles no seu respectivo método. O resultado final ficaria assim:

@RestController
@RequestMapping("/test")
public class TestController {
    @RequestMapping(value = "/anonymous", method = RequestMethod.GET)
    public ResponseEntity<String> getAnonymous() {
        return ResponseEntity.ok("Hello Anonymous");
    }
    @RolesAllowed("user")
    @RequestMapping(value = "/user", method = RequestMethod.GET)
    public ResponseEntity<String> getUser(@RequestHeader String Authorization) {
        return ResponseEntity.ok("Hello User");
    }
    @RolesAllowed("admin")
    @RequestMapping(value = "/admin", method = RequestMethod.GET)
    public ResponseEntity<String> getAdmin(@RequestHeader String Authorization) {
        return ResponseEntity.ok("Hello Admin");
    }
    @RolesAllowed({ "admin", "user" })
    @RequestMapping(value = "/all-user", method = RequestMethod.GET)
    public ResponseEntity<String> getAllUser(@RequestHeader String Authorization) {
        return ResponseEntity.ok("Hello All User");
    }
}

Reinicie o Spring boot e teste as APIs, passando tokens dos tokens de acesso funcionario1, funcionario2, funcionario3 no cabeçalho Authorization (bearer <Token> ).

curl -X GET 'http://localhost:8000/test/user' \
--header 'Authorization: bearer <ACCESS_TOKEN>'
Saídas:
anonymous: 403 Forbidden
funcionario1: Hello User
funcionario2: 403 Forbidden
funcionario3: Hello User
curl -X GET 'http://localhost:8000/test/admin' \
--header 'Authorization: bearer <ACCESS_TOKEN>'
Saídas:
anonymous: 403 Forbidden
funcionario1: 403 Forbidden
funcionario2: Hello Admin
funcionario3: Hello Admin
curl -X GET 'http://localhost:8000/test/all-user' \
--header 'Authorization: bearer <ACCESS_TOKEN>'
Saídas:
anonymous: 403 Forbidden
funcionario1: Hello All User
funcionario2: Hello All User
funcionario3: Hello All User

Assim como a annotation @RolesAllowed você poderá também configurar as roles na classe de configuração do Spring Security (Que criamos anteriormente):

@Override
protected void configure(HttpSecurity http) throws Exception {
    super.configure(http);
    http.authorizeRequests()
        .antMatchers("/test/anonymous").permitAll()
        .antMatchers("/test/user").hasAnyRole("user")
        .antMatchers("/test/admin").hasAnyRole("admin")
        .antMatchers("/test/all-user").hasAnyRole("user","admin")
        .anyRequest()
        .permitAll();
    http.csrf().disable();
}

Bom, é isso pessoal. Espero que tenham curtido esse artigo e até o próximo!