Location>code7788 >text

Hands-On Single Sign-On with JWT

Popularity:346 ℃/2024-07-21 00:35:46

JWT (English full name: JSON Web Token) is currently one of the most popular cross-domain authentication solutions, today we unveil its mysterious veil!

I. Origin of the story

When it comes to JWT, let's first talk about traditionalsessioncertified programs as well as bottlenecks.

legacysessionThe interaction flow, as shown below:

When the browser sends a login request to the server, after it passes authentication, it stores the user's information into theseesionin the server, and then the server generates asessionIdput intocookiewhich is then returned to the browser.

When the browser sends the request again, thecookieput intosessionId, sends the request data to the server together.

The server will then be able to get the information again from theseesionGet user information and the whole process is complete!

Typically on the server side you would set theseesionIf the user has been inactive for, for example, 30 minutes, the stored user information will be removed from theseesionRemove.

(30 * 60);//30 minutes of inactivity, automatically removed

Also, on the server side theseesionto determine whether the current user is logged in, if it is empty means not logged in, directly jump to the login page; if it is not empty, you can start from thesessionThe user's information can be retrieved from the

In a monolithic application, there is nothing wrong with such an interaction.

However, if the volume of requests to the application server becomes very large, and there is a limit to the number of requests that a single server can support, it is easy for requests to slow down or toOOM

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

If the configuration is added to a single server, the volume of requests continues to get larger and still cannot support business processing.

Obviously, adding new servers allows for unlimited horizontal scaling.

But after adding new servers, the different servers between thesessionIdis not the same as it might be inAThe server has been logged in successfully and can be accessed from the server'ssessionto get user information in theBI can't find it on the server.sessionmessage, which must have been incredibly embarrassing at this point, so I had to log out and keep logging in, and I ended upAin the serversessionIt's embarrassing to think that after logging in, you're forced to log out and ask to log in again because the timeout expires~~

Faced with this situation, a few bigwigs then combined their deliberations and came up with atokenPrograms.

Connecting individual applications to the in-memory databaseredisconnected, the user information of successful login is encrypted with certain algorithms, and the generatedIDbe known astokenwilltokenAnd the user's information is depositedredis; wait for the user to initiate the request again by placing thetokenThe request data is also sent to the server, and the server verifies it.tokenwhether or notredisIf it exists, the authentication passes, and if it doesn't, the browser is told to jump to the login page and the process ends.

tokenThe solution ensures that the service is stateless and all the information is present in the distributed cache. Based on distributed storage, this can be scaled horizontally to support high concurrency.

Sure. Now.springbootAlso availablesessionShared programs, similartokenThe program willsessiondeposit intoredisIn the cluster environment, after a single login, each server can obtain the user information.

Second, what is JWT

Above, we talked about thesessionthere aretokenprograms, in a clustered environment, they all rely on third-party caching databasesredisto enable data sharing.

So is there a program that doesn't use a cached databaseredisWhat about sharing user information to achieve the effect of logging in once and seeing it everywhere?

The answer is definitely there, and it's the one we're going to cover todayJWT

JWTfull nameJSON Web TokenThe implementation process is simply to encrypt the user's information after the user has successfully logged in, and then generate atokenreturned to the client, as opposed to the traditionalsessionThe interactions aren't much different.

The interaction flow is as follows:

The only difference is thattokenstores basic information about the user, or more intuitively, the information that would otherwise go into theredisin the user data, put it into thetokenGoing in!

In this way, the client, and the server can get the information from thetokento get basic information about the user, and since the client can get it, theI'm sure you can't store sensitive information., because browsers can directly retrieve the data from thetokenGet user information.

What does JWT look like exactly?

The JWT is made up of three pieces of information, and the text of these three pieces of information is summarized using the.The links together make up theJWTString. 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 is usually fixed;
  • The second part: we call it the payload, in which the user data is stored;
  • The third part: is the visa (signature), mainly used for server-side authentication;
1、header

The header of a JWT carries two parts of information:

  • Declare the type, in this case JWT;
  • Declare the algorithm for encryption, usually directly using HMAC SHA256;

The full header looks like the following JSON:

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

utilizationbase64encryption, constitutes the first part.

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9
2、playload

The load is where the valid information is stored, and this valid information consists of three parts:

  • statement registered in the standard;
  • Public statement;
  • Private statement;

