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
ofPlayload
part just after base64 encoded, so our message is actually completely exposed, and generally do not store sensitive information inJWT
in.JWT
generated bytoken
relatively long, and each timetoken
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 forJWT
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
.
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。