Ir para o conteúdo

O Spring Security, existente desde 2003, é um projeto muito conhecido e utilizado em aplicações com Spring. Espero, com esse artigo, esclarecer algumas questões e trazer novidades para alguns, mas meu principal objetivo é responder duas perguntas:

Figura 1 — Spring Security https://spring.io/projects

Do que o Spring Security nos protege? O que ele faz além de proteger?

Introdução

De fato, o Spring Security é uma biblioteca que fornece proteção, mas também autenticação, autorização e armazenamento de senhas. Sendo que trabalha com vários protocolos para autenticação. Para armazenar senhas em um banco de dados, ele tem opção de vários encoders. Também é possível utilizá-lo em projetos com Java EE, Spring Webflux, e Kotlin. Além disso, ele protege de ataques mais comuns como CSRF ou XSS.

Verificando atentamente a documentação do projeto Spring Security, é possível constatar que a ferramenta fornece um kit completo para proteger uma aplicação, bem como um flexível no qual pode-se selecionar e “plugar” os módulos que desejar.

Para facilitar o entendimento, foi criado um projeto de demonstração, utilizando Basic Auth para autenticação, armazenamento de senhas em banco de dados e autorização.

1. Configuração

Para habilitar o Spring Security em um projeto é necessário, além das dependências, criar a classe de configuração. É nessa classe que se define os protocolos de autenticação, autorização, proteção e armazenamento. Essas configurações são detalhadas no decorrer do artigo.

@Configuration
@EnableConfigurationProperties
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private MyUserDetailsService userDetailsService;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .httpBasic()
                .and()
                .authorizeRequests()
                .antMatchers("/h2-console/**").permitAll()
                .antMatchers("/").permitAll()
                .antMatchers("/books").hasRole("USER")
                .antMatchers("/books2").hasRole("ADMIN")
                .and()
                .csrf().disable()
                .headers().frameOptions().disable();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder builder) throws Exception {
        builder
                .userDetailsService(userDetailsService)
                .passwordEncoder(new BCryptPasswordEncoder());
    }

}

Bloco 1 — Classe responsável pela configuração do Spring Security

Autenticação

A autenticação é a porta de entrada da aplicação e essa porta precisa estar protegida, tanto em uma aplicação externa quanto interna. Para isso, o Spring Security disponibiliza alguns mecanismos que podemos utilizar. São eles:

  • Username and Password: quando se utiliza usuário e senha para logar. Para esse mecanismos podemos utilizar três formas: Form login, Basic Authentication e Digest Authentication.
  • OAuth2: quando se utiliza OpenID connect, é possível se autenticar em uma aplicação usando uma conta e servidor externo, como GitHub.
  • SAML2: é um padrão que surgiu antes do OAuth e que serve para compartilhamento de informações de login, podendo também atender o padrão Single Sign On (SSO).
  • Remember Me: mecanismo utilizado para salvar a sessão do usuário logado na aplicação.

Outros: JAAS Authentication, Central Authentication Server (CAS), OpenID, Pre-Authentication Scenarios e X509 Authentication.

No projeto de demonstração desse artigo está sendo utilizado Basic Auth. É possível ver no trecho de código abaixo que foi extraído da linha 11 da classe SecurityConfig.java.

http 
   .httpBasic()

Autorização

Para autorização, o Spring Security se baseia nas Authorities do usuário que se autentica na aplicação. É possível usar credenciais registradas inMemory (usuário e senha definidos no arquivo de propriedades do projeto, sem acesso externo) para testes ou implementar a estrutura de UserDetailService (usuários, grupos e permissões salvas na base de dados). Porém, para cada endpoint, é preciso informar quais ROLEs poderão acessá-lo; do contrário, todos poderão.

As Authorities representam as autoridades que foram concedidas ao principal (usuário logado na aplicação). Elas são lidas em forma de uma lista de objetos GrantedAuthority, que são inseridos no objeto AuthenticationManager do SecurityContext e são lidos posteriormente pelo AccessDecisionManager.

Existem outras funcionalidades legais, como Pre-invocation ou After-invocation para tratar as Authorities, ou trabalhar com Hierarchical Roles, onde se define uma hierarquia para as roles. Dessa forma, o usuário que tem a role ADMIN acessa também as informações da role USER, sem precisar estar vinculado a ela.

Proteção

