zuul 集成spring security 作为边缘路由访问时的api权限控制策略
- api-server作为资源服务器。
在上一节中,security-server中oauth2作为整个微服务的权限控制中心,主要功能对客户端的
认证和token的发放,与此向对的就是资源服务器,资源服务器依赖于权限服务器。其他客户端想要
调用资源服务器的接口,就必须通过权限服务器的认证。
zuul的基本介绍已在第六节中有过基本介绍,可参考第六节 服务端负载均衡
关于资源服务器的api-server的配置使用如下:
- pom 添加依赖
<dependency>
<groupId>com.xzg</groupId>
<artifactId>online-table-reservation-common</artifactId>
<version>v1</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-netflix-hystrix-stream</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
- 基本配置,启动类@EnableResourceServer标注该服务为资源服务器
@SpringBootApplication
@EnableEurekaClient
@EnableResourceServer
@Configuration
@ComponentScan({"com.xzg.api.service", "com.xzg.common"})
public class ApiApp {
private static final Logger LOG = LoggerFactory.getLogger(ApiApp.class);
static {
// 本地测试
LOG.warn("禁用ssl主机名检查,开发截断使用");
HttpsURLConnection.setDefaultHostnameVerifier((hostname, sslSession) -> true);
}
@LoadBalanced
@Bean
RestTemplate restTemplate() {
return new RestTemplate();
}
public static void main(String[] args) {
LOG.info("Register MDCHystrixConcurrencyStrategy");
HystrixPlugins.getInstance().registerConcurrencyStrategy(new MDCHystrixConcurrencyStrategy());
SpringApplication.run(ApiApp.class, args);
}
}
- 配置文件中添加权限认证服务配置
#其他略
security:
oauth2:
resource:
userInfoUri: https://localhost:9001/user
management:
security:
enabled: false
具体配置可参考源码
- 如何访问资源服务器中的API
如果资源服务器和授权服务器在同一个应用程序中,并且使用DefaultTokenServices,那么不必太考虑这一点,因为它实现所有必要的接口,因此它是自动一致的。如果资源服务器是一个单独的应用程序(比如本工程),那么必须确保匹配授权服务器的功能,并提供知道如何正确解码令牌的ResourceServerTokenServices。与授权服务器一样,可以经常使用DefaultTokenServices,并且选项大多通过TokenStore(后端存储或本地编码)表示
以下基于security-server模块
- 源码用户密码以及客户端client_id、client_secret。均为硬编码所以测试时,需要根据自身调整
按照上一节的步骤先获取token(授权码模式):
- 发送url获取code
https://localhost:9001/oauth/authorize?response_type=code&client_id=client&redirect_uri=http://localhost:7771/1&scope=apiAccess&state=553344
- 注意:如果浏览器未登录,则会跳转用户登陆页面,成功登陆后,重定向
返回到重定向的http://localhost:7771/1?code=ar2lEB&state=553344取得code
- 认证后取得code换取token
curl -X POST -k -H 'Content-Type: application/x-www-form-urlencoded' -i https://localhost:9001/oauth/token --data 'grant_type=authorization_codet&redirect_uri=http://localhost:7771/1&code=ar2lEB'
- 获取token后使用token,就可以在请求资源的请求头添加Bearer token
curl -X GET -H 'Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJ' -i http://localhost:7771/service
4 执行成功返回结果,Oauth2的基本也就实现了
也可以使用隐式许可方式直接获取token(隐式许可模式),方法如下
直接发送:如果未登陆会转向登陆
请求:
https://localhost:9001/oauth/authorize?response_type=token&client_id=client&redirect_uri=http://localhost:7771&scope=apiAccess&state=553344
返回:
http://localhost:7771/#access_token=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2luZm8iOnsidXNlck5hbWUiOiJjbGllbnQiLCJwYXNzd29yOsTSQRjpKp2pE4ru2elm3uqFY_mduVtvwc92ZSPTNtTtBbijfNU86r7giIxsqaqaliu4pnvyXO2CWP7q74lOGWWDWDtI02u-a6jhpqauM-TjGHAMAxr-VUbyduw&token_type=bearer&state=553344&expires_in=7199&user_info=com.xzg.security.service.securityEtity.BaseUser@3e219620&jti=1521428f-5d94-4a72-befb-57531dab784a
还有一种是直接使用用户密码模式(资源所有者密码凭证模式)
请求如下:
curl -X POST -k -H 'Content-Type: application/x-www-form-urlencoded' -i https://localhost:9001/oauth/token --data 'grant_type=password&scope=apiAccess&client_id=client&client_secret=password&username=client&password=password'
- 注意:上面这种发送方式参数携带客户端的client_id和client_secret这两个,这俩是security-server使用https的客户端认证,(ClientDetailsServiceConfigurer:用来配置客户端详情服务中的配置,保存的密码使用BCryptUtil加密。如果使用其他方式比如jdbc等存储是也需要注意)
/**
* @param clientDetailsServiceConfigurer
* ClientDetailsServiceConfigurer:用来配置客户端详情服务(ClientDetailsService)
* 客户端详情信息在这里进行初始化,你能够把客户端详情信息写死在这里或者是通过数据库来存储调取详情信息。
* @throws Exception
*/
@Override
public void configure(ClientDetailsServiceConfigurer clientDetailsServiceConfigurer) throws Exception {
clientDetailsServiceConfigurer
.inMemory()//使用方法代替in-memory、JdbcClientDetailsService、jwt
// client_id: 用来标识客户的Id。第三方用户的id(可理解为账号)
.withClient("client")
// client_secret:第三方应用和授权服务器之间的安全凭证(可理解为密码)
//(需要值得信任的客户端)客户端安全码
.secret(BCryptUtil.encodePassword("password"))
.accessTokenValiditySeconds(7200)
.authorizedGrantTypes("authorization_code", "refresh_token", "client_credentials", "implicit", "password")
.authorities("ROLE_USER")
.scopes("apiAccess");
}
-
需要注意的是
或者使用插件(火狐插件RESTClient)
当然如果刚才发送不带client_id和client_secret这两个的话,发送请求后会spring会去认证客户端,所以会让手动输入client_id和client_secret的值
curl -X POST -k -H 'Content-Type: application/x-www-form-urlencoded' -i https://localhost:9001/oauth/token --data 'grant_type=password&scope=apiAccess&username=client&password=password'
有一点我的username和password同client_id和client_secret相同所以可能会导致一些误解,可以在程序硬编码中做修改,如下:
/**
* @author xzg
*/
@Service
public class BaseUserDetailService implements UserDetailsService {
private Logger logger = LoggerFactory.getLogger(this.getClass());
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
logger.info("获取登陆信息:" + username);
//注意:实际生产应该从数据库中获取,用户名,密码(为BCryptPasswordEncoder hash后的密码)
if (!"zhangsan".equals(username)) {
logger.error("找不到该用户,用户名:" +username);
throw new UsernameNotFoundException("找不到该用户,用户名:" + username);
}
//密码硬编码
String password = BCryptUtil.encodePassword("12345");
// 获取用户权限列表
List<GrantedAuthority> authorities = CreatHardRole.createAuthorities().get();
// 返回带有用户权限信息的User
org.springframework.security.core.userdetails.User user = new org.springframework.security.core.userdetails.User(username,
password, true, true, true, true, authorities);
return new BaseUserDetail(new BaseUser(username, password), user);
}
}
zuul 服务网关
zuul作为边缘路由,这里也属于资源服务,所以重点有两点配置,其一作为资源服务需要配置远程的权限服务器
security:
oauth2:
resource:
userInfoUri: https://localhost:9001/user
同时作为边缘路由,需要配置路由链路
zuul:
ignoredServices: "*"
routes:
restaurantapi:
path: /api/**
serviceId: api-service
stripPrefix: true
其他配置具体可参考源码zuul服务源码
需要说明需要启动本zuul项目,需要依赖eureka server、security-server、rabbitmq、以及其他业务服务