分布式系统认证解决方案SpringSecurityOAuth2.0(三)资源服务器使用Redis令牌、JWT令牌认证及RSA非对称加密算法
目录
- 文章
- 一、简介
- 二、JWT
- 三、代码实现——JWT格式令牌
- 3.1 POM依赖
- 3.2 修改授权服务器
- 修改TokenStore
- 修改令牌管理服务tokenServices()
- 3.3 修改资源服务器
- TokenStore
- 四、测试——JWT令牌申请校验
- 4.1 原来申请的普通令牌
- 4.2 申请的JWT令牌
- 4.3 请求资源服务器认证
- 五、代码实现——数据库存储客户端详情、授权码
- 5.1 建表SQL
- 5.2 数据库存储客户端详情
- 5.3 数据库存储授权码
- 六、测试——数据库存储客户端详情、授权码
- 6.1 申请令牌
- 6.2 校验令牌
- 申请授权码
- 七、JWT RSA非对称加密
- 7.1 生成证书
- 7.2 授权服务器修改TokenStore
- 7.3 资源服务器修改TokenStore
- 7.4 授权服务器使用私钥加密申请令牌
- 7.5 资源服务器使用公钥解密JWT令牌
- 7.6 用不同证书的公钥进行解密
- 八、使用RedisTokenStore颁发令牌
文章
分布式系统认证解决方案SpringSecurityOAuth2.0(一)认证授权
分布式系统认证解决方案SpringSecurityOAuth2.0(二)分布式系统认证流程分析与实现
分布式系统认证解决方案SpringSecurityOAuth2.0(三)资源服务器使用Redis令牌、JWT令牌认证及RSA非对称加密算法
分布式系统认证解决方案SpringSecurityOAuth2.0(四)整合网关认证授权
一、简介
使用JWT令牌格式的话,用户认证通过会得到一个JWT令牌,JWT令牌中已经包括了用户相关的信息,客户端只需要携带JWT访问资源服务,资源服务根据事先约定的算法自行完成令牌校验,无需每次都请求认证服务器完成授权,节省开销。
二、JWT
JSON Web Token(JWT)是一个开放的行业标准,定义了一种简洁的、自包含的协议格式,用于在通信双方传递JSON对象,传递的信息经过数字签名可以被验证和信任,JWT可以使用HMAC算法或者使用RSA的公钥私钥对来签名,防止被修改。
一个JWT实际上就是一个字符串,它由三部分组成:
- 头部用于描述关于该JWT的最基本的信息,例如其类型以及签名所用的算法
- 载荷就是存放有效信息的地方,比如登录的用户名,登录时间,登录过期时间,自定义数据(角色信息等等)
- 签证信息,这个签证信息由三部分组成:
header (base64加密后的)
payload (base64加密后的)
secret // 盐,也是一个秘钥
这个部分需要base64加密后的header和base64加密后的payload使用.连接组成的字符串,然后通过header中声明的加密方式进行加盐secret组合加密。
注意:
secret是保存在服务器端的,secret就是用来进行JWT的签发和验证,它就是你服务端的私钥,在任何场景都不应该流露出去。一旦客户端得知这个secret, 那就意味着客户端是可以自我签发JWT了。
JWT介绍及使用
三、代码实现——JWT格式令牌
3.1 POM依赖
<!-- spring-security-jwt --><dependency><groupId>org.springframework.security</groupId><artifactId>spring-security-jwt</artifactId><version>1.1.0.RELEASE</version></dependency>
3.2 修改授权服务器
修改TokenStore

修改令牌管理服务tokenServices()

3.3 修改资源服务器
TokenStore
将认证服务器中的TokenStore拷到资源服务中:

配置到ResourceServer中:

注意:token秘钥需要与认证服务器中的一致。
四、测试——JWT令牌申请校验
4.1 原来申请的普通令牌

4.2 申请的JWT令牌

4.3 请求资源服务器认证

