详解基于JWT的token认证(Java实现)
观前提示:
本文所使用的IDEA版本为ultimate 2019.1,JDK版本为1.8.0_141。
1.简介
在计算机身份认证中是令牌(临时)的意思,token其实说的更通俗点可以叫暗号,在一些数据传输之前,要先进行暗号的核对,不同的暗号被授权不同的数据操作。
2.JWT
JSON Web Token,JSON Web令牌,我们下面的例子也是基于JWT实现的token认证,如下是一个完成的JWT串
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1ODMyOTQxNzEsInVzZXJOYW1lIjoidXNlciIsInBhc3N3b3JkIjoiMTExMTExMTEifQ.t6JLktclylpt2s5T_B1KDxYCJALr-fbZsy1J_YT57Jk
它是由三部分构成:header,payload和signature
2.1 header
由两部分构成
-
声明类型,为JWT。
-
声明加密的算法,使用 HMAC SHA256
JWT头部分是一个JSON对象,如下所示
{
'typ': 'JWT',
'alg': 'HS256'
}
对其进行Base64加密,则JWT构成第一部分。
2.2 payload
载荷部分是JWT的主体内容部分,也是一个JSON对象,由三部分构成
-
标准中注册的声明
-
公共的声明
-
私有的声明
标准中注册的声明提供了七个默认字段。
-
iss:发行人
-
exp:到期时间
-
sub:主题
-
aud:用户
-
nbf:在此之前不可用
-
iat:发布时间
-
jti:JWT ID用于标识该JWT
公共的声明
公共的声明可以添加任何的信息,一般添加用户的相关信息或其他业务需要的必要信息(不推荐敏感信息)。
私有的声明
私有声明是提供者和消费者所共同定义的声明(不推荐敏感信息)。
如下例是一个样例的payload
{
"sub": "1234567890",
"name": "userName",
"admin": true
}
对其进行Base64加密,构成JWT的第二部分。
注:默认情况下JWT是未加密的,任何人都可以解读其内容,因此不要构建隐私信息字段,存放保密信息,以防止信息泄露。
2.3 signature
签名哈希部分是对上面两部分数据签名,通过指定的算法生成哈希,以确保数据不会被篡改。由三部分组成:
-
header (Base64加密后的)
-
payload (Base64加密后的)
-
secret
服务器需要一个秘钥(secret),且仅仅保存在服务器中,并且不能向用户公开。然后,使用标头中指定的签名算法(默认情况下为HMAC SHA256)根据以下公式生成签名。
HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)
在计算出签名哈希后,JWT头,有效载荷和签名哈希的三个部分组合成一个字符串,每个部分用"."分隔,就构成整个JWT对象。
3.使用
客户端接收服务器返回的JWT,将其存储在Cookie或localStorage中。
此后,客户端将在与服务器交互中都会带JWT。如果将它存储在Cookie中,就可以自动发送,但是不会跨域,因此一般是将它放入HTTP请求的Header Authorization字段中。
使用格式如下
Authorization: Bearer JWT
JWT交互的整个流程如下图所示
4.例子
本例子仅仅提供了java实现JWT的签名和验证,http传输使用待以后补上。
pom.xml需要引用的jar包
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.10.0</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.10.2</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.10.2</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.10.2</version>
</dependency>
工具类 TokenUtil.java
package token;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
public class TokenUtil {
//token过期时长30分钟
private static final long EXPIRE_TIME = 30 * 60 * 1000;
//token私钥
private static final String TOKEN_SECRET = "abcdefg";
public static String sign(String userName, String password) {
String signData = "";
//过期时间
Date date = new Date(System.currentTimeMillis() + EXPIRE_TIME);
//私钥及加密算法
Algorithm algorithm = Algorithm.HMAC256(TOKEN_SECRET);
//设置头信息
Map<String, Object> header = new HashMap();
header.put("typ", "JWT");
header.put("alg", "HS256");
signData = JWT.create()
.withHeader(header)
.withClaim("userName", userName)
.withClaim("password", password)
.withExpiresAt(date)
.sign(algorithm);
return signData;
}
/**
* @Description token解码校验
* @param token
* @return
* @Create 2020-03-03 by jjy
*/
public static boolean verfiy(String token) {
try {
Algorithm algorithm = Algorithm.HMAC256(TOKEN_SECRET);
JWTVerifier jwtVerifier = JWT.require(algorithm).build();
DecodedJWT decodedJWT = jwtVerifier.verify(token);
String userName = decodedJWT.getClaim("userName").asString();
String password = decodedJWT.getClaim("password").asString();
if(!Test.USERNAME.equals(userName) || !Test.PASSWORD.equals(password)) {
return false;
}
if(new Date().getTime() > decodedJWT.getExpiresAt().getTime()){
return false;
}
} catch (Exception e) {
return false;
}
return true;
}
}
测试类 Test.java
package token;
public class Test {
public static final String USERNAME = "user";
public static final String PASSWORD = "11111111";
public static void main(String[] args) {
String token = TokenUtil.sign(USERNAME, PASSWORD);
System.out.println("加密后的token为:" + token);
boolean flag = TokenUtil.verfiy(token);
if(flag){
System.out.println("校验成功");
} else {
System.out.println("校验失败");
}
}
}
运行结果如下