background

Because HTTP protocol is stateless, in order to distinguish users and protect user information on the Internet, session management is generated. There are two main session management implementations:

  • Session: Authenticates sessions based on server storage
  • Token: Authenticates the session based on the checking token

This paper focuses on the second token implementation scheme JWT

JWT is introduced

JWT is JSON Web Tokens. As the name suggests, this is a JSON-based authentication scheme for Internet communication, a common JWT like the following.

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJ SMeKKF2QT4fwpMeJf36POk6yJV_adQssw5cCopy the code

Notice that it is divided by two. The number is divided into three segments. The first segment is header, the second segment is payload, and the third segment is signature.

So the overall form is:

header.payload.signature
Copy the code

JWT constitute

header

Header is a Base64 URL-encoded string. The original content is JSON, which usually consists of two parts. The first part is the signature algorithm used. Generally, HS256(HMAC-SHA256), RS256(RSA-SHA256), and ES256(ECDSA-SHA256) are optional. The second part is the fixed type, which is simply “JWT”. Such as:

{
  "alg": "HS256".// The signature algorithm used
  "typ": "JWT".// Type, which is JWT, does not need to be changed
}
Copy the code

The json above is encoded in base64Url to form the first header.

payload

Payload is also a base64URL-encoded string that contains the actual transmitted data. Payload Provides seven fields for you to choose from.

  • Iss (Issuer) : indicates the issuer
  • Exp (expiration Time) : expiration time
  • Sub (subject) : indicates the topic
  • Aud (audience) : Audience
  • NBF (Not Before) : indicates the effective time
  • Iat (Issued At) : time of issue
  • Jti (JWT ID) : indicates the ID

None of the above fields is optional, except that since this is a payload segment, users can add custom fields. An example of payload in a real scenario:

{
  "exp": 1620887677.// Token expiration time
  "userId": "xxx"  // Custom user ID
}
Copy the code

signature

Signature indicates the signature field. It uses the signature algorithm declared in the header and uses a secret key to sign the base64URL-encoded header JSON and base64URL-encoded payload JSON. The pseudocode is as follows

HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),
  secret)
Copy the code

Note Secret must be stored on the server and cannot be leaked. Header + “.” + Paylod + “.” + Signatrue is a complete JWT token.

JWT usage and principles

Once the JWT token is generated, it’s time to use it. Typically, clients place the HTTP request header Authrization field in the following form:

Authorization: Bearer <token>
Copy the code

Of course, this is not mandatory, you can also put it in a custom header field. To be honest, I have been curious about this prefix Bearer before. Why have I added such a string? Because of this Bearer, I have to intercept the header after removing it to get the token. Later, I found that there is a specific RFC document RFC6750 that makes this stipulation. However, AFTER reading this RFC file, I still do not understand it. One conclusion I came to was that some frameworks might implement this protocol, and if you use an existing framework to extract tokens, it’s best to transfer them in the form of a convention; If you wrote the wheel, you can ignore it. In addition, Postman quickly added authentication information is also the default Authorization: Bearer

.

After receiving the token, the server uses the same algorithm to sign the header and payload and compares it with the signature to verify the validity of the token. If the comparison succeeds, the user data in the payload is retrieved for subsequent operations. If the comparison fails, the authentication fails.

It is worth noting that many friends believe that JWT Token’s information is encrypted, which is actually wrong. The signature is a hash hash value, and the header and payload can be decoded directly. Using base64Url, you can decode the header and payload and see what’s in it. The token verification process is also a token verification process rather than a decryption process.

JWT compared to Session

The advantages of JWT

  • Authentication information is stored in tokens and does not need to be stored on the server, saving resources
  • Transport is placed in the request header and naturally supports cross-domain portability without CORS issues
  • Support distributed, cluster, no expansion problems
  • Cookie support is not required, so there is no CSRF (cross-site request forgery) issue

The shortage of the JWT

  • Once a token is issued, it can still be used within the validity period even if the user logs out of the token, which has certain security risks. (Risk can be reduced by reducing the token validity period and cooperating with refresh token, more on that later.)
  • The token is placed in the request header. If there is too much payload data, the token is too long, affecting the packet transmission efficiency. (So keep custom data to a minimum, I usually use userId.)

The disadvantage of the session

  • Generally stored in memory, when the number of users is large, take up computer resources
  • For distributed, clustered applications, component processing such as Redis needs to be introduced. And the failure of Redis may result in the unavailability of the entire system authentication
  • Based on cookie implementation, users have the possibility to disable cookies
  • Cookie based implementation, so there are CSRF issues to deal with

The advantages of the session

  • Frameworks support friendly, many frameworks directly set, get on the line.
  • The sessionID is invalid when you log out

JWT combines springBoot practices

Here is a quick solution for SpringBoot to use JWT to complete authentication. For detailed implementation, please refer to this project: Experience, Bytemall

