JWT: JSON Web Token
1. jwt概述
用户登录成功后,服务端 如何知道客户端的每次请求对应的是哪个用户呢?怎么做:目前有两种方式实现.
1.1. 一是通过sessionId的方式,登录成功后服务端返回sessionId给客户端,然后浏览器将sessionId保存在Cookie中,这样每次浏览器请求的时候都带上sessionId,通过sessionId就能在服务端找到对应的session,就能知道是哪个用户。 (session里记录了用户的相关信息,登录时间等)
1.2. 二是用户登录成功后,服务端根本就不存用户的session信息,用户登录成功后,服务端将用户的信息返回给客户端,客户端自己保存用户信息,然后每次客户端请求的时候,将用户信息传给服务端,这就是jwt的原理。
2. JWT 的原理
JWT 的原理是,服务器认证以后,生成一个 JSON 对象,发回给用户,就像下面这样。
{ "name": "jack", "role": "admin", "id": 123456 }
以后,用户与服务端通信的时候,都要发回这个 JSON 对象。服务器完全只靠这个对象认定用户身份。为了防止用户篡改数据,服务器在生成这个对象的时候,会加上签名(详见后文)。
服务器就不保存任何 session 数据了,也就是说,服务器变成无状态了,从而比较容易实现扩展。
3. jwt数据结构
jwt是一个很长的字符串,中间用点(.
)分隔成三个部分。注意,JWT 内部是没有换行的,这里只是为了便于展示,将它写成了几行。
JWT 的三个部分依次如下。
- Header(头部)
- Payload(负载)
- Signature(签名)
类似这样的:
3.1 Header
Header 部分是一个 JSON 对象,描述 JWT 的元数据,通常是下面的样子。
{ "alg": "HS256", "typ": "JWT" }
上面代码中,alg
属性表示签名的算法(algorithm),默认是 HMAC SHA256(写成 HS256);typ
属性表示这个令牌(token)的类型(type),JWT 令牌统一写为JWT
。
最后,将上面的 JSON 对象使用 Base64URL 算法(详见后文)转成字符串。
3.2 Payload
Payload 部分也是一个 JSON 对象,用来存放实际需要传递的数据。JWT 规定了7个官方字段,供选用。
- iss (issuer):签发人
- exp (expiration time):过期时间
- sub (subject):主题
- aud (audience):受众
- nbf (Not Before):生效时间
- iat (Issued At):签发时间
- jti (JWT ID):编号
除了官方字段,你还可以在这个部分定义私有字段,下面就是一个例子。
{ "sub": "1234567890", "username": "John Doe", "userId": 123654 }
注意,JWT 默认是不加密的,任何人都可以读到,所以不要把秘密信息放在这个部分。
这个 JSON 对象也要使用 Base64URL 算法转成字符串。
3.3 Signature
Signature 部分是对前两部分的签名,防止数据篡改。
首先,需要指定一个密钥(secret)。这个密钥只有服务器才知道,不能泄露给用户。然后,使用 Header 里面指定的签名算法(默认是 HMAC SHA256),按照下面的公式产生签名。
HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)
算出签名以后,把 Header、Payload、Signature 三个部分拼成一个字符串,每个部分之间用"点"(.
)分隔,就可以返回给用户。
3.4 Base64URL
前面提到,Header 和 Payload 串型化的算法是 Base64URL。这个算法跟 Base64 算法基本类似,但有一些小的不同。
JWT 作为一个令牌(token),有些场合可能会放到 URL(比如 api.example.com/?token=xxx)。Base64 有三个字符+
、/
和=
,在 URL 里面有特殊含义,所以要被替换掉:=
被省略、+
替换成-
,/
替换成_
。这就是 Base64URL 算法。
4、 使用 jwt
客户端收到服务器返回的 JWT,可以储存在 Cookie 里面,也可以储存在 localStorage。
此后,客户端每次与服务器通信,都要带上这个 JWT。你可以把它放在 Cookie 里面自动发送,但是这样不能跨域,所以更好的做法是放在 HTTP 请求的头信息Authorization
字段里面。
类似这样,这些数据传输:
Authorization: Bearer <token>
5、 自定义实现jwt
说了这么多,结下来我们自己来实现定义一个jwt数据的生成,jwt数据的解码工作,通过目前市面上开源的java-jwt jar包,还是很方便的
import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.TokenExpiredException;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.gfm.asset.base.exception.InteractException;
import lombok.SneakyThrows;
import org.joda.time.DateTime;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @author xxx
*/
public class JwtHelper {
private static final Logger logger = LoggerFactory.getLogger(JwtHelper.class);
/**
* 密钥加密token
*/
@SneakyThrows
public static String generateToken(String userKey, String secretKey, int expire) {
Algorithm algorithm = Algorithm.HMAC256(secretKey);
String token = JWT.create()
.withIssuer(userKey)
.withIssuedAt(new Date())
.withExpiresAt(DateTime.now().plusSeconds(expire).toDate())
.sign(algorithm);
logger.info("==> 生成jwtToken, userKey={},secretKey={},过期时间={}s,token={}", userKey, secretKey, expire, token);
return token;
}
/**
* 公钥解析token
*/
public static String parserToken(String token, String secretKey) {
try {
Algorithm algorithm = Algorithm.HMAC256(secretKey);
DecodedJWT jwt = JWT.require(algorithm)
.build().verify(token);
return jwt.getIssuer();
} catch (TokenExpiredException e) {
throw new InteractException("token has expired");
} catch (Exception e) {
e.printStackTrace();
throw new InteractException("token is invalid");
}
}
public static void main(String[] args) {
String userKey = "center_sys";
String secretKey = "center_sys@#$";
String token = generateToken(userKey, secretKey, 60 * 60 * 24 * 7);
System.out.println(token);
// String key = parserToken(token, secretKey);
// System.out.println(key);
}
}
pom.xml文件
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.19.1</version>
<exclusions>
<exclusion>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</exclusion>
</exclusions>
</dependency>
参考:
JSON Web Token 入门教程 - 阮一峰的网络日志