应用背景:当资源服务和认证服务不在一起时,资源服务使用RemoteTokenServices远程请求授权服务验证token,如果访问量较大较会影响系统的性能。这时可以考虑做进一步的优化,令牌采用JWT格式生成,用户认证通过会得到一个JWT令牌,客户端只需要携带JWT令牌访问资源服务,资源服务根据事先约定的算法自行完成令牌校验,无需每次都请求认证服务完成授权。
OAth2.0使用JWT令牌时,不再需要额外的依赖包,仅仅引入Spring Cloud整合OAuth2.0的依赖包即可,这是因为随着技术的发展,使用OAuth2.0的时候,其项目架构必然是分布式。
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
<version>2.1.0.RELEASE</version>
</dependency>
认证服务器 需要做下述配置:
第一步:认证服务中原先产生Token的配置类为:
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;
@Configuration
public class TokenConfig {
//认证服务器产生的Token采用内存进行存储
@Bean
public TokenStore tokenStore() {
//内存方式,生成普通令牌
return new InMemoryTokenStore();
}
}
现在认证服务使用JWT方式产生令牌,因此此配置类需要修改为如下所示:
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;
@Configuration
public class TokenConfig {
//认证服务器产生的Token采用JWT进行存储
@Bean
public TokenStore tokenStore() {
return new JwtTokenStore(accessTokenConverter());
}
@Bean
public JwtAccessTokenConverter accessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
//设置加密的密钥为“key123”,这是一个对称秘钥,资源服务器必须使用该秘钥来验证
converter.setSigningKey("key123");
return converter;
}
}
第二步:资源服务的OAuth2的配置类(AuthorizationServerConfigurerAdapter的继承类)修改如下:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.client.JdbcClientDetailsService;
import org.springframework.security.oauth2.provider.code.AuthorizationCodeServices;
import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.TokenEnhancerChain;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import javax.sql.DataSource;
import java.util.Arrays;
/**
* 授权服务配置
**/
@Configuration
@EnableAuthorizationServer
public class AuthorizationServer extends AuthorizationServerConfigurerAdapter {
@Autowired
private AuthorizationCodeServices authorizationCodeServices;
@Autowired
private TokenStore tokenStore;
@Autowired
private ClientDetailsService clientDetailsService;
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private JwtAccessTokenConverter accessTokenConverter;
@Autowired
PasswordEncoder passwordEncoder;
/**
* 1.配置客户端详细信息服务
*/
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
//统一认证中心:使用内存方式存储客户端详细信息
clients.inMemory()
.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)//false跳转到授权页面
//加上验证回调地址
.redirectUris("http://www.baidu.com");
}
//2.令牌管理服务
@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);
service.setAccessTokenValiditySeconds(7200); // 令牌默认有效期2小时
service.setRefreshTokenValiditySeconds(259200); // 刷新令牌默认有效期3天
return service;
}
/**
* 令牌访问端点
*/
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
endpoints
.authenticationManager(authenticationManager) //认证管理器
.authorizationCodeServices(authorizationCodeServices) //授权码服务
.tokenServices(tokenService())//令牌管理服务
.allowedTokenEndpointRequestMethods(HttpMethod.POST);
}
/**
* 令牌端点的安全约束
*/
@Override
public void configure(AuthorizationServerSecurityConfigurer security){
//允许所有的都可以访问oauth/token_key
security.tokenKeyAccess("permitAll()")
//允许所有的都可以访问oauth/check_token
.checkTokenAccess("permitAll()")
//表单认证(申请令牌)
.allowFormAuthenticationForClients();
}
}
特别提醒:使用JWT方式生成令牌,认证服务和资源服务的签名密钥必须要一致
资源服务器 需要做下述配置:
第一步:资源服务中添加验证JWT Token令牌的配置
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;
/**
* 资源服务验证JWT Token令牌的配置
*/
@Configuration
public class CheckJwtTokenConfig {
//签名密钥与认证服务保持一致
private String SigningKey="key123";
@Bean
public TokenStore tokenStore() {
return new JwtTokenStore(accessTokenConverter());
}
@Bean
public JwtAccessTokenConverter accessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setSigningKey(SigningKey);
return converter;
}
}
第二步:在资源服务的配置类(ResourceServerConfigurerAdapter 的继承类)中将上述配置(tokenStore)应用,示例代码如下:
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.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;
@Configuration
@EnableResourceServer
public class ResouceServerConfig extends ResourceServerConfigurerAdapter {
@Autowired
private TokenStore tokenStore;
@Override
public void configure(ResourceServerSecurityConfigurer resources) {
//资源 id
resources.resourceId("res1")
//资源服务器使用本地认证方式验证JWT令牌
.tokenStore(tokenStore)
//验证令牌的服务,目前将下述这段token验证服务屏蔽掉
//.tokenServices(tokenService())
.stateless(true);
}
@Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/**").access("#oauth2.hasScope('all')")
.and().csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
/**
* 功能描述:配置资源服务的令牌检查服务,如果使用的是JWT令牌,那么下面这段代码可以屏蔽掉
*/
@Bean
public ResourceServerTokenServices tokenService() {
//创建“远程服务请求授权服务器”对象用于校验token
RemoteTokenServices remoteTokenServices=new RemoteTokenServices();
//1.指定校验Token的认证服务器(统一认证中心)的URL
remoteTokenServices.setCheckTokenEndpointUrl("http://localhost:53020/uaa/oauth/check_token");
//2.配置当前资源服务的client_id
remoteTokenServices.setClientId("c1");
//3.配置当前资源服务的client_secret
remoteTokenServices.setClientSecret("secret");
return remoteTokenServices;
}
}
最后进行测试,先访问下述地址获取授权码
http://127.0.0.1:53020/uaa/oauth/authorize?client_id=c1&response_type=code&scope=all&redirect_uri=http://www.baidu.com
获取到了授权码Code以后,调用下述接口获取令牌:
http://127.0.0.1:53020/uaa/oauth/token?code=1e8pTX&grant_type=authorization_code&client_id=c1&client_secret=secret&redirect_uri=http://www.baidu.com
会得到一个类似这样的令牌:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsicmVzMSJdLCJ1c2VyX25hbWUiOiJ4aWFvbWluZyIsInNjb3BlIjpbImFsbCJdLCJleHAiOjE2MzEzNDYxODEsImF1dGhvcml0aWVzIjpbInAxIl0sImp0aSI6IjUzZjcxZTA2LTUzZjktNDk0OC1iYWQxLTM4ZGIzNmI2YmI2NSIsImNsaWVudF9pZCI6ImMxIn0.rmCcyciOFqJyXJbFLWaEXo0U3uU9LO1BaL5bHFUVVD0
最后get请求访问资源服务:
http://127.0.0.1:53021/order/r1?access_token=JWT令牌
至此,OAUTH2.0与JWT令牌的配置应用部分基本上已经完成,其它有什么需求,可自行进行扩展。