spring cloud通过zuul网关结合redis进行权限校验

一.用户操作权限校验

1.环境准备

1.1 首先是开发环境的准备:

导入redis的jar包

	<!--redis存储-->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-redis</artifactId>
		</dependency>
1.2 yml中zuul的配置
server:
  port: 8010
spring:
  application:
    name: zuul-center #给服务起名
  profiles:
    active: innerTest
  servlet:
    multipart:
      max-request-size: 1000MB
      max-file-size: 1000MB
eureka:
  client:
    serviceUrl:
      defaultZone : http://eurekaServer1.com:8000/eureka,http://eurekaServer2.com:8001/eureka
    registry-fetch-interval-seconds: 5
hystrix:
  command:
    default:
      execution:
        isolation:
          thread:
            timeoutInMilliseconds: 6000 # 熔断超时时长:6000ms
  threadpool:
    default:
      coreSize: 500
      maximumSize: 1000
      allowMaximumSizeToDivergeFromCoreSize: true
      maxQueueSize: -1
feign:
  hystrix:
    enable: true
ribbon:
  ConnectTimeout: 2000 # ribbon链接超时时长
  ReadTimeout: 2000 # ribbon读取超时时长
  MaxAutoRetries: 0  # 当前服务重试次数
  MaxAutoRetriesNextServer: 1 # 切换服务重试次数
  OkToRetryOnAllOperations: false # 是否对所有的请求方式都重试,只对get请求重试
management:
  server:
    add-application-context-header: true
  endpoints:
    web:
      exposure:
        include: '*'

#进行token校验的url地址,全部小写
authfilter:
  ignoreUri: /user/sysuser/login,/user/sysuser/sendcode ,/user/sysuser/checkoutcode, /user/sysuser/getverify,/user/sysuser/checkverify

zuul:
  prefix: /api  #给网关加上前缀
  sensitive-headers: Access-Control-Allow-Origin
  routes:
    服务名1: /user/** #服务1映射到/user/**
    服务名2: /zhzd/** #服务2
    服务名3: /support/** #服务3的前缀
 

  ribbonIsolationStrategy: THREAD
  host:
    maxTotalConnections: 1000
    maxPerRouteConnections: 100
    socket-timeout-millis: 120000
    connect-timeout-millis: 120000

---
spring:
  profiles: innerTest
  redis:
    database: 0
    host: 192.168.1.199
    port: 6379
    password: XXX
    block-when-exhausted: true
    jedis:
      pool:
        max-idle: 200
        max-active: 200
        max-wait: -1
        min-idle: 0
    timeout: 10000
2.下面是具体代码

编写一个类AuthFileter继承ZuulFilter

package com.zhzd.zuulcenter.filter;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.faw.jetta.diagnosisCommon.constant.RedisConstants;
import com.faw.jetta.diagnosisCommon.enums.ExceptionEnum;
import com.faw.jetta.diagnosisCommon.exception.ProjectException;
import com.faw.jetta.diagnosisCommon.http.HttpResult;
import com.faw.jetta.diagnosisCommon.utils.CookieUtils;
import com.faw.jetta.diagnosisCommon.utils.JsonUtils;
import com.faw.jetta.diagnosisCommon.utils.ResultUtils;
import com.faw.jetta.diagnosisCommon.utils.StringUtil;
import com.faw.jetta.pojo.diagUserPojo.DTO.SysMenuDTO;
import com.faw.jetta.pojo.diagUserPojo.DTO.SysRoleDTO;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import com.netflix.zuul.http.ServletInputStreamWrapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.util.StreamUtils;

import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.validation.constraints.NotBlank;

import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.PRE_TYPE;

/**
 * 用户登录Token拦截器
 */
@Slf4j
@Component
public class AuthFilter extends ZuulFilter {
    @Value("${authfilter.ignoreUri}")
    private List<String> ignoreUri;

    @Autowired
    StringRedisTemplate stringRedisTemplate;

    //无权限时的提示语
    private static final String INVALID_TOKEN = "登录信息无效,请重新登录";
    private static final String INVALID_USERID = "用户信息无效,请重新登录";
    private static final String USERINFOREDISKEY = "user:login:dto:";

