Alura > Cursos de Programação > Cursos de Java > Conteúdos de Java > Primeiras aulas do curso Java e Spring Security: proteja suas APIs REST

Java e Spring Security: proteja suas APIs REST

Adicionando usuários na aplicação - Apresentação

Boas-vindas a mais um curso de segurança em Java na Alura. Eu sou Iasmin Araújo, faço parte do time de escola de programação.

Audiodescrição: Iasmin se descreve como uma mulher branca. Tem cabelo longo, ondulado e castanho-escuro e olhos verdes. Ela veste uma blusa verde e está sentada em uma cadeira gamer de encosto preto com detalhes rosa. A sua frente tem um microfone em um suporte. Ao fundo, há uma porta no lado direito e uma cama do lado inferior esquerdo. O ambiente do fundo está iluminado por uma luz LED azul.

Pré-requisitos

Para este curso, é importante que tenhamos concluído os cursos anteriores desta formação de segurança em Java. Nos cursos anteriores, abordamos a segurança em aplicações web e agora nosso foco é entender como funciona a segurança em APIs REST.

Vários conceitos serão replicados na parte de APIs, que precisamos ter visto nos cursos anteriores. Também é importante ter conhecimento sobre banco de dados, e familiaridade com o tema, pois trabalharemos bastante com banco de dados, tabelas e relacionamentos.

O que aprenderemos

Vamos entender como criar uma camada de segurança usando Spring Security em APIs REST. Anteriormente, criamos uma camada de segurança em aplicações web e agora faremos o mesmo para nossas APIs. Com isso, exploraremos as diferenças na segurança de aplicações web e APIs. Existem várias configurações que trabalharemos e que exploraremos neste curso.

Além disso, utilizaremos a biblioteca JWT para gerar tokens de acesso. No momento da autenticação, trabalharemos com tokens e, para isso, usaremos a biblioteca JWT. Conheceremos também o funcionamento dos refresh tokens, que são um tipo de token JWT. Entenderemos por que eles são utilizados e por que faz sentido termos refresh tokens.

Configuraremos diferentes estilos de relacionamento de perfis com usuários. Anteriormente, tínhamos um usuário com um único perfil. Agora, trabalharemos com várias configurações diferentes. É possível que um usuário tenha vários perfis e que esses perfis tenham hierarquia entre si. Veremos como configurar tudo isso.

Além disso, trataremos corretamente erros HTTP de segurança. Existem vários códigos específicos quando trabalhamos com HTTP, e alguns deles estão relacionados à segurança, como 401 e 403. Conheceremos esses códigos e como utilizá-los para aplicar boas práticas em nossa API.

Objetivos do curso:

O projeto

Faremos tudo isso no projeto do fórum Hub, uma API que criamos baseada no fórum da Alura. Se ainda não o conhece, recomendamos que acesse o fórum da Alura para utilizá-lo e entender como funciona e conhecer as regras de negócio. Assim, será possível absorver melhor o funcionamento do nosso projeto.

Falando em fórum, lembre-se de que, se tiver qualquer dúvida ou problema, pode enviar no fórum do curso para resolvermos juntos, com a ajuda da comunidade. Além disso, temos o Discord, onde também pode enviar suas dúvidas, problemas e críticas. Não deixe de, ao final do curso, enviar uma avaliação ou comentário para que possamos sempre evoluir nossos conteúdos.

Vamos começar nosso desenvolvimento, então?

Adicionando usuários na aplicação - Criando uma camada de proteção na aplicação

Neste curso, trabalharemos na aplicação do Fórum Hub, um fórum onde as pessoas podem postar dúvidas de programação relacionadas aos cursos que estão fazendo. Trata-se de uma aplicação baseada no Fórum da Alura.

Com o projeto aberto no IntelliJ, podemos ver as classes do nosso domínio: curso, reposta e topico. Com base no curso que a pessoa faz, ela cria tópicos relacionados às dúvidas, e outros usuários podem responder a esses tópicos.

Entendendo o funcionamento da aplicação

Para testamos melhor nossa aplicação, podemos executá-la e efetuar requisições. Faremos essas requisições via API, ao invés de uma aplicação web, como fazíamos anteriormente.

Antes abriríamos o navegador para ver todas as funcionalidades, como nos cursos anteriores. A partir de agora, teremos uma API para fazermos requisições e precisaremos testá-las utilizando o Postman.

