文章目录
- JWT实现
- Mac
- TestMacSigner
- Rsa
- 生成jks证书
- 需要先安装openssl
- keytool生成jks (Java Key Store) 文件
- 测试密钥
- JwtTokenStore
- InMemoryTokenStore&RedisTokenStore&JdbcTokenStore&JwtTokenStore图解
- JwtTokenStore详解
- jdbc实现
- 表结构说明1
- oauth_client_details
- oauth_access_token
- oauth_refresh_token
- 表结构说明2
- oauth_client_details&oauth_client_token&oauth_access_token&oauth_refresh_token&oauth_code 表介绍
- 表结构说明3
- 官网sql(HSQL)
- mysql版本
Vue – token/refresh_token 使用机制
JWT实现
Mac
TestMacSigner
public class TestMacSigner {
public static void main(String[] args) {
// 创建1个MacSigner
MacSigner macSigner = new MacSigner("abc123");
// 使用 macSigner 对 "data" 进行签名
byte[] signedBytes = macSigner.sign("data".getBytes());
// macSigner 对 "data" 签名 的内容进行输出(直接转为字符串会乱码)
System.out.println(Arrays.toString(signedBytes));
// macSigner 对 "data" 签名 的内容 进行 base64 编码
String based64Str = Base64.getEncoder().encodeToString(signedBytes);
// 将签名的字节 以 base64编码 输出
System.out.println(based64Str);
// 将签名的字节 进行 base64编码 得到的字符串, 使用 base64解码 得到的内容 输出, 查看与原内容是否相同
System.out.println(Arrays.toString(Base64.getDecoder().decode(based64Str)));
// 使用macSigner 对 内容 与 签名 进行验签
// (其实就是对 提供的内容 再一次签名 得到签名结果, 与 提供的签名 比较是否相同。
// 如果相同, 说明使用的是同样的密钥, 如果不同, 说明使用的是不同的密钥。进而说明一定是拥有密钥的人通过签名颁发的此令牌)
macSigner.verify("data".getBytes(), signedBytes);
}
}
Rsa
使用jwt(非对称加密)
可参考:https://www.cnblogs.com/hellxz/p/12044340.html
生成jks证书
需要先安装openssl
到https://slproweb.com/products/Win32OpenSSL.html
下载对应的openssl版本,只需要一直点下一步即可,安装完毕后,将它的bin目录配置到环境变量
keytool生成jks (Java Key Store) 文件
首先使用keytool生成jks (Java Key Store) 证书
,每个证书包含公钥
和私钥`
keytool -genkeypair -alias my-auth -keyalg RSA -keypass 123456 -keystore my-auth.jks -storepass 123456
alias
: 秘钥别名keyalg
: 使用的hash算法keypass
: 秘钥访问密码keystore
: 秘钥库文件名,生成证书文件storepass
: 证书的访问密码
在my-auth.jks同目录下,执行命令导出公钥,需要输入口令:123456
keytool -list -rfc --keystore my-auth.jks | openssl x509 -inform pem -pubkey
测试密钥
package com.zzhua;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
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.jwt.crypto.sign.RsaVerifier;
import org.springframework.security.oauth2.provider.token.store.KeyStoreKeyFactory;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.NoSuchAlgorithmException;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
public class TestJwt {
@Test
public void test_encodeJWT() throws JsonProcessingException, NoSuchAlgorithmException, InvalidKeySpecException {
// (这里读取jks文件的流程可以参考:
// ResourceServerTokenServicesConfiguration$JwtKeyStoreConfiguration#accessTokenConverter()的源码处理过程)
/*
// 这个bean只有在JwtKeyStoreCondition条件满足时,才会生效
// (亦即:只要配置了security.oauth2.resource.jwt.key-store就会生效)
@Bean
public JwtAccessTokenConverter accessTokenConverter() {
// security.oauth2.resource.jwt.key-store
// security.oauth2.resource.jwt.key-store-password
// security.oauth2.resource.jwt.key-alias
// 如果提供了key-store, 则以上三个不能为空
Assert.notNull(this.resource.getJwt().getKeyStore(), "keyStore cannot be null");
Assert.notNull(this.resource.getJwt().getKeyStorePassword(), "keyStorePassword cannot be null");
Assert.notNull(this.resource.getJwt().getKeyAlias(), "keyAlias cannot be null");
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
// 拿到jks证书
Resource keyStore = this.context.getResource(this.resource.getJwt().getKeyStore());
// 证书访问密码
char[] keyStorePassword = this.resource.getJwt().getKeyStorePassword().toCharArray();
KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(keyStore, keyStorePassword);
// 秘钥别名
String keyAlias = this.resource.getJwt().getKeyAlias();
// 密钥访问密码 ,先获取秘钥访问密码,如果没有提供,则使用证书访问密码(所以最好这2个保持一致)
char[] keyPassword = Optional.ofNullable(
this.resource.getJwt().getKeyPassword())
.map(String::toCharArray).orElse(keyStorePassword);
// 这个setKeyPair方法在下面有解释
converter.setKeyPair(keyStoreKeyFactory.getKeyPair(keyAlias, keyPassword));
return converter;
}
*/
//加载证书(参数:jks证书文件名)
ClassPathResource classPathResource = new ClassPathResource("my-auth.jks");
//密钥库(第二个参数:证书的访问密码)
KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(classPathResource, "123456".toCharArray());
//获取秘钥对(第一个参数: 密钥别名;第二个参数:证书里面的密钥的访问密码)
KeyPair keyPair = keyStoreKeyFactory.getKeyPair("my-auth", "123456".toCharArray());
//获取私钥 , 私钥加密,公钥验证,是谓签名
RSAPrivateKey privateKey = (RSAPrivateKey)keyPair.getPrivate();
// 获取公钥 (与通过命令获取的公钥一致,只不过没有换行符,一样的效果)
byte[] decode = Base64.getEncoder().encode(keyPair.getPublic().getEncoded());
String s = new String(decode);
System.out.println(s);
// MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAgdlyFwN0MWeLGvqrXbdZsvgHbtTWWpr/bzTlydZt5w0S1TUHwhwZiMXuMWvJU2wtlrw0AlftRbsqEy11sFECwtZ50/V7aEd1O1nHHCrVXgUjjktKBW02mmqF5AjlIMqTeS0QFB2iOI3Zs0YcbIIMP8DQFG5laKg4/C3F0LzSDqLOk1GH14+p+EdJ+fgvu0ip9s6eDA2mVF6Og5PlXdYjnvQwQDduZd/zxzXXxT9ZP4kQDRULjNxleJKljRwIa/mEJokWR4Xmu41uFetBpIjYtLV9teaKEB0GT0XVYmMSmonkqB4pHx8VUknNs7AQgQgniNBLX0nuuqiVx1q2wNxvIQIDAQAB
//准备载荷数据
Map<String,Object> data = new HashMap<>();
data.put("id",1L);
data.put("username","zzhua");
data.put("role","admin");
String content = new ObjectMapper().writeValueAsString(data);
System.out.println(content); // {"role":"admin","id":1,"username":"zzhua"}
//创建令牌
Jwt jwt = JwtHelper.encode(content, new RsaSigner(privateKey));
//获取创建的令牌
String token = jwt.getEncoded();
System.out.println(token);
// eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.
// eyJyb2xlIjoiYWRtaW4iLCJpZCI6MSwidXNlcm5hbWUiOiJ6emh1YSJ9.
// Yw-qmMtW6qqIkrj-8i_3m9QxIMOL9Xb7BKHGUIy7V4sTfkmJ7jAVTD6ij_vAHg_nNBrUtxWxcUnOLWCZGAiMKzjGl4yms8ThFQCpM5LNdH0crZi1fRZHKM0LbMSnPKO7IsuogiTbXNYdk-W3zcmk2zWoNiKMRtbflWlrKJMJEOjv5CPJwxBoWw6v0YIbFDGYC8YijnJ_b-U9YchSlpshEAiAO96l3OfwcTN99nXvMnQoGA-iOQaVNXMU_0Qa-A6xxfL8NtpqLX76ucLr6CcgmJky-VT4SyDOlA7AwiM2nsXwllBkA84T8AtFA9xSvlzN6yKPeAu_PKbJucTg8O51dw
// 在上面的过程中, 我们把公钥的字节转成了Base64编码后的字符串,
// 并且注意到编码后的字符串和使用命令获取的公钥是一致的。
// 既然公钥可以这样玩,那么私钥是不是也可以这样玩呢?
// 如果可以这样玩的话,那在后面的处理过程中就可以舍弃掉这个jks文件了,直接使用公钥和私钥的字符串就行了
// (只是说可以这样玩,但是这样玩私钥就以明文的方式暴露了)
// 私钥以base64字符串编码的形式暴露出去后,该怎么使用呢?
byte[] encoded = privateKey.getEncoded(); // 获取到私钥的字节数据
byte[] encoded2 = Base64.getEncoder().encode(encoded);// 对私钥的字节进行base64编码
String s1 = new String(encoded2); // 获取到私钥进行base64编码后的字符串(这个就可以暴露给外界了, 使用base64编码的好处是,不会出现乱码的情况)
byte[] decode1 = Base64.getDecoder().decode(s1.getBytes()); // 使用外界暴露的私钥字符串,获取到base64编码后的字节,然后对这些字节数据解码,来获取原始的字节数据
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(decode1);
RSAPrivateKey generatePrivate = (RSAPrivateKey) keyFactory.generatePrivate(keySpec); // 强转成RSAPrivateKey
Jwt jwt2 = JwtHelper.encode(content, new RsaSigner(generatePrivate)); // 使用新的私钥对象
//获取创建的令牌
String token2 = jwt2.getEncoded();
System.out.println(token2); // 生成的token与前面完全一致
// eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.
// eyJyb2xlIjoiYWRtaW4iLCJpZCI6MSwidXNlcm5hbWUiOiJ6emh1YSJ9.
// Yw-qmMtW6qqIkrj-8i_3m9QxIMOL9Xb7BKHGUIy7V4sTfkmJ7jAVTD6ij_vAHg_nNBrUtxWxcUnOLWCZGAiMKzjGl4yms8ThFQCpM5LNdH0crZi1fRZHKM0LbMSnPKO7IsuogiTbXNYdk-W3zcmk2zWoNiKMRtbflWlrKJMJEOjv5CPJwxBoWw6v0YIbFDGYC8YijnJ_b-U9YchSlpshEAiAO96l3OfwcTN99nXvMnQoGA-iOQaVNXMU_0Qa-A6xxfL8NtpqLX76ucLr6CcgmJky-VT4SyDOlA7AwiM2nsXwllBkA84T8AtFA9xSvlzN6yKPeAu_PKbJucTg8O51dw
// 既然私钥以base64编码的方式暴露出去了,那么公钥也以这种方式暴露出去,该怎么用呢?
RSAPublicKey rsaPublicKey = (RSAPublicKey) keyPair.getPublic();
byte[] encode = Base64.getEncoder().encode(rsaPublicKey.getEncoded());
String s2 = new String(encode); // 暴露给外界的公钥字符串
// 解析暴露给外界的公钥字符串,以获取RSA公钥对象
X509EncodedKeySpec keySpec2 = new X509EncodedKeySpec(Base64.getDecoder().decode(s2.getBytes()));
RSAPublicKey rsaPublicKey2 = (RSAPublicKey) keyFactory.generatePublic(keySpec2);
String JWTToken = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiYWRtaW4iLCJpZCI6MSwidXNlcm5hbWUiOiJ6emh1YSJ9.Yw-qmMtW6qqIkrj-8i_3m9QxIMOL9Xb7BKHGUIy7V4sTfkmJ7jAVTD6ij_vAHg_nNBrUtxWxcUnOLWCZGAiMKzjGl4yms8ThFQCpM5LNdH0crZi1fRZHKM0LbMSnPKO7IsuogiTbXNYdk-W3zcmk2zWoNiKMRtbflWlrKJMJEOjv5CPJwxBoWw6v0YIbFDGYC8YijnJ_b-U9YchSlpshEAiAO96l3OfwcTN99nXvMnQoGA-iOQaVNXMU_0Qa-A6xxfL8NtpqLX76ucLr6CcgmJky-VT4SyDOlA7AwiM2nsXwllBkA84T8AtFA9xSvlzN6yKPeAu_PKbJucTg8O51dw";
Jwt decodedJwt = JwtHelper.decodeAndVerify(JWTToken, new RsaVerifier(rsaPublicKey2));
//获取载荷数据
String claims = decodedJwt.getClaims();
System.out.println(claims);
//{"role":"admin","id":1,"username":"zzhua"}
}
// 注意一下,下面的校验只会校验签名是否正确(即: 被签名的内容有没有被篡改),不会校验jwt令牌是否过期
@Test
public void test_decodeJWT() {
//JWT的token
String JWTToken = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiYWRtaW4iLCJpZCI6MSwidXNlcm5hbWUiOiJ6emh1YSJ9.Yw-qmMtW6qqIkrj-8i_3m9QxIMOL9Xb7BKHGUIy7V4sTfkmJ7jAVTD6ij_vAHg_nNBrUtxWxcUnOLWCZGAiMKzjGl4yms8ThFQCpM5LNdH0crZi1fRZHKM0LbMSnPKO7IsuogiTbXNYdk-W3zcmk2zWoNiKMRtbflWlrKJMJEOjv5CPJwxBoWw6v0YIbFDGYC8YijnJ_b-U9YchSlpshEAiAO96l3OfwcTN99nXvMnQoGA-iOQaVNXMU_0Qa-A6xxfL8NtpqLX76ucLr6CcgmJky-VT4SyDOlA7AwiM2nsXwllBkA84T8AtFA9xSvlzN6yKPeAu_PKbJucTg8O51dw";
//公钥验证,通过 ‘keytool -list -rfc --keystore whale.jks | openssl x509 -inform pem -pubkey’ 得到公钥
String publicKey = "-----BEGIN PUBLIC KEY-----\n" +
"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAgdlyFwN0MWeLGvqrXbdZ\n" +
"svgHbtTWWpr/bzTlydZt5w0S1TUHwhwZiMXuMWvJU2wtlrw0AlftRbsqEy11sFEC\n" +
"wtZ50/V7aEd1O1nHHCrVXgUjjktKBW02mmqF5AjlIMqTeS0QFB2iOI3Zs0YcbIIM\n" +
"P8DQFG5laKg4/C3F0LzSDqLOk1GH14+p+EdJ+fgvu0ip9s6eDA2mVF6Og5PlXdYj\n" +
"nvQwQDduZd/zxzXXxT9ZP4kQDRULjNxleJKljRwIa/mEJokWR4Xmu41uFetBpIjY\n" +
"tLV9teaKEB0GT0XVYmMSmonkqB4pHx8VUknNs7AQgQgniNBLX0nuuqiVx1q2wNxv\n" +
"IQIDAQAB\n" +
"-----END PUBLIC KEY-----";
//解密和验证令牌(在这里,我们可以看到公钥就是一个字符串,可以直接封装到RsaVerifier中,
// 所以只要暴露了这个公钥字符串, 资源服务器拿着这个公钥字符串,就可以校验签名了)
Jwt jwt = JwtHelper.decodeAndVerify(JWTToken, new RsaVerifier(publicKey));
//获取载荷数据
String claims = jwt.getClaims();
System.out.println(claims);
//{"role":"admin","id":1,"username":"zzhua"}
}
}
JwtTokenStore
InMemoryTokenStore&RedisTokenStore&JdbcTokenStore&JwtTokenStore图解
JwtTokenStore详解
在JwtTokenStore的图解中
jdbc实现
表结构说明1
OAuth2 oauth_client_details,oauth_access_token,oauth_refresh_token表结构
oauth_client_details
oauth_access_token
oauth_refresh_token
表结构说明2
oauth_client_details&oauth_client_token&oauth_access_token&oauth_refresh_token&oauth_code 表介绍
来源:OAuth2相关数据表字段的详细说明
表结构说明3
官网sql(HSQL)
-- used in tests that use HSQL
create table oauth_client_details (
client_id VARCHAR(256) PRIMARY KEY,
resource_ids VARCHAR(256),
client_secret VARCHAR(256),
scope VARCHAR(256),
authorized_grant_types VARCHAR(256),
web_server_redirect_uri VARCHAR(256),
authorities VARCHAR(256),
access_token_validity INTEGER,
refresh_token_validity INTEGER,
additional_information VARCHAR(4096),
autoapprove VARCHAR(256)
);
create table oauth_client_token (
token_id VARCHAR(256),
token LONGVARBINARY,
authentication_id VARCHAR(256) PRIMARY KEY,
user_name VARCHAR(256),
client_id VARCHAR(256)
);
create table oauth_access_token (
token_id VARCHAR(256),
token LONGVARBINARY,
authentication_id VARCHAR(256) PRIMARY KEY,
user_name VARCHAR(256),
client_id VARCHAR(256),
authentication LONGVARBINARY,
refresh_token VARCHAR(256)
);
create table oauth_refresh_token (
token_id VARCHAR(256),
token LONGVARBINARY,
authentication LONGVARBINARY
);
create table oauth_code (
code VARCHAR(256), authentication LONGVARBINARY
);
create table oauth_approvals (
userId VARCHAR(256),
clientId VARCHAR(256),
scope VARCHAR(256),
status VARCHAR(10),
expiresAt TIMESTAMP,
lastModifiedAt TIMESTAMP
);
-- customized oauth_client_details table
create table ClientDetails (
appId VARCHAR(256) PRIMARY KEY,
resourceIds VARCHAR(256),
appSecret VARCHAR(256),
scope VARCHAR(256),
grantTypes VARCHAR(256),
redirectUrl VARCHAR(256),
authorities VARCHAR(256),
access_token_validity INTEGER,
refresh_token_validity INTEGER,
additionalInformation VARCHAR(4096),
autoApproveScopes VARCHAR(256)
);
mysql版本
oauth2表语句及解释
CREATE TABLE `oauth_access_token` (
`create_time` timestamp NOT NULL DEFAULT current_timestamp(),
`token_id` varchar(255) DEFAULT NULL,
`token` blob DEFAULT NULL,
`authentication_id` varchar(255) DEFAULT NULL,
`user_name` varchar(255) DEFAULT NULL,
`client_id` varchar(255) DEFAULT NULL,
`authentication` blob DEFAULT NULL,
`refresh_token` varchar(255) DEFAULT NULL,
UNIQUE KEY `authentication_id` (`authentication_id`),
KEY `token_id_index` (`token_id`),
KEY `authentication_id_index` (`authentication_id`),
KEY `user_name_index` (`user_name`),
KEY `client_id_index` (`client_id`),
KEY `refresh_token_index` (`refresh_token`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COMMENT='授权成功token';
CREATE TABLE `oauth_client_details` (
`client_id` varchar(255) NOT NULL,
`resource_ids` varchar(255) DEFAULT NULL,
`client_secret` varchar(255) DEFAULT NULL,
`scope` varchar(255) DEFAULT NULL,
`authorized_grant_types` varchar(255) DEFAULT NULL,
`web_server_redirect_uri` varchar(255) DEFAULT NULL,
`authorities` varchar(255) DEFAULT NULL,
`access_token_validity` int(11) DEFAULT NULL,
`refresh_token_validity` int(11) DEFAULT NULL,
`additional_information` text DEFAULT NULL,
`create_time` timestamp NOT NULL DEFAULT current_timestamp(),
`archived` tinyint(1) DEFAULT 0,
`trusted` tinyint(1) DEFAULT 0,
`autoapprove` varchar(255) DEFAULT 'false',
PRIMARY KEY (`client_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COMMENT='客户端详情';
CREATE TABLE `oauth_code` (
`create_time` timestamp NOT NULL DEFAULT current_timestamp(),
`code` varchar(255) DEFAULT NULL,
`authentication` blob DEFAULT NULL,
KEY `code_index` (`code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COMMENT='授权code';
CREATE TABLE `oauth_refresh_token` (
`create_time` timestamp NOT NULL DEFAULT current_timestamp(),
`token_id` varchar(255) DEFAULT NULL,
`token` blob DEFAULT NULL,
`authentication` blob DEFAULT NULL,
KEY `token_id_index` (`token_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COMMENT='更新token';
-- Add indexes
create index token_id_index on oauth_access_token (token_id);
create index authentication_id_index on oauth_access_token (authentication_id);
create index user_name_index on oauth_access_token (user_name);
create index client_id_index on oauth_access_token (client_id);
create index refresh_token_index on oauth_access_token (refresh_token);
create index token_id_index on oauth_refresh_token (token_id);
create index code_index on oauth_code (code);