(Transfer + share) JWT realizes user token verification/duck blood fans Java geek technology

JSON Web Token (JWT) is currently one of the most popular cross-domain authentication solutions. Today we will uncover its mystery!

1. The origin of the story

Speaking of JWT, let's talk about the traditional sessionauthentication-based solutions and bottlenecks.

The traditional sessioninteraction process is as follows:

When the browser sends a login request to the server, after the authentication is passed, the user information seesionwill be stored in, and then the server will generate one and sessionIdput it cookiein, and then return it to the browser.

When the browser sends the request again, it will cookieput it in the request header sessionIdand send the request data to the server.

The server can seesionobtain user information again , and the whole process is complete!

Usually at the server will set the seesionduration, such as 30 minutes of inactivity, the user will have to store information from the seesionremoval of the.

  •  
session.setMaxInactiveInterval(30 * 60);//30分钟没活动,自动移除

Meanwhile, the server can also seesionbe determined whether the current user has logged in, if blank indicates no log, a direct jump to the login page; if not empty, from the sessionacquired information, the user can proceed.

In a monolithic application, this kind of interaction is fine.

However, if the request volume of the application server becomes very large, and the request volume that a single server can support is limited, it is easy to slow down or slow down the request at this time OOM.

The solution is to either add configuration to a single server, or add a new server, to meet business needs through load balancing.

If it is to add configuration to a single server, the request volume will continue to increase, and it will still not be able to support business processing.

Obviously, adding new servers can achieve unlimited horizontal expansion.

However, after adding a new server, the difference between different servers sessionIdmay be Asuccessful . You may have successfully logged in on the server, and you can sessionget user information from the Bserver, but you can't find the sessioninformation on the server. This is definitely incomparable. Awkwardly, I had to log out to continue logging in. As a result, the Aserver sessionfailed to time out and was forced to log out to request to log in again after logging in. It was embarrassing to think about it~~

Faced with this situation, several bigwigs discussed together and came up with a tokenplan.

Connect each application to the in-memory database redis, and perform a certain algorithm encryption on the user information that has successfully logged in. The generated IDis called token, tokenand the user's information will be stored redis; when the user initiates the request again, tokenthere will be the request data one. And send it to the server. The server verifies tokenwhether it exists redis. If it exists, it means the verification is passed. If it does not exist, it tells the browser to jump to the login page and the process ends.

tokenThe solution ensures that the service is stateless, and all information is stored in the distributed cache. Based on distributed storage, it can scale horizontally to support high concurrency.

Of course, there is springbootalso a sessionsharing scheme now , and similar tokenschemes will be sessionstored redisin it. After a login is implemented in a cluster environment, each server can obtain user information.

Second, what is JWT

Above, we talked about sessionother tokensolutions. In a cluster environment, they all rely on a third-party cache database redisto share data.

Is there a solution that does not need to cache the database redisto realize the sharing of user information, so as to achieve the effect that can be seen everywhere in one login?

The answer is definitely there, which is what we are going to introduce today JWT!

JWTIn full JSON Web Token, the implementation process simply means that after the user logs in successfully, the user's information is encrypted, and then one is generated and tokenreturned to the client, which is sessionnot much different from traditional interaction.

The interaction process is as follows:

The only difference is that : tokento store the user's basic information, more intuitive point is originally placed redisin the user data, to put tokenin the go!

As a result, the client, the server can be from tokenaccess to basic information of the user, since the client can get, certainly not to store sensitive information , because the browser directly from the tokenget user information.

What exactly does JWT look like?

JWT is composed of three pieces of information, and these three pieces of information text are .linked together to form a JWTstring. like this:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
  • The first part: We call it the header, which is used to store the token type and encryption protocol, which are generally fixed;

  • The second part: We call it the payload, and the user data is stored in it;

  • The third part: is the visa (signature), mainly used for server verification;

1、header

The header of the JWT carries two parts of information:

  • Declaration type, here is JWT;

  • Declare the encryption algorithm, usually HMAC SHA256 directly;

The complete header looks like the following JSON:

{
   
     'typ': 'JWT',  'alg': 'HS256'}

The use of base64encryption constitutes the first part.

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9

2、playload

The payload is where the valid information is stored. The valid information consists of three parts:

  • Declaration registered in the standard;

  • Public statement

  • Private statement

Among them, the statement registered in the standard (recommended but not mandatory) includes the following parts  :

  • iss: jwt issuer;

  • sub: the user that jwt is facing;

  • aud: the party receiving jwt;

  • exp: the expiration time of jwt, this expiration time must be greater than the issuance time;

  • nbf: Define the time before the jwt is unavailable;

  • iat: the issuance time of jwt;

  • The unique identity of jwt is mainly used as a one-time token to avoid replay attacks;

Public statement part : Any information can be added to the public statement, generally adding user-related information or other necessary information required by the business, but it is not recommended to add sensitive information, because this part can be decrypted on the client.

