Não é um substituto para o Spring Security, mas tem estado bem em produção por mais de dois anos.
Tentarei descrever todo o processo com o máximo de detalhes possível, desde a geração de uma chave para um JWT até um controlador, de forma que mesmo alguém não familiarizado com o JWT possa entender tudo.

Conteúdo
- fundo
- Geração de chave
- Criação de projeto de primavera
- TokenHandler
- Anotação e manipulador
- Manipulando AuthenticationException
- Controlador
0. Antecedentes
Para começar, quero dizer exatamente o que me levou a implementar esse método de autenticação de cliente e por que não usei Spring Security. Quem não estiver interessado pode pular para o próximo capítulo.
Naquela época, eu trabalhava em uma pequena empresa que desenvolve sites. Esse era meu primeiro trabalho nessa área, então eu realmente não sabia de nada. Após cerca de um mês de trabalho, disseram que haveria um novo projeto e que era necessário preparar as funcionalidades básicas para ele. Decidi ver com mais detalhes como esse processo foi implementado nos projetos existentes. Para minha tristeza, nem tudo foi tão feliz ali.
Em cada método do controlador, onde era necessário retirar o usuário autorizado, havia algo como o seguinte
@RequestMapping(value = "/endpoint", method = RequestMethod.GET)
public Response endpoint() {
User user = getUser(); //
if (null == user)
return new ErrorResponse.Builder(Error.AUTHENTICATION_ERROR).build();
//
}
E assim estava em todos os lugares ... Adicionar um novo endpoint começou com o fato de que este pedaço de código foi copiado. Achei um
Para resolver esse problema, fui ao google. Talvez estivesse procurando algo errado, mas não consegui encontrar uma solução adequada. As instruções para configurar o Spring Security estavam em toda parte.
Deixe-me explicar por que eu não queria usar o Spring Security. Pareceu-me muito complicado e, de alguma forma, não muito conveniente para usá-lo em REST. E nos métodos de processamento de endpoint, você provavelmente ainda terá que tirar o usuário do contexto. Talvez eu esteja errado, já que não sabia muito sobre isso, mas o artigo não é sobre isso de qualquer maneira.
Eu precisava de algo simples e fácil de usar. A ideia surgiu por meio de anotação.
A ideia é que injetemos nosso usuário em cada método do controlador onde a autorização é necessária. E isso é tudo. Acontece que dentro do método do controlador já haverá um usuário autorizado e será ! = Nulo (exceto nos casos em que a autorização não é necessária).
Nós descobrimos as razões para criar esta bicicleta. Agora vamos começar a praticar.
1. Geração de chave
Primeiro, precisamos gerar uma chave que criptografará as informações mínimas necessárias sobre o usuário.
Existe uma biblioteca muito conveniente para trabalhar em java com jwt .
O github contém todas as instruções de como trabalhar com o jwt, mas para simplificar o processo, darei um exemplo abaixo.
Para gerar a chave, crie um projeto maven regular e adicione as seguintes dependências
dependências
<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>
<version>0.11.2</version>
<scope>runtime</scope>
</dependency>
E a aula que vai gerar o segredo
SecretGenerator.java
package jwt;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.io.Encoders;
import io.jsonwebtoken.security.Keys;
import javax.crypto.SecretKey;
public class SecretGenerator {
public static void main(String[] args) {
SecretKey secretKey = Keys.secretKeyFor(SignatureAlgorithm.HS512);
String secretString = Encoders.BASE64.encode(secretKey.getEncoded());
System.out.println(secretString);
}
}
Como resultado, obtemos uma chave secreta, que usaremos no futuro.
2. Criação de um projeto Spring
Não vou descrever o processo de criação, pois existem muitos artigos e tutoriais sobre este assunto. E no site oficial do Spring há um inicializador , onde você pode criar um projeto mínimo em dois cliques.
Vou deixar apenas o arquivo pom final
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.2.RELEASE</version>
</parent>
<groupId>org.website</groupId>
<artifactId>backend</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<properties>
<java.version>14</java.version>
<start-class>org.website.BackendWebsiteApplication</start-class>
</properties>
<profiles>
<profile>
<id>local</id>
<properties>
<activatedProperties>local</activatedProperties>
</properties>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
</profile>
</profiles>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
<dependencies>
<!--*******SPRING*******-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!--*******JWT*******-->
<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>
<version>0.11.2</version>
<scope>runtime</scope>
</dependency>
<!--*******OTHER*******-->
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>42.2.14</version>
</dependency>
<dependency>
<groupId>org.liquibase</groupId>
<artifactId>liquibase-core</artifactId>
<version>4.0.0</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>29.0-jre</version>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.6</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.11</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.12</version>
<scope>provided</scope>
</dependency>
<!--*******TEST*******-->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest</artifactId>
<version>2.2</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
Depois de criar o projeto, copie a chave criada anteriormente para application.properties
app.api.jwtEncodedSecretKey=teTN1EmB5XADI5iV4daGVAQhBlTwLMAE+LlXZp1JPI2PoQOpgVksRqe79EGOc5opg+AmxOOmyk8q1RbfSWcOyg==
3. TokenHandler
Precisaremos de um serviço para gerar e descriptografar tokens.
O token conterá um mínimo de informações sobre o usuário (apenas seu id) e o tempo de expiração do token. Para fazer isso, vamos criar interfaces.
Para transferir a vida útil do token.
Expiration.java
package org.website.jwt;
import java.time.LocalDateTime;
import java.util.Optional;
public interface Expiration {
Optional<LocalDateTime> getAuthTokenExpire();
}
E para transferência de ID. Será implementado pela entidade usuária
CreateBy.java
package org.website.jwt;
public interface CreateBy {
Long getId();
}
Também criaremos uma implementação padrão para a interface de Expiração . Por padrão, o token viverá por 24 horas.
DefaultExpiration.java
package org.website.jwt;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
import java.util.Optional;
@Component
public class DefaultExpiration implements Expiration {
@Override
public Optional<LocalDateTime> getAuthTokenExpire() {
return Optional.of(LocalDateTime.now().plusHours(24));
}
}
Vamos adicionar algumas classes auxiliares.
GeneratedTokenInfo - para obter informações sobre o token gerado.
TokenInfo - para obter informações sobre o token que chegou até nós.
GeneratedTokenInfo.java
package org.website.jwt;
import java.time.LocalDateTime;
import java.util.Optional;
public class GeneratedTokenInfo {
private final String token;
private final LocalDateTime expiration;
public GeneratedTokenInfo(String token, LocalDateTime expiration) {
this.token = token;
this.expiration = expiration;
}
public String getToken() {
return token;
}
public LocalDateTime getExpiration() {
return expiration;
}
public Optional<String> getSignature() {
if (null != this.token && this.token.length() >= 3)
return Optional.of(this.token.split("\\.")[2]);
return Optional.empty();
}
}
TokenInfo.java
package org.website.jwt;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jws;
import lombok.NonNull;
import java.time.LocalDateTime;
import java.time.ZoneId;
public class TokenInfo {
private final Jws<Claims> claimsJws;
private final String signature;
private final Claims body;
private final Long userId;
private final LocalDateTime expiration;
private TokenInfo() {
throw new UnsupportedOperationException();
}
private TokenInfo(@NonNull final Jws<Claims> claimsJws,
@NonNull final String signature,
@NonNull final Claims body,
@NonNull final Long userId,
@NonNull final LocalDateTime expiration) {
this.claimsJws = claimsJws;
this.signature = signature;
this.body = body;
this.userId = userId;
this.expiration = expiration;
}
public static TokenInfo fromClaimsJws(@NonNull final Jws<Claims> claimsJws) {
final Claims body = claimsJws.getBody();
return new TokenInfo(
claimsJws,
claimsJws.getSignature(),
body,
Long.parseLong(body.getId()),
body.getExpiration().toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime());
}
public Jws<Claims> getClaimsJws() {
return claimsJws;
}
public String getSignature() {
return signature;
}
public Claims getBody() {
return body;
}
public Long getUserId() {
return userId;
}
public LocalDateTime getExpiration() {
return expiration;
}
}
Agora, o próprio TokenHandler . Ele irá gerar um token mediante autorização do usuário, bem como recuperar informações sobre o token com o qual o usuário autorizado anteriormente veio.
TokenHandler.java
package org.website.jwt;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jws;
import io.jsonwebtoken.Jwts;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.sql.Date;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.Base64;
import java.util.Optional;
@Service
@Slf4j
public class TokenHandler {
@Value("${app.api.jwtEncodedSecretKey}")
private String jwtEncodedSecretKey;
private final DefaultExpiration defaultExpiration;
private SecretKey secretKey;
@Autowired
public TokenHandler(final DefaultExpiration defaultExpiration) {
this.defaultExpiration = defaultExpiration;
}
@PostConstruct
private void postConstruct() {
byte[] decode = Base64.getDecoder().decode(jwtEncodedSecretKey);
this.secretKey = new SecretKeySpec(decode, 0, decode.length, "HmacSHA512");
}
public Optional<GeneratedTokenInfo> generateToken(CreateBy createBy, Expiration expire) {
if (null == expire || expire.getAuthTokenExpire().isEmpty())
expire = this.defaultExpiration;
try {
final LocalDateTime expireDateTime = expire.getAuthTokenExpire().get().withNano(0);
String compact = Jwts.builder()
.setId(String.valueOf(createBy.getId()))
.setExpiration(Date.from(expireDateTime.atZone(ZoneId.systemDefault()).toInstant()))
.signWith(this.secretKey)
.compact();
return Optional.of(new GeneratedTokenInfo(compact, expireDateTime));
} catch (Exception e) {
log.error("Error generate new token. CreateByID: {}; Message: {}", createBy.getId(), e.getMessage());
}
return Optional.empty();
}
public Optional<GeneratedTokenInfo> generateToken(CreateBy createBy) {
return this.generateToken(createBy, this.defaultExpiration);
}
public Optional<TokenInfo> extractTokenInfo(final String token) {
try {
Jws<Claims> claimsJws = Jwts.parserBuilder()
.setSigningKey(this.secretKey)
.build()
.parseClaimsJws(token);
return Optional.ofNullable(claimsJws).map(TokenInfo::fromClaimsJws);
} catch (Exception e) {
log.error("Error extract token info. Message: {}", e.getMessage());
}
return Optional.empty();
}
}
Não vou chamar a sua atenção, pois tudo deve ficar claro com isso.
4. Anotação e manipulador
Então, depois de todo o trabalho preparatório, vamos passar para o mais interessante. Conforme mencionado anteriormente, precisamos de uma anotação que será injetada nos métodos do controlador, onde um usuário autorizado é necessário.
Crie uma anotação com o seguinte código
AuthUser.java
package org.website.annotation;
import java.lang.annotation.*;
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface AuthUser {
boolean required() default true;
}
Foi dito anteriormente que a autorização pode ser opcional. Só para isso e precisamos de um método exigido no resumo. Se a autorização para um método específico for opcional e se o usuário de entrada não estiver realmente autorizado, null será injetado no método . Mas estaremos prontos para isso.
A anotação foi criada, mas ainda é necessário um manipulador , que irá recuperar um token da solicitação, recebê-lo da base de usuários e passá-lo para o método controlador. Para tais casos, Spring possui uma interface HandlerMethodArgumentResolver . Vamos implementá-lo.
Crie a classe AuthUserHandlerMethodArgumentResolver que implementa a interface acima.
AuthUserHandlerMethodArgumentResolver.java
package org.website.annotation.handler;
import org.springframework.core.MethodParameter;
import org.springframework.lang.NonNull;
import org.springframework.web.bind.support.WebArgumentResolver;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;
import org.springframework.web.util.WebUtils;
import org.website.annotation.AuthUser;
import org.website.annotation.exception.AuthenticationException;
import org.website.domain.User;
import org.website.domain.UserJwtSignature;
import org.website.jwt.TokenHandler;
import org.website.service.repository.UserJwtSignatureService;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import java.util.Objects;
import java.util.Optional;
public class AuthUserHandlerMethodArgumentResolver implements HandlerMethodArgumentResolver {
private final String AUTH_COOKIE_NAME;
private final String AUTH_HEADER_NAME;
private final TokenHandler tokenHandler;
private final UserJwtSignatureService userJwtSignatureService;
public AuthUserHandlerMethodArgumentResolver(final String authTokenCookieName,
final String authTokenHeaderName,
final TokenHandler tokenHandler,
final UserJwtSignatureService userJwtSignatureService) {
this.AUTH_COOKIE_NAME = authTokenCookieName;
this.AUTH_HEADER_NAME = authTokenHeaderName;
this.tokenHandler = tokenHandler;
this.userJwtSignatureService = userJwtSignatureService;
}
@Override
public boolean supportsParameter(@NonNull final MethodParameter methodParameter) {
return methodParameter.getParameterAnnotation(AuthUser.class) != null && methodParameter.getParameterType().equals(User.class);
}
@Override
public Object resolveArgument(@NonNull final MethodParameter methodParameter,
final ModelAndViewContainer modelAndViewContainer,
@NonNull final NativeWebRequest nativeWebRequest,
final WebDataBinderFactory webDataBinderFactory) throws Exception {
if (!this.supportsParameter(methodParameter))
return WebArgumentResolver.UNRESOLVED;
// required
final boolean required = Objects.requireNonNull(methodParameter.getParameterAnnotation(AuthUser.class)).required();
// HttpServletRequest
Optional<HttpServletRequest> httpServletRequestOptional = Optional.ofNullable(nativeWebRequest.getNativeRequest(HttpServletRequest.class));
//
Optional<UserJwtSignature> userJwtSignature =
this.extractAuthTokenFromRequest(nativeWebRequest, httpServletRequestOptional.orElse(null))
.flatMap(tokenHandler::extractTokenInfo)
.flatMap(userJwtSignatureService::extractByTokenInfo);
if (required) {
//
if (userJwtSignature.isEmpty() || null == userJwtSignature.get().getUser())
//
throw new AuthenticationException(httpServletRequestOptional.map(HttpServletRequest::getMethod).orElse(null),
httpServletRequestOptional.map(HttpServletRequest::getRequestURI).orElse(null));
final User user = userJwtSignature.get().getUser();
//
return this.appendCurrentSignature(user, userJwtSignature.get());
} else {
// , , null
return this.appendCurrentSignature(userJwtSignature.map(UserJwtSignature::getUser).orElse(null),
userJwtSignature.orElse(null));
}
}
private User appendCurrentSignature(User user, UserJwtSignature userJwtSignature) {
Optional.ofNullable(user).ifPresent(u -> u.setCurrentSignature(userJwtSignature));
return user;
}
private Optional<String> extractAuthTokenFromRequest(@NonNull final NativeWebRequest nativeWebRequest,
final HttpServletRequest httpServletRequest) {
return Optional.ofNullable(httpServletRequest)
.flatMap(this::extractAuthTokenFromRequestByCookie)
.or(() -> this.extractAuthTokenFromRequestByHeader(nativeWebRequest));
}
private Optional<String> extractAuthTokenFromRequestByCookie(final HttpServletRequest httpServletRequest) {
return Optional
.ofNullable(httpServletRequest)
.map(request -> WebUtils.getCookie(httpServletRequest, AUTH_COOKIE_NAME))
.map(Cookie::getValue);
}
private Optional<String> extractAuthTokenFromRequestByHeader(@NonNull final NativeWebRequest nativeWebRequest) {
return Optional.ofNullable(nativeWebRequest.getHeader(AUTH_HEADER_NAME));
}
}
No construtor, aceitamos os nomes do cookie e o cabeçalho no qual o token pode ser passado. Eu os tirei em application.properties
app.api.tokenKeyName=Auth-Token
app.api.tokenHeaderName=Auth-Token
O TokenHandler e UserJwtSignatureService criados anteriormente também são transmitidos no construtor .
Não consideraremos UserJwtSignatureService, pois há uma extração padrão de um usuário do banco de dados por seu id e assinatura de token.
Mas vamos analisar o código do próprio manipulador em mais detalhes.
SupportParameter - Verifica se o método atende aos requisitos exigidos.
resolveArgument é o método principal, dentro do qual toda a "mágica" acontece.
Então, o que está acontecendo aqui:
- Obtemos o valor do campo obrigatório de nossa anotação
- HttpServletRequest
- ,
- required, , .
, , ( , ).
, , , . - , required, , null
Um processador de anotação foi criado. Mas isso não é tudo. Ele precisa ser registrado no Spring para saber sobre isso. Tudo é simples aqui. Crie um arquivo de configuração que implemente a interface WebMvcConfigurer do Spring e substitua o método addArgumentResolvers
WebMvcConfig.java
package org.website.configuration;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.website.annotation.handler.AuthUserHandlerMethodArgumentResolver;
import org.website.jwt.TokenHandler;
import org.website.service.repository.UserJwtSignatureService;
import java.util.List;
@Configuration
@EnableWebMvc
public class WebMvcConfig implements WebMvcConfigurer {
@Value("${app.api.tokenKeyName}")
private String tokenKeyName;
@Value("${app.api.tokenHeaderName}")
private String tokenHeaderName;
private final TokenHandler tokenHandler;
private final UserJwtSignatureService userJwtSignatureService;
@Autowired
public WebMvcConfig(final TokenHandler tokenHandler,
final UserJwtSignatureService userJwtSignatureService) {
this.tokenHandler = tokenHandler;
this.userJwtSignatureService = userJwtSignatureService;
}
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
resolvers.add(new AuthUserHandlerMethodArgumentResolver(
this.tokenKeyName,
this.tokenHeaderName,
this.tokenHandler,
this.userJwtSignatureService));
}
}
Isso conclui a escrita da anotação.
5. Lidando com AuthenticationException
Na seção anterior, no manipulador de anotação, se a autorização for necessária para um método do controlador, mas o usuário não estiver autorizado, lançamos um AuthenticationException .
Agora precisamos adicionar a classe dessa exceção e tratá-la para retornar json ao usuário com as informações de que precisamos.
AuthenticationException.java
package org.website.annotation.exception;
public class AuthenticationException extends Exception {
public AuthenticationException(String requestMethod, String url) {
super(String.format("%s - %s", requestMethod, url));
}
}
E agora o próprio manipulador de exceções. Para lidar com as exceções que surgiram e fornecer ao usuário não uma página de erro padrão do Spring, mas o json de que precisamos, o Spring tem uma anotação ControllerAdvice .
Vamos adicionar uma classe para lidar com nossa execução.
AuthenticationExceptionControllerAdvice.java
package org.website.controller.exception.handler;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.website.annotation.exception.AuthenticationException;
import org.website.http.response.Error;
import org.website.http.response.ErrorResponse;
import org.website.http.response.Response;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletResponse;
@ControllerAdvice
@Slf4j
public class AuthenticationExceptionControllerAdvice extends AbstractControllerAdvice {
@Value("${app.api.tokenKeyName}")
private String tokenKeyName;
@ExceptionHandler({AuthenticationException.class})
public Response authenticationException(HttpServletResponse response) {
Cookie cookie = new Cookie(tokenKeyName, "");
cookie.setPath("/");
cookie.setMaxAge(0);
response.addCookie(cookie);
return new ErrorResponse.Builder(Error.AUTHENTICATION_ERROR).build();
}
}
Agora, se uma AuthenticationException for lançada , ela será capturada e um json será retornado ao usuário com um erro AUTHENTICATION_ERROR
6. Controlador
Agora, de fato, por causa do qual tudo foi iniciado. Vamos criar um controlador com 3 métodos:
- Autorização obrigatória
- Com nenhuma autorização obrigatória
- Registro de um novo usuário. Código mínimo. Ele apenas salva o usuário no banco de dados, sem senhas. Que também retornará o token do novo usuário
TestAuthController.java
package org.website.controller;
import com.google.gson.JsonObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.website.annotation.AuthUser;
import org.website.domain.User;
import org.website.http.response.Response;
import org.website.http.response.SuccessResponse;
import org.website.jwt.GeneratedTokenInfo;
import org.website.service.repository.UserJwtSignatureService;
import org.website.service.repository.UserService;
import java.util.Optional;
@RestController
@RequestMapping("/test-auth")
public class TestAuthController {
@Autowired
private UserService userService;
@Autowired
private UserJwtSignatureService userJwtSignatureService;
@RequestMapping(value = "/required", method = RequestMethod.GET)
public Response required(@AuthUser final User user) {
return new SuccessResponse.Builder(user).build();
}
@RequestMapping(value = "/not-required", method = RequestMethod.GET)
public Response notRequired(@AuthUser(required = false) final User user) {
JsonObject response = new JsonObject();
if (null == user) {
response.addProperty("message", "Hello guest!");
} else {
response.addProperty("message", "Hello " + user.getFirstName());
}
return new SuccessResponse.Builder(response).build();
}
@RequestMapping(value = "/sign-up", method = RequestMethod.GET)
public Response signUp(@RequestParam String firstName) {
User user = userService.save(User.builder().firstName(firstName).build());
Optional<GeneratedTokenInfo> generatedTokenInfoOptional =
userJwtSignatureService.generateNewTokenAndSaveToDb(user);
return new SuccessResponse.Builder(user)
.addPropertyToPayload("token", generatedTokenInfoOptional.get().getToken())
.build();
}
}
Nos métodos required e notRequired, inserimos nossa anotação.
No primeiro caso, se o usuário não estiver autorizado, json deve ser retornado com um erro e, se autorizado, serão retornadas informações sobre o usuário.
No segundo caso, se o usuário não estiver logado, a mensagem Hello guest! e, se autorizado, seu nome será retornado.
Vamos verificar se tudo realmente funciona.
Primeiro, vamos verificar os dois métodos como um usuário não autorizado.
/ requeridos

/ não requerido

Tudo está conforme o esperado. Onde a autorização foi necessária, um erro foi retornado e, no segundo caso, a mensagem Hello guest! ...
Agora vamos registrar e tentar chamar os mesmos métodos, mas com a transferência do token nos cabeçalhos da solicitação.
/ inscrever-se

A resposta retornou um token que pode ser usado para as solicitações em que a autorização é necessária.
Vamos verificar isso:
/ requeridos

/ não requerido

No primeiro caso, apenas informações sobre o usuário são retornadas. No segundo caso, uma mensagem de boas-vindas é retornada.
Trabalhando!
7. Conclusão
Este método não pretende ser a única solução correta. Alguém pode preferir usar Spring Security. Mas, como mencionado no início, este método é comprovado, fácil de usar e funciona muito bem.