一、jwt令牌
通过之前的项目我们发现了一个问题:通过上边的测试我们发现,当资源服务和授权服务不在一起时资源服务使用RemoteTokenServices 远程请求授权服务验证token,如果访问量较大将会影响系统的性能 。
解决:令牌采用JWT格式即可解决上边的问题,用户认证通过会得到一个JWT令牌,JWT令牌中已经包括了用户相关的信息,客户端只需要携带JWT访问资源服务,资源服务根据事先约定的算法自行完成令牌校验,无需每次都请求认证服务完成授权。
1、什么是jwt?
JSON Web Token(JWT)是一个开放的行业标准(RFC 7519),它定义了一种简介的、自包含的协议格式,用于 在通信双方传递json对象,传递的信息经过数字签名可以被验证和信任。JWT可以使用HMAC算法或使用RSA的公 钥/私钥对来签名,防止被篡改。
JWT令牌的优点: 1)jwt基于json,非常方便解析。 2)可以在令牌中自定义丰富的内容,易扩展。 3)通过非对称加密算法及数字签名技术,JWT防止篡改,安全性高。 4)资源服务使用JWT可不依赖认证服务即可完成授权。1)JWT令牌较长,占存储空间比较大。
2、jwt令牌结构
通过学习JWT令牌结构为自定义jwt令牌打好基础。 JWT令牌由三部分组成,每部分中间使用点(.)分隔,比如xxxxx.yyyyy.zzzzz
(1)Header
{ "alg": "HS256", "typ": "JWT" }
将上边的内容使用Base64Url编码,得到一个字符串就是JWT令牌的第一部分。
(2)Payload
第二部分是负载,内容也是一个json对象,它是存放有效信息的地方,它可以存放jwt提供的现成字段,比如:iss(签发者),exp(过期时间戳), sub(面向的用户)等,也可自定义字段。
此部分不建议存放敏感信息,因为此部分可以解码还原原始内容。
最后将第二部分负载使用Base64Url编码,得到一个字符串就是JWT令牌的第二部分。
{ "sub": "1234567890", "name": "456", "admin": true }
(3)Signature
第三部分是签名,此部分用于防止jwt内容被篡改。
这个部分使用base64url将前两部分进行编码,编码后使用点(.)连接组成字符串,最后使用header中声明 签名算法进行签名。 一个例子:
HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)
二、配置jwt
1、修改TokenConfig
package com.oauth.security.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.InMemoryTokenStore;
import org.springframework.security.oauth2.provider.token.store.JdbcTokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
/**
* @ClassName TokenConfig
* @Description
* @Author
* @Date 2020/5/9 22:13
* @Version 1.0
**/
@Configuration
public class TokenConfig {
private static String KEY = "uaa123";
/**
* (2)
* InMemoryTokenStore、JdbcTokenStore、JwtTokenStore
*/
@Bean
public TokenStore tokenStore() {
//使用内存存储令牌
// return new InMemoryTokenStore();
return new JwtTokenStore(accessTokenConverter());
}
@Bean
public JwtAccessTokenConverter accessTokenConverter(){
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setSigningKey(KEY); //对称秘钥,资源服务器使用该秘钥来验证
return converter;
}
}
2、修改AuthorizationServer
添加注入:
@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);
// 令牌默认有效期2小时
service.setAccessTokenValiditySeconds(7200);
// 刷新令牌默认有效期3天 return service;
service.setRefreshTokenValiditySeconds(259200);
return service;
}
测试一下,再生成令牌(可以看到,很长的一个令牌)
看一下权限:访问check_token接口
将tokenConifg,拷贝到order中
修改ResouceServerConfig
@Autowired
private TokenStore tokenStore;
@Override
public void configure(ResourceServerSecurityConfigurer resources) {
resources.resourceId(RESOURCE_ID)//资源的id
.tokenStore(tokenStore)
// .tokenServices(tokenService())
.stateless(true);//验证tokenService
}
可以访问资源证明我们成功了
3、完善配置
将tokenconfig copy
作出修改(不再使用tokenservice):
package com.oauth.security.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.token.RemoteTokenServices;
import org.springframework.security.oauth2.provider.token.ResourceServerTokenServices;
import org.springframework.security.oauth2.provider.token.TokenStore;
import javax.interceptor.AroundInvoke;
@Configuration
@EnableResourceServer
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class ResouceServerConfig extends ResourceServerConfigurerAdapter {
public static final String RESOURCE_ID = "res1";
@Autowired
private TokenStore tokenStore;
@Override
public void configure(ResourceServerSecurityConfigurer resources) {
resources.resourceId(RESOURCE_ID)//资源的id
.tokenStore(tokenStore)
.stateless(true);//验证tokenService
}
@Override
public void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/**").access("#oauth2.hasScope('all')")
.and().csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
}
我们再来测试:
依然是可以访问的。证明我们配置成功。
实际上,到现在我们的oauth2.0就基本完事了,但是现在仍然存在很多问题,比如好多数据在内存中,这是不理想的。我们现在来完善配置。
2、完善配置
创建两张表:
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='接入客户端信息';
CREATE TABLE `oauth_code` (
`create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`code` varchar(255) DEFAULT NULL,
`authentication` blob,
KEY `code_index` (`code`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=COMPACT;
这里的表结构跟oauth2.0是对应的。不需要添加javabean之类的。
在AuthorizationServer类中添加
@Autowired
private ClientDetailsService clientDetailsService;
@Autowired
private PasswordEncoder passwordEncoder;
/**
* 客户端信息
*/
@Bean
public ClientDetailsService clientDetailsService(DataSource dataSource) {
ClientDetailsService clientDetailsService = new JdbcClientDetailsService(dataSource);
((JdbcClientDetailsService)clientDetailsService).setPasswordEncoder(passwordEncoder);
return clientDetailsService;
}
修改:
/**
* (1)配置客户端详情,进行clent_id和client_secret等验证
*/
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.withClientDetails(clientDetailsService);
}
/**
* 设置授权码模式的授权码如何 存取,暂时采用数据库存储方式
*/
@Bean
public AuthorizationCodeServices authorizationCodeServices(DataSource dataSource) {
return new JdbcAuthorizationCodeServices(dataSource);
}
然后启动服务类,测试:
完美申请到令牌了。
之后我们再测试一下授权码模式,依然是可用的!!!