上一篇:Spring Security + OAuth2(5. 资源服务搭建与测试)
下一篇:Spring Security + OAuth2(7. Spring Security实现分布式系统授权【从头重写】- UAA)
文章目录
1. JWT 介绍
通过上边的测试我们发现,当资源服务和授权服务不在一起时资源服务使用 RemoteTokenServices 远程请求授权服务验证token,如果访问量较大将会影响系统的性能 。
为了解决上述问题,就有了如下方案:
令牌采用JWT格式即可解决上边的问题
- 用户认证通过会得到一个JWT令牌
- JWT令牌中已经包括了用户相关的信息
- 客户端只需要携带JWT访问资源服务
- 资源服务根据事先约定的算法自行完成令牌校验,无需每次都请求认证服务完成授权。
1.1 什么是JWT
- JSON Web Token(JWT)是一个开放的行业标准(RFC 7519)
- 它定义了一种简介的、自包含的协议格式,用于在通信双方传递json对象,传递的信息经过数字签名可以被验证和信任。
- JWT可以使用HMAC算法或使用RSA的公钥/私钥对来签名,防止被篡改。
官网:https://jwt.io/
标准:https://tools.ietf.org/html/rfc7519
-
JWT令牌的优点:
1)jwt基于json,非常方便解析。
2)可以在令牌中自定义丰富的内容,易扩展。
3)通过非对称加密算法及数字签名技术,JWT防止篡改,安全性高。
4)资源服务使用JWT可不依赖认证服务即可完成授权。 -
缺点:
1)JWT令牌较长,占存储空间比较大。
1.2 JWT令牌结构
通过学习JWT令牌结构为自定义jwt令牌打好基础。
JWT令牌由三部分组成,每部分中间使用点(.)分隔,比如:xxxxx.yyyyy.zzzzz
-
Header
头部包括令牌的类型(即JWT)及使用的哈希算法(如HMAC、SHA256或RSA)
一个例子如下:
{
“alg”: “HS256”,
“typ”: “JWT”
}将上边的内容使用Base64Url编码,得到一个字符串就是JWT令牌的第一部分。
-
Payload
第二部分是负载,内容也是一个json对象,它是存放有效信息的地方,它可以存放jwt提供的现成字段
比如:iss(签发者),exp(过期时间戳), sub(面向的用户)等,也可自定义字段。
此部分不建议存放敏感信息,因为此部分可以解码还原原始内容。
一个例子:
{
“sub”: “1234567890”,
“name”: “456”,
“admin”: true
}最后将第二部分负载使用Base64Url编码,得到一个字符串就是JWT令牌的第二部分。
-
Signature
第三部分是签名,此部分用于防止jwt内容被篡改。
这个部分使用base64url将前两部分进行编码,编码后使用点(.)连接组成字符串,最后使用header中声明签名算法进行签名。
一个例子:
HMACSHA256(
base64UrlEncode(header) + “.” +
base64UrlEncode(payload),
secret)其中
- base64UrlEncode(header):jwt令牌的第一部分。
- base64UrlEncode(payload):jwt令牌的第二部分。
- secret:签名所使用的密钥。
2. 配置JWT令牌服务
- 在uaa中配置jwt令牌服务,即可实现生成jwt格式的令牌
- 修改 UAA 模块下的 Config 包中配置
-
TokenConfig
@Configuration public class TokenConfig { // 对称密钥,资源服务、Uaa 鉴权服务需要一致 private String SIGNING_KEY = "uaa123"; @Bean public TokenStore tokenStore() { //JWT令牌存储方案 return new JwtTokenStore(accessTokenConverter()); } @Bean public JwtAccessTokenConverter accessTokenConverter() { JwtAccessTokenConverter converter = new JwtAccessTokenConverter(); converter.setSigningKey(SIGNING_KEY); //对称秘钥,资源服务器使用该秘钥来验证 return converter; } }
-
AuthorizationServer -> tokenService
@Autowired private JwtAccessTokenConverter accessTokenConverter; //令牌管理服务 @Bean public AuthorizationServerTokenServices tokenService() { DefaultTokenServices service=new DefaultTokenServices(); service.setClientDetailsService(clientDetailsService);//客户端详情服务 service.setSupportRefreshToken(true);//支持刷新令牌 service.setTokenStore(tokenStore);//令牌存储策略 //令牌增强 TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain(); tokenEnhancerChain.setTokenEnhancers(Arrays.asList(accessTokenConverter)); service.setTokenEnhancer(tokenEnhancerChain); service.setAccessTokenValiditySeconds(7200); // 令牌默认有效期2小时 service.setRefreshTokenValiditySeconds(259200); // 刷新令牌默认有效期3天 return service; }
3. 生成jwt令牌
4. 资源服务校验jwt令牌
- 资源服务需要和授权服务拥有一致的签字、令牌服务等
- 下面的操作都是在 资源模块 Order 中进行的
4.1 TokenConfig
- 将 UAA 模块中 Config -> TokenConfig 复制到 Order 模块的 Config 下
4.2 修改资源服务原来的令牌验证部分
-
修改 Config -> ResouceServerConfig
@Configuration @EnableResourceServer public class ResouceServerConfig extends ResourceServerConfigurerAdapter { // 对称密钥,资源服务、Uaa 鉴权服务需要一致 public static final String RESOURCE_ID = "res1"; @Autowired TokenStore tokenStore; @Override public void configure(ResourceServerSecurityConfigurer resources) { resources.resourceId(RESOURCE_ID)//资源 id .tokenStore(tokenStore) .stateless(true); } @Override public void configure(HttpSecurity http) throws Exception { http .authorizeRequests() .antMatchers("/**").access("#oauth2.hasScope('all')")//ROLE_ADMIN .and().csrf().disable() .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS); } }
4.3 测试
- 申请jwt令牌
- 使用令牌请求资源
补充 :
- 令牌申请成功可以使用/uaa/oauth/check_token校验令牌的有效性,并查询令牌的内容
- 例子如下:
5. 完善环境配置
截止目前客户端信息和授权码仍然存储在内存中,生产环境中通过会存储在数据库中,下边完善环境的配置:
5.1 创建表
在user_db中创建如下表:
-
oauth_client_details
存储用户相关信息
SET NAMES utf8mb4; SET FOREIGN_KEY_CHECKS = 0; -- ---------------------------- -- Table structure for oauth_client_details -- ---------------------------- DROP TABLE IF EXISTS `oauth_client_details`; CREATE TABLE `oauth_client_details` ( `client_id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL, `resource_ids` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, `client_secret` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, `scope` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, `authorized_grant_types` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, `web_server_redirect_uri` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, `authorities` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, `access_token_validity` int NULL DEFAULT NULL, `refresh_token_validity` int NULL DEFAULT NULL, `additional_information` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL, `create_time` timestamp(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0), `archived` tinyint NULL DEFAULT NULL, `trusted` tinyint NULL DEFAULT NULL, `autoapprove` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, PRIMARY KEY (`client_id`) USING BTREE ) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic; -- ---------------------------- -- Records of oauth_client_details -- ---------------------------- INSERT INTO `oauth_client_details` VALUES ('c1', 'res1', '$2a$10$Dappn49nM8ZjkCO3ASwR/evnHQN3iPH4npP45js0d9Ug3Vm4ui.sq', 'ROLE_ADMIN,ROLE_USER,ROLE_API', 'authorization_code,password,client_credentials,implicit,refresh_token', 'https://www.baidu.com', NULL, 7200, 259200, NULL, '2020-12-13 15:53:10', 0, 0, 'false'); INSERT INTO `oauth_client_details` VALUES ('c2', 'res2', 'secret', 'ROLE_API', 'authorization_code,password,client_credentials,implicit,refresh_token', 'https://www.baidu.com', NULL, 7200, 259200, NULL, '2020-12-13 15:53:10', 0, 0, 'false'); SET FOREIGN_KEY_CHECKS = 1;
-
oauth_code
Spring Security OAuth2 用来存储授权码SET NAMES utf8mb4; SET FOREIGN_KEY_CHECKS = 0; -- ---------------------------- -- Table structure for oauth_code -- ---------------------------- DROP TABLE IF EXISTS `oauth_code`; CREATE TABLE `oauth_code` ( `code` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, `create_time` timestamp(0) NULL DEFAULT CURRENT_TIMESTAMP(0), `authentication` blob NULL ) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic; -- ---------------------------- -- Records of oauth_code -- ---------------------------- INSERT INTO `oauth_code` VALUES ('Ykj769', '2020-12-13 16:18:25', 0xACED0005737200416F72672E737072696E676672616D65776F726B2E73656375726974792E6F61757468322E70726F76696465722E4F417574683241757468656E7469636174696F6EBD400B02166252130200024C000D73746F7265645265717565737474003C4C6F72672F737072696E676672616D65776F726B2F73656375726974792F6F61757468322F70726F76696465722F4F4175746832526571756573743B4C00127573657241757468656E7469636174696F6E7400324C6F72672F737072696E676672616D65776F726B2F73656375726974792F636F72652F41757468656E7469636174696F6E3B787200476F72672E737072696E676672616D65776F726B2E73656375726974792E61757468656E7469636174696F6E2E416273747261637441757468656E7469636174696F6E546F6B656ED3AA287E6E47640E0200035A000D61757468656E746963617465644C000B617574686F7269746965737400164C6A6176612F7574696C2F436F6C6C656374696F6E3B4C000764657461696C737400124C6A6176612F6C616E672F4F626A6563743B787000737200266A6176612E7574696C2E436F6C6C656374696F6E7324556E6D6F6469666961626C654C697374FC0F2531B5EC8E100200014C00046C6973747400104C6A6176612F7574696C2F4C6973743B7872002C6A6176612E7574696C2E436F6C6C656374696F6E7324556E6D6F6469666961626C65436F6C6C656374696F6E19420080CB5EF71E0200014C00016371007E00047870737200136A6176612E7574696C2E41727261794C6973747881D21D99C7619D03000149000473697A65787000000002770400000002737200426F72672E737072696E676672616D65776F726B2E73656375726974792E636F72652E617574686F726974792E53696D706C654772616E746564417574686F7269747900000000000001FE0200014C0004726F6C657400124C6A6176612F6C616E672F537472696E673B787074000270317371007E000D74000270337871007E000C707372003A6F72672E737072696E676672616D65776F726B2E73656375726974792E6F61757468322E70726F76696465722E4F41757468325265717565737400000000000000010200075A0008617070726F7665644C000B617574686F72697469657371007E00044C000A657874656E73696F6E7374000F4C6A6176612F7574696C2F4D61703B4C000B726564697265637455726971007E000E4C00077265667265736874003B4C6F72672F737072696E676672616D65776F726B2F73656375726974792F6F61757468322F70726F76696465722F546F6B656E526571756573743B4C000B7265736F7572636549647374000F4C6A6176612F7574696C2F5365743B4C000D726573706F6E7365547970657371007E0016787200386F72672E737072696E676672616D65776F726B2E73656375726974792E6F61757468322E70726F76696465722E426173655265717565737436287A3EA37169BD0200034C0008636C69656E74496471007E000E4C001172657175657374506172616D657465727371007E00144C000573636F706571007E001678707400026331737200256A6176612E7574696C2E436F6C6C656374696F6E7324556E6D6F6469666961626C654D6170F1A5A8FE74F507420200014C00016D71007E00147870737200116A6176612E7574696C2E486173684D61700507DAC1C31660D103000246000A6C6F6164466163746F724900097468726573686F6C6478703F400000000000067708000000080000000474000D726573706F6E73655F74797065740004636F6465740009636C69656E745F696471007E001974000C72656469726563745F75726C74001568747470733A2F2F7777772E62616964752E636F6D74000573636F706574000A524F4C455F41444D494E78737200256A6176612E7574696C2E436F6C6C656374696F6E7324556E6D6F6469666961626C65536574801D92D18F9B80550200007871007E0009737200176A6176612E7574696C2E4C696E6B656448617368536574D86CD75A95DD2A1E020000787200116A6176612E7574696C2E48617368536574BA44859596B8B7340300007870770C000000103F4000000000000171007E002478017371007E0028770C000000103F40000000000000787371007E001C3F40000000000000770800000010000000007874001568747470733A2F2F7777772E62616964752E636F6D707371007E0028770C000000103F4000000000000174000472657331787371007E0028770C000000103F4000000000000171007E001F787372004F6F72672E737072696E676672616D65776F726B2E73656375726974792E61757468656E7469636174696F6E2E557365726E616D6550617373776F726441757468656E7469636174696F6E546F6B656E00000000000001FE0200024C000B63726564656E7469616C7371007E00054C00097072696E636970616C71007E00057871007E0003017371007E00077371007E000B0000000277040000000271007E000F71007E00117871007E0033737200486F72672E737072696E676672616D65776F726B2E73656375726974792E7765622E61757468656E7469636174696F6E2E57656241757468656E7469636174696F6E44657461696C7300000000000001FE0200024C000D72656D6F74654164647265737371007E000E4C000973657373696F6E496471007E000E787074000F303A303A303A303A303A303A303A31740020413742323638324643314142443130313035324444414341353444413343453170737200326F72672E737072696E676672616D65776F726B2E73656375726974792E636F72652E7573657264657461696C732E5573657200000000000001FE0200075A00116163636F756E744E6F6E457870697265645A00106163636F756E744E6F6E4C6F636B65645A001563726564656E7469616C734E6F6E457870697265645A0007656E61626C65644C000B617574686F72697469657371007E00164C000870617373776F726471007E000E4C0008757365726E616D6571007E000E7870010101017371007E0025737200116A6176612E7574696C2E54726565536574DD98509395ED875B0300007870737200466F72672E737072696E676672616D65776F726B2E73656375726974792E636F72652E7573657264657461696C732E5573657224417574686F72697479436F6D70617261746F7200000000000001FE020000787077040000000271007E000F71007E00117870740008746F6D7368696469); SET FOREIGN_KEY_CHECKS = 1;
5.2 配置授权服务
-
修改 UAA -> Config -> AuthorizationServer
@Configuration @EnableAuthorizationServer public class AuthorizationServer extends AuthorizationServerConfigurerAdapter { @Autowired private TokenStore tokenStore; @Autowired private ClientDetailsService clientDetailsService; @Autowired private AuthorizationCodeServices authorizationCodeServices; @Autowired private AuthenticationManager authenticationManager; @Autowired private JwtAccessTokenConverter accessTokenConverter; @Autowired private PasswordEncoder passwordEncoder; //将客户端信息存储到数据库 @Bean public ClientDetailsService clientDetailsService(DataSource dataSource) { ClientDetailsService clientDetailsService = new JdbcClientDetailsService(dataSource); ((JdbcClientDetailsService) clientDetailsService).setPasswordEncoder(passwordEncoder); return clientDetailsService; } //客户端详情服务 @Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { clients.withClientDetails(clientDetailsService); } //令牌管理服务 @Bean public AuthorizationServerTokenServices tokenService() { DefaultTokenServices service=new DefaultTokenServices(); service.setClientDetailsService(clientDetailsService);//客户端详情服务 service.setSupportRefreshToken(true);//支持刷新令牌 service.setTokenStore(tokenStore);//令牌存储策略 //令牌增强 TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain(); tokenEnhancerChain.setTokenEnhancers(Arrays.asList(accessTokenConverter)); service.setTokenEnhancer(tokenEnhancerChain); service.setAccessTokenValiditySeconds(7200); // 令牌默认有效期2小时 service.setRefreshTokenValiditySeconds(259200); // 刷新令牌默认有效期3天 return service; } @Bean public AuthorizationCodeServices authorizationCodeServices(DataSource dataSource) { return new JdbcAuthorizationCodeServices(dataSource);//设置授权码模式的授权码如何存取 } @Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) { endpoints .authenticationManager(authenticationManager)//认证管理器 .authorizationCodeServices(authorizationCodeServices)//授权码服务 .tokenServices(tokenService())//令牌管理服务 .allowedTokenEndpointRequestMethods(HttpMethod.POST); } @Override public void configure(AuthorizationServerSecurityConfigurer security){ security .tokenKeyAccess("permitAll()") //oauth/token_key是公开 .checkTokenAccess("permitAll()") //oauth/check_token公开 .allowFormAuthenticationForClients(); //表单认证(申请令牌) } }
5.3 测试
-
测试密码模式申请令牌
使用密码模式申请令牌,客户端信息需要和数据库中的信息一致。
-
测试授权码模式
生成的授权存储到数据库中。
测试完成。