Among other things, the statement of registration in the standard (which is recommended but not mandatory) includes the following sections

  • iss: jwt issuer;
  • sub: The user for which the jwt is intended;
  • aud: The party receiving the jwt;
  • exp: the expiration time of the jwt, this expiration time must be greater than the issuance time;
  • nbf: defines until what time this jwt is unavailable;
  • iat: The issuance time of the jwt;
  • jwt is a unique identifier, mainly used as a one-time token, thus avoiding replay attacks;

The public part of the statement
The public statement can add any information, generally add the user's relevant information or other necessary information for business needs, but it is not recommended to add sensitive information, because the part can be decrypted on the client side.

Private declaration section
A private statement is a statement defined jointly by the provider and the consumer, and is generally not recommended for storing sensitive information, as thebase64is symmetrically decrypted, meaning that that part of the message can be categorized as plaintext.

Define a payload:

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

It is then subjected tobase64Encrypt it to getJwtThe second part of the

eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9
3、signature

The third part of the jwt is a visa information which consists of three parts:

  • header (after base64);
  • payload (after base64);
  • secret (key).

This section requiresbase64encryptedheadercap (a poem)base64encryptedpayloadutilization.concatenates the constituent strings, which are then passed through theheaderSalt the encryption declared insecretCombined encryption then constitutes thejwtThe third part of the

//javascript
var encodedString = base64UrlEncode(header) + '.' + base64UrlEncode(payload);

var signature = HMACSHA256(encodedString, 'keys');

After encryption, you getsignatureSignature information.

TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

Combine these three parts with.Concatenated into a complete string, this constitutes the final jwt:

//jwt final format
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9. TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

This one just goes throughjavascriptrealization of a demo thatJWTThe issuing and saving of keys is done on the server side.

secretgojwtIssuance andjwtof validation, so it should not flow out in any scenario

III. Actual combat

Introduced so much, how to realize it? Without further ado, here we go directly to jack!

  • Create aspringbootproject, add theJWTdependency library (computing)
<!-- jwtbe in favor of -->
<dependency>
    <groupId>com.auth0</groupId>
    <artifactId>java-jwt</artifactId>
    <version>3.4.0</version>
</dependency>
  • Then, create a user information class that will be encrypted and stored in thetokencenter
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
public class UserToken implements Serializable {

    private static final long serialVersionUID = 1L;

    /**
     * subscribersID
     */
    private String userId;

    /**
     * subscribers登录账户
     */
    private String userNo;

    /**
     * subscribers中文名
     */
    private String userName;
}
  • Next, create aJwtTokenUtilTool class for creatingtokenVerificationtoken
public class JwtTokenUtil {

    //definetokenReturn to header
    public static final String AUTH_HEADER_KEY = "Authorization";

    //tokenprefix (linguistics)
    public static final String TOKEN_PREFIX = "Bearer ";

    //signing key
    public static final String KEY = "q3t6w9z$C&F)J@NcQfTjWnZr4u7x";
    
    //The expiration date defaults to 2hour
    public static final Long EXPIRATION_TIME = 1000L*60*60*2;


    /**
     * establishTOKEN
     * @param content
     * @return
     */
    public static String createToken(String content){
        return TOKEN_PREFIX + ()
                .withSubject(content)
                .withExpiresAt(new Date(() + EXPIRATION_TIME))
                .sign(Algorithm.HMAC512(KEY));
    }

    /**
     * validate (a theory)token
     * @param token
     */
    public static String verifyToken(String token) throws Exception {
        try {
            return (Algorithm.HMAC512(KEY))
                    .build()
                    .verify((TOKEN_PREFIX, ""))
                    .getSubject();
        } catch (TokenExpiredException e){
            throw new Exception("tokenexpired,Please log in again",e);
        } catch (JWTVerificationException e) {
            throw new Exception("tokenvalidate (a theory)失败!",e);
        }
    }
}
  • Write a configuration class to allow cross-domain and create a permission interceptor
@Slf4j
@Configuration
public class GlobalWebMvcConfig implements WebMvcConfigurer {
       /**
     * Overrides the interface provided by the parent class for handling cross-domain requests.
     * @param registry
     */
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        // Add the mapping path
        ("/**")
                // Which raw fields to release
                .allowedOrigins("*")
                // Whether to send cookie information
                .allowCredentials(true)
                // Which raw domains to let go of (request methods)
                .allowedMethods("GET", "POST", "DELETE", "PUT", "OPTIONS", "HEAD")
                // Which raw fields to allow (headers)
                .allowedHeaders("*")
                // which headers to expose (since cross-domain access doesn't get all headers by default)
                .exposedHeaders("Server", "Content-Length", "Authorization", "Access-Token", "Access-Control-Allow-Origin", "Access-Control-Allow- Credentials");
    }

    /**
     * Add interceptor
     * @param registry
     */
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // Add a privilege interceptor
        (new AuthenticationInterceptor()).addPathPatterns("/**").excludePathPatterns("/static/**");
    }
}
  • utilizationAuthenticationInterceptorInterceptor validation against interface parameters
