4

Previously wrote an article on the interface of the service specification, original in this , which focuses on security issues through appid , appkey , timestamp , nonce and sign to get token , use token security services to protect the interface. Today we will talk about a more convenient way, using jwt to generate token.

1. What is JWT

JSON Web Token ( JWT ) defines a compact and self-contained way for the secure transmission of information between parties as JSON objects. This information can be verified and trusted because it is digitally signed. JWT can set the validity period.

JWT is a very long string, including Header , Playload and Signature , separated .

Headers

Headers part describes the JWT , which generally includes the signature algorithm and token type. The data is as follows:

{
    "alg": "RS256",
    "typ": "JWT"
}

Playload

Playload is the place where valid information is stored. JWT specifies the following 7 fields, which are recommended but not mandatory:

iss: jwt签发者
sub: jwt所面向的用户
aud: 接收jwt的一方
exp: jwt的过期时间,这个过期时间必须要大于签发时间
nbf: 定义在什么时间之前,该jwt都是不可用的
iat: jwt的签发时间
jti: jwt的唯一身份标识,主要用来作为一次性token

In addition, we can also customize the content

{
    "name":"Java旅途",
    "age":18
}

Signature

Signature is JWT front part two encrypted string, the Headers and Playload be base64 using coded Headers encryption algorithm and a predetermined key in the encrypted obtain JWT third portion.

Two, JWT generation and parsing token

Introduce the dependency of JWT in the application service

<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.9.0</version>
</dependency>

The JWT generated using a defined RSA encryption algorithm is valid for 30 minutes token

public static String createToken(User user) throws Exception{

    return Jwts.builder()
        .claim("name",user.getName())
        .claim("age",user.getAge())
        // rsa加密
        .signWith(SignatureAlgorithm.RS256, RsaUtil.getPrivateKey(PRIVATE_KEY))
        // 有效期30分钟
        .setExpiration(DateTime.now().plusSeconds(30 * 60).toDate())
        .compact();
}

After the login interface is verified, call JWT generate a token response with the user ID to the user. In the next request, the header carries token for verification. After verification, the application service is accessed normally.

public static Claims parseToken(String token) throws Exception{
    return Jwts
        .parser()
        .setSigningKey(RsaUtil.getPublicKey(PUBLIC_KEY))
        .parseClaimsJws(token)
        .getBody();
}

Three, token renewal issues

The above tells about the JWT . Now we consider such a problem. The client carries token access the order interface, token verification, the client places the order successfully, returns the order result, and then the client calls the payment interface token During the payment, the token is found to be invalid when verifying the signature. What should I do at this time? Can only tell the user that token invalid, and then ask the user to log in again to get token ? This kind of experience is very bad. oauth2 does a good job in this regard. In addition to issuing token , it will also issue refresh_token . When token expires, it will call refresh_token to retrieve token . If refresh_token has expired, then remind the user Go to log in. Now we simulate oauth2 implementation to complete JWT of refresh_token .

The idea is probably that after the user logs in successfully, when token is issued, an encrypted string is generated as refresh_token , refresh_token stored in redis , and a reasonable expiration time is set (generally, the expiration time of refresh_token Then the token and refresh_token response to the client. The pseudo code is as follows:

@PostMapping("getToken")
public ResultBean getToken(@RequestBody LoingUser user){

    ResultBean resultBean = new ResultBean();
    // 用户信息校验失败,响应错误
    if(!user){
        resultBean.fillCode(401,"账户密码不正确");
        return resultBean;
    }
    String token = null;
    String refresh_token = null;
    try {
        // jwt 生成的token
        token = JwtUtil.createToken(user);
        // 刷新token
        refresh_token = Md5Utils.hash(System.currentTimeMillis()+"");
        // refresh_token过期时间为24小时
        redisUtils.set("refresh_token:"+refresh_token,token,30*24*60*60);
    } catch (Exception e) {
        e.printStackTrace();
    }

    Map<String,Object> map = new HashMap<>();
    map.put("access_token",token);
    map.put("refresh_token",refresh_token);
    map.put("expires_in",2*60*60);
    resultBean.fillInfo(map);
    return resultBean;
}

When the client calls the interface, it carries token in the request header, intercepts the request in the interceptor, and verifies token . If the verification of token fails, go to redis to determine whether it is refresh_token . If the refresh_token also fails, give The client responds to the authentication exception and prompts the client to log in again. The pseudo code is as follows:

HttpHeaders headers = request.getHeaders();
// 请求头中获取令牌
String token = headers.getFirst("Authorization");
// 判断请求头中是否有令牌
if (StringUtils.isEmpty(token)) {
    resultBean.fillCode(401,"鉴权失败,请携带有效token");
    return resultBean;
}
if(!token.contains("Bearer")){
    resultBean.fillCode(401,"鉴权失败,请携带有效token");
    return resultBean;
}

token = token.replace("Bearer ","");
// 如果请求头中有令牌则解析令牌
try {
    Claims claims = TokenUtil.parseToken(token).getBody();
} catch (Exception e) {
    e.printStackTrace();
    String refreshToken = redisUtils.get("refresh_token:" + token)+"";
    if(StringUtils.isBlank(refreshToken) || "null".equals(refreshToken)){
        resultBean.fillCode(403,"refresh_token已过期,请重新获取token");
        return resultbean;
    }
}

The pseudo code of refresh_token in exchange for token

@PostMapping("refreshToken")
public Result refreshToken(String token){

    ResultBean resultBean = new ResultBean();
    String refreshToken = redisUtils.get(TokenConstants.REFRESHTOKEN + token)+"";
    String access_token = null;
    try {
        Claims claims = JwtUtil.parseToken(refreshToken);
        String username = claims.get("username")+"";
        String password = claims.get("password")+"";
        LoginUser loginUser = new LoginUser();
        loginUser.setUsername(username);
        loginUser.setPassword(password);
        access_token = JwtUtil.createToken(loginUser);
    } catch (Exception e) {
        e.printStackTrace();
    }
    Map<String,Object> map = new HashMap<>();
    map.put("access_token",access_token);
    map.put("refresh_token",token);
    map.put("expires_in",30*60);
    resultBean.fillInfo(map);
    return resultBean;
}

Through the above analysis, we simply realized token issued, signed and renewed experience problems, JWT as a lightweight authentication framework, ease of use, but also there are some problems,

  • JWT of Playload part just after base64 encoded, so our message is actually completely exposed, and generally do not store sensitive information in JWT in.
  • JWT generated by token relatively long, and each time token is carried in the request header, the request stolen will be relatively large, which has certain performance problems.
  • JWT generated, the server cannot be discarded and can only wait for JWT actively expire.

The following paragraph is a scene I saw on the Internet about JWT

  • Short period of validity
  • Only want to be used once

For example, a user sends an email to activate the account after registration. Usually, there is a link in the email. This link needs to have the following characteristics: it can identify the user, and the link is time-sensitive (usually only allowed to activate within a few hours), Can not be tampered with to activate other possible accounts, one-time. This scenario is suitable for use JWT .


Java旅途
1.1k 声望6.1k 粉丝