Solucionado (ver solução)
Solucionado
(ver solução)
3
respostas

Exemplo de Boa pratica no cadastro de Usuário e gravação de senha criptografada

Boa noite!

Complementei a prática desenvolvida no curso "Spring Boot 3: aplique boas práticas e proteja uma API Rest" para além do Endpoint de Longin , ter o endpoint de cadastro de usuário.

Por gentileza avaliar se o código pode ser melhorado, segue fonte para avaliação. Obs. Usei Objeto Record ( UserAuthenticationData) para receber os dados do usuário a ser cadastrado, por que essa entrada precisa ser imutável. Já para inserir no banco uso o DTO Lombok (User), já que o atributo password será modifcado para ser enviado criptografada.


/**
 * @author Aline Divino
 *
 */
@RestController
@RequestMapping("/login")
public class AutenticationController {

  /*
   * Inject Spring Class that triggers the Authentication process
   */
  @Autowired
  private AuthenticationManager authenticationManager;


  @Autowired
  private TokenService tokenService;

  @Autowired
  private UserRepository userRepository;

  @Autowired
  private BCryptPasswordEncoder bCryptPasswordEncoder;

  @PostMapping
  public ResponseEntity<JWTTokenData> loginIn(@RequestBody @Valid UserAuthenticationData userData) {

    Authentication authenticationToken =
        new UsernamePasswordAuthenticationToken(userData.login(), userData.pass());
    Authentication authenticate = authenticationManager.authenticate(authenticationToken);

    String tokenJwt = tokenService.generateToken((User) authenticate.getPrincipal());
    return ResponseEntity.ok(new JWTTokenData(tokenJwt));

  }

  @PostMapping("/new")
  @Transactional
  public ResponseEntity<?> newUser(@RequestBody @Valid UserAuthenticationData json, UriComponentsBuilder uriBuilder) {

    User user = new User();

    String encriptedPasswd = bCryptPasswordEncoder.encode(json.pass());

    user.setLogin(json.login());
    user.setPass(encriptedPasswd);    

    userRepository.save(user);
    return ResponseEntity.ok().build();

  }

}
3 respostas

Olá, Aline! Tudo bem?

Parabéns por complementar a prática desenvolvida no curso e criar um endpoint de cadastro de usuário! É sempre importante aplicar boas práticas de segurança em nossas aplicações.

Sobre o código que você compartilhou, acredito que ele esteja bem estruturado e organizado. A utilização do objeto Record para receber os dados do usuário a ser cadastrado é uma boa prática, pois garante a imutabilidade dos dados. Além disso, o uso do DTO Lombok para inserir no banco é uma boa prática também, pois separa a camada de apresentação da camada de persistência.

Outro ponto positivo é a utilização do BCryptPasswordEncoder para criptografar a senha do usuário antes de persistir no banco. Essa é uma boa prática de segurança, pois evita que a senha fique exposta no banco de dados.

No entanto, sugiro que você adicione validações extras para garantir que os dados do usuário estejam corretos antes de persistir no banco. Por exemplo, você pode verificar se o login já existe no banco antes de salvar um novo usuário com o mesmo login. Além disso, você pode adicionar validações para garantir que a senha atenda aos requisitos de segurança, como ter pelo menos 8 caracteres, ter letras maiúsculas e minúsculas, números e caracteres especiais.

Espero ter ajudado e bons estudos!

Oi Otávio, obrigada pelo retorno!

Fiz as melhorias sugeridas! Porém como entrou regras extras (rsrs), achei melhor não deixar no Controller e passar a usar um serviço para deixar o código mais limpo, seguindo as orientações do curso inclusive. Segue as alteração para você avaliar. Desde já muito grata pela atenção.

No Controller passei a usar um serviço, que será responsável por chamar as validações para: 1 - Garantir que a senha informada atenda aos requisitos de segurança que nesse caso eu deixei para ter de 6 à 8 caracteres, ter letras maiúsculas e minúsculas, e números. 2- Verificar se o login já existe no banco ;

Segue código do método no Controller

  @PostMapping("/new")
  @Transactional
  public ResponseEntity<?> newUser(@RequestBody @Valid UserAuthenticationData json,
      UriComponentsBuilder uriBuilder) {

    User user = userTransactionService.newUSerTransaction(json);

    uriComponentsBuilder = uriBuilder.path("/login/{id}");

    UriComponents uriComponents = uriComponentsBuilder.buildAndExpand(user.getId());

    // HTTP Code 201 -
    return ResponseEntity.created(uriComponents.toUri()).body(new DetaildeUserData(user));

  }

Segue código do serviço UserTransactionService que injetei no Controller

/**
 * Responsible Encapsulate the business rules 
 * @author Aline Divino
 *
 */
@Service
public class UserTransactionService {

  @Autowired
  private UserRepository userRepository;

  @Autowired
  private List<UserValidator> validators;

  @Autowired
  private BCryptPasswordEncoder bCryptPasswordEncoder;


  public User newUSerTransaction(UserAuthenticationData json) {

    validators.forEach(v -> v.validator(json));

    User user = new User();

    String encriptedPasswd = bCryptPasswordEncoder.encode(json.pass());

    user.setLogin(json.login());
    user.setPass(encriptedPasswd);

    userRepository.save(user);

    return user;

  }

Segue código do Validador que é responsável por fazer as validações que você sugeriu :)

/**
 * @author Aline Divino
 *
 */
@Component
public class NewUserValidator  implements UserValidator{

  @Autowired
  private UserRepository userRepository;

  @Override
  public void validator(UserAuthenticationData json) {

    Pattern pattern = Pattern.compile("(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])[a-zA-Z0-9]{6,8}$");

    Matcher matcher = pattern.matcher(json.pass());

    if (!matcher.find()) {
      throw new ValidationException("Password must have at least one uppercase character, one lowercase character and at least one digit");
    }

    User user = (User) userRepository.findByLogin(json.login());

    if(user !=null) {
      throw new ValidationException("User login already exists!");
    }

  }

}

Segue resultado final foi o esperado:

(Cenário de senha inválida! )

(Usuário criado! )

(Usuário criado no banco com senha criptografada )

(Cenário de validação de Login já em uso! )

solução!

Parabéns pelas melhorias feitas no seu código :)

A separação da lógica de validação de dados do usuário em um serviço separado é uma ótima prática, pois mantém a responsabilidade do Controller em lidar apenas com as requisições HTTP e passa a responsabilidade de lidar com as regras de negócio para o serviço.

Parabéns novamente pelas melhorias no código e continue praticando :)