3
respostas

07 Autenticando o usuário

A maneira abordada fere totalmente o propósito de JWT, que é ser auto contido: diz quem você é e o que você pode fazer. No token, poderia vir informações RBAC ou mais granulares (geradas no processo de autenticação). Fazer a consulta de um usuário não é o correto para esse tipo de requisito. Uma vez que o token carrega as informações de quem e o que, bastaria validar se o token está assinado e não-expirado.

3 respostas

Olá, Renato!

Entendo sua preocupação em relação ao uso do JWT de forma que ele seja autocontido, evitando consultas adicionais ao banco de dados para verificar as permissões do usuário. Você está correto ao dizer que um dos propósitos do JWT é carregar informações suficientes para que o servidor possa validar o usuário e suas permissões sem a necessidade de consultas adicionais.

Uma alternativa para tornar o token mais autocontido seria incluir as permissões do usuário (roles ou claims) diretamente no token durante o processo de autenticação. Isso poderia ser feito modificando o serviço que gera o token para incluir essas informações. Aqui está um exemplo de como você poderia modificar a geração do token para incluir as permissões do usuário:

public String gerarToken(Usuario usuario) {
    try {
        var algoritmo = Algorithm.HMAC256(secret);
        return JWT.create()
                .withIssuer("API Voll.med")
                .withSubject(usuario.getLogin())
                .withExpiresAt(dataExpiracao())
                .claim("authorities", usuario.getAuthorities().stream().map(authority -> new SimpleGrantedAuthority(authority.getAuthority())).collect(Collectors.toList()))
                .sign(algoritmo);
    } catch (JWTCreationException exception){
        throw new RuntimeException("erro ao gerar token jwt", exception);
    }
}

E então, no filtro de segurança, você poderia extrair essas permissões diretamente do token, sem a necessidade de consultar o banco de dados:

@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
    throws ServletException, IOException {
    var token = recuperarToken(request);
    if (token != null && tokenService.validateToken(token)) {
        var claims = tokenService.getAllClaimsFromToken(token);
        var authorities = claims.get("authorities", List.class);
        var authentication = new UsernamePasswordAuthenticationToken(claims.getSubject(), null, authorities);
        SecurityContextHolder.getContext().setAuthentication(authentication);
    }
    
    filterChain.doFilter(request, response);
}

Essas mudanças ajudariam a tornar o uso do JWT mais alinhado com sua função de ser autocontido, reduzindo a dependência de consultas ao banco de dados para cada requisição.

É importante reconhecer o trade-off envolvido nessa abordagem. Ao incluir as permissões do usuário diretamente no token JWT durante o processo de autenticação, você está tornando o token mais autocontido e reduzindo a necessidade de consultas adicionais ao banco de dados a cada requisição. Isso pode levar a uma melhoria significativa no desempenho e escalabilidade do sistema, especialmente em cenários de alta concorrência e grande volume de requisições.

No entanto, como você mencionou, uma das consequências desse método é que o token JWT mantém as permissões do usuário até sua expiração, mesmo que essas permissões sejam alteradas no banco de dados antes disso. Isso significa que o token pode não refletir imediatamente as permissões atualizadas do usuário.

Essa falta de sincronização em tempo real entre as permissões do usuário no banco de dados e as permissões contidas no token pode ser considerada um trade-off em relação à consistência em tempo real dos dados de permissão. Dependendo dos requisitos de segurança e da natureza das permissões do sistema, essa falta de sincronização pode ou não ser um problema significativo.

Espero ter ajudado e bons estudos!

Entre as duas abordagem qual seria a melhor?

Oi Claudinei!

Não tem uma maneira melhor ou pior. Ambas possuem vantagens e desvantagens e cabe avaliar qual é mais apropriada para a necessidade da sua aplicação.

A estratégia de utilizar um token autocontido tem como vantagem evitar o select no banco de dados para buscar os dados do usuário a cada requisição, mas como desvantagem o risco do token possuir dados desatualizados. Dá para contornar esse problema criando uma lista de tokens bloqueados, que guarda os tokens dos usuários que tiveram as informações atualizadas e no filter colocar uma lógica para checar se o token da requisição está nessa lista e bloquear a requisição. Um trabalho extra, mas não muito complexo.

Bons estudos!