Nossa aplicação já está em execução, então abriremos o Postman para realizar os testes.

Testando com o Postman

No Postman, temos as collections (coleções) de requisições que podemos fazer. Para testar rapidamente as funcionalidades, faremos um post (envio) da coleção "topicos", nomeada "POST cadastro".

Observação: Essas coleções estarão disponíveis para download, execução e teste do projeto.

Nessa requisição, para criarmos um tópico, precisamos de titulo, mensagem, autor e o cursoId. Já existe um cursoId cadastrado no banco, que é um curso de programação em Java com orientação a objetos, então enviarei essa requisição.

{
  "titulo": "Como usar classes abstratas em Java?",
  "mensagem": "Estou com dúvidas sobre como e quando usar classes abstratas no Java. Alguém pode me ajudar?",
  "autor": "ana",
  "cursoId": 1
}

Após enviarmos a requisição, na parte inferior do postman temos o resultado. O tópico foi criado, e temos o resultado, como observamos com o status HTTP 201 Created (Criado), no lado superior direito do resultado. Além disso, emos os dados do tópico criado, como status, data de criação e quantidade de respostas.

{
  "id": 1,
  "titulo": "Como usar classes abstratas em Java?",
  "mensagem": "Estou com dúvidas sobre como e quando usar classes abstratas no Java. Alguém pode me ajudar?",
  "autor": "ana",
  "status": "NAO_RESPONDIDO",
  "dataCriacao": "2024-10-22T10:09:04.614629",
  "quantidadeRespostas": 0,
  "curso": "Orientação a Objetos com Java"
}

Se quisermos listar esse tópico na aplicação para verificar se está tudo certo, também conseguimos. Na coluna do lado esquerdo, ainda na coleção "topicos", buscaremos pela requisição "GET listagem geral".

Quando enviamos essa requisição, clicando em "Send", recebemos o status HTTP 200 OK e o tópico criado. Nosso tópico é uma lista content com o tópico de id 1, com as informações que enviamos. Tudo está funcionando corretamente na nossa aplicação.

A aplicação possui várias funcionalidades. Você pode executá-la para conhecer todas elas. Disponibilizaremos um comparativo do nosso fórum da Alura com o fórum que estamos trabalhando, mostrando o funcionamento e todas as funcionalidades corretas.

Identificando um problema do projeto

No entanto, há um pequeno problema: ao cadastrar um tópico, podemos passar o nome do autor que está criando o tópico. Por exemplo, criamos um tópico com o autor "Ana", mas o autor real foi "Iasmin". Isso permite criar tópicos em nome de outras pessoas, o que não é seguro, pois alguém pode escrever algo inadequado e parecer que outra pessoa fez isso.

O correto seria que, ao criar um tópico, ele já fosse registrado no nome do usuário logado. Precisamos, portanto, de usuários configurados na aplicação e suporte para login desses usuários. Não adianta ter a entidade usuário mapeada se não conseguimos obter informações de quem está logado.

Quem fornecerá essa informação de login é o Spring Security, que autoriza requisições e consegue identificar o usuário logado, guardando suas informações. Para utilizar o Spring Security, precisamos acessar o start.spring.io no navegador, para pegar a dependência.

Adicionando o Spring Security

Com a página aberta do Spring aberta, na metade superior direita, temos o título "Dependencies" (Dependências) e, ao lado dele, o botão "Add dependencies" (Adicionar dependências), que também pode der ativado pelo atalho "Ctrl + B". Clicamos nesse botão, abrindo uma janela flutuante.

Na parte superior da janela flutuante, temos um campo de busca, onde escreveremos "security" e selecionaremos o "Spring Security". Quando clicamos, voltamos para página principal do Spring e

Na parte centro-inferior da página, tem uma barra com três botões. Clicaremos no "Explore", que é o botão central, e que pode ser ativado com o atalho "Ctrl + Espaço". Ao clicarmos, uma nova página abre, mostrando o código .xml. Nele, procuramos a dependência específica do Spring Security, que está entre a linha 33 e 36.

    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-security</artifactId>
    </dependency>

Observação: Antes de clicar em "Explore", confira as informações do lado esquerdo do site. No campo "Project", precisa está com o "Maven" selecionado, e a linguagem (Language) precisa ser Java.

Copiamos essa dependência e retornamos ao IntelliJ. Abrimos o arquivo pom.xml e, logo após a dependência do spring-boot-starter-mail, colamos o nosso código. Assim garantimos a organização do código.

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-mail</artifactId>
</dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-security</artifactId>
    </dependency>

