背景说明
zuul在spring cloud里常用于API网关接入,用于对外提供统一服务接口,为外部访问提供鉴权等安全控制,保护内部各微服务资源。而ory hydra是一个开源的轻量化认证管理中心,支持oauth2规范标准,具体可参考这里。我们通过spring security来实现两者的关联,将zuul作为一个resource server, 让其调用认证中心hydra的oauth2 token校验接口来验证请求是否合法。下面按步骤说明实施过程。
准备工作
首先需在hydra里添加oauth2记录。这里的hydra通过docker-compose安装的,4445是hydra的管理端口。通过如下指令添加一个记录: client_id是id_pkce, 支持PKCE认证模式,所以token-endpoint-auth-method为none。
docker-compose -f quickstart.yml exec hydra \
hydra clients create \
--endpoint http://127.0.0.1:4445/ \
--allowed-cors-origins http://localhost \
--id id_pkce \
--grant-types authorization_code,refresh_token \
--response-types code,id_token \ \
--scope openid,offline,first_party \ \
--callbacks http://localhost/auth/ \
--token-endpoint-auth-method none
实施步骤
pom.xml文件
这里列出关键的几个依赖
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Hoxton.SR8</spring-cloud.version>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependencies>
参数配置
分两部分,一个是zuul的路由配置,另一个访问oauth2认证中心hydra所需的参数配置。
zuul配置
这里假设有两个路径转发配置,/api/svc1/开头的转微服务msc-1,/api/svc2/开头的转外部连接服务
zuul:
routes:
svc1:
path: /api/svc1/**
service-id: msc-1
svc2:
path: /api/svc2/**
url: http://192.168.0.1:10000/api/
oauth2配置
这里的配置用于连接hydra用。192.168.0.2是hydra验证中心的内网IP,端口5000是管理端口4445的外部映射。为安全起见,hydra的管理端口只能内部访问,不能被外部访问。/oauth2/introspect就是hydra的校验token的restful接口
security:
oauth2:
client:
client-id: id_pkce
client-secret: "{noop}"
resource:
token-info-uri: http://192.168.0.2:5000/oauth2/introspect
编写WebSecurity类
这里配置所有请求都需经过验证
@Configuration
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
// @formatter:off
http.cors()
.and()
.authorizeRequests()
.anyRequest().authenticated()
.and()
.csrf().disable()
;
// @formatter:on
}
}
编写ResourceServer类
首先定义类
@Configuration
@EnableResourceServer
@Slf4j
public class ResServerConfig extends ResourceServerConfigurerAdapter{
然后实现访问hydra, 注入前面定义的oauth2参数,通过RemoteTokenServices实现。
@Value("${security.oauth2.client.client-id}")
private String clientId;
@Value("${security.oauth2.client.client-secret}")
private String clientSecret;
@Value("${security.oauth2.resource.token-info-uri}")
private String tokenCheckUrl;
@Autowired
RemoteTokenServices remoteTokenServices;
@Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
remoteTokenServices.setClientId(clientId);
remoteTokenServices.setClientSecret(clientSecret);
remoteTokenServices.setCheckTokenEndpointUrl(tokenCheckUrl);
resources.tokenServices(remoteTokenServices);
}
接着配置哪些请求需走oauth2校验,这里定义所有/api开头的请求都要有scope"first_party"。这是前面在hydra创建的oauth2记录的属性之一。
@Override
public void configure(HttpSecurity http) throws Exception {
final String[] urlPattern = {"/api/**"};
http
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.requestMatchers().antMatchers(urlPattern)
.and()
.authorizeRequests()
.antMatchers(urlPattern)
.access("#oauth2.hasScope('first_party')")
;
}
}
测试验证
- 做一个get请求/api/showData,通过RestController实现
@RequestMapping({"/api/showData","/api/**"})
public Object showData(){
Map<String,String> map = new HashMap<String,String>();
map.put("t1","this is test1");
map.put("t2","this is test2");
return map;
}
-
直接请求,会返回401错误
-
带错误的Bearer token, 返回401错误
-
带正确的Bearer token, 返回200 OK