4
respostas

[Dúvida] Pensando em uma API onde precisa de

Pensando em uma APi onde o usuário precisa efetuar um registro antes de fazer o login... "Ainda irei realizar a implementação...." na classe de SecurityConfig, na assinatura do método precisaria adicionar outra autorização para o caminho desejado, certo?

@Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        // Desabilitando contra Ataques 'CRSF' e utilizar TOKEN
        return http.csrf(csrf -> csrf.disable())
                // Desabilitando Processo de Autenticação de Formulario Statefull
                // E Habilitando para Stateless para API REST
                .sessionManagement(sm -> sm.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
                .sessionManagement(sm -> sm.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
                .authorizeHttpRequests(req -> {
                    req.requestMatchers("/api/corragil/v1/login").permitAll()
                            .requestMatchers("/api/corragil/v1/register").permitAll();
                    req.anyRequest().authenticated();
                })
                .addFilterBefore(securityFilter, UsernamePasswordAuthenticationFilter.class)
                .build();
    }

Pensando que para esse caminho ele não precisa do bearer token, Seria algo tipo isso? E quais seriam os próximos passos para tentar ajudar?

4 respostas

Oi Neander!

Você está no caminho certo!

As urls de login e de cadastro precisam ser públicas e você configurou isso corretamente com o permitAll para elas. Agora você precisa fazer o seguinte:

  1. Criar o Controller (ou um novo método em algum controller já existente) para mapear a URL de cadastro e nele receber um DTO com os dados que serão enviados nessa requisição
  2. Implementar a lógica de cadastro do usuário (pode ser no controller ou em uma classe service que será chamada pelo controller)

Essa lógica deve fazer o seguinte:

  1. Verificar se o email já está cadastrado e lançar uma exception caso esteja
  2. Converter a senha enviada para o formato BCrypt (você pode injetar um objeto PasswordEncoder do Spring para isso)
  3. Instanciar um objeto usuario e setar nele os dados e a senha bcrypt
  4. Salvar o usuário no banco de dados

Bons estudos!

Meu caro! Será que você poderia dar uma avaliada no processo? Sinceramente fiz, porém a idéia ficou um pouco confuso kkkkk Até Por que eu já tinha um código antigo e tentei refazer algumas novas utilidades, acho que confundiu muito e desde já te peço desculpa por essa confusão toda, Poderia me auxiliar no que fazer melhor?

Dito isso... Gostaria de perguntar outras coisas...

» Acabei por criar 2 tabelas para ter a funcionalidade de novos cadastros no banco de dados, basicamente ficaram com as mesmas variáveis mas, a classe TokenService o método criado foi direcionado para o "UsuarioLogin" (como no curso), provavelmente vai ter conflito. Teria que criar outro método como parâmetro "CadastroModel"?

 public String generateToken(UsuarioLogin usuarioModel) {
        try {
            // Algoritmo de criptografia para utilizar
            Algorithm algorithm = Algorithm.HMAC256(secret);

            // Criação do Token
            return JWT.create()
                    .withIssuer("API Pipoca Agil")
                    .withSubject(usuarioModel.getLogin()) // Subject 'getLogin()' para validar de acordo com 'SecurityFilter'
                    .withExpiresAt(this.generateExpirationDate()) // Tempo de Validade
                    .sign(algorithm);
        } catch (JWTCreationException e) {
            throw new RuntimeException("Erro ao autenticar!", e);
        }
    }

» Dito isso acima... Pensei em realizar o "implements UserDetails" na classe CadastroModel para validar talvez os dados como registro e não perder as rotas ja criadas. E a partir disso criar uma relação entre banco de dados que vai inserir os dados de uma tabela a outra, existe como fazer isto? E qual seria a idéia mais lógica para fazer essa operação? Será que ao realizar isso vai ser interessante para aprendizado? Mesmo que talvez o código esteja todo errado e eu possa re-fatorar futuramente.

Continuação....

» Como poderia lançar a exception? Sendo que existe uma GlobalExceptionHandler com:

  1. EntityNotFoundException
  2. ConstraintViolationException
  3. DataIntegrityViolationException
  4. Entre outras que está no curso...

Ainda seria necessário lançar alguma ou modificar o método para acrescenter um ".orElseThrow(() -> new RuntimeException("Invalid"));" ?

Ideia de como ficou o AutenticationController

package br.com.pipocaagil.CorraAgil.controller;

import br.com.pipocaagil.CorraAgil.DTO.DadosCadastroDTO;
import br.com.pipocaagil.CorraAgil.DTO.RegisterRequestDTO;
import br.com.pipocaagil.CorraAgil.domain.UsuarioLogin;
import br.com.pipocaagil.CorraAgil.domain.dto.DadosLoginRequestDTO;
import br.com.pipocaagil.CorraAgil.infra.DadosTokenJWTDTO;
import br.com.pipocaagil.CorraAgil.infra.security.TokenService;
import br.com.pipocaagil.CorraAgil.model.CadastroModel;
import br.com.pipocaagil.CorraAgil.repositories.CadastroRepository;
import jakarta.validation.Valid;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.Optional;

@RestController
@RequestMapping("/api/corragil/v1")
public class AutenticationController {
    @Autowired
    private AuthenticationManager manager;
    @Autowired
    private TokenService tokenService;
    @Autowired
    private CadastroRepository repository;

    @PostMapping(value = "/login",
            consumes = MediaType.APPLICATION_JSON_VALUE,
            produces = MediaType.APPLICATION_JSON_VALUE)
    public ResponseEntity<?> login(@RequestBody @Valid DadosLoginRequestDTO dadosLoginRequestDTO) {
        // Variável 'token' do login e senha do usuario para classe do Spring
        var authenticationToken = new UsernamePasswordAuthenticationToken(dadosLoginRequestDTO.login(), dadosLoginRequestDTO.senha());

        // Conversão da variável 'token' para um método de Objeto reference para retornar o usuário autenticado no sistema com o token
        var authentication = manager.authenticate(authenticationToken);

        // Criando JSON de token para return
        var tokenJWT = tokenService.generateToken((UsuarioLogin) authentication.getPrincipal());

        // Return do Token com validação de 2h para usuário Logado e autenticado acessar app
        return ResponseEntity.ok(new DadosTokenJWTDTO(tokenJWT));
    }

    @PostMapping(value = "/register",
            consumes = MediaType.APPLICATION_JSON_VALUE,
            produces = MediaType.APPLICATION_JSON_VALUE)
    public ResponseEntity<?> register(@RequestBody @Valid DadosCadastroDTO dadosRegisterRequestDTO) {
        String tokenJWT = null;
        
        // Verificação se o email está cadastrado
        Optional<CadastroModel> entityModel = repository.findByEmail(dadosRegisterRequestDTO.email());

        // Validação para converter dados de novo usuário registrado com senha formato BCrypt
        if (entityModel.isEmpty()) {
            // Novo Usuário
            CadastroModel newEntity = new CadastroModel();

            // Variável 'token' do login e senha do usuario para classe do Spring
            var authenticationToken = new UsernamePasswordAuthenticationToken(dadosRegisterRequestDTO.email(), dadosRegisterRequestDTO.senha());

            // Conversão da variável 'token' para um método de Objeto reference para retornar o usuário autenticado no sistema com o token
            var authentication = manager.authenticate(authenticationToken);

            // Criando JSON de token para return
            tokenJWT = tokenService.generateToken((UsuarioLogin) authentication.getPrincipal());

            newEntity.setSenha(tokenJWT);
            newEntity.setEmail(dadosRegisterRequestDTO.nomecompleto());
            newEntity.setNomecompleto(dadosRegisterRequestDTO.nomecompleto());

            // Gerar valor
            repository.save(newEntity);
        }

        // Return do Token com validação de 2h para usuário Logado e autenticado acessar app
        return ResponseEntity.ok(new DadosTokenJWTDTO(tokenJWT));
    }
}

Oi!

Na verdade na sua lógica de cadastro você escreveu um código para realizar login e gerar um token. Isso deve ser feito apenas na funcionalidade de login.

No cadastro a lógica deve ser para converter a senha de texto aberto para BCrypt. Exemplo:

public ResponseEntity<?> register(@RequestBody @Valid DadosCadastroDTO dadosRegisterRequestDTO) {
    var emailJaCadastrado = this.repository.existsByEmail(dadosRegisterRequestDTO.email());
    if (emailJaCadastrado) {
        throw new ValidacaoException("Email já cadastrado para outro usuário!");
    }

    var senhaBCrypt = passwordEncoder.encode(dadosRegisterRequestDTO.senha());

    //setar os dados via construtor ou via metodos setters:
    var usuario = new Usuario(dadosRegisterRequestDTO, senhaBCrypt);

    this.repository.save(usuario);
    
    return ResponseEntity.ok();
}

Precisa injetar no controller o PasswordEncoder:

@Autowired
private PasswordEncoder passwordEncoder;

E na classe de tratamento de erros tratar a ValidacaoException:

@ExceptionHandler(ValidacaoException.class)
public ResponseEntity tratarErroValidacao(ValidacaoException ex) {
    return ResponseEntity.badRequest().body(ex.getMessage());
}