Spring Security Oauth2(二)
认识JWT令牌
在介绍JWT之前先看一下传统校验令牌的方法,如下图:
问题:
传统授权方法的问题是用户每次访问资源服务,资源服务都须携带令牌去认证服务中心请求验证令牌合法性,并根据令牌获取用户相关信息,性能低下。
这个时候的令牌只是作为唯一标识,并没有其他用途。
解决:
使用JWT的思路,用户认证通过会得到一个JWT令牌,JWT令牌中已经包含了用户相关信息,客户端只需要携带JWT访问服务资源,资源服务根据事先约定的算法自行完成令牌效验,这个就不用每次请求认证服务完成授权。
什么是jwt?
JSON Web Token(JWT)是一个开放的行业标(RFC 7519),它定义了一种简介的、自包含的协议格式,用于在通信双方传递json对象,传递的信息经过数字签名可以被验证和信任。JWT可以使用HMAC算法或使用RSA的公钥/私钥对来签名,防止被篡改。
官网:https://jwt.io/ 标准:https://tools.ietf.org/html/rfc7519
jwt令牌优点:
- jwt令牌采用json格式,便于解析
- 可以在令牌中自定义丰富的内容,易于扩展
- 通过非对称加密算法和数字签名技术,jwt防止篡改,安全性高
- 资源服务使用jwt可不用依赖认证服务即可完成授权。
缺点:
- 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中声明
签名算法进行签名。
一个例子:
HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)
base64UrlEncode(header):jwt令牌的第一部分。
base64UrlEncode(payload):jwt令牌的第二部分。
secret:签名所使用的密钥。
生成私钥和公钥
JWT令牌生成采用非对称加密算法
1、生成密钥证书
下边命令生成密钥证书,采用RSA 算法每个证书包含公钥和私钥。
keytool -genkeypair -alias xckey -keyalg RSA -keypass xuecheng -keystore xc.keystore -storepass xuechengkeystore
Keytool 是一个java提供的证书管理工具
-alias:密钥的别名
-keyalg:使用的hash算法
-keypass:密钥的访问密码
-keystore:密钥库文件名,xc.keystore保存了生成的证书
-storepass:密钥库的访问密码
查询证书信息:
keytool -list -keystore xc.keystore
删除别名
keytool -delete -alias xckey -keystore xc.keystore
2.导出公钥
openssl是一个加解密工具包,这里使用openssl来导出公钥信息。
安装 openssl:
https://pan.baidu.com/s/133OZPVr4jlTu9vIkPuDN4g
安装资料目录下的Win64OpenSSL-1_1_0g.exe
配置openssl的path环境变量,本教程配置在D:\OpenSSL-Win64\bin
cmd进入xc.keystore文件所在目录执行如下命令:
keytool -list -rfc --keystore xc.keystore | openssl x509 -inform pem -pubkey
输入密钥库密码:
xuechengkeystore
下边这一段就是公钥内容:
-----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAijyxMdq4S6L1Af1rtB8SjCZHNgsQG8JTfGy55eYvzG0B /E4AudR2prSRBvF7NYPL47scRCNPgLnvbQczBHbBug6uOr78qnWsYxHlW6Aa5dI5NsmOD4DLtSw8eX0hFyK5F j6ScYOSFBz9cd1nNTvx2+oIv0lJDcpQdQhsfgsEr1ntvWterZt/8r7xNN83gHYuZ6TM5MYvjQNBc5qC7Krs9wM7U oQuL+s0X6RlOib7/mcLn/lFLsLDdYQAZkSDx/6+t+1oHdMarChIPYT1sx9Dwj2j2mvFNDTKKKKAq0cv14Vrhz67Vj mz2yMJePDqUi0JYS2r0iIo7n8vN7s83v5uOQIDAQAB -----END PUBLIC KEY-----
将上边的公钥拷贝到文本文件中。
生成jwt令牌
创建测试类,测试jwt令牌的生成与验证。
//创建jwt令牌 @Test public void testCreateJwt(){ //密钥库文件,之前生成的私钥文件 String keystore = "xc.keystore"; //密钥库的密码 String keystore_password = "xuechengkeystore"; //密钥库文件路径 ClassPathResource classPathResource = new ClassPathResource(keystore); //密钥别名 String alias = "xckey"; //密钥的访问密码 String key_password = "xuecheng"; //密钥工厂 KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(classPathResource,keystore_password.toCharArray()); //密钥对(公钥和私钥) KeyPair keyPair = keyStoreKeyFactory.getKeyPair(alias, key_password.toCharArray()); //获取私钥 RSAPrivateKey aPrivate = (RSAPrivateKey) keyPair.getPrivate(); //jwt令牌的内容 Map<String,String> body = new HashMap<>(); body.put("name","itcast"); String bodyString = JSON.toJSONString(body); //生成jwt令牌 Jwt jwt = JwtHelper.encode(bodyString, new RsaSigner(aPrivate)); //生成jwt令牌编码 String encoded = jwt.getEncoded(); System.out.println(encoded); }
验证JWT令牌
//校验jwt令牌 @Test public void testVerify(){ //公钥 String publickey = "-----BEGIN PUBLIC KEY-----MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnASXh9oSvLRLxk901HANYM6KcYMzX8vFPnH/To2R+SrUVw1O9rEX6m1+rIaMzrEKPm12qPjVq3HMXDbRdUaJEXsB7NgGrAhepYAdJnYMizdltLdGsbfyjITUCOvzZ/QgM1M4INPMD+Ce859xse06jnOkCUzinZmasxrmgNV3Db1GtpyHIiGVUY0lSO1Frr9m5dpemylaT0BV3UwTQWVW9ljm6yR3dBncOdDENumT5tGbaDVyClV0FEB1XdSKd7VjiDCDbUAUbDTG1fm3K9sx7kO1uMGElbXLgMfboJ963HEJcU01km7BmFntqI5liyKheX+HBUCD4zbYNPw236U+7QIDAQAB-----END PUBLIC KEY-----"; //jwt令牌 String jwtString = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiaXRjYXN0In0.lQOqL1s4DpDHROUAibkz6EMf6hcM7HmTPgmg-SlkacVoQAV7y3XQ7LXxiua6SJlN_uNX_EFjzIshEg_kyy972DtymtRMc2NIO5HzIF5I4oQCxNPsJdhu6qQni6sTas3q0JbAarMZSajDX7HhzVSYWPQJCussA4e1r9oFxDcoAo6TEAXOW8gRHzNIygQz1yCj6mdf4UOHI070kRy7f3BdhmrUJdOuDIMoRBYS4WsEOibAU1UCNPaJAXpZC0ihrtdY7SCg1N43fimeFOHrfpLb6OmRF7v7uvGMgrhg9JIYDbJ6nbode5OJkNceRx8QUICre2yKAe0ctlvXO0REf6OpRA"; //校验jwt令牌 Jwt jwt = JwtHelper.decodeAndVerify(jwtString, new RsaVerifier(publickey)); //拿到jwt令牌中自定义的内容 String claims = jwt.getClaims(); System.out.println(claims); }