Feito isso, reiniciaremos a aplicação com o Maven, clicando no botão que está no canto superior direito do código. Após recarregar a aplicação, podemos reiniciá-la, clicando no botão de reiniciar do IntelliJ, para verificar o que aconteceu.

Conferindo as mudanças

Após reiniciarmos a aplicação, podemos voltar ao Postman para fazemos uma nova requisição de GET. Rodaremos a "topicos/listagem geral", mas ao clicarmos em "Send", recebemos um status HTTP 401 Unauthorized (não autorizado). Isso indica que o Spring Security está funcionando.

Antes, a requisição funcionava, mas agora não mais, porque não temos permissão para acessar essa URL. Concluímos o primeiro passo, que é configurar o Spring Security na aplicação. Na sequência, começaremos a pensar nas configurações personalizadas dos nossos usuários.

Adicionando usuários na aplicação - Definindo usuários personalizados

Adicionamos o Spring Security à aplicação e observamos que ele já estava bloqueando as requisições, retornando um erro 401. Se voltarmos ao IntelliJ e acessarmos o log da aplicação, onde ela foi iniciada, podemos relembrar algumas configurações padrão do Spring.

Analisando o erro que recebemos

Ao adicionar a configuração do Spring Security, ele gera as configurações padrão, do UserDetailsServiceAutoConfiguration. Ele gera um usuário padrão e uma senha sempre reinicializada a cada vez que reiniciamos a aplicação. O usuário padrão é o user, e recebemos uma senha aleatória.

generated security password: fd044664-8790-4a83-803f-44941da2537b

(Tradução: Senha de segurança gerada: fd044664-8790-4a83-803f-44941da2537b)

Copiaremos a senha para abriremos o navegador, onde descobrimos que o Spring Security gera um formulá rio. Basta digitarmos [localhost 8080/topicos](http://localhost 8080/topicos). Ao acessarmos esse endereço, a página é redirecionada para uma página de login.

Observação: Tudo isso é o padrão do Spring .Aprendemos isso no primeiro curso da formação, onde trabalhamos bastante com isso.

Essa página de login é bem simples, tem o título "Please sign in" (Por favor, entre) seguido de dois campos: user (usuário) e password (senha). Por fim, tem o botão "Sing in" (Entrar). Preencheremos o formulário com o nome de usuário padrão e a senha gerada que copiamos.

Usuário: user

Senha: fd044664-8790-4a83-803f-44941da2537b

Se clicarmos no botão "Sign in", a página atualiza para uma página os dados dos tópicos que criamos. Na parte superior tem uma barra com a caixa de seleção "Estilo de formatação" e, se clicarmos, melhora a nossa leitura. Percebe que são os dados do tópico da Ana, criado pela Yasmin. Já temos essa parte muito bem criada pelo Spring.

Se voltarmos ao IntelliJ novamente, logo abaixo da senha, temos uma mensagem do próprio Spring Security informando que essa senha deve ser usada somente em momentos de desenvolvimento. Quando essa aplicação entrar em produção, precisamos ter configurações específicas de usuário, ou seja, ter nosso próprio UserDetailsService na nossa aplicação.

Tudo isso já foi visto nessa formação. Fizemos no primeiro curso toda a parte da configuração de usuários, então replicaremos isso nesse curso, copiando e colando algumas partes do fizemos anteriormente.

Configurando os dados de usuário

Inicialmente, precisamos cadastrar nomes de usuários. Esses usuários são uma classe específica que precisarão fazer login. Portanto, precisamos criar uma classe Usuario, que será uma entidade, mas também um UserDetailsService. Como já fizemos isso, basta copiarmos do primeiro projeto, da Clínica Volmed.

No explorador de arquivos, clicaremos na pasta "domain" e pressionaremos "Alt + Insert". Selecionamos "Package", para criarmos um novo pacote chamado "usuario". Agora clicamos no "usuario", pressionamos "Alt + Insert" novamente e, dessa vez, criaremos uma nova classe, chamada Usuario. Temos a nossa classe Usuario, onde colaremos o código do outro projeto.

Arquivo Usuario.java

package br.com.forum_hub.domain.usuario;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.util.Collection;

@Entity
@Table(name="usuarios")
public class Usuario implements UserDetails {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String nome;
    private String email;
    private String senha;

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return null;
    }

    @Override
    public String getPassword() {
        return senha;
    }

    @Override
    public String getUsername() {
        return email;
    }

    public String getNome() {
        return nome;
    }
}