五、代码实现——数据库存储客户端详情、授权码
5.1 建表SQL
CREATE TABLE `oauth_client_details` (`client_id` varchar(128) NOT NULL COMMENT '客户端ID',`resource_ids` varchar(256) DEFAULT NULL COMMENT '资源ID集合,多个资源时用英文逗号分隔',`client_secret` varchar(256) DEFAULT NULL COMMENT '客户端密匙',`scope` varchar(256) DEFAULT NULL COMMENT '客户端申请的权限范围',`authorized_grant_types` varchar(256) DEFAULT NULL COMMENT '客户端支持的grant_type',`web_server_redirect_uri` varchar(256) DEFAULT NULL COMMENT '重定向URI',`authorities` varchar(256) DEFAULT NULL COMMENT '客户端所拥有的SpringSecurity的权限值,多个用英文逗号分隔',`access_token_validity` int(11) DEFAULT NULL COMMENT '访问令牌有效时间值(单位秒)',`refresh_token_validity` int(11) DEFAULT NULL COMMENT '更新令牌有效时间值(单位秒)',`additional_information` varchar(4096) DEFAULT NULL COMMENT '预留字段',`autoapprove` varchar(256) DEFAULT NULL COMMENT '用户是否自动Approval操作',PRIMARY KEY (`client_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='客户端信息';INSERT INTO `seata`.`oauth_client_details`(`client_id`, `resource_ids`, `client_secret`, `scope`, `authorized_grant_types`, `web_server_redirect_uri`, `authorities`, `access_token_validity`, `refresh_token_validity`, `additional_information`, `autoapprove`) VALUES ('c1', 'all', '$2a$10$n/wCt3PAUiFRwWmygwphaODTGf4nZku6UJQh1Lyy8YKmi1cfGtrDW', 'ROLE_ADMIN,ROLE_USER,ROLE_API,ALL', 'authorization_code,password,client_credentials,implicit,refresh_token', 'http://www.baidu.com', NULL, NULL, NULL, NULL, 'false');
INSERT INTO `seata`.`oauth_client_details`(`client_id`, `resource_ids`, `client_secret`, `scope`, `authorized_grant_types`, `web_server_redirect_uri`, `authorities`, `access_token_validity`, `refresh_token_validity`, `additional_information`, `autoapprove`) VALUES ('c2', 'all', '$2a$10$4ITes3sAlqV9B2lUB3nJA.9cd16aGC8cyEP9VNpa6pr8Ag9CqzyJy', 'ROLE_ADMIN,ROLE_USER,ROLE_API,ALL', 'authorization_code,password,client_credentials,implicit,refresh_token', 'http://www.baidu.com', NULL, NULL, NULL, NULL, 'false');CREATE TABLE `oauth_code` (`id` int(11) NOT NULL AUTO_INCREMENT,`CODE` varchar(256) DEFAULT NULL COMMENT '授权码(未加密)',`authentication` blob COMMENT 'AuthorizationRequestHolder.java对象序列化后的二进制数据',`create_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 COMMENT='授权码';
将原来我们保存在内存中的客户端详细信息存储到数据库中:
注意:客户端秘钥是经过我们BCrypt加密的。

5.2 数据库存储客户端详情




5.3 数据库存储授权码

六、测试——数据库存储客户端详情、授权码
6.1 申请令牌

6.2 校验令牌

申请授权码
GET请求申请授权码:http://localhost:8081/oauth2/oauth/authorize?client_id=c1&client_secret=order&response_type=code&scope=ALL&redirect_uri=http://www.baidu.com
注意:scope范围要是数据库中存储的。

可以看到授权码存储在数据库中:

七、JWT RSA非对称加密
使用RSA非对称加密方式生成公钥私钥
7.1 生成证书
创建一个目录,存放jks文件,在该目录下执行:keytool -genkeypair -alias com.lsh.rsa -keyalg RSA -keypass rsapassword -keystore rsa_first.jks -storepass rsapassword
- -alias:密钥的别名 : com.lsh.rsa
- -keyalg:使用的hash算法 : RSA
- -keypass:密钥的访问密码 : rsapassword
- -keystore:密钥库文件名,rsa_first.jks保存了生成的证书
- -storepass:密钥库的访问密码: rsapassword 查看证书时用


查看证书:keytool -list -keystore rsa_first.jks
输入创建证书时的秘钥库访问密码。

需要将jks文件导出,才能获得我们需要的公钥/私钥信息。
导出证书是用openssl工具,openssl是一个加解密工具包,来导出公钥信息:

7.2 授权服务器修改TokenStore
将生成的jks证书拷贝到resources目录下:

修改原来的对称加密的accessTokenConverter()方法:
/**创建jks文件时的别名-alias ims.abc.com*/public String rsaAlias = "rsafirst";/**创建jks文件时的访问密码-keypass */public String rsaPassword = "rsapassword";/**RSA证书 */public String certificateFileName = "rsa_first.jks";/*** 使用JWT存储令牌 RSA非对称加密* @return*/@Beanpublic JwtAccessTokenConverter accessTokenConverter(){//使用JWT存储令牌JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter();//创建 密钥存储密钥工厂ClassPathResource classPathResource = new ClassPathResource(certificateFileName);KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(classPathResource,rsaPassword.toCharArray());//设置密钥对(私钥) 此处传入的是创建jks文件时的别名-alias 和 秘钥库访问密码jwtAccessTokenConverter.setKeyPair(keyStoreKeyFactory.getKeyPair(rsaAlias));//用户身份验证转换器 给Token中加入额外的扩展数据DefaultAccessTokenConverter accessTokenConverter = (DefaultAccessTokenConverter) jwtAccessTokenConverter.getAccessTokenConverter();accessTokenConverter.setUserTokenConverter(customUserAuthenticationConverter);//在资源服务 验证JWT时 使用公钥进行解密 如:Order服务return jwtAccessTokenConverter;}
/*** @author :LiuShihao* @date :Created in 2021/8/24 9:03 上午* @desc :用户身份验证转换器,简单理解就是可以给Token中加入额外的扩展数据*/
@Component
public class CustomUserAuthenticationConverter extends DefaultUserAuthenticationConverter {@Overridepublic Map<String, ?> convertUserAuthentication(Authentication authentication) {LinkedHashMap response = new LinkedHashMap();String name = authentication.getName();response.put("user_name", name);response.put("user_age",18);//根据自己的情况增加 扩展数据if (authentication.getAuthorities() != null && !authentication.getAuthorities().isEmpty()) {//权限response.put("authorities", AuthorityUtils.authorityListToSet(authentication.getAuthorities()));}return response;}
}
7.3 资源服务器修改TokenStore


修改原来使用对称加密的accessTokenConverter()方法,使用公钥解密:
/**存放公钥的文件名 */public String first_public = "first_public.txt";public String second_public = "second_public.txt";/*** 使用JWT存储令牌 RSA非对称加密** 使用私钥 在认证服务器进行加密* 使用公钥 在资源服务器解析JWT时解密* @return*/@Beanpublic JwtAccessTokenConverter accessTokenConverter(){//使用JWT存储令牌 (普通令牌)JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter();//公钥(读取public.txt的公钥信息)Resource resource = new ClassPathResource(publicFileName);String publicKey = null;try {publicKey = inputStream2String(resource.getInputStream());} catch (final IOException e) {throw new RuntimeException(e);}//设置验证秘钥(公钥)jwtAccessTokenConverter.setVerifierKey(publicKey);return jwtAccessTokenConverter;}public String inputStream2String(InputStream is) throws IOException {BufferedReader in = new BufferedReader(new InputStreamReader(is));StringBuffer buffer = new StringBuffer();String line = "";while ((line = in.readLine()) != null) {buffer.append(line);}return buffer.toString();}
7.4 授权服务器使用私钥加密申请令牌

我们发现令牌的长度变得很长。
调用授权服务器校验令牌:

可以看到我们自定义内容也解析出来了。
7.5 资源服务器使用公钥解密JWT令牌

7.6 用不同证书的公钥进行解密
使用RSA算法生成了两个jks证书:

在授权服务器我们使用rsa_first.jks证书进行加密:

在资源服务器使用rsa_second.jks证书的公钥进行解密:


可以看到使用不同证书的公钥进行解密是不行的。
八、使用RedisTokenStore颁发令牌


在Redis中输入get access:token值可以看到存入的令牌信息:


注意:如果授权服务器的TokenStore的类型改了,资源服务器也需要统一。
本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!
