在微服务下Spring Security实现Oauth2协议 入门篇我们介绍了如何在微服务下基于OAuth2协议构建认证服务器和资源服务器。资源服务器会检查请求头里面是否带上了token,并去认证服务器校验这个token是否合法,是否过期,是否有权限做对应的操作。显然随着微服务数量的增长,在每一个微服务上都要做资源服务器的配置实不可取的,因此我们需要把这部分的操作交由网关去处理。在上一节中check_token这部分的工作,资源服务器利用RemoteTokenServices帮我们实现了,但是这一节中我们需要在网关上自己手动实现这部分的逻辑。
网关逻辑上是资源服务器,需要订单服务,库存服务不再作为资源服务器。
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-zuulartifactId>
dependency>
开启@EnableZuulProxy
@SpringBootApplication
@EnableZuulProxy
public class GatewayServer {
public static void main(String[] args) {
SpringApplication.run(GatewayServer.class, args);
}
}
配置路由规则application.yml
zuul:
routes:
token:
url: http://localhost:9090
order:
url: http://localhost:9080
price:
url: http://localhost:9060
sensitive-headers:
/**
* 定义zuul pre前置过滤器
*
*/
@Slf4j
@Component
public class OAuthFilter extends ZuulFilter {
private RestTemplate restTemplate = new RestTemplate();
@Override
public boolean shouldFilter() {
return true;
}
@Override
public Object run() throws ZuulException {
log.info("oauth start");
// 为了获取request请求
RequestContext requestContext = RequestContext.getCurrentContext();
HttpServletRequest request = requestContext.getRequest();
// 获取token请求不进行过滤
if(StringUtils.startsWith(request.getRequestURI(), "/token")) {
return null;
}
String authHeader = request.getHeader("Authorization");
if(StringUtils.isBlank(authHeader)) {
return null;
}
// 如果请求头带了以barer开头的请求,则去认证服务器校验token
if(!StringUtils.startsWithIgnoreCase(authHeader, "bearer ")) {
return null;
}
try {
// 将token放入请求头里
TokenInfo info = getTokenInfo(authHeader);
request.setAttribute("tokenInfo", info);
} catch (Exception e) {
log.error("get token info fail", e);
}
return null;
}
private TokenInfo getTokenInfo(String authHeader) {
String token = StringUtils.substringAfter(authHeader, "bearer ");
String oauthServiceUrl = "http://localhost:9090/oauth/check_token";
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
headers.setBasicAuth("gateway", "123456");
MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
params.add("token", token);
HttpEntity<MultiValueMap<String, String>> entity = new HttpEntity<>(params, headers);
ResponseEntity<TokenInfo> response = restTemplate.exchange(oauthServiceUrl, HttpMethod.POST, entity, TokenInfo.class);
log.info("token info :" + response.getBody().toString());
return response.getBody();
}
/**
* zuul 提供了四种过滤器类型
* pre
* post
* error
* route
* @return
*/
@Override
public String filterType() {
return "pre";
}
@Override
public int filterOrder() {
return 1;
}
}
AuthorizationFilter
/**
*
* 授权过滤器
*/
@Slf4j
@Component
public class AuthorizationFilter extends ZuulFilter {
@Override
public boolean shouldFilter() {
return true;
}
@Override
public Object run() throws ZuulException {
log.info("authorization start");
RequestContext requestContext = RequestContext.getCurrentContext();
HttpServletRequest request = requestContext.getRequest();
if(isNeedAuth(request)) {
// OAuthFilter token 校验通过,会将token信息放在请求头里
TokenInfo tokenInfo = (TokenInfo)request.getAttribute("tokenInfo");
// 校验是否有权限
if(tokenInfo != null && tokenInfo.isActive()) {
if(!hasPermission(tokenInfo, request)) {
log.info("audit log update fail 403");
handleError(403, requestContext);
}
requestContext.addZuulRequestHeader("username", tokenInfo.getUser_name());
}else {
if(!StringUtils.startsWith(request.getRequestURI(), "/token")) {
log.info("audit log update fail 401");
handleError(401, requestContext);
}
}
}
return null;
}
private void handleError(int status, RequestContext requestContext) {
requestContext.getResponse().setContentType("application/json");
requestContext.setResponseStatusCode(status);
requestContext.setResponseBody("{\"message\":\"auth fail\"}");
requestContext.setSendZuulResponse(false);
}
private boolean hasPermission(TokenInfo tokenInfo, HttpServletRequest request) {
return true; //RandomUtils.nextInt() % 2 == 0;
}
private boolean isNeedAuth(HttpServletRequest request) {
return true;
}
@Override
public String filterType() {
return "pre";
}
@Override
public int filterOrder() {
return 3;
}
}
网关上的限流工作只做适合粗粒度的限流,不要做业务上的线流,网关与网关后面的微服务之间需要是低耦合的。因为业务规则改变了,需要调整网关上的限流规则,甚至要重新部署,这是不可取的。
这一小节内容主要介绍网关上如何做限流操作,这里我们使用了开源组件spring-cloud-zuul-ratelimit
引入依赖:
<dependency>
<groupId>com.marcosbarbero.cloudgroupId>
<artifactId>spring-cloud-zuul-ratelimitartifactId>
<version>2.2.4.RELEASEversion>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-jpaartifactId>
dependency>
限流规则配置:
# 限流的配置 这里存在数据库中,生产环境时候 需要放在redis中
# ratelimit:
# enabled: true
# repository: JPA
# default-policy-list:
# - limit: 2
# quota: 1
# refresh-interval: 3
# type:
# - url /a get # 针对指定的url进行限制
# - httpmethod
# jpa:
# generate-ddl: true
# show-sql: true
网关用到了数据库,因此还有数据源的配置,这里不贴出来了