1
resposta

Como criar usuários em tempo de execução?

Na aula, os usuários autorizados foram definidos "hard coded" na classe WebSecurityConfig que extende WebSecurityConfigurerAdapter, implementando o método configure:

@Override protected void configure(AuthenticationManagerBuilder auth) throws Exception {

    BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();

    UserDetails joao =
         User.builder()
        .username("joao")
        .password(encoder.encode("joao"))
        .roles("USER")
        .build();

    auth.jdbcAuthentication()
    .dataSource(dataSource)
    .passwordEncoder(encoder)
    .withUser(joao)
    ;
}

O usuário foi automaticamente criado quando o projeto foi complidao/executado. Para criar outro usuário, o usuário foi novamente "hard coded", alterando os dados do usuário, e o projeto foi novamente compilado/executado. Depois foi necessário retirar o código de configuração do usuário, para não fosse tentado novamente criar um usuário no próximo lançamento do sistema.

Como criar usuários em tempo de execução, por exemplo com uma tela formulário que chamasse um endpoint com o nome e senha como parâmetros? Ou seja, como um RestController poderia invocar o código de configuração do usuário (UserDetails) e de gravação JDBC (auth.jdbcAuthentication) em um método que não fosse executado no lançamento do sistema, mas sim após um request?

1 resposta

Segue um exemplo simples feito em Thymeleaf, mas em Vue não mudaria mta coisa:

O primeiro passo seria mapear as entidades para User e Authority ( vou omitir gets/sets):

@Entity
@Table(name = "users")
public class User {

    @Id
    @NotBlank
    private String username;

    @NotBlank
    private String password;
    private Boolean enabled;

    @OneToMany(cascade = CascadeType.ALL, mappedBy = "user", fetch = FetchType.LAZY)
    private List<Pedido> pedidos;

    @ElementCollection
    @CollectionTable(name = "authorities", joinColumns = @JoinColumn(name = "username"))
    private Set<Authority> authorities = new HashSet<>();
}
@Embeddable
public class Authority  {

    private String authority;

    public Authority() {
    }

    public Authority(String authority) {
        this.authority = authority;
    }
}    

Então cria-se um Controller para gerenciar a tela e salvar o registro:

@Controller
public class CadastroUsuarioController {

    @Autowired
    private UserRepository repository;

    @GetMapping("/cadastrar")
    public ModelAndView login() {
        ModelAndView mv = new ModelAndView("cadastrar");
        mv.addObject("user", new User());

        return mv;
    }


    @PostMapping("/cadastrar")
    public String cadastrar(@Valid User user, BindingResult result) {
        if (result.hasErrors()) {
            return "cadastrar";
        }

        user.setPassword(new BCryptPasswordEncoder().encode(user.getPassword()));
        user.setEnabled(true);
        user.addAuthority("USER");
        repository.save(user);

        return "redirect:/login";
    }
}

Cria uma tela para cadastro:

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head th:replace="~{base :: head}"></head>
<body>
    <div th:replace="~{base :: logo}"></div>

    <div class="container">
        <div th:replace="~{base :: titulo('Novo Usuário')}"></div>

        <div class="card mb-3">
            <form th:object="${user}" th:action="@{/cadastrar}" class="card-body" method="post">
                <div class="form-group">
                    <label for="username">Usuário</label> 
                    <input th:field="*{username}" th:errorClass="is-invalid" class="form-control" placeholder="usuário" />
                    <div class="invalid-feedback" th:errors="*{username}"></div>
                </div>
                <div class="form-group">
                    <label for="password">Senha</label> 
                    <input type="password" th:field="*{password}" th:errorClass="is-invalid" class="form-control" placeholder="senha" />
                    <div class="invalid-feedback" th:errors="*{password}"></div>
                </div>
                <button class="btn btn-primary" type="submit">Cadastrar</button>

            </form>
        </div>


    </div>
</body>
</html>

e por fim, adiciona permissão para a tela de cadastro

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
             .antMatchers("/cadastrar").permitAll()
             .antMatchers("/home").permitAll()
            .anyRequest().authenticated()
        .and()
            .formLogin(form -> 
                form.loginPage("/login")
                    .permitAll()
                    .defaultSuccessUrl("/usuario/pedido", true)
            )

         .logout(logout -> logout.logoutUrl("/logout").logoutSuccessUrl("/home")) 
         .csrf().disable();
    }

Basicamente é isso, poderia fazer umas melhorias como criar um DTO para receber os dados do usuário, validar se o usuário já está cadastrado, criar a tela usando vue (dai teria que adaptar controller para um @RestController) mas no geral é isso. Um pena não ter adicionado uma aula com isso, teria acrescido bastante ao curso.