Autenticação JWT com níveis de acesso usando Spring Security

Odilio Noronha
RapaduraTech
Published in
4 min readOct 18, 2020

Este artigo é uma evolução do Criando um token JWT para autenticação em Spring boot, lá criamos um projeto que gera um token JWT e valida esse token, mas não possuíamos níveis de acesso com roles, que é o que vamos criar agora nesse artigo. Iremos criar as roles ADMIN e USER.

Não irei postar as configurações básicas, como maven, porque são as mesmas do outro projeto.

Por padrão, Spring Security adiciona um filtro nos filtros do Spring, que é capaz de persistir o Contexto de Segurança (classe SecurityContextPersistenceFilter). Para definir a autenticação na request e torná-la disponível para todas as solicitações do cliente, precisamos definir manualmente o SecurityContext que contém a autenticação na sessão HTTP:

SecurityContext sc = SecurityContextHolder.getContext();
sc.setAuthentication(auth);
HttpSession session = req.getSession(true);
session.setAttribute(SPRING_SECURITY_CONTEXT_KEY, sc);

Mas ainda não temos o que fazer com essa autenticação, precisamos criar regras de proteção para nossos controllers.

Anotações de para segurança de métodos

Para isso o Spring possui algumas anotations que facilitam e muito a nossa vida, para usa-las, primeiro precisamos habilitar nossa aplicação, para isso usaremos o @EnableGlobalMethodSecurity.

@EnableGlobalMethodSecurity(
prePostEnabled = true,
securedEnabled = true,
jsr250Enabled = true)

  • A propriedade prePostEnabled ativa as anotações pré / pós Spring Security
  • A propriedade secureEnabled determina se a anotação @Secured deve ser habilitada
  • A propriedade jsr250Enabled nos permite usar a anotação @RoleAllowed

A anotação @Secured é usada para especificar uma lista de funções em um método. Portanto, um usuário só pode acessar esse método se tiver pelo menos uma das funções especificadas.

Vamos definir um método de teste

@Secured(“ADMIN”)
@RequestMapping(value=”/users”, method = RequestMethod.GET)
public List<User> listUser(){
return userService.findAll();
}

A anotação @RoleAllowed é a anotação equivalente do JSR-250 da anotação @Secured.

Basicamente, podemos usar a anotação @RoleAllowed de maneira semelhante a @Secured. Assim, poderíamos redefinir o método listUser:

@RoleAllowed(“ADMIN”)
@RequestMapping(value=”/users”, method = RequestMethod.GET)
public List<User> listUser(){
return userService.findAll();
}

As anotações @PreAuthorize e @PostAuthorize fornecem controle de acesso baseado em expressão. Portanto, podemos escrever usando SpEL (Spring Expression Language).

A anotação @PreAuthorize verifica a expressão dada antes de inserir o método, enquanto a anotação @PostAuthorize verifica após a execução do método e pode alterar o resultado.

Agora vamos ver a implementação dele em nosso método

//@PreAuthorize(“hasAnyRole(‘USER’, ‘ADMIN’)”)
//@PreAuthorize(“hasRole(‘(‘USER’) or hasRole(‘ADMIN’)”)
@PreAuthorize(“hasRole(‘ADMIN’)”)
@RequestMapping(value=”/users”, method = RequestMethod.GET)
public List<User> listUser(){
return userService.findAll();
}

Além disso, a anotação @PostAuthorize fornece a capacidade de acessar o resultado do método:

@PostAuthorize
(“returnObject.username == authentication.principal.nickName”)

Se estivermos usando a mesma anotação de segurança para todos os métodos de uma classe, podemos considerar colocar essa anotação no nível da classe:

@Controller
@PreAuthorize(“hasRole(‘ROLE_ADMIN’)”)
public class UserController{

Certo, temos como setar o contexto com a autenticação e temos os métodos que fazem a proteção dos nossos controllers, então precisamos do cara que pode setar estas roles para autorizar os acessos.

Para isso temos o GrantedAuthority.

Podemos pensar em cada papel como um GrantedAuthority que é representado como uma string e prefixado com “ROLE”. Ao usar uma função diretamente, por exemplo, por meio de uma expressão como hasRole (“ADMIN”), estamos restringindo o acesso. É importante notar que o prefixo “ROLE” padrão é configurável.

GrantedAuthority admin = new SimpleGrantedAuthority(“ROLE_ADMIN”);
GrantedAuthority editor = new SimpleGrantedAuthority(“ROLE_EDITOR”);
GrantedAuthority viewer = new SimpleGrantedAuthority(“ROLE_VIEWER”);

Em nosso exemplo o banco já está setado com alguns usuarios e roles definidas, então só precisamos obeter e setar essas roles.

List<GrantedAuthority> authorities = user.getRoles().stream().map(role ->
new SimpleGrantedAuthority(role.getName())
).collect(Collectors.toList());

CustomUserDetailsService

Para autenticar um usuário ou realizar várias verificações baseadas em funções, o Spring precisa carregar os dados dos usuários de alguma forma.

Para isso, temos uma interface chamada UserDetailsService que possui um único método que carrega um usuário com base no nome de usuário.

UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;

Definiremos um UserServiceImpl que implementa a interface UserDetailsService e fornece a implementação para o método loadUserByUsername ().

Observe que, o método loadUserByUsername () retorna um objeto UserDetails que o Spring Security usa para realizar várias autenticações e validações.

Agora que temos todas as partes necessárias podemos efetivamente construir o nosso token,

return Jwts.builder()
.setSubject(Long.toString(userPrincipal.getId()))
.setIssuedAt(new Date())
.setExpiration(expiryDate)
.signWith(SignatureAlgorithm.HS512, jwtSecret)
.compact();

O retorno desse método é a String contendo o nosso token com as permissões setadas.

Deixarei o projeto completo no git com instruções de como testar e as collections do postman prontas, num próximo artigo irei utilizar essa autenticação em uma aplicação com angular 10.

--

--