1.项目概要
传统的web应用中,我们通常通过cookie+session机制来保证调用的安全,在没有认证的情况下自动重定向到登录页面或者调用失败页面,而现在整个架构编程微服务模式了,cookie和session机制已经不能很好的满足保护API的需求了,更多的情况下采用token的验证机制,JWT的本质也是一种token。
JWT:JSON Web Token,是JSON风格的轻量级授权和认证规范,可以实现无状态,分布式的web应用授权。JWT的内容由三部分组成,分别是Header,Payload,Signature,三个部分之间通过.分割,举例
xxxxx.yyyyyy.zzzzz
Header
头部Header一般由2个部分组成alg和typ,alg是加密算法,如HMAC或SHA256,typ是token类型,取值为jwt,一个Header的例子
{
"alg": "HS256",
"typ": "JWT"
}
然后对Header部分进行Base64编码,得到第一部分的值
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.{PAYLOAD}.{SIGNATURE}
Payload
内容部分Payload是JWT存储信息的主体,包含三类内容
- 标准中注册的声明
- 公共的声明
- 私有的声明
标准中注册的声明
- iss:jwt签发者
- sub:jwt所面向的用户
- aud:接收jwt的一方
- exp:jwt的过期时间
- nbf:定义在什么时间之前该jwt是不可用的
- iat:jwt的签发时间
- jti:jwt的唯一标识,主要用作一次性token,避免重放攻击
公共的声明:
可以存放任何信息,根据业务实际需要添加,如用户id,名称等,但不要存放敏感信息
私有的声明:
私有声是提供者和消费者所共同定义的声明,不建议存放敏感信息
举例:定义一个payload:
{ "sub": "1234567890", "name": "John Doe", "admin": true }对其进行Base64编码,得到第二部分
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.{SIGNATURE}
Signature
token的签名部分由三部分组成256签名
var encodedString = base64UrlEncode(header) + '.' + base64UrlEncode(payload);
var signature = HMACSHA256(encodedString, 'secret');
得到最终的token串
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
2.Oauth2-Server中生成JWT Token
a)通过keytool生成证书
keytool -genkeypair -alias kevin_key -keyalg RSA -keypass 123456 -keystore kevin_key.jks -storepass 123456
查看证书信息:
keytool -list -v -keystore kevin_key.jks -storepass 123456
查看公钥信息
keytool -list -rfc -keystore kevin_key.jks -storepass 123456
b)将生成的kevin_key.jks文件放到oauth2-server工程的srce/main/resources目录下
c)添加jwt相关jar包依赖
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-jwt</artifactId>
</dependency>
d)在OAuth2服务器端配置核心类AuthorizationServerConfiguration中增加jwt token相关配置
3.测试Oauth2服务
出现登录页面,输入用户名:admin 密码;123456
点击Submit按钮,进入用户授权确认页面
点击Approve,跳转到baidu页面,后面携带了code和state参数
https://www.baidu.com/?code=F7LsMB&state=123
根据code换取access_code,注意使用post方法
http://localhost:8888/oauth/token?client_id=client&grant_type=authorization_code&redirect_uri=http://baidu.com&code= F7LsMB
注意这个code要和上个步骤中获得的code保持一致
用户名输入client,密码是secret,点击确定,可以看到access_token已经是jwt格式的字符了
eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1MTAxNzQ2MDYsInVzZXJfbmFtZSI6ImFkbWluIiwiYXV0aG9yaXRpZXMiOlsiYWRtaW4iXSwianRpIjoiNTJhOGM5MTUtMTE2OS00YzU5LWI0MmEtZGY4ZDM0Y2QwZWU0IiwiY2xpZW50X2lkIjoiY2xpZW50Iiwic2NvcGUiOlsiYXBwIl19.GecJM-FHApwznyYl-D3IjB0TpjhdhUXfYv782kfS9vdT0VZsu2HN-MGb-N-6Hf0efZ_mmz54IahJaq3KTw251v4L2O5A1r_iMuUP7GXs_qPHAGn3K1b4l-mNnpJdH5hhS5zYIRqOX2a8DXyI4zD7g8BQL-9PiR3kj9k_z9nW8vY9l2_x5Kyoc-sehxxQ5uQHM3xu6DzOwBpbbER7U_NnUwmcz5nS9YyAexSDnBbZAVpQavL2s1yYQVMJ5Dreq2asXHFbeQHXu5UqVbbTFuOgAylbFJ9K-3nsGAKT9NbzqBPRovI3s_X9HgjrzJHAuojBMeK0QMbvYSbUg2HB7MNNJw","token_type":"bearer","expires_in":43199,"scope":"app","user_name":"admin","jti":"52a8c915-1169-4c59-b42a-df8d34cd0ee4"}
4.access_token信息解析
我们通过上个步骤得到的token信息是不可读的,但是因为header,body都是经过base64转码过的,因此我们可以通过Base64将其解码,spring cloud里也提供了jwt相关的工具类帮我们来反解析这个串
package com.pachiraframework.springcloud.oauth2.config;
import org.junit.Test;
import org.springframework.security.jwt.Jwt;
import org.springframework.security.jwt.JwtHelper;
public class JwtTest {
@Test
public void test() {
eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1MTAxNzQ2MDYsInVzZXJfbmFtZSI6ImFkbWluIiwiYXV0aG9yaXRpZXMiOlsiYWRtaW4iXSwianRpIjoiNTJhOGM5MTUtMTE2OS00YzU5LWI0MmEtZGY4ZDM0Y2QwZWU0IiwiY2xpZW50X2lkIjoiY2xpZW50Iiwic2NvcGUiOlsiYXBwIl19.GecJM-FHApwznyYl-D3IjB0TpjhdhUXfYv782kfS9vdT0VZsu2HN-MGb-N-6Hf0efZ_mmz54IahJaq3KTw251v4L2O5A1r_iMuUP7GXs_qPHAGn3K1b4l-mNnpJdH5hhS5zYIRqOX2a8DXyI4zD7g8BQL-9PiR3kj9k_z9nW8vY9l2_x5Kyoc-sehxxQ5uQHM3xu6DzOwBpbbER7U_NnUwmcz5nS9YyAexSDnBbZAVpQavL2s1yYQVMJ5Dreq2asXHFbeQHXu5UqVbbTFuOgAylbFJ9K-3nsGAKT9NbzqBPRovI3s_X9HgjrzJHAuojBMeK0QMbvYSbUg2HB7MNNJw";
Jwt jwt = JwtHelper.decode(token);
System.out.println(jwt.toString());
}
}
通过这个测试我们可以看出来,token中已经包含了当前用户的信息了,包括我们在accessTokenConvertor()方法中给token中添加的额外信息user_name