赞
踩
前提:在看完这两篇文章的基础上再进行本篇文章的开发
(七)Spring Security (spring-cloud-starter-oauth2)应用详解------认证授权服务------授权码模式和密码模式
(八)Spring Security (spring-cloud-starter-oauth2)应用详解------资源服务------密码模式
通过上边的测试我们发现,当资源服务和授权服务不在一起时,资源服务使用RemoteTokenServices 远程请求授权服务验证token,如果访问量较大将会影响系统的性能 。
解决上边问题:
令牌采用JWT格式即可解决上边的问题,用户认证通过会得到一个JWT令牌,JWT令牌中已经包括了用户相关的信息,客户端只需要携带JWT访问资源服务,资源服务根据事先约定的算法自行完成令牌校验,无需每次都请求认证服务完成授权。
1、什么是JWT
JSON Web Token(JWT)是一个开放的行业标准(RFC 7519),它定义了一种简介的、自包含的协议格式,用于在通信双方传递json对象,传递的信息经过数字签名可以被验证和信任。JWT可以使用HMAC算法或使用RSA的公钥/私钥对来签名,防止被篡改。
标准:https://tools.ietf.org/html/rfc7519
JWT令牌的优点:
1)jwt基于json,非常方便解析。
2)可以在令牌中自定义丰富的内容,易扩展。
3)通过非对称加密算法及数字签名技术,JWT防止篡改,安全性高。
4)资源服务使用JWT可不依赖认证服务即可完成授权。
JWT令牌的缺点:
1)JWT令牌较长,占存储空间比较大。
2、JWT令牌结构
JWT令牌由三部分组成,每部分中间使用点(.)分隔,比如:xxxxx.yyyyy.zzzzz
Header
头部包括令牌的类型(即JWT)及使用的哈希算法(如HMAC SHA256或RSA)
Payload
第二部分是负载,内容也是一个json对象,它是存放有效信息的地方,它可以存放jwt提供的现成字段,比如:iss(签发者),exp(过期时间戳), sub(面向的用户)等,也可自定义字段。
此部分不建议存放敏感信息,因为此部分可以解码还原原始内容。
最后将第二部分负载使用Base64Url编码,得到一个字符串就是JWT令牌的第二部分。
Signature
第三部分是签名,此部分用于防止jwt内容被篡改。
这个部分使用base64url将前两部分进行编码,编码后使用点(.)连接组成字符串,最后使用header中声明签名算法进行签名。
base64UrlEncode(header):jwt令牌的第一部分。
base64UrlEncode(payload):jwt令牌的第二部分。
secret:签名所使用的密钥。
在auth服务中配置jwt令牌服务,即可实现生成jwt格式的令牌。
补充:为了方便管理,我将AuthorizationServer 类中 这令牌存储策略删除,在接下来的TokenConfig类中我重新注入了一个新的TokenStore
新建 TokenConfig
package com.cyj.security.oauth2.auth.jwt.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
/**
* @Program: Spring-Security-OAuth2
* @Description:
* @Author C_Y_J
* @Create: 2021-01-31 17:53
**/
@Configuration
public class TokenConfig {
/**
* 密钥 分为对称性和非对称性 这里采用对称性
*/
private String SIGNING_KEY = "auth123";
@Bean
public TokenStore tokenStore() {
//JWT令牌存储方案
return new JwtTokenStore(accessTokenConverter());
}
@Bean
public JwtAccessTokenConverter accessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
//对称秘钥,资源服务器使用该秘钥来验证
converter.setSigningKey(SIGNING_KEY);
return converter;
}
}
AuthorizationServer
/**
* jwt存储方案
*/
@Autowired
private JwtAccessTokenConverter accessTokenConverter;
/**
* 令牌管理服务
*
* @return
*/
@Bean
public AuthorizationServerTokenServices tokenService() {
DefaultTokenServices service = new DefaultTokenServices();
//客户端详情服务
service.setClientDetailsService(clientDetailsService);
//支持刷新令牌
service.setSupportRefreshToken(true);
//令牌存储策略
service.setTokenStore(tokenStore);
//jwt令牌增强
TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
tokenEnhancerChain.setTokenEnhancers(Arrays.asList(accessTokenConverter));
service.setTokenEnhancer(tokenEnhancerChain);
// 令牌默认有效期2小时
service.setAccessTokenValiditySeconds(7200);
// 刷新令牌默认有效期3天
service.setRefreshTokenValiditySeconds(259200);
return service;
}
启动auth 服务
在order 服务中配置校验jwt令牌服务,即可实现解析jwt令牌。
1、将auth 认证授权服务中的TokenConfig类拷贝到order资源服务中
2、屏蔽order资源服务中ResouceServerConfig类的令牌解析服务
3、添加已经修改
@Autowired
TokenStore tokenStore;
@Override
public void configure(ResourceServerSecurityConfigurer resources) {
resources
//客户端访问的资源列表
.resourceId(RESOURCE_ID)
//验证令牌的服务
//.tokenServices(tokenService())
//将原来的远程调用改成jwt解析
.tokenStore(tokenStore)
.stateless(true);
}
1)申请jwt令牌
2)使用令牌请求资源
截止目前客户端信息和授权码仍然存储在内存中,生产环境中通过会存储在数据库中,下边完善环境的配置:
在spring-security中创建如下表:
oauth_client_details 存储客户端信息
USE `spring-security` ;
/*Table structure for table `oauth_client_details` */
DROP TABLE IF EXISTS `oauth_client_details` ;
CREATE TABLE `oauth_client_details` (
`client_id` VARCHAR (255) NOT NULL COMMENT '客户端标识',
`resource_ids` VARCHAR (255) DEFAULT NULL COMMENT '接入资源列表',
`client_secret` VARCHAR (255) DEFAULT NULL COMMENT '客户端秘钥',
`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` LONGTEXT,
`create_time` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`archived` TINYINT (4) DEFAULT NULL,
`trusted` TINYINT (4) DEFAULT NULL,
`autoapprove` VARCHAR (255) DEFAULT NULL,
PRIMARY KEY (`client_id`) USING BTREE
) ENGINE = INNODB DEFAULT CHARSET = utf8 ROW_FORMAT = DYNAMIC COMMENT = '接入客户端信息' ;
/*Data for the table `oauth_client_details` */
INSERT INTO `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`,
`create_time`,
`archived`,
`trusted`,
`autoapprove`
)
VALUES
(
'c1',
'res1',
'$2a$10$27P99rHURZHuws1f6LLOouh/hYPh3I0TDCg1LWtiYJ7oupdmZ7mlO',
'ROLE_ADMIN,ROLE_USER,ROLE_API',
'client_credentials,password,authorization_code,implicit,refresh_token',
'http://www.baidu.com',
NULL,
7200,
259200,
NULL,
'2020-12-13 15:28:13',
0,
0,
'false'
),
(
'c2',
'res2',
'$2a$10$27P99rHURZHuws1f6LLOouh/hYPh3I0TDCg1LWtiYJ7oupdmZ7mlO',
'ROLE_API',
'client_credentials,password,authorization_code,implicit,refresh_token',
'http://www.baidu.com',
NULL,
31536000,
2592000,
NULL,
'2020-12-13 15:28:30',
0,
0,
'false'
) ;
oauth_code 存储授权码
USE `spring-security`;
/*Table structure for table `oauth_code` */
DROP TABLE IF EXISTS `oauth_code`;
CREATE TABLE `oauth_code` (
`create_time` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
`code` VARCHAR(255) DEFAULT NULL COMMENT '授权码',
`authentication` BLOB,
KEY `code_index` (`code`) USING BTREE
) ENGINE=INNODB DEFAULT CHARSET=utf8 ROW_FORMAT=COMPACT;
persistent_logins 存储用户登录信息
USE `spring-security`;
/*Table structure for table `persistent_logins` */
DROP TABLE IF EXISTS `persistent_logins`;
CREATE TABLE `persistent_logins` (
`username` VARCHAR(64) NOT NULL,
`series` VARCHAR(64) NOT NULL,
`token` VARCHAR(64) NOT NULL,
`last_used` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`series`)
) ENGINE=INNODB DEFAULT CHARSET=utf8mb4;
AuthorizationServer
/**
* 客户端密码加密规则
*/
@Autowired
PasswordEncoder passwordEncoder;
/**
* 将客户端信息存储到数据库
*
* @param dataSource
* @return
*/
@Bean
public ClientDetailsService clientDetailsService(DataSource dataSource) {
ClientDetailsService clientDetailsService = new JdbcClientDetailsService(dataSource);
((JdbcClientDetailsService) clientDetailsService).setPasswordEncoder(passwordEncoder);
return clientDetailsService;
}
/**
* 客户端详情
*
* @param clients
* @throws Exception
*/
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
//客户端信息存储到数据库
clients.withClientDetails(clientDetailsService);
// clients
// // 使用in‐memory存储
// .inMemory()
// // client_id
// .withClient("c1")
// //客户端密钥
// .secret(new BCryptPasswordEncoder().encode("secret"))
// //客户端访问的资源列表
// .resourceIds("res1")
// // 该client允许的授权类型 authorization_code,password,refresh_token,implicit,client_credentials
// .authorizedGrantTypes("authorization_code", "password", "client_credentials", "implicit", "refresh_token")
// // 允许的授权范围
// .scopes("all")
// //false 允许跳转到授权页面
// .autoApprove(false)
// //加上验证回调地址
// .redirectUris("http://www.baidu.com");
}
@Bean
public AuthorizationCodeServices authorizationCodeServices(DataSource dataSource) {
//设置授权码模式的授权码如何存取,暂时采用内存方式
// return new InMemoryAuthorizationCodeServices();
//设置授权码模式的授权码存储到数据库
return new JdbcAuthorizationCodeServices(dataSource);
}
bug:
The bean ‘clientDetailsService’, defined in class path resource [com/cyj/security/oauth2/auth/jwt/config/AuthorizationServer.class], could not be registered. A bean with that name has already been defined in BeanDefinition defined in class path resource [org/springframework/security/oauth2/config/annotation/configuration/ClientDetailsServiceConfiguration.class] and overriding is disabled.
Action:
Consider renaming one of the beans or enabling overriding by setting
spring.main.allow-bean-definition-overriding=true
解决: 在application.properties添加
spring.main.allow-bean-definition-overriding = true
测试授权码模式
重启项目,登录 http://localhost:8080/auth/login
注意 参数 scope = ROLE_API 。这个参数值要和数据库保存的值一致,三个值随便哪个都可以
返回的code值是1D71hR
观察数据库,发现一模一样
注意: 如果要去访问资源服务,就会发现出问题
原因是在资源服务设置成all,你可以修改代码成数据库里的值,也可以在数据库添加all
测试密码模式
原文链接
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。