Autenticação com Spring e OAuth2
Last updated
Was this helpful?
Last updated
Was this helpful?
Antes de mais nada, vale ressaltar que este não é o único jeito de fazer um sistema de autenticação com Spring. Escolhi fazer um tutorial de autenticação com o OAuth2 porque, na minha opinião, sua documentação e seus tutoriais na internet estavam bem mais claros que o JWT, por exemplo. Como é uma questão de opinião, recomendo que pesquisem também os outros métodos.
Enfim. Inicialmente, crie um projeto Maven ou Gradle com as configurações .
Nosso projeto consiste basicamente de:
Um pacote model
contendo uma classe chamada Usuario.java
, representando a nossa entidade Usuario no banco de dados.
Um pacote repository
contendo uma classe chamada UsuarioRepository.java
, que será responsável por fazer a integração da nossa aplicação com a tabela de Usuarios no banco de dados.
Um pacote controller
contendo uma classe chamada UsuarioController.java
, que é a interface de comunicação da nossa aplicação com o mundo exterior.
Estas três classes estão definidas assim:
O Spring já possui algumas ferramentas para a segurança de uma aplicação. Ao simplesmente adicionarmos a linha
compile('org.springframework.boot:spring-boot-starter-security')
ao nosso build.gradle
e iniciarmos a aplicação, ao realizarmos qualquer uma das requisições, teremos a seguinte resposta:
Esta mensagem significa que não possuimos autorização para realizar nenhuma das requisições. Porém, como queremos que apenas a requisição de listagem precise de autorização para ser executada, este comportamento está errado.
Precisamos agora colocar mais uam dependência no nosso projeto: compile('org.springframework.security.oauth:spring-security-oauth2:2.3.4.RELEASE')
Esta biblioteca possui o que nós precisamos para criarmos nosso gerenciador de tokens de segurança.
Aplicações que utilizam OAuth2 geralmente possuem um servidor próprio de autenticação. Nesse nosso caso, vamos configurar a parte de autenticação dentro do nosso próprio projeto.
Para isso, precisamos criar um pacote chamado config
ou security
e, dentro dele, uma classe chamada AuthorizationServerConfig.java
. Precisaremos anotar esse classe com @Configuration
, que significa que a classe possui Beans de configuração que serão utilizadas em toda a aplicação, e com a anotação @EnableAuthorizationServer
, que é utilizada para marcar um mecanismo de gerenciamento de autenticação. Além disso, esta deve estender a classe AuthorizationServerConfigurerAdapter
, utilizada para configurar o servidor de autenticação.
Após isso teremos a seguinte classe:
Para realizar a configuração do servidor, precisamos sobrescrever três métodos. Todos possuem o nome configure
, mas cada um recebe um parâmetro diferente:
AuthorizationServerSecurityConfigurer
define as configurações de segurança nos endpoints relativos aos tokens de acesso;
ClientDetailsServiceConfigurer
define os detalhes para o acesso da aplicação cliente ao servidor de autenticação;
AuthorizationServerEndpointsConfigurer
, que define configurações para os endpoints de autenticação e geração de tokens.
O primeiro método deve ficar parecido com isso:
Por padrão, o Spring Security provem dois endpoints relacionados à tokens existentes, que são /oauth/check_token
e /oauth/token_key
. Aqui, estamos liberando o acesso à essas requisições.
Agora, precisamos configurar o segundo método:
Com isso estamos dizendo que os tokens ficarão armazenados na memória e estarão disponíveis através do client client-id
e do secret secret-id
. Esses dois dados serão importantes na hora de gerarmos os tokens. Além disso, estamos dando acesso aos usuários através de password
, authorization_code
, refresh_token
e implicit
, com os escopos de leitura e/ou escrita. Também foram definidos os tempos que o token de acesso e o refresh token levarão para expirar, em segundos. O refresh token ainda não foi citado, mas ele é utilizado para atualizar o token de segurança de um usuário, gerando um novo token e um novo tempo de expiração.
Por fim, o último método (desta classe):
Neste caso estamos definindo que nossos tokens poderão ser gerados através de requisições GET e POST utilizando o gerenciador authenticationManager, que deve ser definido como:
Neste momento podemos executar a aplicação e... erro! O Spring irá avisar que nesta declaração do AuthenticationManager
, estamos utilizando a anotação @Autowired
em um Bean que não está definido. Portanto, iremos criá-lo agora.
Para isso, criaremos no mesmo pacote uma classe chamada WebSecurityConfigAdapter.java
e colocaremos nela a definição do Bean do gerenciador de autenticação.
Agora, ao iniciarmos a aplicação, tudo deve ocorrer normalmente. Se observarmos o log da aplicação, devemos encontrar o mapeamento das urls do OAuth2, mas a que nos importa no momento é a /oauth/token
.
Para realizarmos a geração dos tokens, precisaremos do client-id e secret-id definidos acima e do nome de usuário e senha de quem está utilizando a aplicação. Essas duas últimas informações, porém, nós ainda não possuímos.
O Spring fornece uma interface chamada UserDetails.java
, que é utilizada para autenticação com os servidores do OAuth2. Já possuímos uma class de usuário, mas esta ainda não está conectada de nenhuma forma com essa interface.
Criaremos agora, dentro de um pacote dto
, a classe UsuarioCustomDTO.java
, que irá implementar a interface UserDetails
. Neste DTO colocaremos as informações do nosso usuário (login e senha). Como estamos implementando a interface, teremos que sobrescrever alguns métodos. A classe ficará da seguinte maneira:
Note que precisamos alterar os métodos getPassword()
e getUsername()
para retornarem os dados que nós definimos, além de que todos os outros métodos booleanos deverão ter seu retorno alterado para true
.
Precisamos agora de uma forma de conectar a nossa entidade Usuario com o UsuarioCustomDTO que acabamos de criar. Para isso, devemos utilizar um construtor da seguinte maneira:
Precisamos agora fazer com que o usuário que é passado na requisição seja buscado no banco de dados e, a partir do resultado, tenha seu token de acesso gerado. Para isso, podemos adicionar à classe WebSecurityConfigAdapter
o seguinte trecho:
Com isso estamos estamos dizendo que, quando recebermos uma requisição solicitando um token, criaremos o nosso objeto de UserDetails
a partir do usuário retornado pela função findByLogin
do nosso repositório de usuários. Note que um usuário inicial está sendo cadastrado, apenas para fins de testes. Com isso podemos executar novamente a aplicação e começar a testar com alguma aplicação que realize requisições REST, como o Postman ou Insomnia.
A função findByLogin
deve ser adicionada ao UsuarioRepository.java
da seguinte forma:
Note que o prefixo findBy
seguido por um nome de campo existente dentro da classe já é suficiente para realizar a busca, pois o JPA identifica e cria o método da maneira que definimos.
Se tentarmos realizar um GET direto para a url http://localhost:8080/oauth/token?grant_type=password&username=admin&password=admin
, receberemos o código de erro 401 (Unauthorized). Portanto, precisamos configurar o client e o secret que colocamos na nossa aplicação. Para isso acesse a aba de autenticação do seu serviço, selecione o modo de autenticação básica e preencha os campos:
username: client-id
password: secret-id
Agora, ao realizarmos a requisição, devemos obter outro (mais um) erro: There is no PasswordEncoder mapped for the id "null"
. Isso significa que o OAuth2 não aceita senhas no formato de texto puro e que devemos adicionar algum encriptador de senhas.
Neste exemplo vamos utilizar o BCryptPasswordEncoder. Para isso, voltaremos à classe WebSecurityConfigAdapter
e adicionaremos o seguinte trecho:
Com isso, na mesma classe alteraremos o método authenticationManager
:
usuario.setSenha("admin");
vira usuario.setSenha(passwordEncoder().encode("admin"));
builder.userDetailsService(login -> new UsuarioCustomDTO(usuarioRepository.findByLogin(login)))
terá adicionado ao seu final a instrução .passwordEncoder(passwordEncoder());
.
Com isso, estamos criando um usuário com a senha encriptada e estamos avisando ao serviço de usuário qual encriptador estamos utilizando.
Além disso, na classe AuthorizationServerConfig
também teremos que alterar a instrução .secret("secret-id")
para .secret(new BCryptPasswordEncoder().encode("secret-id"))
, já que o secret também é uma senha de acesso.
Agora, ao realizarmos uma requisição GET para http://localhost:8080/oauth/token?grant_type=password&username=admin&password=admin
, o resultado deve ser algo parecido com:
Agora podemos utilizar esse token para realizarmos a requisição. Para isso, supondo que queriamos uma lista de usuários, devemos configurar a requisição da seguinte maneira: selecionaremos o tipo de autenticação como "Bearer", colocaremos o nosso token gerado no campo "token" e preencheremos o prefixo com o valor "Bearer"
Porém, ao realizarmos essa requisição, ainda teremos problemas de falta de autorização. Por que? Porque não dissemos que esta URL estaria disponível para quem estivesse autenticado.
Para realizar a configuração das URLs, precisaremos de uma nova classe no pacote config
, chamada ResourceServerConfig.java
. Como queremos que o cadastro de usuários não necessite de autenticação e a listagem precise, o conteúdo dessa classe deve ser algo como:
Aqui estamos definindo que as requisições do método POST para a url http://localhost:8080/usuario
estão permitidas para todos, enquanto o GET está liberado apenas para usuários autenticados. Com isso, pode-se realizar os testes e o comportamento deverá ser como o esperado. Ou seja, podemos realizar um POST para cadastrar um novo usuário:
Para que os usuários que estão sendo cadastrados com o método POST também possam ser utilizados na geração dos tokens, precisaremos adicionar uma linha no nosso método de salvar dentro do nosso controller de Usuários:
pois, a partir de agora, todos os nossos usuários devem ter a senha encriptada.
Quando quisermos apenas atualizar o token do usuário já conectado, podemos enviar uma requisição para a mesma url, porém da forma http://localhost:8080/oauth/token?grant_type=refresh_token&refresh_token={REFRESH_TOKEN}
, sendo REFRESH_TOKEN o mesmo que foi retornado no momento da autenticação do usuário.
A descrição mais detalhada de como estas anotações funcionam e qual sua utilidade pode ser encontrada . Após estas configurações já deve ser possível realizar o cadastro e edição de um usuário pela mensagem POST, além da listagem de todos os usuários com o GET. Porém, imaginemos que alguém só possa listar os usuários se já estiver cadastrada no sistema.
Ao realizarmos uma requisição GET sem configurar o token, o comportamento deve ser:
Porém, após adicionarmos o token de segurança, devemos obter:
O projeto construído durante esse tutorial pode ser encontrado .