JWT是什么:
JSON Web Token(JWT)是一个开放标准(RFC 7519),它定义了一种紧凑且独立的方式,用于在各方之间作为JSON对象安全地传输信息。
Ø 官方网址:https://jwt.io/
Ø 调试页面:https://jwt.io/
Ø 学习文档:https://jwt.io/introduction/
用途:
Ø 授权:这是我们使用JWT最广泛的应用场景。一次用户登录,后续请求将会包含JWT,对于那些合法的token,允许用户连接路由,服务和资源。目前JWT广泛应用在SSO(Single Sign On)(单点登录)上。因为他们开销很小并且可以在不同领域轻松使用。
Ø 信息交换:JSON Web Token是一种在各方面之间安全信息传输的好的方式 因为JWT可以签名 - 例如,使用公钥/私钥对 - 您可以确定发件人是他们所说的人。 此外,由于使用标头和有效负载计算签名,您还可以验证内容是否未被篡改。
组成:
一个JWT由三部分组成,各部分以点分隔:
Header(头部)——-base64编码的Json字符串 Payload(载荷)—-base64编码的Json字符串 Signature(签名)—-使用指定算法,通过Header和Playload加盐计算的字符串 |
---|
一个JWT看起来像下面这样:
xxxxx.yyyyy.zzzzz |
---|
header
此部分有两部分组成:
Ø 一部分是token的类型,目前只能是JWT
Ø 另一部分是签名算法,比如HMAC 、 SHA256 、 RSA
示例:
{ “alg”:”HS256”, “typ”:”JWT” } |
---|
base64编码命令:
echo -n ‘{“alg”:”HS256”,”typ”:”JWT”}’ | base64 或者 echo -n “{\“alg\“:\“HS256\“,\“typ\“:\“JWT\“}” | base64 |
---|
Payload
token的第二部分是payload(有效负载),其中包含claims(声明)。Claims是关于一个实体(通常是用户)和其他数据类型的声明。
claims有三种类型:registered,public,and private claims。
Ø Registered(已注册的声明):这些是一组预定义声明,不是强制性的,但建议使用,以提供一组有用的,可互操作的声明。 其中一些是:iss(发行人),exp(到期时间),sub(主题),aud(观众)and others。(请注意,声明名称只有三个字符,因为JWT意味着紧凑。)
Ø Public(公开声明):这些可以由使用JWT的人随意定义。 但为避免冲突,应在IANA JSON Web Token Registry中定义它们,或者将其定义为包含防冲突命名空间的URI。
Ø private (私人声明):这些声明是为了在同意使用它们的各方之间共享信息而创建的,并且既不是注册声明也不是公开声明。
Signature
要创建签名部分,您需要使用base64编码后的header,base64编码后的payload,a secret,标头中指定的算法,并对其进行签名。
示例:
HMACSHA256( base64UrlEncode(header) + “.” + base64UrlEncode(payload), secret) |
---|
Base64和Base64Url 的区别
Base64Url在Base64的基础上进行以下处理:
- 去除了尾部填充的“=”
- 把“+”替换成“-”
- 斜线“/”替换成下划线“_”
Spring-boot集成
导入依赖:
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>4.0.0</version>
</dependency>
编写JwtUtil:
@Component
@Slf4j
public class JwtUtil {
/**
* 创建token
* @return
*/
public String createToken() {
// token 密钥
String secret = "123456";
//签名算法
Algorithm algorithm = Algorithm.HMAC256(secret);
// 头部信息
Map<String, Object> map = new HashMap<>();
//JWT 的签名算法
map.put("alg", "HS256");
//token的类型
map.put("typ", "JWT");
//获取当前时间
long currentTime = System.currentTimeMillis();// + 30 * 60 * 1000;
//获取当前日期
Date nowDate = new Date(currentTime);
//当前时间加上30分钟
Date expireDate = new Date(currentTime + 30 * 60 * 1000);// 半小时过期
//创建token
String token = JWT.create()
.withHeader(map)// 设置头部信息 Header
.withIssuer("Thomas")//设置 载荷 签名是有谁生成 例如 服务器
.withSubject("this is test token")//设置 载荷 签名的主题
// .withNotBefore(new Date())//设置 载荷 定义在什么时间之前,该jwt都是不可用的.
.withAudience("APP")//设置 载荷 签名的观众 也可以理解谁接受签名的
.withIssuedAt(nowDate) //设置 载荷 生成签名的时间
.withExpiresAt(expireDate)//设置 载荷 签名过期的时间
.sign(algorithm);//签名 Signature
log.info("生成的JWT 为:"+token);
return token;
}
/**
* 校验token
* @param token
* @throws UnsupportedEncodingException
*/
public void verifyToken(String token) throws UnsupportedEncodingException {
// token 密钥
String secret = "123456";
Algorithm algorithm = Algorithm.HMAC256(secret);
JWTVerifier verifier = JWT.require(algorithm).withIssuer("Thomas").build(); // Reusable verifier instance
try {
DecodedJWT jwt = verifier.verify(token);
String subject = jwt.getSubject();
List<String> audience = jwt.getAudience();
Map<String, Claim> claims = jwt.getClaims();
for (Map.Entry<String, Claim> entry : claims.entrySet()) {
String key = entry.getKey();
Claim claim = entry.getValue();
System.out.println("key:" + key + " value:" + claim.asString());
}
// Claim claim = claims.get("loginName");
//
// System.out.println(claim.asString());
// System.out.println(subject);
// System.out.println(audience.get(0));
}catch(Exception ex){
System.out.println("验证失败!!");
}
}
public String createTokenWithChineseClaim() {
long currentTime = System.currentTimeMillis();// + 30 * 60 * 1000;
Date nowDate = new Date(currentTime);
Date expireDate = new Date(currentTime+ 30 * 60 * 1000);// 半小时过期
Map<String, Object> map = new HashMap<String, Object>();
map.put("alg", "HS256");
map.put("typ", "JWT");
Algorithm algorithm = Algorithm.HMAC256("secret");
String token = JWT.create().withHeader(map)
/* 设置 载荷 Payload */
.withClaim("loginName", "zhuoqianmingyue").withClaim("userName", "张三").withClaim("deptName", "技术部")
.withIssuer("SERVICE")// 签名是有谁生成 例如 服务器
.withSubject("this is test token")// 签名的主题
// .withNotBefore(new Date())//定义在什么时间之前,该jwt都是不可用的
.withAudience("APP")// 签名的观众 也可以理解谁接受签名的
.withIssuedAt(nowDate) // 生成签名的时间
.withExpiresAt(expireDate)// 签名过期的时间
/* 签名 Signature */
.sign(algorithm);
return token;
}
// public String createTokenWithChineseClaim2() throws UnsupportedEncodingException {
//
// long currentTime = System.currentTimeMillis();// + 30 * 60 * 1000;
//
// Date nowDate = new Date(currentTime);
//
//
// Date expireDate = new Date(currentTime+ 30 * 60 * 1000);// 半小时过期
// //生产头部信息
// Map<String, Object> map = new HashMap<String, Object>();
// map.put("alg", "HS256");
// map.put("typ", "JWT");
//
// Member member = new Member();
// member.setMemberAccount("admin");
// member.setMemberName("张三");
//
// Gson gson = new Gson();
//
//
// String userJson = gson.toJson(member);
//
//
// String userJsonBase64 = BaseEncoding.base64().encode(userJson.getBytes());
//
// Algorithm algorithm = Algorithm.HMAC256("secret");
// String token = JWT.create().withHeader(map)
//
// .withClaim("loginName", "zhuoqianmingyue").withClaim("user", userJsonBase64).withIssuer("SERVICE")// 签名是有谁生成
// .withSubject("this is test token")// 签名的主题
// // .withNotBefore(new Date())//该jwt都是不可用的时间
// .withAudience("APP")// 签名的观众 也可以理解谁接受签名的
// .withIssuedAt(nowDate) // 生成签名的时间
// .withExpiresAt(expireDate)// 签名过期的时间
// .sign(algorithm);//签名 Signature
//
// return token;
//
// }
}
编写测试类:
@SpringBootTest
@RunWith(SpringRunner.class)
public class JwtUtilTest {
@Resource
private JwtUtil jwtUtil;
@Test
public void createToken() {
jwtUtil.createToken();
}
@Test
public void verifyToken() {
String token="eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ0aGlzIGlzIHRlc3QgdG9rZW4iLCJhdWQiOiJBUFAiLCJpc3MiOiJUaG9tYXMiLCJleHAiOjE2MTkwNzM5NTAsImlhdCI6MTYxOTA3MjE1MH0.7Os9SyBdsFbEgMZoG6ZwOlqqgx0qpbk36B2AOKKixXY";
try {
jwtUtil.verifyToken(token);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}
}