Spring Security 6 - O que temos de novo?
- #Spring Security / Spring
Spring Security
Você utiliza o Spring Security como camada de segurança em suas aplicações Java? Se sim, fique atento a algumas mudanças sofridas pela novíssima versão do Spring Security. Isso mesmo, o Spring Security deixou para trás a versão 5 e agora tem como versão mais atual a 6. Essa nova versão trouxe algumas importantes mudanças no processo de codificação da classe de configuração do Spring Security, sim, aquela classe onde adicionamos as regras de acesso ou mesmo o tipo de criptografia de senha que será utilizada na sua aplicação.
Do Spring Boot 2 ao Spring Boot 3
É importante destacar que a versão 2 do Spring Boot utiliza o Spring Security 5 na camada de segurança. Por conta disso, caso você migre para o Spring Boot 3, estará migrando também para o Spring Security 6. Essa mudança vai necessitar que a classe de configuração do Spring Security passe por alterações em relação aquilo que fazia ao configurar o Spring Security na versão 5 ou enquanto usava o Spring Boot 2.
Se você chegou a utilizar o Spring Boot versão 2.7.x, já deveria saber que a classe WebSecurityConfigurerAdapter foi descontinuada na versão do Spring Security trazia pelo starter do Spring Boot 2.7.x. A partir dessa versão, deixamos de herdar e sobrescrever os métodos da classe WebSecurityConfigurerAdapter para trabalhar com beans.
Vamos ver um exemplo, de como era antes da versão 2.7.x do Spring Boot:
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
public void configure(HttpSecurity http) throws Exception {
// configurações aqui
}
}
E a partir da versão 2.7.x do Spring Boot:
@Configuration
@EnableWebSecurity
public class WebSecurityConfig {
@Bean
public SecurityFilterChain configure(HttpSecurity http) throws Exception {
// configurações aqui
}
}
As instruções do corpo do método configure()
A diferença citada na parte inicial da configuração do Spring Security se estendeu ao Spring Boot 3, mas não apenas vamos trabalhar com bean como também a estrutura do corpo do método com nossas regras de acessos e autorizações vão precisar ser modificadas. Isso, em decorrência das novas exigências do Spring Security 6.
Um pequeno exemplo de como a configuração deverá ser incluída no corpo de método pode ser vista a seguir:
@Bean
public SecurityFilterChain configure(HttpSecurity http) throws Exception {
http.authorizeHttpRequests( (authorize) -> authorize
.requestMatchers("/").permitAll()
.requestMatchers("/user/cadastro").hasAuthority(ADMIN)
.anyRequest().authenticated()
)
... // demais instruções
}
Observe com atenção, o antigo método authorizeRequests() foi substituído pelo authorizeHttpRequests(). E a declaração das regras passam a ser realizadas por meio de expressão lambda. Outro ponto a destacar é o novo método requestHttpMatchers() que chegou para substituir o anyMatechers(). Entretanto, a forma de passar as instruções ao requestHttpMatchers() se mantém iguais aqueles usadas pelo anyMatechers().
Temos uma alteração importante na assinatura do método, que deixou de ter um retorno do tipo void e passou a retornar um objeto da classe SecurityFilterChain.
Até agora tudo certo, mas e a parte referente ao login, logout e acesso negado? Bem, essas configurações ainda podem ser utilizadas como era no tempo do Spring Security 5, mas temos um nova maneira de adiciona-las, o que me faz pensar que logo logo a forma antiga poderá ser descontinuada. Então, é bom seguir com o novo e deixar o antigo para trás. Vamos conferir um configuração mais completa do método configure():
@Bean
public SecurityFilterChain configure(HttpSecurity http) throws Exception {
http.authorizeHttpRequests( (authorize) -> authorize
.requestMatchers("/").permitAll()
.requestMatchers("/user/cadastro").hasAuthority(ADMIN)
.anyRequest().authenticated()
).formLogin( (form) -> form
.loginPage("/login")
.defaultSuccessUrl("/", true)
.failureUrl("/login-error")
.permitAll()
).logout( (logout) -> logout
.logoutSuccessUrl("/")
.deleteCookies("JSESSIONID")
).exceptionHandling( (ex) -> ex
.accessDeniedPage("/negado")
);
return http.build();
}
Observe que não fazemos mais uso do método and(), para ligar as configurações entre cada etapa da configuração. Agora, a ligação é feita diretamente entre as instruções de formLogin(), logout() e exceptionHandling(). E como podemos ver, também fazemos uso de expressão lambda em cada uma das etapas apresentadas. Caso venha a ter mais etapas, elas seguirão a mesma proposta.
Por fim, podemos notar que temos uma instrução de retorno baseada na variável do tipo HttpSecurity. Por meio desta, vamos retornar via método build(), um objeto do tipo SecurityFilterChain.
Criptografia de Senha
Um passo importante ao trabalhar com o Spring Security é definir qual o tipo de criptografia que ele deve usar para dar segurança as senhas cadastradas. Em versões anteriores do módulo de segurança, usávamos uma instrução como esta:
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService)
.passwordEncoder(new BCryptPasswordEncoder());
}
O método apresentado era herdado da classe WebSecurityConfigurerAdapter e o argumento AuthenticationManagerBuilder fornecia o acesso ao método passwordEncoder() para definição do tipo de criptografia a ser usada. Entretanto, como não trabalhamos mais com herança e sim com bean, devemos criar um bean para definir o tipo de criptografia, como no exemplo a seguir:
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
Algo interessante em relação ao método sobrescrito configure(), é que nele definíamos não apenas o tipo de criptografia, mas a classe de nossa aplicação que realiza o processo de login via implementação da interface UserDetailsService. Na nova versão, com o uso do bean PasswordEncoder, não é mais necessário realizar a configuração indicando qual a classe implementa UserDetailsService. O Spring Security 6 vai identificar isso de forma automática.
Porém, se você é daqueles que gosta de ter tudo destrinchado e vai se sentir mais seguro definindo na configuração a classe de implementação de UserDetailsService, poderá criar um novo bean para isso, como no exemplo a seguir:
@Bean
public AuthenticationManager authenticationManager(HttpSecurity http,
PasswordEncoder passwordEncoder,
UsuarioService userDetailsService) throws Exception {
return http.getSharedObject(AuthenticationManagerBuilder.class)
.userDetailsService(userDetailsService)
.passwordEncoder(passwordEncoder)
.and()
.build();
}
E quando uso Thymeleaf como template de páginas
Se você sua o Thymeleaf será necessário alterar no arquivo pom.xml a dependência de integração entre o Thymeleaf e o Spring Security. A nova dependência passou a ser:
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity6</artifactId>
</dependency>
Conclusão
Vimos que as alterações do Spring Security 5 para o Spring Security 6 não foram tão sucintas, mas nada que afugente o programador de continuar usando esse excelente módulo de segurança do Spring Framework.
Se tiver algo novo a compartilhar sobre o tema, não deixe de postar no sistema de comentários abaixo.
Referencias:
- https://docs.spring.io/spring-security/reference/index.html
- https://docs.spring.io/spring-security/reference/migration/index.html