Private declaration part : A private declaration is a declaration jointly defined by providers and consumers. It is generally not recommended to store sensitive information, because it base64is symmetrically decrypted, which means that this part of information can be classified as plaintext information.

Define a payload:

{
   
     "sub": "1234567890",  "name": "John Doe",  "admin": true}

Then base64encrypt it to get Jwtthe second part:

eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9

3、signature

The third part of jwt is a visa information, this visa information consists of three parts:

  • header (after base64);

  • payload (after base64);

  • secret (key);

This part needs to be base64encrypted headerand base64encrypted payloadusing .the string composed of the connection, and then through headerthe encryption method declared in the salt secretcombination encryption, and then constitute the jwtthird part.

//javascriptvar encodedString = base64UrlEncode(header) + '.' + base64UrlEncode(payload);var signature = HMACSHA256(encodedString, '密钥');

After encryption, the signaturesignature information is obtained .

TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

.Connect these three parts into a complete string to form the final jwt:

//jwt最终格式eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

This is just javascripta demonstration through implementation, JWTthe issuance and key storage are all done on the server side.

secretIt is used for jwtissuance and jwtverification, so it should not be revealed in any scene .

Three, actual combat

I have introduced so much, how to achieve it? Don't talk nonsense, let's start directly below!

  • Create a springbootproject and add JWTdependent libraries

<!-- jwt支持 --><dependency>    <groupId>com.auth0</groupId>    <artifactId>java-jwt</artifactId>    <version>3.4.0</version></dependency>
  • Then, create a class of user information will be stored in an encrypted by tokenthe

@Data@EqualsAndHashCode(callSuper = false)@Accessors(chain = true)public class UserToken implements Serializable {
   
   
    private static final long serialVersionUID = 1L;
    /**     * 用户ID     */    private String userId;    /**     * 用户登录账户     */    private String userNo;    /**     * 用户中文名     */    private String userName;}
  • Next, create a JwtTokenUtiltool class to create tokenand verifytoken

public class JwtTokenUtil {
   
   	//定义token返回头部    public static final String AUTH_HEADER_KEY = "Authorization";	//token前缀    public static final String TOKEN_PREFIX = "Bearer ";	//签名密钥    public static final String KEY = "q3t6w9z$C&F)J@NcQfTjWnZr4u7x";		//有效期默认为 2hour    public static final Long EXPIRATION_TIME = 1000L*60*60*2;    /**     * 创建TOKEN     * @param content     * @return     */    public static String createToken(String content){
   
           return TOKEN_PREFIX + JWT.create()                .withSubject(content)                .withExpiresAt(new Date(System.currentTimeMillis() + EXPIRATION_TIME))                .sign(Algorithm.HMAC512(KEY));    }    /**     * 验证token     * @param token     */    public static String verifyToken(String token) throws Exception {
   
           try {
   
               return JWT.require(Algorithm.HMAC512(KEY))                    .build()                    .verify(token.replace(TOKEN_PREFIX, ""))                    .getSubject();        } catch (TokenExpiredException e){
   
               throw new Exception("token已失效,请重新登录",e);        } catch (JWTVerificationException e) {
   
               throw new Exception("token验证失败!",e);        }    }}
  • Write configuration classes, allow cross-domain, and create a permission interceptor

@Slf4j@Configurationpublic class GlobalWebMvcConfig implements WebMvcConfigurer {
   
   	   /**     * 重写父类提供的跨域请求处理的接口     * @param registry     */    @Override    public void addCorsMappings(CorsRegistry registry) {
   
           // 添加映射路径        registry.addMapping("/**")                // 放行哪些原始域                .allowedOrigins("*")                // 是否发送Cookie信息                .allowCredentials(true)                // 放行哪些原始域(请求方式)                .allowedMethods("GET", "POST", "DELETE", "PUT", "OPTIONS", "HEAD")                // 放行哪些原始域(头部信息)                .allowedHeaders("*")                // 暴露哪些头部信息(因为跨域访问默认不能获取全部头部信息)                .exposedHeaders("Server","Content-Length", "Authorization",
                               "Access-Token", 
                               "Access-Control-Allow-Origin",
                               "Access-Control-Allow-Credentials");    }
    /**     * 添加拦截器     * @param registry     */    @Override    public void addInterceptors(InterceptorRegistry registry) {
   
           //添加权限拦截器        registry.addInterceptor(
                 new AuthenticationInterceptor())
                .addPathPatterns("/**").excludePathPatterns("/static/**");    }}
  • Use AuthenticationInterceptorinterceptors to verify interface parameters

@Slf4jpublic class AuthenticationInterceptor implements HandlerInterceptor {
   
   
    @Override    public boolean preHandle(
                   HttpServletRequest request, 
                   HttpServletResponse response, Object handler) throws Exception {
   
   		// 从http请求头中取出token        final String token = request.getHeader(JwtTokenUtil.AUTH_HEADER_KEY);        //如果不是映射到方法,直接通过        if(!(handler instanceof HandlerMethod)){
   
               return true;        }        //如果是方法探测,直接通过        if (HttpMethod.OPTIONS.equals(request.getMethod())) {
   