@Slf4j
public class AuthenticationInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // through (a gap)httpin the request headertoken
        final String token = (JwtTokenUtil.AUTH_HEADER_KEY);
        //If not mapped to a method,Directly through
        if(!(handler instanceof HandlerMethod)){
            return true;
        }
        //If the method detects,Directly through
        if ((())) {
            (HttpServletResponse.SC_OK);
            return true;
        }
        //If the method hasJwtIgnoreexplanatory note,Directly through
        HandlerMethod handlerMethod = (HandlerMethod) handler;
        Method method=();
        if (()) {
            JwtIgnore jwtIgnore = ();
            if(()){
                return true;
            }
        }
        (token, "tokenempty,authentication failure!");
        //validate (a theory),and gettokenInternal information
        String userToken = (token);
        
        //commander-in-chief (military)tokenlocal cache
        (userToken);
        return true;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        //At the end of the method,Remove the cachedtoken
        ();
    }
}
  • Finally, in thecontrollerAfter the layer user logs in, create atokenJust store it in the head.
/**
 * log in
 * @param userDto
 * @return
 */
@JwtIgnore
@RequestMapping(value = "/login", method = , produces = {"application/json;charset=UTF-8"})
public UserVo login(@RequestBody UserDto userDto, HttpServletResponse response){
    //...Parameter legitimacy verification

    //Getting user information from the database
    User dbUser = ();

    //....subscribers、password verification

    //establishtoken,includetokenPut it in the response header
    UserToken userToken = new UserToken();
    (dbUser,userToken);

    String token = ((userToken));
    (JwtTokenUtil.AUTH_HEADER_KEY, token);


    //Define the return result
    UserVo result = new UserVo();
    (dbUser,result);
    return result;
}

It's basically done at this point!

included among theseAuthenticationInterceptorinJwtIgnoreis an annotation for use without validationtokenon methods such as CAPTCHA fetching and so on.

@Target({, })
@Retention()
public @interface JwtIgnore {

    boolean value() default true;
}

(indicates contrast)WebContextUtilis a thread caching utility class through which other interfaces can be accessed from thetokenGet user information in the

public class WebContextUtil {

    //Local thread cachingtoken
    private static ThreadLocal<String> local = new ThreadLocal<>();

    /**
     * set uptokentext
     * @param content
     */
    public static void setUserToken(String content){
        removeUserToken();
        (content);
    }

    /**
     * gaintokentext
     * @return
     */
    public static UserToken getUserToken(){
        if(() != null){
            UserToken userToken = (() , );
            return userToken;
        }
        return null;
    }

    /**
     * removestokentext
     * @return
     */
    public static void removeUserToken(){
        if(() != null){
            ();
        }
    }
}

Finally, to start the project, let's use thepostmanTest it and see what the header returns.

We extract the returned information and process it, using the browser'sbase64Decrypt the first two parts.

  • The first part, the header, results in the following:

  • The second part, which is playload, results in the following:

It can be clearly seen that the header, load information can be passed through thebase64Decrypt it out.

So, make sure you don't get intokenStoring sensitive information in

When we need to request other service interfaces, we just need to add the following to the request headerheadersadd inAuthorizationparameter is sufficient.

Once the privilege interceptor has been validated, the interface method only needs to be validated by theWebContextUtilThe tool class will be able to get user information.

// Get user token information
UserToken userToken = ();

IV. Summary

JWTcomparesessionprogram becausejsonThe generalization of theJWTIt is possible to have cross-language support, likeJAVAJavaScriptPHPand many other languages are available, and thesessionPrograms are only forJAVA

Because of thepayloadpart, soJWTSome non-sensitive information necessary for other business logic can be stored.

In the meantime, protect the server sidesecretThe private key is very important because the private key can validate and decrypt the data. If you can, use thehttpsAgreement!

The project source code is located at the following address!

/pzblogs/spring-boot-example-demo

V. References

1、Jane's Book - What is JWT -- JSON WEB TOKEN

2、Blogspot - Session and token based authentication scheme