Pular para o conteúdo principal

Autenticação com Spring Boot, Spring Security e AngularJS - Parte 1

Hoje iremos analisar o processo de autenticação em uma aplicação que utiliza SpringBoot, SpringSecurity e AngularJS.

Conhecendo o Spring Security


Em toda aplicação o processo de autenticação é extremamente importante, sendo um dos requisitos não funcionais mais importantes em 80% das aplicações.

Para executar tal tarefa , podemos criar rotinas ou usar frameworks já preparados para os mais diferentes cenários, no caso, vamos analisar o Spring Security, este faz parte dos componentes da plataforma Spring.

Um ponto bem interessante do Spring Security, é que ele pode ser usando em projetos que usam ou não o Spring em sua essência, por exemplo, um projeto feito somente com JEE, pode usar o Spring Security para gerenciar a parte de segurança normalmente, para mais detalhes podemos acessar a documentação oficial:


Integrando o SpringSecurity com SpringBoot


Para utilizar o SpringSecurity em uma aplicação SpringBoot, temos que adicionar o seguinte starter no arquivo pom.xml:

1
2
3
4
<dependency>         
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-security</artifactId>
</dependency>

Configurando o Spring Security


Para configurar o Spring Security, necessitamos criar uma classe com a lógica de segurança, esta deve conter a annotation @EnableWebSecurity:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter{

    private final String[] pagesAuthorizes  = {"/index.html"};
  
    @Override
    protected void configure(AuthenticationManagerBuilder auth) 
    throws Exception {
       auth.inMemoryAuthentication().withUser("usuario").password("123").roles("APP");
    }
 
    @Override
    public void configure(WebSecurity web) 
    throws Exception {
       web.ignoring().antMatchers("/resources/**");
    }
 
    @Override
    protected void configure(HttpSecurity http) 
    throws Exception {
       http.authorizeRequests()
           .antMatchers(this.pagesAuthorizes)
              .permitAll()
           .antMatchers("/**").hasRole("APP")
              .anyRequest()
              .authenticated()
           .and()
           .formLogin()
              .loginPage("/index.html")
              .loginProcessingUrl("/login")
              .failureHandler(this.customFailureHandler())
              .successHandler(this.customSuccessHandler())
              .permitAll()
           .and()
           .csrf()
              .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
           .and()
           .logout()
              .logoutSuccessHandler(this.customLogoutSuccessHandler())
              .invalidateHttpSession(true)
              .logoutRequestMatcher(new AntPathRequestMatcher("/logout"));
    }
   
    private AuthenticationFailureHandler customFailureHandler(){
       return (request, response, exception) -> response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
    }

    private AuthenticationSuccessHandler customSuccessHandler(){
       return (request, response, authentication) -> response.sendError(HttpServletResponse.SC_OK);
    }
 
    private LogoutSuccessHandler customLogoutSuccessHandler(){
       return (request, response, authentication) -> response.sendError(HttpServletResponse.SC_OK);
    }
}

Vamos analisar cada item da classe acima:

1- Autenticação: Nessa parte, definimos o resource onde o Spring Security irá consulta os dados de autenticação, aqui podemos usar banco de dados, LDAP, etc, em nosso exemplo utilizamos uma persistência em memória:

1
2
3
4
5
 @Override
 protected void configure(AuthenticationManagerBuilder auth) 
 throws Exception {
    auth.inMemoryAuthentication().withUser("usuario").password("123").roles("APP");
 }

2- Exceção de Recursos: Nessa parte, marcamos para o Spring Security excluir da autenticação tudo que esteja dentro o path /resources/, este armazena os recursos estáticos como .js, .jpg, .css, etc:

1
2
3
4
5
 @Override
 public void configure(WebSecurity web) 
 throws Exception {
      web.ignoring().antMatchers("/resources/**");
 }


3- Lógicas de Acesso: Aqui é onde realizamos os mapeamentos de urls, lógicas de sucesso/falha e logout:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
 @Override
 protected void configure(HttpSecurity http) 
 throws Exception {
    http.authorizeRequests()
        .antMatchers(this.pagesAuthorizes)
           .permitAll()
        .antMatchers("/**").hasRole("APP")
           .anyRequest()
           .authenticated()
        .and()
        .formLogin()
           .loginPage("/index.html")
           .loginProcessingUrl("/login")
           .failureHandler(this.customFailureHandler())
           .successHandler(this.customSuccessHandler())
           .permitAll()
        .and()
        .csrf()
           .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
        .and()
        .logout()
           .logoutSuccessHandler(this.customLogoutSuccessHandler())
           .invalidateHttpSession(true)
           .logoutRequestMatcher(new AntPathRequestMatcher("/logout"));
 }

Vamos analisar as partes que envolvem as respostas em caso de falha, sucesso e logout.

A interface AuthenticationFailureHandler


Na linha 14 temos o método failureHandler(this.customFailureHandler()), que recebe como parâmetro uma AuthenticationFailureHandler, nossa implementação usando uma expressão lambda ficou da seguinte forma:

1
2
3
 private AuthenticationFailureHandler customFailureHandler(){
    return (request, response, exception) -> response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
 }

Sem utilizar expressão lambda, basta criar um @Component como abaixo:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
@Component
public class AsyncAuthenticationFailureHandler implements AuthenticationFailureHandler {

    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception)
    throws IOException, ServletException {
     
        response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
    }
}

Aqui falamos ao SpringSecurity, que em caso de falha no login, o mesmo deve apenas enviar como resposta um HttpStatus 401, que representa falha na autenticação.

A interface AuthenticationSuccessHandler


Na linha 15, temos o método successHandler(this.customSuccessHandler()), este manipula a resposta em caso de sucesso, nossa implementação usando uma expressão lambda ficou da seguinte forma:

1
2
3
 private AuthenticationSuccessHandler customSuccessHandler(){
    return (request, response, authentication) -> response.sendError(HttpServletResponse.SC_OK);
 }

Sem utilizar expressão lambda, basta criar um @Component como abaixo:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
@Component
public class AsyncAuthenticationSuccessHandler implements AuthenticationSuccessHandler{

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication)
    throws IOException, ServletException {
  
       response.sendError(HttpServletResponse.SC_OK);
    }
}

Este encaminha ao frontend um HttpStatus 200, que representa que a tentativa de autenticação foi realizada com sucesso.

A interface LogoutSuccessHandler


Na linha 22, temos o método logoutSuccessHandler(this.customLogoutSuccessHandler()), este manipula a resposta no processo de logout, veja a implementação:


1
2
3
private LogoutSuccessHandler customLogoutSuccessHandler(){
   return (request, response, authentication) -> response.sendError(HttpServletResponse.SC_OK);
}

Sem utilizar expressão lambda, basta criar um @Component como abaixo:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
@Component
public class AsyncLogoutSuccessHandler implements LogoutSuccessHandler{

  @Override
  public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication)
  throws IOException, ServletException {

     response.sendError(HttpServletResponse.SC_OK);
  }
}

Este encaminha ao frontend um HttpStatus 200, que representa  sucesso na operação de logout.

Realizado todo o procedimento acima, temos toda a camada do Spring Security pronta para processar as solicitações vindas do AngularJS.

Até a próxima.

Referências



Comentários

Postagens mais visitadas do Blog