在Spring Cloud进阶之路 | 七:服务网关(zuul)一文中,已详细阐述了网关的作用及重要性,其不仅仅可进行路由转发和请求过滤,还可以进行权限校验、审计、接口监控、限流以及日志收集等逻辑。
要完成这些额外逻辑,需集成其它框架,或者开发相应的过滤器,亦或是代码逻辑。本文就以整合安全框架中为例,演示如何在网关进行身份认证。
前面说过,微服务架构中,把一个大型的单个应用程序和服务拆分为数个甚至数十个的支持微服务,为了保证高可用,每个微服务都可能会部署集群。
根据之前文章Spring Cloud进阶之路 | 八:授权服务(Spring Cloud Oauth2)、Spring Cloud进阶之路 | 九:资源服务(Spring Cloud Oauth2)的阐述,拆分后的单个服务均为资源服务器,提供资源服务。
既然是对外提供资源服务,势必会引起一个问题,即安全问题。此时,便有两种方案:各资源服务自行处理、统一交由网关处理,各资源服务只关注业务。
如果各资源服务自行处理,微服务拆分后,每个服务均需处理相同安全逻辑,工作量严重重复。即便提取公共部分,也解决不了大问题。因为公共部分只能解决身份认证问题,解决不了鉴权,每个资源服务权限都不尽相同。
统一交由网关处理,网关本身作为资源服务,先期直接进行身份认证,身份认证通过之后再进行鉴权,均通过以后再执行后续逻辑。此时,各资源服务只用关注自身业务,无需处理这些繁琐的安全策略。
至于权限问题,可开发统一的权限支撑平台,统一管理用户及所有资源服务的权限,服务上线自动注册资源到权限支撑平台,再由维护人员统一分配即可。
复用之前文章Spring Cloud进阶之路 | 七:服务网关(zuul)、Spring Cloud进阶之路 | 八:授权服务(Spring Cloud Oauth2)、Spring Cloud进阶之路 | 九:资源服务(Spring Cloud Oauth2)的工程。
据前文所述,各资源服务不再作为授权服务相对的资源服务而存在,所以,需要删除Spring Cloud Oauth2相关内容。
修改后的pom文件如下。
4.0.0
com.luas.cloud
java-boot-parent-2.1
1.0.0-SNAPSHOT
../../java-boot-parent-2.1
com.luas.cloud
xmall-product
1.0.0-SNAPSHOT
xmall-product
Spring Cloud Learning,nacos-client
org.springframework.boot
spring-boot-starter-web
com.alibaba.cloud
spring-cloud-starter-alibaba-nacos-config
com.alibaba.cloud
spring-cloud-starter-alibaba-nacos-discovery
org.springframework.boot
spring-boot-starter-test
test
org.springframework.boot
spring-boot-maven-plugin
删除application.yml中oauth2资源配置信息,删除后内容如下。
server:
port: 8080
如启动类有@EnableResourceServer注解,则删除。
package com.luas.xmall;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class XmallProductApplication {
public static void main(String[] args) {
SpringApplication.run(XmallProductApplication.class, args);
}
}
如存在ResourceServerConfiguration配置类,则删除。
package com.luas.xmall.configuration;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
@Override
public void configure(HttpSecurity http) throws Exception {
http
.requestMatchers()
.anyRequest()
.and()
.authorizeRequests()
.antMatchers("/application").permitAll()
.anyRequest()
.authenticated();
}
}
添加spring-cloud-starter-oauth2依赖,修改后的pom文件如下。
4.0.0
com.luas.cloud
java-boot-parent-2.1
1.0.0-SNAPSHOT
../../java-boot-parent-2.1
com.luas.xmall
xmall-zuul
1.0.0-SNAPSHOT
xmall-zuul
网关服务
org.springframework.cloud
spring-cloud-starter-netflix-zuul
org.springframework.cloud
spring-cloud-starter-oauth2
com.alibaba.cloud
spring-cloud-starter-alibaba-nacos-config
com.alibaba.cloud
spring-cloud-starter-alibaba-nacos-discovery
org.springframework.boot
spring-boot-starter-test
test
org.junit.vintage
junit-vintage-engine
org.springframework.boot
spring-boot-maven-plugin
添加授权服务器路由规则、oauth2资源配置。
server:
port: 5566
zuul:
prefix: /gateway
sensitive-headers:
routes:
auth:
path: /auth/**
service-id: xmall-auth
strip-prefix: true
product:
path: /product/**
service-id: xmall-product
strip-prefix: true
security:
oauth2:
resource:
user-info-uri: http://localhost:7777/oauth/user
prefer-token-info: false
此处有一个点,比较重要:为什么不直接在启动类添加@EnableResourceServer注解开启资源服务,而要特殊定义资源服务配置?
原因就在于,默认资源服务器安全策略,所以请求均需身份认证,那么获取token、token校验、token key获取等这些无需身份认证即可访问的端点,也会被拦截。所以,需要特殊定义资源服务安全策略,放开这些公共端点。
ResourceServerConfiguration类如下。
package com.luas.xmall.gateway.configuration;
import com.luas.xmall.gateway.filter.PreAuthenticationFilter;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
@Configuration
@EnableResourceServer
public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
@Override
public void configure(HttpSecurity http) throws Exception {
http
.requestMatchers()
.anyRequest()
.and()
.authorizeRequests()
.antMatchers("/application").permitAll()
// 放开token授权、token key获取、token验证端点
.antMatchers(PreAuthenticationFilter.TOKEN_ENDPOINT).permitAll()
.antMatchers(PreAuthenticationFilter.TOKEN_KEY_ENDPOINT).permitAll()
.antMatchers(PreAuthenticationFilter.CHECK_TOKEN_ENDPOINT).permitAll()
.anyRequest()
.authenticated();
}
}
同ResourceServerConfiguration,前置安全校验也需要放开公共端点。另外,此过滤器还可进行相关权限拦截、用户信息解析并传递等逻辑。至于其它的,如接口审计,可单独创建过滤器处理,不过需要注意执行顺序及过滤器类型。
package com.luas.xmall.gateway.filter;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.provider.authentication.BearerTokenExtractor;
import org.springframework.security.oauth2.provider.authentication.TokenExtractor;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.PRE_TYPE;
@Component
public class PreAuthenticationFilter extends ZuulFilter {
public static final String TOKEN_ENDPOINT = "/gateway/auth/oauth/token";
public static final String TOKEN_KEY_ENDPOINT = "/gateway/auth/oauth/token_key";
public static final String CHECK_TOKEN_ENDPOINT = "/gateway/auth/oauth/check_token";
private Logger logger = LoggerFactory.getLogger(getClass());
private TokenExtractor tokenExtractor = new BearerTokenExtractor();
@Override
public String filterType() {
return PRE_TYPE;
}
@Override
public int filterOrder() {
return 0;
}
@Override
public boolean shouldFilter() {
RequestContext requestContext = RequestContext.getCurrentContext();
HttpServletRequest request = requestContext.getRequest();
String requestURI = request.getRequestURI();
return !(requestURI.equals(TOKEN_ENDPOINT) && requestURI.equals(TOKEN_KEY_ENDPOINT) && requestURI.equals(CHECK_TOKEN_ENDPOINT));
}
@Override
public Object run() throws ZuulException {
RequestContext requestContext = RequestContext.getCurrentContext();
HttpServletRequest request = requestContext.getRequest();
if (this.logger.isDebugEnabled()) {
this.logger.debug("uri {}", request.getRequestURI());
}
Authentication authentication = this.tokenExtractor.extract(request);
if (authentication == null || authentication.getPrincipal() == null) {
//不会继续往下执行 不会调用服务接口了 网关直接响应给客户了
requestContext.setSendZuulResponse(false);
requestContext.setResponseBody("Full authentication is required to access this resource");
requestContext.setResponseStatusCode(401);
return null;
}
String accessToken = (String) authentication.getPrincipal();
this.logger.info("token {}", accessToken);
// todo 解析token,调用权限支撑平台,获取权限信息,组织到用户信息中传递给下游微服务
return null;
}
}
依次启动xmall-auth、xmall-product、xmall-zuul,端口分别为7777、8080、5566。
先直接访问http://localhost:8080/sku/1122,可正常访问。
通过网关访问http://localhost:5566/gateway/product/sku/1122,发现已不能正常访问,需要先进行身份认证。
通过网关调用授权服务,进行授权,地址为:
http://localhost:5566/gateway/auth/oauth/token?client_id=client_1&client_secret=123456&username=user_1&password=123456&scope=server&grant_type=password。
请求之后,正常返回授权结果。
此时,说明网关改造成功,已正常发挥资源服务器作用,针对所请求资源联合授权服务器进行身份认证。
header方式携带授权,重新访问http://localhost:5566/gateway/product/sku/1122,可正常访问。
网关整合安全框架以在网关进行身份认证完成,其它功能如日志收集、接口审计、鉴权等,读者可自行去参整合实现。后续也会出相关文章,对部分功能进行整合说明。
github
https://github.com/liuminglei/SpringCloudLearning/tree/master/10/
gitee
https://gitee.com/xbd521/SpringCloudLearning/tree/master/10/
微信搜索【银河架构师】,发现更多精彩内容。