I heard that wechat search “Java fish” will change strong oh!

This article is in Java Server, which contains my complete series of Java articles, can be read for study or interview

(1) Preface

In the previous article about distributed session, some readers asked a question: Isn’t it sweet to use JWT? Even Redis server is not needed, and personnel information authentication can be realized in distributed environment. I haven’t written about JWT yet, and I’ve used it in my projects, so I’ll share it today. Once again, theory first, practice later. Brainstorm if you have any questions.

(B) What is JWT

JWT Full JSON Web Token. User session information is stored in the client browser. Information is securely transmitted between parties through JSON objects and can be encrypted using encryption algorithms.

This description may be difficult to understand, but JWT simply means that when you log in, you generate a string of encrypted string tokens, and carry this string with you every time you request. If the server can decrypt this string, it means that you are logged in.

We generally encrypt non-sensitive data to generate tokens. If the server side needs personnel information, it will decrypt the personnel information.

You will find that using JWT directly solves the problem of personnel authentication in a distributed cluster environment because the generated string of tokens can be resolved on any server. As shown below:

After understanding the principle, let’s talk about the practical aspects. JWT data consists of three parts:

Header 2 Payload 3 Signature

The three are combined with “.” and encrypted to generate a token:

Header.Payload.Signature

Here is a token I generated

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJqYXZheXoiLCJjcmVhdGVkYXRlIjoxNjExNzU1MDIxNjk1LCJpZCI6MSwiZXhwIjoxNjEyMzU 5ODIxLCJ1c2VyTGV2ZWxJZCI6bnVsbH0.ahFWQ_BJ1WNWp9GnlTrSNThVa3i3dydzcaNxLmPb7HICopy the code

Header is used to describe the JWT metadata, consisting of two pieces of data, where ALG represents the signature algorithm, HS256 by default, and the TYP attribute represents the token type, in this case JWT. The first “in the token generated above. The previous base64 data decoded is the following JSON

{
    alg : "HS256",
    typ : "JWT"
}
Copy the code

Payload Records user information in JSON format. The user information can be customized. And the second “.” The base64 decoded data is the following JSON

{
    "sub":"javayz"."createdate":1611755021695."id":1."exp":1612359821."userLevelId":null
}
Copy the code

Signature Stores the encryption string. The specified algorithm adds salt to the Header and Payload to encrypt each part. Segmentation. Constitute the token.

(iii) JWT identity authentication process

After reading the above, I believe you have a good understanding of JWT authentication. Its authentication process is as follows:

1. Enter the user name and password to log in

2. The server verifies that the user name and password are correct and returns the token to the client

3. The client saves the token in a cookie or local storage

4. Each subsequent request from the client must carry this token

5. The server determines the user based on the token

(4) Code practice

Next, the JWT certification implementation is simulated by code. I wrote this code in the environment of springBoot2.4.2, introducing JWT dependencies:

<! - JWT dependence - >
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.9.0</version>
</dependency>
Copy the code

Configure the following properties in the configuration file:

jwt:
  tokenHeader: Authorization
  secret: javayz
  expiration: 604800
  tokenHead: Bearer
Copy the code

Write a class to read these parameters:

@Data
@ConfigurationProperties(prefix = "jwt")
@Component
public class JwtProperties {
    private String tokenHeader;
    private String secret;
    private Long expiration;
    private String tokenHead;
}
Copy the code

Write the JWT utility class to realize the function of generating and decoding tokens:

public class JwtKit {

    @Autowired
    private JwtProperties jwtProperties;

