弹簧。使用JWT的自定义身份验证

我认为,在本文中,我将分享使用JWT在REST API中编写用于用户身份验证的自行车的成功经验。

它不能替代Spring Security,但是在生产中已经有两年多的历史了。



我将尝试尽可能详细地描述整个过程,从生成JWT的密钥到控制器,以便即使是不熟悉JWT的人也可以理解所有内容。







内容



  • 背景
  • 密钥生成
  • 春季项目创建
  • 令牌处理器
  • 注释和处理程序
  • 处理AuthenticationException
  • 控制者


0.背景



首先,我想告诉您什么正是促使我实施这种客户端身份验证方法的原因,以及为什么我不使用Spring Security。如果您不感兴趣,则可以跳到下一章。



那时,我在一家开发网站的小公司工作。这是我在这方面的第一份工作,所以我什么都不知道。经过大约一个月的工作,他们说将会有一个新项目,有必要为其准备基本功能。我决定更详细地了解如何在现有项目中实施此过程。令我遗憾的是,那里的一切都不尽如人意。



在控制器的每种方法中,有必要拉出授权用户,都有类似以下内容



@RequestMapping(value = "/endpoint", method = RequestMethod.GET)
 public Response endpoint() {
     User user = getUser(); //   
     if (null == user)
         return new ErrorResponse.Builder(Error.AUTHENTICATION_ERROR).build();

     //  
 }


因此无处不在...添加新端点始于复制了这段代码。我发现这是一个有点陌生,完全不好用。



为了解决这个问题,我去了谷歌。也许我在寻找问题,但找不到合适的解决方案。有关配置Spring Security的说明无处不在。



让我解释一下为什么我不想使用Spring Security。在我看来,它似乎太复杂了,以某种方式在REST中使用它并不是很方便。在端点处理方法中,您可能仍然必须使用户脱离上下文。也许我错了,因为我对此了解不多,但是无论如何,这篇文章并不是关于这一点的。



我需要一些简单易用的东西。这个想法是通过注释来实现的。



我们的想法是将用户注入需要授权的控制器的每种方法中。就这样。事实证明,在controller方法内已经有一个授权用户,并且它将为!= Null(除非不需要授权的情况除外)。



我们找出了制造这款自行车的原因。现在让我们开始练习。



1.密钥生成



首先,我们需要生成一个密钥,该密钥将加密有关用户的最少必需信息。



有一个非常方便的,可以使用jwt在Java中工作



github上有关于如何使用jwt的所有说明,但是为了简化过程,我将在下面给出一个示例。



要生成密钥,请创建一个常规的Maven项目并添加以下依赖项



依存关系
<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>




而将产生秘密的类



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);
    }
}




结果,我们获得了一个秘密密钥,我们将在将来使用它。



2.创建一个Spring项目



我不会描述创建过程,因为有很多关于此主题的文章和教程。在Spring官方网站上有一个Initializer,您可以单击两次以创建一个最小的项目。



我只会留下最后的pom文件



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>




创建项目后,将先前创建的密钥复制到application.properties



app.api.jwtEncodedSecretKey=teTN1EmB5XADI5iV4daGVAQhBlTwLMAE+LlXZp1JPI2PoQOpgVksRqe79EGOc5opg+AmxOOmyk8q1RbfSWcOyg==


3.令牌处理器



我们将需要一个用于生成和解密令牌的服务。



令牌将包含有关用户的最少信息(仅用户ID)和令牌到期时间。为此,我们将创建接口。



转移令牌的生命周期。



Expiration.java
package org.website.jwt;

import java.time.LocalDateTime;
import java.util.Optional;

public interface Expiration {

    Optional<LocalDateTime> getAuthTokenExpire();
}




并用于传输ID。它将由用户实体实现



CreateBy.java
package org.website.jwt;

public interface CreateBy {

    Long getId();
}




我们还将为Expiration接口创建一个默认实现默认情况下,令牌将保留24小时。



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));
    }
}




让我们添加几个辅助类。



GeneratedTokenInfo-有关生成的令牌的信息。

