一、Zuul
介绍
什么是
Zuul
?
Spring Cloud Zuul
是整合
Netflflix
公司的
Zuul
开源项目实现的微服务网关,它实现了请求路由、负载均衡、校验过
虑等 功能。
官方:
https://github.com/Netflflix/zuul
什么是网关?
服务网关是在微服务前边设置一道屏障,请求先到服务网关,网关会对请求进行过虑、校验、路由等处理。有了服
务网关可以提高微服务的安全性,网关校验请求的合法性,请求不合法将被拦截,拒绝访问。
Zuul
与
Nginx
怎么配合使用?
Zuul
与
Nginx
在实际项目中需要配合使用,如下图,
Nginx
的作用是反向代理、负载均衡,
Zuul
的作用是保障微服
务的安全访问,拦截微服务请求,校验合法性及负载均衡。
创建springcloud的module工程:
pom需要有
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
在启动类中添加 @EnableZuulProxy
二、路由配置:
在appcation.yml中配置:
zuul:
routes:
manage-course:
path: /test/**
serviceId: test-service #微服务名称,网关会从eureka中获取该服务名称下的服务实例的地址
# 例子:将请求转发到http://localhost:31200/test
#url: http://www.baidu.com #也可指定url,此url也可以是外网地址\
strip-prefix: false #true:代理转发时去掉前缀,false:代理转发时不去掉前缀
sensitiveHeaders: #默认zuul会屏蔽cookie,cookie不会传到下游服务,这里设置为空则取消默认的黑名单,如果设置了具体的头信息则不会传到下游服务
# ignoredHeaders: 默认为空表示不过虑任何头
serviceId
:推荐使用
serviceId
,
zuul
会从
Eureka
中找到服务
id
对应的
ip
和端口。
strip-prefifix: false #true
:代理转发时去掉前缀,
false:
代理转发时不去掉前缀,例如,为
true
请
求
/test/coursebase/get/..
,代理转发到
/coursebase/get/
,如果为
false
则代理转发到
/test/coursebase/get
sensitiveHeaders
:敏感头设置,默认会过虑掉
cookie
,这里设置为空表示不过虑
ignoredHeaders
:可以设置过虑的头信息,默认为空表示不过虑任何头
三、过虑器
Zuul
的核心就是过虑器,通过过虑器实现请求过虑,身份校验等。
1 、ZuulFilter
自定义过虑器需要继承
ZuulFilter
,
ZuulFilter
是一个抽象类,需要覆盖它的四个方法,如下:
1
、
shouldFilter
:返回一个
Boolean
值,判断该过滤器是否需要执行。返回
true
表示要执行此过虑器,否则不执 行。
2
、
run
:过滤器的业务逻辑。
3
、
filterType
:返回字符串代表过滤器的类型,如下
pre
:请求在被路由之前
执行
routing
:在路由请求时调用
post
:在
routing
和
errror
过滤器之后调用
error
:处理请求时发生错误调用
4
、
filterOrder
:此方法返回整型数值,通过此数值来定义过滤器的执行顺序,数字越小优先级越高。
2、
测试
过虑所有请求,判断头部信息是否有
Authorization
,如果没有则拒绝访问,否则转发到微服务。
定义过虑器,使用
@Component
标识为
bean
。
mport com.alibaba.fastjson.JSON;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import org.apache.commons.lang3.StringUtils;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* @author Administrator
* @version 1.0
**/
@Component
public class LoginFilterTest extends ZuulFilter {
//过虑器的类型
@Override
public String filterType() {
/**
pre:请求在被路由之前执行
routing:在路由请求时调用
post:在routing和errror过滤器之后调用
error:处理请求时发生错误调用
*/
return "pre";
}
//过虑器序号,越小越被优先执行
@Override
public int filterOrder() {
return 0;
}
@Override
public boolean shouldFilter() {
//返回true表示要执行此过虑器
return true;
}
//过虑器的内容
//测试的需求:过虑所有请求,判断头部信息是否有Authorization,如果没有则拒绝访问,否则转发到微服务。
@Override
public Object run() throws ZuulException {
RequestContext requestContext = RequestContext.getCurrentContext();
//得到request
HttpServletRequest request = requestContext.getRequest();
//得到response
HttpServletResponse response = requestContext.getResponse();
//得到Authorization头
String authorization = request.getHeader("Authorization");
if(StringUtils.isEmpty(authorization)){
//拒绝访问
requestContext.setSendZuulResponse(false);
//设置响应代码
requestContext.setResponseStatusCode(200);
//构建响应的信息
ResponseResult responseResult = new ResponseResult(CommonCode.UNAUTHENTICATED);
//转成json
String jsonString = JSON.toJSONString(responseResult);
requestContext.setResponseBody(jsonString);
//转成json,设置contentType
response.setContentType("application/json;charset=utf-8");
return null;
}
return null;
}
}
CommonCode
@ToString
public enum CommonCode implements ResultCode{
INVALID_PARAM(false,10003,"非法参数!"),
SUCCESS(true,10000,"操作成功!"),
FAIL(false,11111,"操作失败!"),
UNAUTHENTICATED(false,10001,"此操作需要登陆系统!"),
UNAUTHORISE(false,10002,"权限不足,无权操作!"),
SERVER_ERROR(false,99999,"抱歉,系统繁忙,请稍后重试!");
// private static ImmutableMap<Integer, CommonCode> codes ;
//操作是否成功
boolean success;
//操作代码
int code;
//提示信息
String message;
private CommonCode(boolean success,int code, String message){
this.success = success;
this.code = code;
this.message = message;
}
@Override
public boolean success() {
return success;
}
@Override
public int code() {
return code;
}
@Override
public String message() {
return message;
}
}
ResultCode
/**
*
* 10000-- 通用错误代码
* 22000-- 媒资错误代码
* 23000-- 用户中心错误代码
* 24000-- cms错误代码
* 25000-- 文件系统
*/
public interface ResultCode {
//操作是否成功,true为成功,false操作失败
boolean success();
//操作代码
int code();
//提示信息
String message();
}
ResponseResult
@Data
@ToString
@NoArgsConstructor
public class ResponseResult implements Response {
//操作是否成功
boolean success = SUCCESS;
//操作代码
int code = SUCCESS_CODE;
//提示信息
String message;
public ResponseResult(ResultCode resultCode){
this.success = resultCode.success();
this.code = resultCode.code();
this.message = resultCode.message();
}
public static ResponseResult SUCCESS(){
return new ResponseResult(CommonCode.SUCCESS);
}
public static ResponseResult FAIL(){
return new ResponseResult(CommonCode.FAIL);
}
}
Response
public interface Response {
public static final boolean SUCCESS = true;
public static final int SUCCESS_CODE = 10000;
}
四,过滤器实例(Redis+filter)实现登陆后,其他系统身份验证
1
、从
cookie
查询用户身份令牌是否存在,不存在则拒绝访问
2
、从
http header
查询
jwt
令牌是否存在,不存在则拒绝访问
3
、从
Redis
查询
user_token
令牌是否过期,过期则拒绝访问
1
、配置application.yml 配置
redis
链接参数:
server:
port: 50201
servlet:
context-path: /api
spring:
application:
name: test-gateway
redis:
host: ${REDIS_HOST:127.0.0.1}
port: ${REDIS_PORT:6379}
timeout: 5000 #连接超时 毫秒
jedis:
pool:
maxActive: 3
maxIdle: 3
minIdle: 1
maxWait: -1 #连接池最大等行时间 -1没有限制
2
、使用
StringRedisTemplate
查询
key
的有效期
在
service
包下定义
AuthService
类:
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import javax.servlet.http.HttpServletRequest;
import java.util.Map;
import java.util.concurrent.TimeUnit;
/**
* @author Administrator
* @version 1.0
**/
@Service
public class AuthService {
@Autowired
StringRedisTemplate stringRedisTemplate;
//从头取出jwt令牌
public String getJwtFromHeader(HttpServletRequest request){
//取出头信息
String authorization = request.getHeader("Authorization");
if(StringUtils.isEmpty(authorization)){
return null;
}
if(!authorization.startsWith("Bearer ")){
return null;
}
//取到jwt令牌
String jwt = authorization.substring(7);
return jwt;
}
//从cookie取出token
//查询身份令牌
public String getTokenFromCookie(HttpServletRequest request){
Map<String, String> cookieMap = CookieUtil.readCookie(request, "uid");
String access_token = cookieMap.get("uid");
if(StringUtils.isEmpty(access_token)){
return null;
}
return access_token;
}
//查询令牌的有效期
public long getExpire(String access_token){
//key
String key = "user_token:"+access_token;
Long expire = stringRedisTemplate.getExpire(key, TimeUnit.SECONDS);
return expire;
}
}
CookieUtil工具类
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
import java.util.Map;
/**
* Created by admin on 2018/3/18.
*/
public class CookieUtil {
/**
* 设置cookie
*
* @param response
* @param name cookie名字
* @param value cookie值
* @param maxAge cookie生命周期 以秒为单位
*/
public static void addCookie(HttpServletResponse response,String domain,String path, String name,
String value, int maxAge,boolean httpOnly) {
Cookie cookie = new Cookie(name, value);
cookie.setDomain(domain);
cookie.setPath(path);
cookie.setMaxAge(maxAge);
cookie.setHttpOnly(httpOnly);
response.addCookie(cookie);
}
/**
* 根据cookie名称读取cookie
* @param request
* @param cookieName1,cookieName2
* @return map<cookieName,cookieValue>
*/
public static Map<String,String> readCookie(HttpServletRequest request,String ... cookieNames) {
Map<String,String> cookieMap = new HashMap<String,String>();
Cookie[] cookies = request.getCookies();
if (cookies != null) {
for (Cookie cookie : cookies) {
String cookieName = cookie.getName();
String cookieValue = cookie.getValue();
for(int i=0;i<cookieNames.length;i++){
if(cookieNames[i].equals(cookieName)){
cookieMap.put(cookieName,cookieValue);
}
}
}
}
return cookieMap;
}
}
说明:由于令牌存储时采用
String
序列化策略,所以这里用
StringRedisTemplate
来查询,使用
RedisTemplate
无
法完成查询。
3
、定义
LoginFilter
import com.alibaba.fastjson.JSON;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Component
public class LoginFilter extends ZuulFilter {
@Autowired
AuthService authService;
//过虑器的类型
@Override
public String filterType() {
/**
pre:请求在被路由之前执行
routing:在路由请求时调用
post:在routing和errror过滤器之后调用
error:处理请求时发生错误调用
*/
return "pre";
}
//过虑器序号,越小越被优先执行
@Override
public int filterOrder() {
return 0;
}
@Override
public boolean shouldFilter() {
//返回true表示要执行此过虑器
return true;
}
//过虑器的内容
//测试的需求:过虑所有请求,判断头部信息是否有Authorization,如果没有则拒绝访问,否则转发到微服务。
@Override
public Object run() throws ZuulException {
RequestContext requestContext = RequestContext.getCurrentContext();
//得到request
HttpServletRequest request = requestContext.getRequest();
//得到response
HttpServletResponse response = requestContext.getResponse();
//取cookie中的身份令牌
String tokenFromCookie = authService.getTokenFromCookie(request);
if(StringUtils.isEmpty(tokenFromCookie)){
//拒绝访问
access_denied();
return null;
}
//从header中取jwt
String jwtFromHeader = authService.getJwtFromHeader(request);
if(StringUtils.isEmpty(jwtFromHeader)){
//拒绝访问
access_denied();
return null;
}
//从redis取出jwt的过期时间
long expire = authService.getExpire(tokenFromCookie);
if(expire<0){
//拒绝访问
access_denied();
return null;
}
return null;
}
//拒绝访问
private void access_denied(){
RequestContext requestContext = RequestContext.getCurrentContext();
//得到response
HttpServletResponse response = requestContext.getResponse();
//拒绝访问
requestContext.setSendZuulResponse(false);
//设置响应代码
requestContext.setResponseStatusCode(200);
//构建响应的信息
ResponseResult responseResult = new ResponseResult(CommonCode.UNAUTHENTICATED);
//转成json
String jsonString = JSON.toJSONString(responseResult);
requestContext.setResponseBody(jsonString);
//转成json,设置contentType
response.setContentType("application/json;charset=utf-8");
}
}