               response.setStatus(HttpServletResponse.SC_OK);            return true;        }        //如果方法有JwtIgnore注解,直接通过        HandlerMethod handlerMethod = (HandlerMethod) handler;        Method method=handlerMethod.getMethod();        if (method.isAnnotationPresent(JwtIgnore.class)) {
   
               JwtIgnore jwtIgnore = method.getAnnotation(JwtIgnore.class);            if(jwtIgnore.value()){
   
                   return true;            }        }        LocalAssert.isStringEmpty(token, "token为空,鉴权失败!");        //验证,并获取token内部信息        String userToken = JwtTokenUtil.verifyToken(token);				//将token放入本地缓存        WebContextUtil.setUserToken(userToken);        return true;    }    @Override    public void afterCompletion(
                         HttpServletRequest request, HttpServletResponse response, 
                        Object handler, Exception ex) throws Exception {
   
           //方法结束后,移除缓存的token        WebContextUtil.removeUserToken();    }}
  • Finally, after the controllerlayer user logs in, create one tokenand store it in the header.

/** * 登录 * @param userDto * @return */@JwtIgnore@RequestMapping(value = "/login", method = RequestMethod.POST,
                produces = {"application/json;charset=UTF-8"})public UserVo login(@RequestBody UserDto userDto, HttpServletResponse response){
   
       //...参数合法性验证    //从数据库获取用户信息    User dbUser = userService.selectByUserNo(userDto.getUserNo);    //....用户、密码验证    //创建token,并将token放在响应头    UserToken userToken = new UserToken();    BeanUtils.copyProperties(dbUser,userToken);    String token = JwtTokenUtil.createToken(JSONObject.toJSONString(userToken));    response.setHeader(JwtTokenUtil.AUTH_HEADER_KEY, token);    //定义返回结果    UserVo result = new UserVo();    BeanUtils.copyProperties(dbUser,result);    return result;}

It's basically done here!

What AuthenticationInterceptoris used in it JwtIgnoreis an annotation, which is used for tokenmethods that do not require verification , such as obtaining a verification code, and so on. @Target({ElementType.METHOD, ElementType.TYPE})

@Retention(RetentionPolicy.RUNTIME)public @interface JwtIgnore {
   
       boolean value() default true;}

And WebContextUtila thread cache tools, other interfaces can be from by this method tokento obtain user information.

​​​​​​

public class WebContextUtil {
   
       //本地线程缓存token    private static ThreadLocal<String> local = new ThreadLocal<>();    /**     * 设置token信息     * @param content     */    public static void setUserToken(String content){
   
           removeUserToken();        local.set(content);    }    /**     * 获取token信息     * @return     */    public static UserToken getUserToken(){
   
           if(local.get() != null){
   
               UserToken userToken = JSONObject.parseObject(local.get() , UserToken.class);            return userToken;        }        return null;    }    /**     * 移除token信息     * @return     */    public static void removeUserToken(){
   
           if(local.get() != null){
   
               local.remove();        }    }}

Finally, start the project, let's postmantest it and see if the header returns the result.

We extract the returned information and use the browser base64to decrypt the first two parts.

  • The first part, the header, the result is as follows:

  • The second part, which is the playload, the results are as follows:

It can be clearly seen that the header and payload information can be base64decrypted.

So, be sure not tokento store sensitive information in it !

When we need to request other service interfaces, we only need headersto add Authorizationparameters to the request header .

After the permission interceptor is verified, the WebContextUtiluser information can be obtained only through the tool class in the interface method .

//获取用户token信息UserToken userToken = WebContextUtil.getUserToken();

Four, summary

JWTCompared sessionprogram, because jsonof the versatility, it JWTcan be cross-language support, like JAVA, JavaScript, PHPand many other languages can be used, but sessionthe program only for JAVA.

Because of the payloadpart, it JWTcan store some non-sensitive information necessary for other business logic.

At the same time, it is secretvery important to protect the private key of the server , because the private key can verify and decrypt the data!

If you can, please use the httpsagreement!

Five, reference

1. Brief Book-What is JWT-JSON WEB TOKEN

2. Blog Garden-an identity authentication scheme based on session and token

                                     ------------------------------------------- < END >---------------------------------------

Teach you how to use JWT to achieve single sign-on

Original Duck Blood Fans  Java Geek Technology  May 15

https://mp.weixin.qq.com/s/3qm3RvSG_1LQ0v676XU7jA

If you like our article, you are welcome to forward it and click on it to let more people see it. We also welcome friends who love technology and learning to join our knowledge planet. We grow and progress together.

Guess you like

Origin blog.csdn.net/qq_31653405/article/details/105659832