Spring Cloud Security OAuth2(六)
JWT令牌
JWT介绍
当资源服务和授权服务不在一起时资源服务使用RemoteTokenServices
远程请求授权服务验证token,如果访问量较大将会影响系统的性能 。
解决上边问题:
令牌采用JWT格式即可解决上边的问题,用户认证通过会得到一个JWT令牌,JWT令牌中已经包括了用户相关的信 息,客户端只需要携带JWT访问资源服务,资源服务根据事先约定的算法自行完成令牌校验,无需每次都请求认证服务完成授权。
什么是JWT?
JSON Web Token(JWT)是一个开放的行业标准(RFC 7519),它定义了一种简介的、自包含的协议格式,用于 在通信双方传递json对象,传递的信息经过数字签名可以被验证和信任。JWT可以使用HMAC算法或使用RSA的公 钥/私钥对来签名,防止被篡改
JWT令牌的优点:
- jwt基于json,非常方便解析。
- 可以在令牌中自定义丰富的内容,易扩展。
- 通过非对称加密算法及数字签名技术,JWT防止篡改,安全性高。
- 资源服务使用JWT可不依赖认证服务即可完成授权。
JWT令牌结构
JWT令牌由三部分组成,每部分中间使用点(.)分隔,比如:xxxxx.yyyyy.zzzzz
- Header
头部包括令牌的类型(即JWT)及使用的哈希算法(如HMAC SHA256或RSA)
将上边的内容使用Base64Url编码,得到一个字符串就是JWT令牌的第一部分。{ "alg": "HS256", "typ": "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)
配置JWT令牌服务
在uaa中配置jwt令牌服务,即可实现生成jwt格式的令牌。
1.TokenConfig
@Configuration
public class TokenConfig {
private String SIGNING_KEY = "uaa123";
@Bean
public TokenStore tokenStore() {
return new JwtTokenStore(accessTokenConverter());
}
@Bean
public JwtAccessTokenConverter accessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setSigningKey(SIGNING_KEY); //对称秘钥,资源服务器使用该秘钥来验证
return converter;
}
}
2.定义JWT令牌服务,在AuthorizationServer配置类中
@Autowired
private JwtAccessTokenConverter accessTokenConverter;
@Bean
public AuthorizationServerTokenServices tokenServices() {
DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
defaultTokenServices.setClientDetailsService(clientDetailsService);
defaultTokenServices.setSupportRefreshToken(true);
defaultTokenServices.setTokenStore(tokenStore);
TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
tokenEnhancerChain.setTokenEnhancers(Arrays.asList(accessTokenConverter));
defaultTokenServices.setTokenEnhancer(tokenEnhancerChain);
defaultTokenServices.setAccessTokenValiditySeconds(60*60*2); // 令牌默认有效期2小时
defaultTokenServices.setRefreshTokenValiditySeconds(60*60*24*3);// 刷新令牌默认有效期3天
return defaultTokenServices;
}
生成jwt令牌
使用客户端模式
校验jwt令牌
资源服务需要和授权服务拥有一致的签字、令牌服务等:
1.将授权服务中的TokenConfig类拷贝到资源 服务中
2.屏蔽资源 服务原来的令牌服务类
@Configuration
@EnableResourceServer
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class ResouceServerConfig extends ResourceServerConfigurerAdapter {
public static final String RESOURCE_ID = "res1";
//资源服务令牌解析服务
// public ResourceServerTokenServices tokenService() {
// //使用远程服务请求授权服务器校验token,必须指定校验token 的url、client_id,client_secret
// RemoteTokenServices service = new RemoteTokenServices();
// service.setCheckTokenEndpointUrl("http://localhost:53020/uaa/oauth/check_token");
// service.setClientId("c1");
// service.setClientSecret("secret");
// return service;
// }
// @Override
// public void configure(ResourceServerSecurityConfigurer resources) {
// resources.resourceId(RESOURCE_ID).tokenServices(tokenService()).stateless(true);
// /**
// * tokenServices:ResourceServerTokenServices 类的实例,用来实现令牌服务。
// * tokenStore:TokenStore类的实例,指定令牌如何访问,与tokenServices配置可选
// * resourceId:这个资源服务的ID,这个属性是可选的,但是推荐设置并在授权服务中进行验证
// */
// }
@Autowired
TokenStore tokenStore;
//使用jwt校验令牌
@Override
public void configure(ResourceServerSecurityConfigurer resources) {
resources.resourceId(RESOURCE_ID).tokenStore(tokenStore).stateless(true);
}
}
测试
1.申请jwt令牌:这里使用密码模式
2.使用令牌请求资源
3.校验令牌
数据库中读取客户端信息
oauth_client_details
存放客户端信息
CREATE TABLE `oauth_client_details` (
`client_id` VARCHAR (255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '客户端标 识',
`resource_ids` VARCHAR (255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '接入资源列表',
`client_secret` VARCHAR (255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '客户端秘钥',
`scope` VARCHAR (255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`authorized_grant_types` VARCHAR (255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`web_server_redirect_uri` VARCHAR (255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`authorities` VARCHAR (255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`access_token_validity` INT (11) NULL DEFAULT NULL,
`refresh_token_validity` INT (11) NULL DEFAULT NULL,
`additional_information` LONGTEXT CHARACTER SET utf8 COLLATE utf8_general_ci NULL,
`create_time` TIMESTAMP(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0) ON UPDATE CURRENT_TIMESTAMP(0),
`archived` TINYINT (4) NULL DEFAULT NULL,
`trusted` TINYINT (4) NULL DEFAULT NULL,
`autoapprove` VARCHAR (255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
PRIMARY KEY (`client_id`) USING BTREE
) ENGINE = INNODB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '接入客户端信息' ROW_FORMAT = DYNAMIC;
oauth_code表,Spring Security OAuth2使用,用来存储授权码:
CREATE TABLE `oauth_code` (
`create_time` TIMESTAMP(0) NOT NULL DEFAULT CURRENT_TIMESTAMP,
`code` VARCHAR (255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`authentication` BLOB NULL,
INDEX `code_index` (`code`) USING BTREE
) ENGINE = INNODB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = COMPACT;
配置授权服务
ClientDetailsService
和AuthorizationCodeServices
从数据库读取数据
@Configuration
@EnableAuthorizationServer
public class AuthorizationServer extends AuthorizationServerConfigurerAdapter {
@Autowired
private TokenStore tokenStore;
@Autowired
private ClientDetailsService clientDetailsService;
@Autowired
private AuthorizationCodeServices authorizationCodeServices;
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
PasswordEncoder passwordEncoder;
//用来配置令牌端点的安全约束
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security.tokenKeyAccess("permitAll()") // /oauth/token_key 安全配置
.checkTokenAccess("permitAll()") // /oauth/check_token 安全配置
.allowFormAuthenticationForClients(); // 允许表单认证
}
@Bean
public ClientDetailsService clientDetailsService(DataSource dataSource) {
JdbcClientDetailsService jdbcClientDetailsService = new JdbcClientDetailsService(dataSource);
jdbcClientDetailsService.setPasswordEncoder(passwordEncoder);
return jdbcClientDetailsService;
}
//用来配置客户端详情服务(ClientDetailsService),客户端详情信息在这里进行初始化,
//你能够把客户端详情信息写死在这里或者是通过数据库来存储调取详情信息。
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
// clients.inMemory() //使用in‐memory存储
// .withClient("c1") // client_id
// .secret(new BCryptPasswordEncoder().encode("secret"))
// .resourceIds("res1")
// .authorizedGrantTypes("authorization_code", "password","client_credentials","implicit","refresh_token")
// // 该client允许的授权类型 authorization_code,password,refresh_token,implicit,client_credentials
// .scopes("all")// 允许的授权范围
// .autoApprove(false)
// //加上验证回调地址
// .redirectUris("http://www.baidu.com");
clients.withClientDetails(clientDetailsService); //读数据库中客户信息
}
//用来配置令牌(token)的访问端点和令牌服务(token services)。
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.authenticationManager(authenticationManager)
.authorizationCodeServices(authorizationCodeServices)
.tokenServices(tokenServices())
.allowedTokenEndpointRequestMethods(HttpMethod.POST,HttpMethod.GET);
}
@Autowired
private JwtAccessTokenConverter accessTokenConverter;
@Bean
public AuthorizationServerTokenServices tokenServices() {
DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
defaultTokenServices.setClientDetailsService(clientDetailsService);
defaultTokenServices.setSupportRefreshToken(true);
defaultTokenServices.setTokenStore(tokenStore);
TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
tokenEnhancerChain.setTokenEnhancers(Arrays.asList(accessTokenConverter));
defaultTokenServices.setTokenEnhancer(tokenEnhancerChain);
defaultTokenServices.setAccessTokenValiditySeconds(60*60*2); // 令牌默认有效期2小时
defaultTokenServices.setRefreshTokenValiditySeconds(60*60*24*3);// 刷新令牌默认有效期3天
return defaultTokenServices;
}
// @Bean
// public AuthorizationServerTokenServices tokenServices() {
// DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
// defaultTokenServices.setClientDetailsService(clientDetailsService);
// defaultTokenServices.setSupportRefreshToken(true);
// defaultTokenServices.setTokenStore(tokenStore);
// defaultTokenServices.setAccessTokenValiditySeconds(60*60*2); // 令牌默认有效期2小时
// defaultTokenServices.setRefreshTokenValiditySeconds(60*60*24*3);// 刷新令牌默认有效期3天
// return defaultTokenServices;
// }
// @Bean
// public AuthorizationCodeServices authorizationCodeServices() {
// //设置授权码模式的授权码如何 存取,暂时采用内存方式
// return new InMemoryAuthorizationCodeServices();
// }
@Bean
public AuthorizationCodeServices authorizationCodeServices(DataSource dataSource) {
//设置授权码模式的授权码如何 存取,暂时采用内存方式
return new JdbcAuthorizationCodeServices(dataSource);
}
}