Tínhamos a entidade e a tabela definidas, com essas anotações em cima da classe. A classe Usuario implementava UserDetails, porque o Spring precisa entender como fará o login dos nomes usuários.

Precisamos dizer para ele quem é o nome de usuário e quem é a senha. Por isso temos os métodos getPassword() e getUserName() sendo sobrescritos e retornando os atributos da nossa própria aplicação. Cada cadastro de usuario tem id, nome, email e senha.

Esse código também tem o método getAuthorities(), que está relacionado às permissões, e trabalharemos nele depois. Além disso, as definições de getPassword(), getUsername() e o getName(), para poder retornar o nome da pessoa usuária.

Personalizando para o nosso projeto

Como nossa aplicação é um fórum, com temos várias diferentes de usuários, podemos adicionar atributos diferentes nesta classe. Para começar, temos nomes diferentes no nosso fórum, como o nome real e o nome de pessoa usuária (um apelido).

Por exemplo, meu nome real, é Yasmin Araújo, e no fórum tenho um nome de pessoa usuária "Yasmin Araújo C", que é o meu apelido, o meu nome específico de usuário. Portanto, temos dois nomes. Vamos diferenciá-los como "nome completo" e "nome de usuário", e faremos essa mudança que acabamos de colar na nossa classe Usuario.

Dentro da classe Usuario o lugar de nome, em privete String nome, escreveremos nomeCompleto. No final do código, também mudaremos de getNome() para getNomeCompleto(), retornando nomeCompeto.

Arquivo Usuario.java

@Entity
@Table(name="usuarios")
public class Usuario implements UserDetails {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String nomeCompleto;
    private String email;
    private String senha;

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return null;
    }

    @Override
    public String getPassword() {
        return senha;
    }

    @Override
    public String getUsername() {
        return email;
    }

    public String getNomeCompleto() {
        return nomeCompleto;
    }
}

Além disso, teremos o nome de usuário, ou seja, o apelido que estamos acostumados a utilizar. Abaixo da senha, criaremos o private string nomeUsuario;.

Nos fóruns também é comum ter uma biografia específica da pessoa usuária, onde ele conta um pouco da sua trajetória com a programação, o que ele faz, quais são os hobbies. Isso aproxima a comunidade, portanto é interessante termos esse atributo. Na linha abaixo escrevemos private string biografia.

Como algumas pessoas não gostam de ler biografias muito grandes, podemos acrescentar uma mini-biografia, onde a pessoa escreve o que ela quer destacar. Por exemplo, escrever "sou uma pessoa desenvolvedora Java" na mini-biografia. É onde destacamos o que queremos enfatizar. Para criarmos esse atributo, escrevemos private string miniBiografia.

@Entity
@Table(name="usuarios")
public class Usuario implements UserDetails {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String nomeCompleto;
    private String email;
    private String senha;
    private String nomeUsuario;
    private String biografia;
    private String miniBiografia;
        
        // código omitido

Para trabalharmos com esses atributos, podemos ter os Getters específicos, não vamos declarar os setters por enquanto. Então, no final do código, depois do getNomeCompleto(){}, pressionaremos "Enter" para criar uma nova linha.

Na linha vazia, pressionaremos "Alt + Insert" e selecionamos Getters no menu que surge. A janela "Select Fields to Generate Getters" (Selecione os Campos para Gerar Getters) aparece no centro da tela. Selecionaremos nomeUsuario, biografia e miniBiografia antesde clicar em "Ok" no lado inferior direito da janela. Assim temos nossos Getters.

@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
    return null;
}

@Override
public String getPassword() {
    return senha;
}

@Override
public String getUsername() {
    return email;
}

public String getNomeCompleto() {
    return nomeCompleto;
}

public String getNomeUsuario() {
    return nomeUsuario;
}

public String getBiografia() {
    return biografia;
}

public String getMiniBiografia() {
    return miniBiografia;
}

Armazenando os usuarios no banco de dados

Uma vez que temos a nossa classe Usuario, que é uma entidade, teremos também uma migration para criar essa tabela de usuários no banco de dados. Então vamos parar o projeto para fazer essa configuração.

