JSON Web Token(JWT)是一个开放的行业标准(RFC 7519),它定义了一种简介的、自包含的协议格式,用于
在通信双方传递json对象,传递的信息经过数字签名可以被验证和信任。JWT可以使用HMAC算法或使用RSA的公
钥/私钥对来签名,防止被篡改。
目前jwt是最流行的跨域身份验证解决的方案
该对象是一个很长的字符串,字符之间通过.分割,jwt令牌分为三段数据,分别为 header
{
"alg": "HS256",
"typ": "JWT"
}
头部包括令牌的类型(即JWT)及使用的哈希算法(如HMAC SHA256或RSA)编码过后得到的就是 jwt令牌 第一部分字符串
Payload挂载
第二部分是挂载部分,内容也是一个json对象,它是存放有效信息的地方,它可以存放jwt提供的现成字段,比如:iss(签发者),exp(过期时间戳), sub(面向的用户)等,也可自定义字段。
{
"sub": "1234567890",
"name": "456",
"admin": true
}
使用Base64Url编码,得到一个字符串就是JWT令牌的第二部分。
JWT指定七个默认字段供选择。
iss:发行人
exp:到期时间
sub:主题
aud:用户
nbf:在此之前不可用
iat:发布时间
jti:JWT ID用于标识该JWT
Signature 签名
该部分是为了防止用户盗取签名,此部分用于防止jwt内容被篡改。
jwt 令牌结构如下图所释
下面是网上找的俩张使用jwt前后对比图
缺点: 虽然系统看上去没有问题,但是每次请求都需要资源服务器请求认证令牌是否合法,存在速度缓慢的问题....
使用jwt令牌后的流程图如下
使用了jwt令牌后,资源服务器拥有了 效验token 的能力,并且可以从jwt 字符串中获取用户相关信息,
JWT令牌的优点:
1、jwt基于json,非常方便解析。
2、可以在令牌中自定义丰富的内容,易扩展。
3、通过非对称加密算法及数字签名技术,JWT防止篡改,安全性高。
4、资源服务使用JWT可不依赖认证服务即可完成授权
缺点
占用内存大,消耗了更多资源,
JWT 用法
客户端接收服务器返回的JWT,将其存储在Cookie或localStorage中。
此后,客户端将在与服务器交互中都会带JWT。如果将它存储在Cookie中,就可以自动发送,但是不会跨域,因此一般是将它放入HTTP请求的Header Authorization字段中。
Authorization: Bearer
当跨域时,也可以将JWT被放置于POST请求的数据主体中。
JWT令牌生成采用非对称加密算法
下边命令生成密钥证书,采用RSA 算法每个证书包含公钥和私钥
keytool -genkeypair -alias testkey -keyalg RSA -keypass testtest -keystore test.keystore -storepass testkeystore
Keytool 是一个java提供的证书管理工具
-alias:密钥的别名
-keyalg:使用的hash算法
-keypass:密钥的访问密码
-keystore:密钥库文件名,test.keystore保存了生成的证书
-storepass:密钥库的访问密码
查询证书信息:
keytool -list -keystore test.keystore
密钥口令 testkeystore
导出密钥
openssl是一个加解密工具包,这里使用openssl来导出公钥信息。
下载地址 http://slproweb.com/products/Win32OpenSSL.html
下载完成后,配置环境变量
配置openssl的path环境变量,xxx\OpenSSL-Win64\bin
cmd进入test.keystore文件所在目录执行如下命令:
输入
keytool -list -rfc --keystore test.keystore | openssl x509 -inform pem -pubkey
把公钥私钥拷贝到text的文本中,并清空换行符
-----BEGIN PUBLIC KEY-----MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAlirGVWAEmo2hjQY5LYDrc/8UvIHKIZG4mXWmbJvesDEKNsNAJ5N3pPNpqcded3OkCNOuU1kqSNVsi+8HzMLk6ID5KHTDyLSCDRAVTRMBgIOneDC/uU2hGld+lHr9C8cZgIY2+JXaAvmrp+IOLAD0J0jisnT3zi+FK8/9rZB2r8YSJk15Qg0tYIPcPNRZAq/D5W6B0X9HyAMI4k40XElhQkCNKwOuTbAq/cPin4FKGhAQrhBeQuvo/FBKYMzbujPysWbZhjum6ni5CyD6vwToI+lzoXIi2Y+O4tM24A3SuX4VPtnwWCUqu2Ovfe77/yW4AkV7+JcxtuEoIZ451BuiLwIDAQAB-----END PUBLIC KEY-----
-----BEGIN CERTIFICATE-----MIIDRzCCAi+gAwIBAgIEJJziijANBgkqhkiG9w0BAQsFADBUMQswCQYDVQQGEwJjbjELMAkGA1UECBMCc3oxCzAJBgNVBAcTAmdkMQ0wCwYDVQQKEwR0ZXN0MQ0wCwYDVQQLEwR0ZXN0MQ0wCwYDVQQDEwR4aWFvMB4XDTE5MDMwODAwNTgyOVoXDTE5MDYwNjAwNTgyOVowVDELMAkGA1UEBhMCY24xCzAJBgNVBAgTAnN6MQswCQYDVQQHEwJnZDENMAsGA1UEChMEdGVzdDENMAsGA1UECxMEdGVzdDENMAsGA1UEAxMEeGlhbzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJYqxlVgBJqNoY0GOS2A63P/FLyByiGRuJl1pmyb3rAxCjbDQCeTd6TzaanHXndzpAjTrlNZKkjVbIvvB8zC5OiA+Sh0w8i0gg0QFU0TAYCDp3gwv7lNoRpXfpR6/QvHGYCGNviV2gL5q6fiDiwA9CdI4rJ0984vhSvP/a2Qdq/GEiZNeUINLWCD3DzUWQKvw+VugdF/R8gDCOJONFxJYUJAjSsDrk2wKv3D4p+BShoQEK4QXkLr6PxQSmDM27oz8rFm2YY7pup4uQsg+r8E6CPpc6FyItmPjuLTNuAN0rl+FT7Z8FglKrtjr33u+/8luAJFe/iXMbbhKCGeOdQboi8CAwEAAaMhMB8wHQYDVR0OBBYEFEdMFgpDW/uLMsibvtI1v5KgPOZNMA0GCSqGSIb3DQEBCwUAA4IBAQBFpWlFlDVhb+foS2hufE6chsyUTeVCqJzMS3vDBnxbAdI3JeWiGu24IYmuCNxt7U7PJnbxR0w7S9KdtswwEDtqxUWYbhHUghekcKzZZVD/nugZaYrxlu4Kj/KQRuiBXhY+U9AuJepxz2MN7CS4fbyWXmfAlfYcGmNA/6PieprFCbG+NPCLvR7dHJPKzMdgMSUbhtsmXn5vy78F923kUbXdc7U24RRHT5tdvw+5RU+Pf+YQMzzoH0OfjBKjtD047QIDaPf7ljzLwW44lhTGPyCQ79Ap3tJCwKuQdJ9f3sDBnQdKjTgPD4luLCq6pti4jdpF7HSMUtZzDSgU/TqaHH0F-----END CERTIFICATE-----
生成 JWT令牌
把 test.keystore 拷贝到 Resource 目录中
@Test
public void testCreateJwt(){
// 证书文件
String key_location = "test.keystore";
//密钥库密码
String keystore_password = "testkeystore";
//访问证书路径
ClassPathResource resource = new ClassPathResource(key_location);
// 密钥工厂
KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(resource, keystore_password.toCharArray());
//密钥的密码,此密码和别名要匹配
String key_password = "testtest";
// 密钥别名
String alias = "testkey";
// 密钥对 公私密钥
KeyPair keyPair = keyStoreKeyFactory.getKeyPair(alias, key_password.toCharArray());
//获取私钥
RSAPrivateKey aPrivate = (RSAPrivateKey)keyPair.getPrivate();
//定义payload信息
Map tokenMap = new HashMap<>();
tokenMap.put("id", "123");
tokenMap.put("name", "mrt");
tokenMap.put("ext", "1");
//生成jwt令牌
Jwt jwt = JwtHelper.encode(JSON.toJSONString(tokenMap), new RsaSigner( aPrivate));
String token = jwt.getEncoded();
System.out.println(token);
}
打印结果如下
eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9
.eyJleHQiOiIxIiwibmFtZSI6Im1ydCIsImlkIjoiMTIzIn0
.TEvGV_eQc3_tI-0lQnSnTc1UUYdLEq_J_AbmkfFe3b_uUdIdR6LZEm6nKT5doRq8f9YyGg1JUk0-eaGSiA_jokEoVQTk5ucpLgqU46MYVpjgmoFyd0StrOHDu-GbYXASc-dml0EK3FdEPG2XVZa-ty3y8P2KeTE97DngHZQWnY4ef5Ndr3glFmKEyMzC0_VHaPkZ9ZvzQuukYQrG2acBTWX2kM9RYhRwazIOvDfCafvW7IHD7T6T3fWI0epZ8dO1nQajOLTPWiz4udTjnm3mOfAogdkCxq3Tbt-r8m1mRMQNBrCigew-JjdiJM16qPlkHaeSYH50Jw_BB3R8vtrEWA
JWT解析
@Test
public void testVerify(){
// 公钥
String publickey = "-----BEGIN PUBLIC KEY-----MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAlirGVWAEmo2hjQY5LYDrc/8UvIHKIZG4mXWmbJvesDEKNsNAJ5N3pPNpqcded3OkCNOuU1kqSNVsi+8HzMLk6ID5KHTDyLSCDRAVTRMBgIOneDC/uU2hGld+lHr9C8cZgIY2+JXaAvmrp+IOLAD0J0jisnT3zi+FK8/9rZB2r8YSJk15Qg0tYIPcPNRZAq/D5W6B0X9HyAMI4k40XElhQkCNKwOuTbAq/cPin4FKGhAQrhBeQuvo/FBKYMzbujPysWbZhjum6ni5CyD6vwToI+lzoXIi2Y+O4tM24A3SuX4VPtnwWCUqu2Ovfe77/yW4AkV7+JcxtuEoIZ451BuiLwIDAQAB-----END PUBLIC KEY-----";
// JWT令牌
String jwtStr = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHQiOiIxIiwibmFtZSI6Im1ydCIsImlkIjoiMTIzIn0.TEvGV_eQc3_tI-0lQnSnTc1UUYdLEq_J_AbmkfFe3b_uUdIdR6LZEm6nKT5doRq8f9YyGg1JUk0-eaGSiA_jokEoVQTk5ucpLgqU46MYVpjgmoFyd0StrOHDu-GbYXASc-dml0EK3FdEPG2XVZa-ty3y8P2KeTE97DngHZQWnY4ef5Ndr3glFmKEyMzC0_VHaPkZ9ZvzQuukYQrG2acBTWX2kM9RYhRwazIOvDfCafvW7IHD7T6T3fWI0epZ8dO1nQajOLTPWiz4udTjnm3mOfAogdkCxq3Tbt-r8m1mRMQNBrCigew-JjdiJM16qPlkHaeSYH50Jw_BB3R8vtrEWA";
//效验令牌 无效令牌会抛出异常
Jwt jwt = JwtHelper.decodeAndVerify(jwtStr, new RsaVerifier(publickey));
String encoded = jwt.getEncoded();
System.out.println(encoded);
}