O Spring Security fornece proteção contra vulnerabilidades comuns em aplicações web, que podem ser exploradas de diferentes formas. São elas: o Cross Site Request Forgery (CSRF), Security HTTP Response Headers, HTTP e HTTP Firewall.

Não é o objetivo desse artigo entrar em detalhes sobre cada vulnerabilidade, pois é um assunto mais extenso. Porém, foram adicionadas referências para quem tiver interesse em estender a pesquisa.

Armazenamento de senhas

Outra funcionalidade legal do Spring Security é a estrutura para armazenamento de senhas. É possível salvar usuários, senhas e grupos no banco, e encriptar senhas com sistemas de criptografias bem avançados como bcrypt, PBKDF2, scrypt ou argon2.

Para isso, é preciso implementar o UserDetailsService, que vai fornecer métodos para consultar os usuários na base de dados. Conforme a classe MyUserDetailsService.java abaixo:

@Component
public class MyUserDetailsService implements UserDetailsService {

    @Autowired
    private UsuarioRepository usuarios;

    @Autowired
    private GrupoRepository grupos;

    @Autowired
    private PermissaoRepository permissoes;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        Usuario usuario = usuarios.findByLogin(username);

        if (usuario == null) {
            throw new UsernameNotFoundException("Usuário não encontrado!");
        }

        return new UsuarioSistema(usuario.getNome(), usuario.getLogin(), usuario.getSenha(), authorities(usuario));
    }

    public Collection<? extends GrantedAuthority> authorities(Usuario usuario) {
        return authorities(grupos.findByUsuariosIn(Lists.newArrayList(usuario)));
    }

    public Collection<? extends GrantedAuthority> authorities(List<Grupo> grupos) {
        Collection<GrantedAuthority> auths = new ArrayList<>();

        for (Grupo grupo: grupos) {
            List<Permissao> lista = permissoes.findByGruposIn(Lists.newArrayList(grupo));

            for (Permissao permissao: lista) {
                auths.add(new SimpleGrantedAuthority("ROLE_" + permissao.getNome()));
            }
        }

        return auths;
    }

}

Bloco 2 — Classe de serviço que implementa UserDetailsService

Após criar a classe MyUserDetailsService.java, é preciso informar na configuração que será utilizado um userDetailsService, conforme linha 37 da classe SecurityConfig.java.

builder
  .userDetailsService(userDetailsService)
  .passwordEncoder(new BCryptPasswordEncoder())

Lembrando que é preciso criar as classes de repositórios e domínios. Porém, a estrutura de banco de dados pode ser criada e alimentada automaticamente, conforme o projeto de demonstração está fazendo. Isso ocorre porque, no arquivo application.yml, linha 12, existe a configuração ddl-auto: create-drop.

jpa:
  hibernate:
    ddl-auto: create-drop

Para o projeto alimentar as tabelas do banco de dados, é necessário criar o arquivo /resources/import.sql, e nele por o script necessário.

2. Execução

Agora que está entendido o que cada parte do código está fazendo, é possível executar e ver o resultado. O projeto de demonstração de encontra no GitHub, e pode ser clonado e executado localmente.

Na raíz do projeto, execute os comandos:

$ ./gradlew build
$ ./gradlw bootRun

Sua aplicação deve estar disponível na porta 8080, mais ou menos como mostra o log abaixo:

Image for post
Figura 2— Log exibido ao rodar o projeto

Para testar, execute o seguinte comando em algum terminal bash. Antes, é preciso gerar o Authorization baseado no usuário e senha que podem acessar os endpoints. Para gerar o Authorization, existem opções na internet como o blitter.se (você pode utilizar usuário silas, senha 123).

curl --location --request GET 'localhost:8080/books' \
--header 'Authorization: Basic c2lsYXM6MTIz' \
--header 'Cookie: JSESSIONID=56AD4F472FF75F792FD0F2415ED82A6D'

O resultado que você deve receber será como o abaixo:

Image for post
Figura 3— Resposta retornada do serviço /books

3. Considerações finais

Espero que esse artigo ajude a entender o Spring Security e dê uma perspectiva de onde ele pode chegar. O objetivo era introduzir o assunto e, assim, ter uma base para testar recursos mais avançados.

Segurança é algo essencial para todas as aplicações e, baseado nos estudos abordados nesse artigo, ficou evidente que o Spring Security é uma biblioteca que pode suprir essa necessidade.


Referências:

Spring Security Reference

Thanks to Pablo Oliveira and Alexandre Silveira.

Outras publicações