3.Jwt令牌回顾
JSON Web Token(JWT)是一个开放的行业标准(RFC 7519),它定义了一种简介的、自包含的协议格式,用于 在通信双方传递json对象,传递的信息经过数字签名可以被验证和信任。JWT可以使用HMAC算法或使用RSA的公 钥/私钥对来签名,防止被篡改。
官网:https://jwt.io/
标准:https://tools.ietf.org/html/rfc7519
JWT令牌的优点:
1、jwt基于json,非常方便解析。
2、可以在令牌中自定义丰富的内容,易扩展。
3、通过非对称加密算法及数字签名技术,JWT防止篡改,安全性高。
4、资源服务使用JWT可不依赖认证服务即可完成授权。
缺点:
占用存储空间大
3.1 令牌结构
通过学习JWT令牌结构为自定义jwt令牌打好基础。
JWT令牌由三部分组成,每部分中间使用点(.)分隔,比如:xxxxx.yyyyy.zzzzz
Header
头部包括令牌的类型(即JWT)及使用的哈希算法(如HMAC SHA256或RSA)
一个例子如下:
下边是Header部分的内容
{
"alg": "HS256",
"typ": "JWT"
}
将上边的内容使用Base64Url编码,得到一个字符串就是JWT令牌的第一部分。
Payload
第二部分是负载,内容也是一个json对象,它是存放有效信息的地方,它可以存放jwt提供的现成字段,比 如:iss(签发者),exp(过期时间戳), sub(面向的用户)等,也可自定义字段。此部分不建议存放敏感信息,因为此部分可以解码还原原始内容。最后将第二部分负载使用Base64Url编码,得到一个字符串就是JWT令牌的第二部分。
一个例子:
{
"sub": "1234567890",
"name": "456",
"admin": true
}
Signature
第三部分是签名,此部分用于防止jwt内容被篡改。这个部分使用base64url将前两部分进行编码,编码后使用点(.)连接组成字符串,最后使用header中声明 签名算法进行签名。
一个例子:
ACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret)
base64UrlEncode(header):jwt令牌的第一部分。
base64UrlEncode(payload):jwt令牌的第二部分。
secret:签名所使用的密钥。
3.2 生成私钥公钥
JWT令牌生成采用非对称加密算法
1、生成密钥证书 下边命令生成密钥证书,采用RSA 算法每个证书包含公钥和私钥
keytool -genkeypair -alias changgou -keyalg RSA -keypass changgou -keystore
changgou.jks -storepass changgou
Keytool 是一个java提供的证书管理工具
-alias:密钥的别名
-keyalg:使用的hash算法
-keypass:密钥的访问密码
-keystore:密钥库文件名,changgou.jks保存了生成的证书
-storepass:密钥库的访问密码
查询证书信息:
keytool -list -keystore changgou.jks
2、导出公钥
openssl是一个加解密工具包,这里使用openssl来导出公钥信息。
安装 openssl:http://slproweb.com/products/Win32OpenSSL.html
安装资料目录下的Win64OpenSSL-1_1_1b.exe
配置openssl的path环境变量,
cmd进入changgou.jks文件所在目录执行如下命令:
keytool -list -rfc --keystore changgou.jks | openssl x509 -inform pem -pubkey
下面段内容是公钥
----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvFsEiaLvij9C1Mz+oyAm
t47whAaRkRu/8kePM+X8760UGU0RMwGti6Z9y3LQ0RvK6I0brXmbGB/RsN38PVnh
cP8ZfxGUH26kX0RK+tlrxcrG+HkPYOH4XPAL8Q1lu1n9x3tLcIPxq8ZZtuIyKYEm
oLKyMsvTviG5flTpDprT25unWgE4md1kthRWXOnfWHATVY7Y/r4obiOL1mS5bEa/
iNKotQNnvIAKtjBM4RlIDWMa6dmz+lHtLtqDD2LF1qwoiSIHI75LQZ/CNYaHCfZS
xtOydpNKq8eb1/PGiLNolD4La2zf0/1dlcr5mkesV570NxRmU1tFm8Zd3MZlZmyv
9QIDAQAB
-----END PUBLIC KEY-----
将上边的公钥拷贝到文本public.key文件中,合并为一行,可以将它放到需要实现授权认证的工程中。
3.3 基于私钥生成jwt令牌
3.3.1导入认证服务
-
将课件中 changgou_user_auth 的工程导入到项目中去,如下图:
-
启动eureka,再启动认证服务
3.3.2 认证服务中创建测试类
package com.changgou.oauth;
import com.alibaba.fastjson.JSON;
import org.junit.Test;
import org.springframework.core.io.ClassPathResource;
import org.springframework.security.jwt.Jwt;
import org.springframework.security.jwt.JwtHelper;
import org.springframework.security.jwt.crypto.sign.RsaSigner;
import org.springframework.security.rsa.crypto.KeyStoreKeyFactory;
import java.security.KeyPair;
import java.security.interfaces.RSAPrivateKey;
import java.util.HashMap;
import java.util.Map;
public class CreateJWTTest {
@Test
public void createJWT(){
//基于私钥生成jwt
//1. 创建一个秘钥工厂
//1: 指定私钥的位置
ClassPathResource classPathResource = new ClassPathResource("changgou.jks");
//2: 指定秘钥库的密码
String keyPass = "changgou";
KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(classPathResource,keyPass.toCharArray());
//2. 基于工厂获取私钥
String alias = "changgou";
String password = "changgou";
KeyPair keyPair = keyStoreKeyFactory.getKeyPair(alias, password.toCharArray());
//将当前的私钥转换为rsa私钥
RSAPrivateKey rsaPrivateKey = (RSAPrivateKey) keyPair.getPrivate();
//3.生成jwt
Map<String,String> map = new HashMap();
map.put("company","heima");
map.put("address","beijing");
Jwt jwt = JwtHelper.encode(JSON.toJSONString(map), new RsaSigner(rsaPrivateKey));
String jwtEncoded = jwt.getEncoded();
System.out.println(jwtEncoded);
}
}
3.4 基于公钥解析jwt令牌
上面创建令牌后,我们可以对JWT令牌进行解析,这里解析需要用到公钥,我们可以将之前生成的公钥public.key拷贝出来用字符串变量token存储,然后通过公钥解密。在changgou-user-oauth创建测试类com.changgou.token.ParseJwtTest实现解析校验令牌数据,代码如下
package com.changgou.oauth;
import org.junit.Test;
import org.springframework.security.jwt.Jwt;
import org.springframework.security.jwt.JwtHelper;
import org.springframework.security.jwt.crypto.sign.RsaVerifier;
public class ParseJwtTest {
@Test
public void parseJwt(){
//基于公钥去解析jwt
String jwt ="eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJhZGRyZXNzIjoiYmVpamluZyIsImNvbXBhbnkiOiJoZWltYSJ9.cjZNz8G0m4noNYN2VM1SH3ujAtbHElW5Vtbadb0NDI0cjM1DaAXzMA53Qbj4pmVQPl_IfSKqUEXbLxowdRa5NHR43laFsR0kzGbJiTINfSVSroSslYpDdEVwCeAF_a7I-R819YTj4p6sjuYKXbzXpeZQErczFbWWWGR2_U44xH6u1ejRNv8PikFiuzNw-muL7zUJkvqeSJzbEMnQdZMbfvZp4LtSI6B4G_PqpdNXkv19-juxAh99VgJInH_ItF0y5IBOxofA7gRebCZmU8L57gO9ohf2L00D95kis_Ji8lmA1ptLIfXqO_qLVvLBUNH-VtgjGAF0-0pyB-5jlbHP7w";
String publicKey ="-----BEGIN PUBLIC KEY-----MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvFsEiaLvij9C1Mz+oyAmt47whAaRkRu/8kePM+X8760UGU0RMwGti6Z9y3LQ0RvK6I0brXmbGB/RsN38PVnhcP8ZfxGUH26kX0RK+tlrxcrG+HkPYOH4XPAL8Q1lu1n9x3tLcIPxq8ZZtuIyKYEmoLKyMsvTviG5flTpDprT25unWgE4md1kthRWXOnfWHATVY7Y/r4obiOL1mS5bEa/iNKotQNnvIAKtjBM4RlIDWMa6dmz+lHtLtqDD2LF1qwoiSIHI75LQZ/CNYaHCfZSxtOydpNKq8eb1/PGiLNolD4La2zf0/1dlcr5mkesV570NxRmU1tFm8Zd3MZlZmyv9QIDAQAB-----END PUBLIC KEY-----";
Jwt token = JwtHelper.decodeAndVerify(jwt, new RsaVerifier(publicKey));
String claims = token.getClaims();
System.out.println(claims);
}
}