Olá a todos! Partindo para o final de semana, estamos compartilhando com você um artigo que foi traduzido antes do início do curso "Desenvolvedor no Spring Framework" .
Nos artigos anteriores, criamos um serviço da web RESTful, agora falaremos sobre segurançaIntrodução
Em uma postagem anterior, vimos como criar uma API REST usando as estruturas Java Spring Boot e MongoDB. A API, no entanto, não requer autenticação, o que significa que provavelmente ainda não está pronta para uso. Portanto, este guia mostrará como usar o ambiente de segurança interno do Spring para adicionar um nível de autenticação a esta API.Por que nossa API precisa de autenticação?
As APIs fornecem uma interface simples para interagir com dados internos; portanto, faz sentido que você não queira que ninguém tenha acesso a esses dados e os altere. A autenticação garante que apenas usuários confiáveis possam acessar a API.Como funciona
Usaremos a autenticação HTTP básica , que usa um nome de usuário e senha. O nome de usuário e a senha são separados em uma linha por dois pontos no seguinte formato username:password
.Essa linha é então codificada usando a codificação Base64 , então a linha admin:p@55w0Rd
será codificada na próxima linha YWRtaW46cEA1NXcwUmQ=
(embora eu sugira usar uma senha mais forte que "p @ 55w0Rd"). Podemos anexar essa autenticação aos nossos pedidos adicionando um cabeçalho Authentication
. Este cabeçalho do exemplo anterior ficaria assim (onde "Básico" significa que a senha usa autenticação HTTP básica):Authentication: Basic YWRtaW46cEA1NXcwUmQ=
Como o Spring gerencia a segurança
O Spring oferece um suplemento chamado Spring Security , que torna a autenticação altamente personalizável e extremamente simples. Podemos até usar algumas das habilidades que aprendemos em um post anterior ao configurar!O que nós precisamos
- Uma nova coleção em nossa instância do MongoDB chamada "users"
- Um novo documento na coleção de usuários com os seguintes campos (quaisquer outros campos são opcionais, mas são necessários): nome de usuário, senha (hash usando o algoritmo BCrypt, mais sobre isso posteriormente)
- Fontes da postagem anterior
BCrypt para hash de senha
Hashing é um algoritmo de criptografia unidirecional. De fato, após o hash, é quase impossível descobrir como eram os dados originais. O algoritmo de hash BCrypt primeiro salga um pedaço de texto e, em seguida, o hashe em uma cadeia de 60 caracteres. O codificador Java BCrypt oferece um método matches
que verifica se uma sequência corresponde a um hash. Por exemplo, uma senha p@55w0Rd
que é hash com BCrypt pode ter um significado $2b$10$Qrc6rGzIGaHpbgPM5kVXdeNZ9NiyRWC69Wk/17mttHKnDR2lW49KS
. Ao chamar o método matches
BCrypt para uma senha não criptografada e com hash, obtemos um valor true
. Esses hashes podem ser gerados usando o codificador BCrypt incorporado ao Spring Security.Por que devemos hash senhas?
Todos nós já ouvimos falar de ataques cibernéticos recentes que resultaram em senhas roubadas de grandes empresas. Então, por que é recomendado apenas alterar nossas senhas após o hacking? Porque essas grandes empresas certificaram-se de que as senhas sejam sempre hash em seus bancos de dados!Embora sempre valha a pena alterar as senhas após esses hacks de dados, o hash de senha torna extremamente difícil encontrar a senha real do usuário, pois é um algoritmo unidirecional. De fato, pode levar anos para quebrar uma senha complexa com hash corretamente. Isso fornece um nível adicional de proteção contra roubo de senha. E o Spring Security simplifica o hash, então a verdadeira pergunta deve ser: "Por que não?"Adicionando um usuário ao MongoDB
Vou adicionar um mínimo de campos necessários para minha coleção users
(usuários), para que um documento com usuários no meu banco de dados contenha apenas username
(nome de usuário) e BCrypt password
(senha) com hash . Neste exemplo, meu nome de usuário será admin
e minha senha será welcome1
, mas eu sugeriria o uso de um nome de usuário e senha mais robustos na API no nível de produção.db.users.insert({
“username” : “admin”,
“password” : “$2a$10$AjHGc4x3Nez/p4ZpvFDWeO6FGxee/cVqj5KHHnHfuLnIOzC5ag4fm”
});
Essas são todas as configurações necessárias no MongoDB! O restante da configuração será feito em nosso código Java.Incluindo um Modelo de Usuário e Repositório
O post anterior descrito em detalhes sobre os modelos e repositórios do Mongo, portanto, não entrarei em detalhes sobre como eles funcionam aqui - se você quiser atualizar seu conhecimento, não hesite em visitar o meu post anterior!A desvantagem é que o Spring precisa saber como será o documento user
(modelo) e como acessar a coleção user
no banco de dados (repositórios). Podemos colocar esses arquivos nas mesmas pastas de modelos e repositórios, respectivamente, como fizemos no exercício anterior.Modelo
O modelo será uma classe base Java com custom _id
, username
e password
. O arquivo será nomeado Users.java
. e ficará assim:package com.example.gtommee.rest_tutorial.models;
import org.bson.types.ObjectId;
import org.springframework.data.annotation.Id;
public class Users {
@Id
public ObjectId _id;
public String username;
public String password;
public Users() {}
public Users(ObjectId _id, String username, String password)
{
this._id = _id;
this.username = username;
this.password = password;
}
public void set_id(ObjectId _id) { this._id = _id; }
public String get_id() { return this._id.toHexString(); }
public void setPassword(String password) { this.password = password; }
public String getPassword() { return password; }
public void setUsername(String username) { this.username = username; }
public String getUsername() { return username; }
}
Repositório
O repositório será chamado UsersRepository.java
e terá a seguinte aparência - lembre-se, precisaremos encontrar usuários por eles username
, portanto, precisaremos incluir o método findByUsername
na interface do repositório.package com.example.gtommee.rest_tutorial.repositories;
import com.example.gtommee.rest_tutorial.models.Users;
import org.springframework.data.mongodb.repository.MongoRepository;
public interface UsersRepository extends MongoRepository<Users, String> {
Users findByUsername(String username);
}
E é isso para o modelo e o repositório!Adicionando dependências de segurança
Deve haver um arquivo com um nome no diretório raiz do projeto pom.xml
. Ainda não tocamos nesse arquivo, mas o arquivo pom contém todas as dependências do nosso projeto, e vamos adicionar algumas, então vamos começar abrindo esse arquivo e rolando para baixo até a tag . A única nova dependência de que precisamos é a segurança inicial . O Spring possui um gerenciador de versão interno, portanto, a dependência que precisamos adicionar à tag é a seguinte:<
dependencies>
<
dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
E o Maven fará o download dos arquivos de origem para nós, portanto nossas dependências devem estar prontas!Criar serviço de autenticação
Precisamos informar ao Spring onde estão os dados de nossos usuários e onde encontrar as informações necessárias para autenticação. Para fazer isso, podemos criar um serviço de autenticação (Serviço de Autenticação). Vamos começar por criar uma nova pasta no src/main/resources/java/[package name]
chamado serviços, e nós podemos criar um novo arquivo nesta pasta de configuração com o nome MongoUserDetailsService.java
.MongoUserDetailsService.java
Esta classe tem um componente principal, então eu darei a classe inteira aqui e depois a explicarei abaixo:package com.example.gtommee.rest_tutorial.services;
import com.example.gtommee.rest_tutorial.models.Users;
import com.example.gtommee.rest_tutorial.repositories.UsersRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Component;
import java.util.Arrays;
import java.util.List;
@Component
public class MongoUserDetailsService implements UserDetailsService{
@Autowired
private UsersRepository repository;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
Users user = repository.findByUsername(username);
if(user == null) {
throw new UsernameNotFoundException(“User not found”);
}
List<SimpleGrantedAuthority> authorities = Arrays.asList(new SimpleGrantedAuthority(“user”));
return new User(user.getUsername(), user.getPassword(), authorities);
}
}
Este trecho começa com as importações necessárias no arquivo. Além disso, a seção implements UserDetailsService
indica que essa classe criará um serviço para pesquisar e autenticar usuários. Em seguida, a anotação @Component
indica que essa classe pode ser incorporada em outro arquivo (por exemplo, o arquivo SecurityConfiguration, sobre o qual passaremos por várias seções).Anotação @Autowired
encerrada private UsersRepository repository
; é um exemplo de implementação, essa propriedade nos fornece uma instância nossa UsersRepository
para o trabalho. A anotação @Override
indica que este método será usado em vez do método padrão UserDetailsService. Primeiro, esse método obtém o objeto Users
da fonte de dados do MongoDB usando o método findByUsername
que declaramos UsersRepository
.O método verifica se o usuário foi encontrado ou não. Em seguida, o usuário recebe permissões / função (isso pode adicionar níveis de autenticação adicionais aos níveis de acesso, mas uma função será suficiente para esta lição). Finalmente, o método retorna um objeto Spring User
com username
, password
e o role
usuário autenticado.Criar configuração de segurança
Precisamos redefinir alguns dos protocolos de segurança internos do Spring para usar nosso banco de dados e algoritmo de hash; portanto, precisamos de um arquivo de configuração especial. Para criá-lo, devemos criar uma nova pasta no src/main/resources/java/[package name]
com o nome config
, e também precisará criar um novo arquivo nesta pasta de configuração com o nome SecurityConfiguration.java
. Este arquivo possui várias partes importantes, então vamos começar com a classe base SecurityConfiguration:SecurityConfiguration.java
package com.example.gtommee.rest_tutorial.config;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
@Configuration
@EnableConfigurationProperties
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
@Autowired
MongoUserDetailsService userDetailsService;
}
Já existe o suficiente para lidar, então vamos começar de cima. Uma anotação @Configuration
indica que a classe conterá Java beans, descritos em detalhes aqui. Uma anotação @EnableConfigurationProperties
indica o que a classe conterá como um bean de configuração especial. Em seguida, a instrução extends WebSecurityConfigurerAdapter
mapeará a classe pai WebSecurityConfigurerAdapter
para nossa classe de configuração , fornecendo à nossa classe todo o necessário para garantir que suas regras de segurança sejam respeitadas. Por fim, a classe injeta automaticamente uma instância ( @Autowired
) MongoUserDetailsService
, que podemos usar posteriormente neste arquivo.Etapa de autenticação
Em seguida, precisamos informar ao Spring Security como queremos lidar com a autenticação do usuário. Por padrão, o Spring Security tem um nome de usuário e senha predefinidos, proteção CSRF e gerenciamento de sessões . No entanto, queremos que nossos usuários usem seu nome de usuário e senha para acessar o banco de dados. Além disso, como nossos usuários serão autenticados novamente a cada solicitação, em vez de efetuar login, não precisamos da proteção CSRF e do gerenciamento de sessões, portanto, podemos adicionar um método com um nome configure
que substitua o esquema de autenticação padrão para informar exatamente ao Spring como queremos lidar com a autenticação e teremos a seguinte aparência:@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests().anyRequest().authenticated()
.and().httpBasic()
.and().sessionManagement().disable();
}
Novamente, muitas coisas estão acontecendo aqui, então vamos resolver isso em etapas. A anotação @Override
informa ao Spring Boot para usar o método em configure (HttpSecurity http)
vez da configuração padrão do Spring. Em seguida, chamamos uma série de métodos para o objeto em http
que a configuração real ocorre. Esses métodos fazem o seguinte:csrf().disable()
: Desativa a proteção CSRF porque não é necessário para a APIauthorizeRequests().anyRequest().authenticated()
: Declara que todas as solicitações para qualquer terminal devem ser autorizadas, caso contrário, devem ser rejeitadas.and().httpBasic(): Spring
para que ele espere autenticação HTTP básica (discutida acima)..and().sessionManagement().disable()
: informa ao Spring para não armazenar informações da sessão para os usuários, pois isso não é necessário para a API
Adicionando um codificador Bcrypt
Agora precisamos dizer ao Spring para usar o codificador BCrypt para fazer hash e comparar senhas - parece uma tarefa difícil, mas na verdade é muito simples. Podemos adicionar esse codificador simplesmente adicionando as seguintes linhas à nossa classe SecurityConfiguration
:@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
E todo o trabalho! Esse bean simples informa ao Spring que o PasswordEncoder que queremos usar é o Spring Boot BCryptPasswordEncoder()
para codificar e comparar os hashes de senha. O Spring Boot também inclui vários outros codificadores de senha - eu recomendo experimentá-los se quiser experimentar!Especifique o Gerenciador de autenticação
Finalmente, devemos indicar no nosso SecurityConfiguration
que queremos usar MongoUserDetailsService
(que criamos na seção anterior) para nossa autenticação. Podemos fazer isso usando o seguinte método:@Override
public void configure(AuthenticationManagerBuilder builder)
throws Exception {
builder.userDetailsService(userDetailsService);
}
Esse método simplesmente substitui a configuração padrão AuthenticationManagerBuilder
, substituindo nosso próprio serviço de transferência de dados personalizado.Arquivo final SecurityConfiguration.java
import com.example.gtommee.rest_tutorial.services.MongoUserDetailsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
@Configuration
@EnableConfigurationProperties
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
@Autowired
MongoUserDetailsService userDetailsService;
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests().anyRequest().authenticated()
.and().httpBasic()
.and().sessionManagement().disable();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Override
public void configure(AuthenticationManagerBuilder builder)
throws Exception {
builder.userDetailsService(userDetailsService);
}
}
Verificação de autenticação
Testarei uma GET
solicitação rápida com autenticação válida e incorreta para garantir que a configuração funcione conforme o planejado.Nome de usuário / senha incorretosURL: http://localhost:8080/pets/
Método: GET
Login: Basic YWRtaW46d2VsY29tZQ==
Resposta:401 Unauthorized
Nome de usuário / senha válidosURL: http://localhost:8080/pets/
Método: GET
Login: Basic YWRtaW46d2VsY29tZTE=
Responder:[
{
“_id”: “5aeccb0a18365ba07414356c”,
“name”: “Spot”,
“species”: “dog”,
“breed”: “pitbull”
},
{
“_id”: “5aeccb0a18365ba07414356d”,
“name”: “Daisy”,
“species”: “cat”,
“breed”: “calico”
},
{
“_id”: “5aeccb0a18365ba07414356e”,
“name”: “Bella”,
“species”: “dog”,
“breed”: “australian shepard”
}
]
Conclusão
Funciona como deveria! A autenticação HTTP básica para a API do Spring Boot pode ser complicada, mas espero que este guia ajude a torná-lo mais compreensível. A autenticação é essencial no clima cibernético de hoje; portanto, ferramentas como o Spring Security são essenciais para garantir a integridade e a segurança dos seus dados.Isso é tudo! Encontre-me no curso .