Nem todos os endpoints de uma aplicação precisam de validação JWT prévia. Para isso, criamos uma anotação personalizada @JwtToken que marca os métodos que exigem verificação do token. Exemplo:
@GetMapping("/fazerAlgo")
// Adiciona a anotação personalizada @JwtToken
// Antes de executar o método do Controller, é feita a validação JWT
// Se a validação passar, o método é executado
// Caso contrário, retorna erro de autorização
@JwtToken
public ResponseObject fazerAlgo(){
return new ResponseObject("...");
}
Pasos de desenvolvimento
1. Adicionar dependências JJWT no pom.xml
<!--JJWT-->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.11.2</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.11.2</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId> <!-- ou jjwt-gson -->
<version>0.11.2</version>
<scope>runtime</scope>
</dependency>
2. Configurar chave secreta no application.yml
# Chave secreta para JWT, deve coincidir com o serviço de autenticação
app:
secretKey: 1234567890-1234567890-1234567890
3. Criar anotação personalizada @JwtToken
Esta anotação só pode ser aplciada a métodos.
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface JwtToken {
// required = true significa que o token é obrigatório; se ausente, ocorre erro
// required = false indica que o token não é obrigatório; a requisição continua para a lógica de negócio
boolean required() default true;
}
4. Adiiconar endpoint de teste no ArticleController
@JwtToken // aplica validação JWT em dosth
@GetMapping("/dosth")
public ResponseObject dosth() {
return new ResponseObject("Processamento concluído com sucesso");
}
5. Criar TokenInterceptor para interceptar e validar JWT
/**
* Antes de executar o método alvo da URI, verifica o JWT no cabeçalho da requisição.
* Se válido, executa o método; caso contrário, retorna erro.
*/
public class TokenInteceptor implements HandlerInterceptor {
@Value("${app.secretKey}")
private String chaveApp = null;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("TokenInterceptor.preHandle()");
// Se não for uma chamada de método, permite a passagem
if (!(handler instanceof HandlerMethod)) {
return true;
}
HandlerMethod handlerMethod = (HandlerMethod) handler;
Method method = handlerMethod.getMethod();
response.setContentType("text/json;charset=utf-8");
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
// Verifica se o método possui a anotação @JwtToken
if (method.isAnnotationPresent(JwtToken.class)) {
String token = request.getHeader("token");
JwtToken jwtToken = method.getAnnotation(JwtToken.class);
// Token ausente
if (token == null) {
if (jwtToken.required()) {
response.setStatus(401);
ResponseObject<Object> responseObject = new ResponseObject<>("SecurityException", "Token ausente, verifique o cabeçalho da requisição");
response.getWriter().println(objectMapper.writeValueAsString(responseObject));
return false;
}
} else {
// Token presente, faz a validação JWT
String base64Key = new BASE64Encoder().encode(chaveApp.getBytes());
SecretKey key = Keys.hmacShaKeyFor(base64Key.getBytes());
try {
Jws<Claims> claimsJws = Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token);
String userJson = claimsJws.getBody().getSubject();
User user = objectMapper.readValue(userJson, User.class);
request.setAttribute("$usuario", user); // salva o usuário logado no atributo da requisição
return true;
} catch (JsonProcessingException e) {
e.printStackTrace();
response.setStatus(500);
ResponseObject<Object> responseObject = new ResponseObject<>(e.getClass().getSimpleName(), e.getMessage());
response.getWriter().println(objectMapper.writeValueAsString(responseObject));
return false;
} catch (JwtException e) {
e.printStackTrace();
response.setStatus(401);
ResponseObject<Object> responseObject = new ResponseObject<>(e.getClass().getSimpleName(), e.getMessage());
response.getWriter().println(objectMapper.writeValueAsString(responseObject));
return false;
}
}
}
return true;
}
}
6. Iniciar a aplicação e testar
Acesse http://localhost:8100/dosth.
7. Vincular dados do usuário extraídos do token à lógica de negócio
Quando o token é validado, o TokenInterceptor coloca o objeto User no atributo $usuario da requisição. No método list, podemos recuperá-lo com @RequestAttribute.
/**
* Usuários VIP podem ver todos os artigos (normais e selecionados)
* Usuários normais veem apenas artigos comuns
*/
@GetMapping("/list")
@JwtToken(required = false)
public ResponseObject list(@RequestAttribute(value = "$usuario", required = false) User usuario) {
System.out.println(usuario);
return new ResponseObject("0", "success", articleService.list(usuario));
}
No ArticleService, a lógica de negócio filtra de acordo com o nível do usuário:
public List<Article> list(User usuario){
int nivel = 0;
if(usuario == null || usuario.getGrade().equals("normal")){
nivel = 1;
}else if(usuario.getGrade().equals("vip")){
nivel = 2;
}
List<Article> list = articleMapper.list(nivel);
for(Article article : list){
ResponseObject<Video> videoResponseObject = videoFeignClient.findByArticleId(article.getArticleId());
article.setVideo(videoResponseObject.getData());
}
return list;
}
O ArticleMapper é modificado para aceitar o parâmetro de nível:
@Mapper
public interface ArticleMapper {
@Select("select * from article where article_type <= #{valor} order by create_time desc")
public List<Article> list(int valor);
}
Com isso, a lógica de negócio está finalizada:
- Usuários não logados ou comuns veem apenas artigos de
ArticleType = 1. - Usuários VIP podem ver todos os artigos.