Define the JWT utility class

Import JWT packages into POM

<! -- jwt --> <dependency> <groupId>com.auth0</groupId> <artifactId>java-jwt</artifactId> <version>3.103.</version>
</dependency>
Copy the code

Defining utility classes

@Component
public class JwtUtil {
	// Read configuration secret
	@Value(value = "${jwt.secret}")
	public String SECRET;
	
    / / token is generated
	public String createToken(Long userId, Integer expireHours){
		Algorithm algorithm = Algorithm.HMAC256(SECRET);
		Map<String, Object> map = new HashMap<String, Object>();
		LocalDateTime now = LocalDateTime.now();
		// Expiration time: 2 hours
		LocalDateTime expireDate = now.plusHours(expireHours);
		map.put("alg"."HS256");
		map.put("typ"."JWT");
		return JWT.create()
			// Set the Header information
			.withHeader(map)
			// Set the Payload
			.withClaim("userId", userId)
			// The time to generate the signature
			.withIssuedAt(Date.from(now.atZone(ZoneId.systemDefault()).toInstant()))
			// When the signature expires
			.withExpiresAt(Date.from(expireDate.atZone(ZoneId.systemDefault()).toInstant()))
			/ / sign Signature
			.sign(algorithm);
	}
	
    / / authentication token
	public Long verifyTokenAndGetUserId(String token) {
		Algorithm algorithm = Algorithm.HMAC256(SECRET);
		JWTVerifier verifier = JWT.require(algorithm).build();
		DecodedJWT jwt = verifier.verify(token);
		Map<String, Claim> claims = jwt.getClaims();
		Claim claim = claims.get("userId");
		returnclaim.asLong(); }}Copy the code

Defining login annotations

@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface LoginRequire {
}
Copy the code

Added a custom ArgumentResolver

@Component
public class LoginArgumentResolver implements HandlerMethodArgumentResolver {
    private final Logger logger = LoggerFactory.getLogger(getClass());
	
    // Define a header to exchange tokens
    public static final String LOGIN_TOKEN_KEY = "Token";
	
    Inject the JWT tool defined above
    @Autowired
    private JwtUtil jwtUtil;
	
    // Override parameter support methods
    @Override
    public boolean supportsParameter(MethodParameter methodParameter) {
        return Long.class.isAssignableFrom(methodParameter.getParameterType()) && methodParameter.hasParameterAnnotation(LoginRequire.class);
    }
	
    // Override the parameter handling method
    @Override
    public Object resolveArgument(MethodParameter methodParameter, ModelAndViewContainer modelAndViewContainer, NativeWebRequest nativeWebRequest, WebDataBinderFactory webDataBinderFactory) throws Exception {
        String token = nativeWebRequest.getHeader(LOGIN_TOKEN_KEY);
        if (token == null || token.isEmpty()) {
            throw new AuthException("There is no token");
        }

        try {
            return jwtUtil.verifyTokenAndGetUserId(token);
        } catch (JWTVerificationException e) {
            logger.error("Token decoding failed" + e.getMessage(), e);
            throw new AuthException("Authentication failed"); }}}Copy the code

Configure the custom resolver to mvcConfiguration

@Configuration
public class BytemallMvcConfiguration implements WebMvcConfigurer {

    @Autowired
    private LoginArgumentResolver loginArgumentResolver;
	
    // Add a custom parameter handler
    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) { resolvers.add(loginArgumentResolver); }}Copy the code

The controller is used in

The login

@RestController
@RequestMapping("/api/user")
public class ApiUserController {
    
    // Inject the JWT tool
    @Resource
    private JwtUtil jwtUtil;
    
    @PostMapping("/login")
    public Object login(@RequestBody AdminLoginParamVO adminLoginParamVO) {
        String username = adminLoginParamVO.getUsername();
        String password = adminLoginParamVO.getPassword();

        BytemallAdmin admin = adminService.findByUsernameAndPwd(username, Md5Util.md5Hash(password));
        if (admin == null) {
            throw new BizException(ErrorCodeEnum.FAILED.getErrCode(), "Wrong account or password");
        }

        AdminLoginResultVO respInfo = new AdminLoginResultVO();
        respInfo.setUsername(admin.getUsername());
        // Generate token and validity period
        respInfo.setToken(jwtUtil.createToken(admin.getId(), 24));
        returnResponseUtil.ok(respInfo); }}Copy the code

use

// Use defined annotations in specific routing functions
@GetMapping("/list")
public Object userList(@LoginRequire Long userId) {
    System.out.println(userId);
    return "ok";
}
Copy the code

conclusion

To use a fancy phrase, session management has no silver bullet! Both Session and JWT have their own advantages and disadvantages. The correct approach is to choose an appropriate solution according to the actual requirements of business scenarios. JWT, did you quit school?