TokenInfo-用于获取有关我们的令牌的信息。



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;
    }
}




现在是TokenHandler本身它将在用户授权后生成令牌,并检索有关先前授权用户所使用的令牌的信息。



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();
    }

}




我不会引起您的注意,因为与此相关的所有事情都应该清楚。



4.注释和处理程序



因此,在完成所有准备工作之后,让我们继续进行最有趣的事情。如前所述,我们需要一个注解,该注解将被注入到需要授权用户的控制器方法中。



使用以下代码创建注释



AuthUser.java
package org.website.annotation;

import java.lang.annotation.*;

@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface AuthUser {
    boolean required() default true;
}




早先有人说授权可能是可选的。为此,我们需要摘要中要求的方法如果对特定方法的授权是可选的,并且如果传入用户确实未被授权,则将null注入该方法但是我们将为此做好准备。



注释已创建,但是仍然需要一个处理程序,该处理程序将从请求中检索令牌,从用户库接收令牌并将其传递给controller方法。对于这种情况,Spring具有HandlerMethodArgumentResolver接口我们将执行它。



创建实现上述接口AuthUserHandlerMethodArgumentResolver



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));
    }
}




在构造函数中,我们接受cookie的名称以及可以在其中传递令牌的标头。我在application.properties中将它们取出



app.api.tokenKeyName=Auth-Token
app.api.tokenHeaderName=Auth-Token


先前创建的TokenHandlerUserJwtSignatureService也将在构造函数中传递



我们将不考虑UserJwtSignatureService,因为通过ID和令牌签名从数据库中对用户进行了标准提取。



但是,让我们更详细地分析处理程序本身的代码。



supportsParameter-检查方法是否满足要求。



resolveArgument是主要方法,在其中所有“魔术”都会发生。



所以这是怎么回事:



  1. 我们从注释中获得了必填字段值
  2. HttpServletRequest
  3. ,
  4. required, , .

    , , ( , ).

    , , , .
  5. , required, , null


注释处理器已创建。但这还不是全部。需要注册才能让Spring知道。这里的一切都很简单。创建一个实现Spring的WebMvcConfigurer接口的配置文件,并覆盖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));
    }
}




这样就完成了注释的编写。



5.处理AuthenticationException



在上一节中,在注释处理程序中,如果controller方法需要授权,但未授权用户,则抛出AuthenticationException



现在,我们需要添加此异常的类并对其进行处理,以便将json和所需的信息返回给用户。



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));
    }
}




现在是异常处理程序本身。为了处理已发生的异常并为用户提供一些标准的Spring错误页面(而非我们需要的json),Spring提供了ControllerAdvice批注



让我们添加一个用于处理执行的类。



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();
    }
}




现在,如果抛出AuthenticationException,它将被捕获,并且将向用户返回带有AUTHENTICATION_ERROR错误的json



6.控制器



现在,实际上是为了一切而开始。让我们用3种方法创建一个控制器:



  1. 强制授权
  2. 随着强制性认证
  3. 注册新用户。最少的代码。它只是将用户保存到数据库,没有密码。这也将返回新用户的令牌


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();
    }
}




requirednotRequired方法中,我们插入注释。

在第一种情况下,如果未授权用户,则应返回错误的json,如果已授权,则将返回有关用户的信息。



在第二种情况下,如果用户未登录,则消息Hello guest!,如果获得授权,将返回其名称。

让我们检查一下是否一切正常。



首先,让我们以未授权用户的身份检查这两种方法。



/必填




/不需要




一切都如预期。需要授权的地方,将返回错误,在第二种情况下,将显示消息Hello guest!...



现在让我们注册并尝试调用相同的方法,只是在请求标头中传递令牌。



/ 注册




响应返回一个令牌,该令牌可用于需要授权的那些请求。



让我们检查一下:



/必填




/不需要




在第一种情况下,仅返回有关用户的信息。在第二种情况下,将返回欢迎消息。



加工!



7.结论



此方法并不声称是唯一正确的解决方案。有人可能更喜欢使用Spring Security。但是,正如一开始提到的那样,该方法已被证明,易于使用且效果很好。



All Articles