授权服务器可参考另一篇文章
搭建资源服务器
创建一个新的子模块client
介绍
@EnableResourceServer 注解到一个
@Confifiguration
配置类上,并且必须使用
ResourceServerConfifigurer
这个配置对象来进行配置(可以选择继承自ResourceServerConfifigurerAdapter
然后覆写其中的方法,参数就是这个对象的实例),下面是一些可以配置的属性:
ResourceServerSecurityConfifigurer中主要包括:
- tokenServices:ResourceServerTokenServices 类的实例,用来实现令牌服务。
- tokenStore:TokenStore类的实例,指定令牌如何访问,与tokenServices配置可选
- resourceId:这个资源服务的ID,这个属性是可选的,但是推荐设置并在授权服务中进行验证。
- 其他的拓展属性例如 tokenExtractor 令牌提取器用来提取请求中的令牌。
HttpSecurity
配置这个与
Spring Security
类似:
- 请求匹配器,用来设置需要进行保护的资源路径,默认的情况下是保护资源服务的全部路径。
- 通过http.authorizeRequests()来设置受保护资源的访问规则
- 其他的自定义权限保护规则通过 HttpSecurity 来进行配置。
@EnableResourceServer
注解自动增加了一个类型为
OAuth2AuthenticationProcessingFilter
的过滤器链
编写
ResouceServerConfifig
:
package com.security.oauth2.oauth2client.config;
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;
@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
public static final String RESOURCE_ID = "res1";
@Override
public void configure(ResourceServerSecurityConfigurer resources) {
resources
//资源id
.resourceId(RESOURCE_ID)
//验证令牌的服务,远程验证
.tokenServices(tokenService())
//无状态
.stateless(true);
}
//资源服务令牌解析服务,会向授权服务器发送请求验证令牌的正确性
@Bean
public ResourceServerTokenServices tokenService() {
//使用远程服务请求授权服务器校验token,必须指定校验token 的url、client_id,client_secret
RemoteTokenServices service=new RemoteTokenServices();
service.setCheckTokenEndpointUrl("http://localhost:8081/demo2/oauth/check_token");
service.setClientId("c1");
service.setClientSecret("secret");
return service;
}
@Override
public void configure(HttpSecurity http) throws Exception {
http .authorizeRequests()
//如果令牌中的授权是‘ROLE_ADMIN’,则可以访问任意路径
.antMatchers("/**").access("#oauth2.hasScope('ROLE_ADMIN')")
.and()
//跨域
.csrf().disable()
.sessionManagement()
//关闭session
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
}
验证token
ResourceServerTokenServices
是组成授权服务的另一半,如果你的授权服务和资源服务在同一个应用程序上的话,你可以使用 DefaultTokenServices
,这样的话,你就不用考虑关于实现所有必要的接口的一致性问题。如果你的资源服务器是分离开的,那么你就必须要确保能够有匹配授权服务提供的 ResourceServerTokenServices
,它知道如何对令牌进行解码。
令牌解析方法: 使用 DefaultTokenServices 在资源服务器本地配置令牌存储、解码、解析方式 使用 RemoteTokenServices 资源服务器通过
HTTP
请求来解码令牌,每次都请求授权服务器端点
/oauth/check_token 使用授权服务的 /oauth/check_token
端点你需要在授权服务将这个端点暴露出去,以便资源服务可以进行访问,
这在咱们授权服务配置中已经提到了,下面是一个例子
,
在这个例子中,我们在授权服务中配置了
/oauth/check_token
和
/oauth/token_key
这两个端点:
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security
.tokenKeyAccess("permitAll()")// /oauth/token_key 安全配置
.checkTokenAccess("permitAll()") // /oauth/check_token 安全配置
}
在资源 服务配置
RemoteTokenServices
,在
ResouceServerConfifig
中配置:
public static final String RESOURCE_ID = "res1";
@Override
public void configure(ResourceServerSecurityConfigurer resources) {
resources
//资源id
.resourceId(RESOURCE_ID)
//验证令牌的服务,远程验证
.tokenServices(tokenService())
//无状态
.stateless(true);
}
//资源服务令牌解析服务,会向授权服务器发送请求验证令牌的正确性
@Bean
public ResourceServerTokenServices tokenService() {
//使用远程服务请求授权服务器校验token,必须指定校验token 的url、client_id,client_secret
RemoteTokenServices service=new RemoteTokenServices();
service.setCheckTokenEndpointUrl("http://localhost:8081/demo2/oauth/check_token");
service.setClientId("c1");
service.setClientSecret("secret");
return service;
}
编写资源
在
controller
包下编写
OrderController
,此
controller
表示订单资源的访问类
package com.security.oauth2.oauth2client.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class ClientController {
@GetMapping("/r/r1")
public String r1(){
return "访问资源1";
}
@GetMapping("/r/r2")
public String r2(){
return "访问资源2";
}
}
启动访问:
获取令牌
按照
oauth2.0
协议要求,请求资源需要携带
token
,如下:
token
的参数名称为:
Authorization
,值为:
Bearer token
值
访问r1:
访问r2
添加资源控制
package com.security.oauth2.oauth2client.config;
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.annotation.web.configuration.WebSecurityConfigurerAdapter;
@Configuration
@EnableGlobalMethodSecurity(securedEnabled = true,prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
//安全拦截机制(最重要)
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
// .antMatchers("/r/r1").hasAuthority("p2")
// .antMatchers("/r/r2").hasAuthority("p2")
.antMatchers("/r/**").authenticated()//所有/r/**的请求必须认证通过
.anyRequest().permitAll();//除了/r/**,其它的请求可以访问 ;
}
}
修改controller增加权限限制
package com.security.oauth2.oauth2client.controller;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class ClientController {
@GetMapping("/r/r1")
@PreAuthorize("hasAnyAuthority('p1')")
public String r1(){
return "访问资源1";
}
@GetMapping("/r/r2")
@PreAuthorize("hasAnyAuthority('p2')")
public String r2(){
return "访问资源2";
}
}
重新访问
访问 r1
访问 r2
由于现在每次的令牌验证都需要向授权服务器发送请求验证,比较消耗性能,推荐使用jwt令牌验证
jwt令牌:
修改授权服务器配置
修改令牌策略TokenConfig:
package com.security.oauth2.oauth2service.config.oauth2;
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.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
@Configuration
public class TokenConfig {
// /**
// * 令牌策略-内存方式
// * @return
// */
// @Bean
// public TokenStore tokenStore(){
// //内存方式
// return new InMemoryTokenStore();
// }
//加密盐,防止被篡改
private String SIGNING_KEY = "uaa123";
/**
* 令牌策略-jwt方式
* @return
*/
@Bean
public TokenStore tokenStore(){
return new JwtTokenStore(jwtAccessTokenConverter());
}
@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter(){
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
//对称密匙,资源服务器使用该密匙验证
converter.setSigningKey(SIGNING_KEY);
return converter;
}
}
在授权服务器中AuthorizationServer配置文件中的令牌配置中 添加以下代码:
TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
tokenEnhancerChain.setTokenEnhancers(Arrays.asList(jwtAccessTokenConverter));
services.setTokenEnhancer(tokenEnhancerChain);
完整配置
package com.security.oauth2.oauth2service.config.oauth2;
import com.security.oauth2.oauth2service.config.security.MD5PasswordEncoder;
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.password.NoOpPasswordEncoder;
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.code.InMemoryAuthorizationCodeServices;
import org.springframework.security.oauth2.provider.code.JdbcAuthorizationCodeServices;
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;
/**
* oauth2
*/
@Configuration
@EnableAuthorizationServer
public class AuthorizationServer extends AuthorizationServerConfigurerAdapter {
@Autowired
private PasswordEncoder passwordEncoder;
@Autowired
private DataSource dataSource;
@Bean
public ClientDetailsService clientDetails() {
//使用数据库
ClientDetailsService clientDetailsService = new JdbcClientDetailsService(dataSource);
((JdbcClientDetailsService) clientDetailsService)
//配置密匙加密为md5
.setPasswordEncoder(passwordEncoder);
return clientDetailsService;
}
/**
* 配置客户端认证(谁来申请令牌)使用数据库
* @param clients
* @throws Exception
*/
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.withClientDetails(clientDetails());
}
//注入令牌策略-内存方式
@Autowired
private TokenStore tokenStore;
//jwt令牌
@Autowired
private JwtAccessTokenConverter jwtAccessTokenConverter;
/**
* 配置令牌服务
*/
@Bean
public AuthorizationServerTokenServices tokenService(){
DefaultTokenServices services = new DefaultTokenServices();
//使用客户端配置
services.setClientDetailsService(clientDetails());
//是否使用刷新令牌
services.setSupportRefreshToken(true);
//令牌的存储策略
services.setTokenStore(tokenStore);
//使用jwt令牌增强
TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
tokenEnhancerChain.setTokenEnhancers(Arrays.asList(jwtAccessTokenConverter));
services.setTokenEnhancer(tokenEnhancerChain);
//令牌默认有效期2小时
services.setAccessTokenValiditySeconds(7200);
//刷新令牌默认有效期3天
services.setRefreshTokenValiditySeconds(259200);
return services;
}
/**
* 授权码服务 使用数据库
* @return
*/
@Bean
public AuthorizationCodeServices authorizationCodeServices(){
//使用数据库
return new JdbcAuthorizationCodeServices(dataSource);
}
/**
* 在security配置文件WebSecurityConfig 中加入bean容器
*/
@Autowired
private AuthenticationManager authenticationManager;
/**
* 配置令牌的访问端点(申请令牌的地址)和令牌服务(令牌怎么发放)
* @param endpoints
* @throws Exception
*/
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints
//认证管理器,密码模式需要
.authenticationManager(authenticationManager)
//授权码服务
.authorizationCodeServices(authorizationCodeServices())
//令牌管理服务
.tokenServices(tokenService())
.allowedTokenEndpointRequestMethods(HttpMethod.POST);
}
/**
* 令牌的安全约束(验证有没有资格申请令牌)
* @param security
* @throws Exception
*/
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security
//oauth/token_key是公开
.tokenKeyAccess("permitAll()")
//oauth/check_token公开
.checkTokenAccess("permitAll()")
//表单认证(申请令牌)
.allowFormAuthenticationForClients();
}
}
测试
修改资源服务器
将授权服务器中的令牌策略文件 TokenConfig 复制到资源服务器
然后修改配置文件ResourceServerConfig 令牌策略:
package com.security.oauth2.oauth2client.config;
import org.springframework.beans.factory.annotation.Autowired;
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.TokenStore;
@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
public static final String RESOURCE_ID = "res1";
@Autowired
private TokenStore tokenStore;
@Override
public void configure(ResourceServerSecurityConfigurer resources) {
resources
//资源id
.resourceId(RESOURCE_ID)
//验证令牌的服务,jwt令牌
.tokenStore(tokenStore)
//无状态
.stateless(true);
}
@Override
public void configure(HttpSecurity http) throws Exception {
http .authorizeRequests()
//如果令牌中的授权是‘ROLE_ADMIN’,则可以访问任意路径
.antMatchers("/**").access("#oauth2.hasScope('ROLE_ADMIN')")
.and()
//跨域
.csrf().disable()
.sessionManagement()
//关闭session
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
}
启动测试:
访问 r1:
访问 r2