    /** * Create token *@param user
     * @return* /
    public String generateJwtToken(User user){
        // All user data is placed in claims
        Map<String,Object> claims=new HashMap<>();
        claims.put("sub",user.getUsername());
        claims.put("createdate".new Date());
        claims.put("id",user.getId());
        claims.put("userLevelId",user.getLevelId());

        return Jwts.builder()
                .setClaims(claims)
                .setHeaderParam("typ"."JWT")
                .setExpiration(new Date(System.currentTimeMillis()+jwtProperties.getExpiration()*1000))
                .signWith(SignatureAlgorithm.HS256, jwtProperties.getSecret())
                .compact();
    }

    /** * decodes token *@param jwtToken
     * @return* /
    public Claims parseJwtToken(String jwtToken){
        Claims claims=null;
        try {
            claims=Jwts.parser()
                    .setSigningKey(jwtProperties.getSecret())
                    .parseClaimsJws(jwtToken)
                    .getBody();
        }catch (ExpiredJwtException e){
            throw new MyException("JWTtoken expired");
        }catch (UnsupportedJwtException e){
            throw new MyException("JWTtoken format not supported");
        }catch (MalformedJwtException e){
            throw new MyException("Invalid token");
        }catch (SignatureException e){
            throw new MyException("Invalid token");
        }catch (IllegalArgumentException e){
            throw new MyException("Invalid token");
        }
        returnclaims; }}Copy the code

Inject the utility class into the Bean container:

@Configuration
public class JwtConfiguration {

    @Bean
    public JwtKit jwtKit(a){
        return newJwtKit(); }}Copy the code

Then you can use it happily

@RestController
@RequestMapping("/sso")
public class UserController extends BaseController {

    @Autowired
    private UserService userService;

    @Autowired
    private JwtKit jwtKit;

    @Autowired
    private JwtProperties jwtProperties;

    @PostMapping("jwtlogin")
    public CommonResult jwtLogin(@RequestParam String username,@RequestParam String password){
        User user = userService.login(username, password);
        if(user! =null){
            Map<String,Object> map=new HashMap<>();
            String token=jwtKit.generateJwtToken(user);
            map.put("tokenHead",jwtProperties.getTokenHead());
            map.put("token",token);
            return new CommonResult(ResponseCode.SUCCESS.getCode(),ResponseCode.SUCCESS.getMsg(),map);
        }
        return new CommonResult(ResponseCode.USER_NOT_EXISTS.getCode(),ResponseCode.USER_NOT_EXISTS.getMsg(),""); }}Copy the code

If the login succeeds, the beginning of the token and the actual token value are returned to the backend.

Then write interceptors that intercept requests other than/SSO /*, which require login to access.

@Configuration
public class IntercepterConfiguration implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        List list=new ArrayList();
        list.add("/sso/**");
        registry.addInterceptor(authInterceptorHandler())
                .addPathPatterns("/ * *")
                .excludePathPatterns(list);
    }

    @Bean
    public AuthInterceptorHandler authInterceptorHandler(a){
        return newAuthInterceptorHandler(); }}Copy the code

Then the interception is processed. First, determine whether there is data with Authorization key in the header. If there is no data, it indicates that there is no login, and return the result. If data with the key as Authorization exists in the header, it is determined whether the data is Bearer (this can be customized). If so, it indicates that the login has been performed, and then it returns true.

@Slf4j
public class AuthInterceptorHandler implements HandlerInterceptor {

    @Autowired
    private JwtKit jwtKit;

    @Autowired
    private JwtProperties jwtProperties;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        log.info("Enter interceptor.");
        String aa = request.getHeader("aa");
        String authorization =request.getHeader(jwtProperties.getTokenHeader());
        log.info("Authorization"+authorization);
        // If the head is not empty, the data stored in the head is saved.
        if(StringUtils.isNotEmpty(authorization) && authorization.startsWith(jwtProperties.getTokenHead())){
            String authToken = authorization.substring(jwtProperties.getTokenHead().length());
            Claims claims=null;
            try {
                claims=jwtKit.parseJwtToken(authToken);
                if(claims! =null) {return true; }}catch (MyException e){
                log.info(e.getMessage()+":"+authToken);
            }
        }

        response.setHeader("Content-Type"."application/json");
        response.setCharacterEncoding("UTF-8");
        String result = new ObjectMapper().writeValueAsString(new CommonResult(ResponseCode.NEED_LOGIN.getCode(), ResponseCode.NEED_LOGIN.getMsg(), ""));
        response.getWriter().println(result);
        return false; }}Copy the code

Finally, write a test class:

@RestController
public class IndexController extends BaseController{

    @Autowired
    private JwtProperties jwtProperties;
    @Autowired
    private JwtKit jwtKit;

    @RequestMapping(value = "/index",method = RequestMethod.GET)
    public CommonResult index(a){

        String authorization = getRequest().getHeader(jwtProperties.getTokenHeader());
        String authToken = authorization.substring(jwtProperties.getTokenHead().length());
        Claims claims=jwtKit.parseJwtToken(authToken);
        return new CommonResult(ResponseCode.SUCCESS.getCode(),ResponseCode.SUCCESS.getMsg(),claims.get("sub")); }}Copy the code

(5) Effect test

First of all, I have direct access to http://localhost:8189/index, return the result to log in failure, because there is no header

So the first access login interface: http://localhost:8189/sso/jwtlogin

The actual tokenHead and token values are returned. Put this token into the header and access the index interface again:

The operation succeeds, and the user information can be obtained.

(6) Comparison between JWT and Session

In the previous article I used Session to implement authentication, but additional Redis servers are required to implement distributed authentication. Using JWT does not require an additional server; it puts the token in the header.

However, JWT also has disadvantages. The most obvious one is that the token cannot be manually invalidated. After the token is generated, the token is still valid even if you log out.

The second disadvantage is that personnel information is base64 data, which means it can be used as soon as it is available, so JWT can only transmit non-sensitive personnel data.

The third disadvantage is that each request needs to carry token information in the header, which increases the bandwidth pressure. Don’t think the header of a single request takes up a bit more bandwidth. What about 10,000 or 100,000 requests? Bandwidth is a precious resource.

To sum up, JWT and Session have their pros and cons, and how they are used depends on your system. All right, if you have any questions, leave them in the comments and we’ll see you next time!