Abriremos o explorador de arquivos e, no final da lista de arquivos, navegaremos para "resources > db.migration". Clicaremos em "db.migration", pressionaremos "Alt + Insert" e selecionaremos "File" (Arquivo), criando um novo arquivo chamado V4__create-table-usuarios.sql. Assim criaremos uma migration específica, colando o código que já deixei pronto para facilitar.

Arquivo V4__create-table-usuario.sql

CREATE TABLE usuarios(
    id BIGINT AUTO_INCREMENT PRIMARY KEY,
    email VARCHAR(100) NOT NULL UNIQUE,
    senha VARCHAR(100) NOT NULL,
    nome_completo VARCHAR(100) NOT NULL,
    nome_usuario VARCHAR(100) NOT NULL UNIQUE,
    mini_biografia VARCHAR(30),
    biografia TEXT
);

Temos esse create-table-usuários com os vários atributos que definimos na classe. Agora que temos a classe Usuario e a tabela Usuario, também precisaremos definir como as pessoas usuárias farão login.

Não adianta cadastrar o nome de usuário, mas o Spring não saber que precisará chamar um método específico para pegar os dados de nome de usuário e senha. Então o Spring precisa entender que ele não faz login com o usuário padrão, e sim do jeito que determinamos. Fazemos isso com a classe UserDetailsService.

Usando o Spring para login com novos nomes de usuários

Portanto, precisamos de um serviço para utilizar a classe específica do Spring no nosso login. Para isso, teremos a classe UsuarioService.

No explorador de arquivos, clicamos no pacote "usuario". Pressionamos "Alt + Insert" e selecionamos "Java Class", criando uma nova classe Java chamada UsuarioService. Também já definimos essa classe no nosso primeiro projeto, então apenas colaremos o código.

Arquivo UsuarioService.java

package br.com.forum_hub.domain.usuario;

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.Service;

@Service
public class UsuarioService implements UserDetailsService {

    private final UsuarioRepository usuarioRepository;

    public UsuarioService(UsuarioRepository usuarioRepository) {
        this.usuarioRepository = usuarioRepository;
    }

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        return usuarioRepository.findByEmailIgnoreCase(username)
                .orElseThrow(() -> new UsernameNotFoundException("O usuário não foi encontrado!"));
    }
}

Nesse código, temos a classe UsuarioService. Por ser uma classe Service, precisa ser um bin do Spring. Por isso, nós a anotamos com @Service. Ela deve implementar a classe UserDetailsService. Nessa classe, definiremos como o login é realizado.

Primeiro buscamos o nome de usuário com o método findByEmailIgnoreCase(username). No entanto, para realizar essa busca, também precisamos da classe UsuarioRepository, que ainda não está definida em nosso projeto.

Portanto, clicamos na pasta "usuario", pressionamos "Alt + Insert" e selecionamos mais uma vez "Java Class". Nomeamos a classe como UsuarioRepository e, mais uma vez, colamos o código do projeto anterior para finalizarmos a nossa configuração de usuário.

Arquivo UsuarioRepository.java

package br.com.forum_hub.domain.usuario;

import org.springframework.data.jpa.repository.JpaRepository;

import java.util.Optional;

public interface UsuarioRepository extends JpaRepository<Usuario, Long> {
    Optional<Usuario> findByEmailIgnoreCase(String email);
}

Agora, temos a nossa interface UsuarioRepository com o método findByEmailIgnoreCase(). Retornando à classe UsuarioService, percebemos não haver mais nenhum erro, portanto as configurações específicas de usuário foram finalizadas.

Próximos passos

A única coisa que precisamos fazer é criptografar as senhas das pessoas usuárias do nosso fórum. Porque quando formos inserir os dados no banco, a senha não estará salva exatamente da maneira que a pessoa usuária definiu. Ela precisa estar criptografada.

Para podermos fazer as comparações corretamente no momento do login, precisamos utilizar essa criptografia. Na sequência, veremos como funciona essa parte da criptografia também.

Sobre o curso Java e Spring Security: proteja suas APIs REST

O curso Java e Spring Security: proteja suas APIs REST possui 266 minutos de vídeos, em um total de 66 atividades. Gostou? Conheça nossos outros cursos de Java em Programação, ou leia nossos artigos de Programação.

Matricule-se e comece a estudar com a gente hoje! Conheça outros tópicos abordados durante o curso:

Aprenda Java acessando integralmente esse e outros cursos, comece hoje!

Conheça os Planos para Empresas