    @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 uri = request.getRequestURI().toLowerCase();
        //log.info("uri="+uri);
        //判断是否需要拦截
        if (request.getHeader("content-type") != null && request.getHeader("content-type").indexOf("multipart/form-data") > -1) {
            return false;
        }
        if (ignoreUri.contains(uri)) {
            return false;
        } else {
            if (request.getMethod().equals("OPTIONS")) {
                return false;
            }
            return true;
        }
    }

    @Override
    public Object run() throws ZuulException {
        RequestContext requestContext = RequestContext.getCurrentContext();
        HttpServletRequest request = requestContext.getRequest();
        try {
            //先从 cookie 中取 token,cookie 中取失败再从 header 中取,两重校验
            //通过工具类从 Cookie 中取出 token
            String token;
            String tokenCookie = CookieUtils.getCookieValue(request, "x-auth-token");
            String tokenHeader = request.getHeader("x-auth-token");
            token = tokenHeader;
            if (token == null || StringUtils.isEmpty(token)) {
                token = tokenCookie;
            }
            verifyToken(requestContext, request, token);
            //从redis中获取用户登陆信息, 用来校验用户的访问权限
            //1.获取到访问路径
            String requestURI = request.getRequestURI();
            //2.获取到用户登陆信息
            String userId = stringRedisTemplate.opsForValue().get(RedisConstants.loginUid + token);
            String userDTOJson = stringRedisTemplate.opsForValue().get(RedisConstants.loginUserDTO + userId);
            JSONObject jsonObject = JSONObject.parseObject(userDTOJson);
            JSONArray roleDTOListJson = jsonObject.getJSONArray("roleList");
            List<SysRoleDTO> roleDTOList = JsonUtils.jsonToList(roleDTOListJson.toJSONString(), SysRoleDTO.class);
            List<SysMenuDTO> menuDTOList = new ArrayList<>();
            //遍历角色集合, 给角色赋值
            for (SysRoleDTO sysRoleDTO : roleDTOList) {
                menuDTOList.addAll(sysRoleDTO.getSysMenuDTOList());
            }
            //校验是否具有该访问权限
            for (SysMenuDTO sysMenuDTO : menuDTOList) {
                String menuUrl = sysMenuDTO.getMenuUrl();
                if (StringUtils.isNotEmpty(requestURI)&&StringUtils.isNotEmpty(menuUrl)&&requestURI.equals(menuUrl)) {
                    return null;
                }
            }
            //TODO 没有权限访问时, 应该返回一个提示
            ResultUtils resultUtils = new ResultUtils<>();
            resultUtils.setCode(401);
            resultUtils.setMsg("没有权限访问");
            String responseJson = JsonUtils.objectToJson(resultUtils);
            requestContext.setResponseBody(responseJson);
        } catch (Exception e) {
            e.printStackTrace();
            requestContext.setSendZuulResponse(false);
            requestContext.setResponseStatusCode(HttpStatus.UNAUTHORIZED.value());
            log.error("非法访问,未授权,地址:{}", request.getRemoteHost(), e );
        }
        return null;
    }

    /**
     * 从Redis中校验token
     */
    private void verifyToken(RequestContext requestContext, HttpServletRequest request, String token) {
        //需要从cookie或header 中取出 userId 来校验 token 的有效性,因为每个用户对应一个token,在Redis中是以 TOKEN_userId 为键的
        String userIdFromCookie = CookieUtils.getCookieValue(request, "uid");
        String userIdFromHeader = request.getHeader("uid");

        //禁止前端直接从参数中传递uid参数,防止进行跨用户攻击
        /**
         String uid = request.getParameter("uid") ;//从参数中获取uid


         if(uid == null || StringUtils.isEmpty(uid))
         {
         uid = userIdFromHeader;
         }
         if(uid == null || StringUtils.isEmpty(uid))
         {
         uid = userIdFromCookie;
         }
         **/
        String uid = null;
        //如果传递了token,则要校验token的有效性
        if (token != null && !StringUtils.isEmpty(token)) {
            String redisUid = stringRedisTemplate.opsForValue().get(RedisConstants.loginUid + token);
            if (uid == null || StringUtils.isEmpty(uid)) { //客户端没有传递uid
                uid = redisUid;// 系统自动将token转成uid
                if (uid != null && !StringUtils.isEmpty(uid)) {
                    addUidParam(uid, requestContext, token);
                } else { //客户端传递了一个无效的token
                    setUnauthorizedResponse(requestContext, INVALID_TOKEN);
                }
            } else { //有token,有uid,验证二者是否一致
                if (redisUid.equalsIgnoreCase(uid)) {
                    addUidParam(uid, requestContext, token);
                } else { //传递的token 和uid不一致
                    setUnauthorizedResponse(requestContext, INVALID_USERID);
                }
            }
        } else { //没有传递token,放过,微服务进行验权
            addUidParam("0", requestContext, token);
        }
    }

    /**
     * 向请求信息中增加uid参数
     *
     * @param uid 用户uid值
     * @param ctx
     */
    private void addUidParam(String uid, RequestContext ctx, String sessionIdInHeader) {
        try {
            if (sessionIdInHeader != null && !StringUtils.isEmpty(sessionIdInHeader)) {
                ctx.addZuulRequestHeader("x-auth-token", sessionIdInHeader);
            }
            //获取Redis中的用户信息
            //TODO 现在dealerId和comId指的是一个id,提供给外部调用的是comId(整型id)
            String dealerId = "0";
            String comId = "0";
            String comIdStr = "0";
            String userType = "0";
            //整型userId用作门店请求头
            String userIdInt = "0";
            try {
                String userStr = stringRedisTemplate.opsForValue().get(USERINFOREDISKEY + uid);
                if (!StringUtils.isEmpty(userStr)) {
                    JSONObject object = JSONObject.parseObject(userStr);
                    dealerId = object.getString("dealerId");
                    comId = object.getString("companyIdInt");
                    comIdStr = object.getString("companyId");
                    userType = object.getString("userType");
                    userIdInt = object.getString("idInt");
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
            HttpServletRequest request = ctx.getRequest();
            String method = request.getMethod();
            log.info(String.format("%s >>> %s", method, request.getRequestURL().toString()));
            InputStream in = request.getInputStream();
            String body = StreamUtils.copyToString(in, Charset.forName("UTF-8"));
            // 如果body为空初始化为空json
            if (StringUtils.isBlank(body)) {
                body = "{}";
            }
            //log.info("body" + body);
            // 转化成json
            //JSONObject json = JSONObject.parseObject(body);
            if ("GET".equalsIgnoreCase(method)) {
                request.getParameterMap();
                Map<String, List<String>> requestQueryParams = ctx.getRequestQueryParams();
                if (requestQueryParams == null) {
                    requestQueryParams = new HashMap<>();
                }
                //1.将字符串型用户id放入head中
                List<String> uids = new ArrayList<>();
                uids.add(uid);
                requestQueryParams.put("uid", uids);

                //2.将整型用户id放入head中
                List<String> userIdInts = new ArrayList<>();
                userIdInts.add(userIdInt);
                requestQueryParams.put("userIdInt", userIdInts);
                //3.将dealerId(整型)放入head中
                List<String> dealerIds = new ArrayList<>();
                dealerIds.add(dealerId);
                requestQueryParams.put("dealerId", dealerIds);
                //4.将comId(整型)放入head中
                List<String> comIds = new ArrayList<>();
                comIds.add(comId);
                requestQueryParams.put("comId", comIds);
                //4.1将comIdStr(字符串类型放入head中)
                List<String> comIdStrList = new ArrayList<>();
                comIdStrList.add(comIdStr);
                requestQueryParams.put("comIdStr", comIdStrList);
                //5.将用户类型放入head中
                List<String> userTypes = new ArrayList<>();
                userTypes.add(userType);
                requestQueryParams.put("userType", userTypes);

                ctx.setRequestQueryParams(requestQueryParams);
            } else if ("POST".equalsIgnoreCase(method)) {
                //json.put("uid",uid);
                // byte[] reqBodyBytes = JSON.toJSONString(json).getBytes();
                byte[] reqBodyBytes = body.getBytes("UTF-8");
                Map<String, List<String>> requestQueryParams = ctx.getRequestQueryParams();
                if (requestQueryParams == null) {
                    requestQueryParams = new HashMap<>();
                }
                List<String> uids = new ArrayList<>();
                uids.add(uid);
                requestQueryParams.put("uid", uids);

                List<String> dealerIds = new ArrayList<>();
                dealerIds.add(dealerId);
                requestQueryParams.put("dealerId", dealerIds);

                List<String> comIds = new ArrayList<>();
                comIds.add(comId);
                requestQueryParams.put("comId", comIds);

                List<String> userTypes = new ArrayList<>();
                userTypes.add(userType);
                requestQueryParams.put("userType", userTypes);

                ctx.setRequestQueryParams(requestQueryParams);
                ctx.setRequest(new HttpServletRequestWrapper(request) {
                    @Override
                    public ServletInputStream getInputStream() throws IOException {
                        return new ServletInputStreamWrapper(reqBodyBytes);
                    }

                    @Override
                    public int getContentLength() {
                        return reqBodyBytes.length;
                    }

                    @Override
                    public long getContentLengthLong() {
                        return reqBodyBytes.length;
                    }
                });
            }
            // ctx.setSendZuulResponse(false);
        } catch (Exception e) {
            log.error("addUidParam", e);
        }
    }

    /**
     * 设置 401 无权限状态
     */
    private void setUnauthorizedResponse(RequestContext requestContext, String msg) {
        requestContext.setSendZuulResponse(false);
        requestContext.setResponseStatusCode(HttpStatus.OK.value());
        HttpResult vo = HttpResult.error();
        vo.setCode(401);
        vo.setMsg(msg);
        String result = JSON.toJSONString(vo);
        requestContext.setResponseBody(result);
    }
}

未完待续

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

意田天

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值