Spring Boot Security

A proposta deste material é apresentar a implementação do Spring Boot Security, inicialmente para autenticação dos usuários da aplicação. Esse material foi baseado no vídeo: https://www.youtube.com/watch?v=X80nJ5T7YpE

O primeiro passo é no VSCode teclar F1 e selecionar a opção Spring Initializr: Edit starters

Em seguida selecione a opção Spring Security e confirme a seleção das dependências.

Confirme a alteração no arquivo POM.xml

Em seguida vamos criar uma nova classe no pacote model, chamada Usuario com os atributos: id, usuario e senha.

package br.univille.dacs2020.model;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

@Entity
public class Usuario {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long id;
    private String usuario;
    private String senha;

    public long getId() {
        return id;
    }

    public void setId(long id) {
        this.id = id;
    }

    public String getUsuario() {
        return usuario;
    }

    public void setUsuario(String usuario) {
        this.usuario = usuario;
    }

    public String getSenha() {
        return senha;
    }

    public void setSenha(String senha) {
        this.senha = senha;
    }
}

Para dar acesso a esta nova entidade, precisamos criar uma interface de repositório que vai conter dois métodos para buscar o usuário pelo atributo usuário e outro método para buscar pelo usuario e pela senha.

package br.univille.dacs2020.repository;

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

import br.univille.dacs2020.model.Usuario;

public interface UsuarioRepository extends JpaRepository<Usuario, Long> {
    Usuario findByUsuario(String usuario);
    Usuario findByUsuarioAndSenha(String usuario,String senha);
}

Em seguida, vamos incluir uma nova linha no arquivo data.sql para incluir um usuário padrão na aplicação na nova tabela do banco de dados.

insert IGNORE into paciente(id,nome,sexo,data_nascimento) values(1,'zezinho','masculino','2020-07-08');
insert IGNORE into usuario(id,usuario,senha) values(1,'admin','admin');

O próximo passo é criar uma nova classe de Serviço chamada MyUserDetailsService, que será chamada pelo mecanismo de segurança para para recuperar o usuário do banco pelo nome do usuário e também retornar o usuário pelo nome do usuário e pela senha.

package br.univille.dacs2020.service.impl;

import java.util.ArrayList;
import org.springframework.beans.factory.annotation.Autowired;
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.Service;
 
import br.univille.dacs2020.repository.UsuarioRepository;
import br.univille.dacs2020.model.Usuario;
@Service
public class MyUserDetailsService implements UserDetailsService {
 
    @Autowired
    private UsuarioRepository repository; 
 
    public Usuario buscaUsuarioSenha(String nomeUsuario, String senhaUsuario){
        return repository.findByUsuarioAndSenha(nomeUsuario, senhaUsuario);
    }
 
    @Override
    public UserDetails loadUserByUsername(String nomeUsuario) throws UsernameNotFoundException {
        Usuario usuario = repository.findByUsuario(nomeUsuario);
        return new User(usuario.getUsuario(),usuario.getSenha(), new ArrayList<>());
    }  
     
}

Agora vamos cria um novo pacote security onde vamos criar as classes para controle da segurança da aplicação.

Em seguida vamos criar uma classe chamada SecurityConfigurer, esta classe terá as seguintes características.

  • Ela implementa a classe abstrata WebSecurityConfigurerAdapter que obriga a classe SecurityConfigurer a ter um método configure (AuthenticationManagerBuilder). Esse método será chamado automaticamente pelo mecanismo de autenticação forçando a verificação do usuário pelo serviço MyUserDetailsService.
  • Essa classe deve ser anotada com o @EnableWebSecurity para habilitar a o sistema de segurança do Spring Boot Security.
  • Deve ser implementado um método chamado passwordEncoder() que tem por objetivo retornar uma instância de uma classe responsável por codificar a senha.
    • IMPORTANTE: no futuro precisamos modificar a escolha feita neste código para utilizar um algoritmo que codifique a senha.
package br.univille.dacs2020.security;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

import br.univille.dacs2020.service.impl.MyUserDetailsService;

@EnableWebSecurity
public class SecurityConfigurer extends WebSecurityConfigurerAdapter {
    @Autowired
    private MyUserDetailsService myUserDetailsService;

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(myUserDetailsService);
    }
    
    @Bean
    public PasswordEncoder passwordEncoder() {
        return NoOpPasswordEncoder.getInstance();
    }
}

Se a aplicação for executada e a URL principal for acessada, o Spring Boot Security irá obrigar o usuário a passar pela tela de login. Utilize o usuário admin senha admin.

Em seguida do login, tecle F12 e selecione a aba de application da ferramenta de desenvolvimento do navegador, observe que foi criado um Cookie de JSESSIONID para reconhecer o usuário autenticado.

Para concluir vamos alterar a classe SecurityConfigurer para sobreescrever o método configure e liberar o acesso ao console do H2 do processo de autenticação, pois ele mesmo possui seu mecanismo.

package br.univille.dacs2020.security;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

import br.univille.dacs2020.service.impl.MyUserDetailsService;

@EnableWebSecurity
public class SecurityConfigurer extends WebSecurityConfigurerAdapter {
    @Autowired
    private MyUserDetailsService myUserDetailsService;
    
    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception {
        httpSecurity
        .authorizeRequests()
        .antMatchers("/fonte_dados/**").permitAll()
        .anyRequest().authenticated().and().formLogin();

        httpSecurity.csrf().ignoringAntMatchers("/fonte_dados/**");
        httpSecurity.headers().frameOptions().sameOrigin();
    }
    
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(myUserDetailsService);
    }
    
    @Bean
    public PasswordEncoder passwordEncoder() {
        return NoOpPasswordEncoder.getInstance();
    }
}