Zuul网关学习,使用redis+ZuulFilter实现访问其他页面身份信息的验证

一、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");
    }


}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值