zuul过滤器

 

一,功能点

(1) 指点特定路径无需过滤

比如登录接口,字典接口,

(2) token校验

通过redis实现token校验,失效:token校验失败;有效:延长过期时间

(3) 鉴权

根据权限树校验请求的url是否合法

(4) 封装请求数据

二,代码

(1) 过滤器

@RefreshScope
public class AccessFilter extends ZuulFilter {
	private static Logger log = LoggerFactory.getLogger(AccessFilter.class);
	
	@Autowired
	private HystrixWrappedAuthServiceClient hystrixWrappedAuthServiceClient;
	
	@Value("${timestamp.validate.limit:180}")
	private String validateLimit;
	@Override
	public String filterType() {
		return "pre";
	}
	@Override
	public int filterOrder() {
		return 0;
	}
	@Override
	public boolean shouldFilter() {
		return true;
	}
    
	@Override
    public Object run() {
        RequestContext ctx = RequestContext.getCurrentContext();
        HttpServletRequest request = ctx.getRequest();
        log.info(String.format("收到来自IP为 %s 的请求%s request to %s", getIpAddr(request), request.getMethod(), request.getRequestURL().toString()));
        log.info("url is :" + request.getRequestURL().toString());
        if (skipAllFilters(request.getRequestURL().toString())) {
            log.info("跳过所有验证");
            return null;
        }
        String url = request.getRequestURL().toString();
        log.info("开始验证.....url:{}",url);
        // 此处单独获取并校验token的原因是,有一个上传功能token入参形式不同
        if (containUpload(url)) {
        	String token = request.getParameter("token");
        	if (!validateToken(url, token)) {
                ZuulResponseModel<String> responseModel = new ZuulResponseModel<String>();
                log.warn("token校验失败");
                responseModel.setRepCode(RespCode.ZUUL_TOKEN_ERROR);
                responseModel.setRepMsg("token校验失败");
                ctx = getContextForError(ctx, responseModel);
                return null;
            }
        	ZuulRequestModel requestModel=new ZuulRequestModel();
			requestModel.setUrl(url);
			requestModel.setAuthUrl(request.getServletPath());
			requestModel.setToken(token);
        	if (!validateAuth(requestModel)) {
                ZuulResponseModel<String> responseModel = new ZuulResponseModel<String>();
                log.warn("权限校验失败");
                responseModel.setRepCode(RespCode.ZUUL_AUTH_ERROR);
                responseModel.setRepMsg(RespMsg.ZUUL_AUTH_ERROR_MSG);
                ctx = getContextForError(ctx, responseModel);
                return null;
            }
        	log.info("上传接口验证通过");
            return null;
        }
        ZuulResponseModel<String> parameterCheckResult = validateWithParameters(request);
        if (null != parameterCheckResult) {
            if (RespCode.SUCCESS == parameterCheckResult.getRepCode()) {
                return null;
            } else {
                ctx = getContextForError(ctx, parameterCheckResult);
                return null;
            }
        }
        // 获取请求参数及token
        String jsonStr = getData(request);
        JSONObject jsonData = JSONObject.parseObject(jsonStr);
        ZuulRequestModel requestModel;
        ZuulRequestArrayModel requestArrayModel = null;
        try {
            requestModel = JSON.parseObject(jsonData.toString(), ZuulRequestModel.class);
        } catch (Exception e) {
            requestArrayModel=JSON.parseObject(jsonData.toString(), ZuulRequestArrayModel.class);
            requestModel=new ZuulRequestModel();
            requestModel.setAuthUrl(requestArrayModel.getAuthUrl());
            requestModel.setJsonStr(requestArrayModel.getJsonStr());
            requestModel.setSign(requestArrayModel.getSign());
            requestModel.setTime(requestArrayModel.getTime());
            requestModel.setToken(requestArrayModel.getToken());
            requestModel.setUrl(requestArrayModel.getUrl());
            requestModel.setUserId(requestArrayModel.getUserId());
        }
        requestModel.setJsonStr(jsonStr);
        requestModel.setUrl(url);
        requestModel.setAuthUrl(request.getServletPath());
        if (!validateToken(url, requestModel.getToken())) {
            ZuulResponseModel<String> responseModel = new ZuulResponseModel<String>();
            log.warn("token校验失败");
            responseModel.setRepCode(RespCode.ZUUL_TOKEN_ERROR);
            responseModel.setRepMsg("token校验失败");
            ctx = getContextForError(ctx, responseModel);
            return null;
        }
        log.debug("验证token通过");
//        //TODO 测试通道,可跳过权限验证,后续需要删除该代码
//        String[] tokenInfo = requestModel.getToken().split("_");
//        if (!Pattern.matches("w.*", tokenInfo[0])) {
//        	return null;
//        }
        if (!validateAuth(requestModel)) {
            ZuulResponseModel<String> responseModel = new ZuulResponseModel<String>();
            log.warn("权限校验失败");
            responseModel.setRepCode(RespCode.ZUUL_AUTH_ERROR);
            responseModel.setRepMsg(RespMsg.ZUUL_AUTH_ERROR_MSG);
            ctx = getContextForError(ctx, responseModel);
            return null;
        }
        log.debug("验证权限通过");
        return null;
        
    }

	private boolean containUpload(String url) {
		if (Pattern.matches(".*/upload", url)) {
			return true;
		}
		return false;
	}
	private String getData(HttpServletRequest req) {
		String result = null;
		try {
			BufferedReader br = new BufferedReader(new InputStreamReader((ServletInputStream) req.getInputStream(), "utf-8"));
			StringBuffer sb = new StringBuffer("");
			String line;
			while ((line = br.readLine()) != null) {
				sb.append(line);
			}
			br.close();
			result = sb.toString();
			log.info("获取data参数 " + result);
		} catch (Exception e) {
			e.printStackTrace();
		}
		return result;
	}

	private String getIpAddr(HttpServletRequest request) {
		String ip = request.getHeader("x-forwarded-for");
		if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
			ip = request.getHeader("Proxy-Client-IP");
		}
		if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
			ip = request.getHeader("WL-Proxy-Client-IP");
		}
		if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
			ip = request.getRemoteAddr();
		}
		return ip;
	}

	private boolean validateToken(String url, String token) {
		log.info("validateToken开始()");
		log.info("入参: {url:" + url + ",token:" + token + "}");
		boolean isValid = false;
		if (Pattern.matches(".*/noauth/.*|.*/login/.*", url)) { // 无需token校验
			return true;
		}
		if (null == token || "".equals(token.trim())) {
			log.warn("token is null");
			log.info("token is null");
			isValid = false;
		}
		isValid = hystrixWrappedAuthServiceClient.validate(token);
		log.warn("token:" + token + "----- isValid :" + isValid);
		log.info("validateToken()结束,isValid:" + isValid);
		return isValid;
	}

	private RequestContext getContextForError(RequestContext ctx, ZuulResponseModel<String> responseModel) {
		RequestContext requestContext = ctx;
		ctx.setResponseBody(responseModel.toJsonString());
		ctx.setSendZuulResponse(false);
		ctx.getResponse().setContentType("text/html;charset=UTF-8");
		ctx.getResponse().setContentType(String.valueOf(MediaType.APPLICATION_JSON));
		return requestContext;
	}

	private boolean skipAllFilters(String requestUrl) {
		boolean isSkip = false;
		if (Pattern.matches(".*/noauth/.*|.*/dict/.*", requestUrl)) {
			isSkip = true;
		}
		return isSkip;
	}

    @SuppressWarnings("static-access")
	private ZuulResponseModel<String> validateWithParameters(HttpServletRequest request) {
        if (!Pattern.matches(".*/exportInfoByExcel|.*/exportVehicleByExcel|.*/storagePlan/importTemplateDownload|.*/order/exportInfoByExcel|.*/stock/exportInfoByExcel", request.getRequestURL())) {
            return null;
        }
        ZuulResponseModel<String> responseModel = new ZuulResponseModel<String>();
        Map<String, String[]> params = request.getParameterMap();
        StringBuffer parameterbf = new StringBuffer();
        Iterator<Entry<String, String[]>> paramsEntryI = params.entrySet().iterator();
        while (paramsEntryI.hasNext()) {
            Entry<String, String[]> e = paramsEntryI.next();
            String[] values = e.getValue();
            for (int i = 0; i < values.length; i++) {
                String value = values[i];
                parameterbf.append(e.getKey()).append("=").append(value).append("&");
            }
        }
        log.info("--------------parameter in url is: " + parameterbf.toString());

        String[] timeValues = params.get("time");
        if (null != timeValues && timeValues.length == 1) {
            Timestamp nowTime = new Timestamp(System.currentTimeMillis());
            Timestamp requestTime = new Timestamp(Long.parseLong(timeValues[0]));
            log.info("时间戳校验间隔:" + Integer.parseInt(validateLimit));
            if (Math.abs(nowTime.getTime() / 1000 - requestTime.getTime()) > Integer.parseInt(validateLimit)) {
                log.warn("时间戳校验不正确");
                responseModel.setRepCode(RespCode.ZUUL_TIMESTAMP_ERROR);
                responseModel.setRepMsg(RespMsg.ZUUL_TIMESTAMP_ERROR_MSG);
                return responseModel;
            }
            responseModel.setRepCode(RespCode.SUCCESS);
            log.debug("验证时间戳通过");
            return responseModel;
        }
        String[] tokens = params.get("token");
        String[] signs = params.get("sign");
        String baseSignMsg = "time=" + timeValues[0] + "&token=" + tokens[0];
        log.info("----------------------------base sign message is:(" + baseSignMsg + ")------");
        log.info("----------------------------encrypt sign message is:(" + MD5Util.getInstance().encrypt(baseSignMsg) + ")------");
        if (null != signs && 1 <= signs.length) {
            log.info("----------------------------received sign message is:(" + signs[0] + ")------");
        }
        if (null == signs || 1 != signs.length || !MD5Util.getInstance().encrypt(baseSignMsg).equalsIgnoreCase(signs[0])) {
            log.warn("sign校验失败");
            responseModel.setRepCode(RespCode.ZUUL_SIGN_ERROR);
            responseModel.setRepMsg("");
            return responseModel;
        }
        log.info("----------------------------sign is valid!------");
        return null;
    }

	// 鉴权
	private boolean validateAuth(ZuulRequestModel requestModel) {
		boolean isValid = false;
		if (Pattern.matches(".*/noauth/.*", requestModel.getUrl())) {
			return true;
		}
		log.info(requestModel.getUserId());
		log.info(requestModel.getAuthUrl());
		String userId = requestModel.getUserId();
		String authUrl = requestModel.getAuthUrl();
		log.info("开始鉴权......authUrl={},userId={}",authUrl,userId);
		if (StringUtils.isEmpty(userId) || StringUtils.isEmpty(authUrl)) {
			return isValid;
		}
		return hystrixWrappedAuthServiceClient.validateByUrl(Long.valueOf(userId), authUrl);
	}

(2) token校验实现

由于token校验逻辑在另外的权限服务,需要跨服务调用

   @RequestMapping("/token/validate")
	public boolean validateToken(@RequestParam("token") String token) {
		return userService.validateToken(token);
	}
	@Override
	public boolean validateToken(String token) {
		TokenEntity tokenModel = tokenManager.getToken(token);
		logger.info("validateToken(){}=====tokenModel:" + tokenModel);
		boolean isValidate = tokenManager.checkToken(tokenModel);
		logger.info("validateToken(){}=====isValidate:" + isValidate);
		return isValidate;
	}

token实体,key指定字符串+userId

public class TokenEntity {

	private static final String LOGIN_TOKEN_PREFIX = "LOGIN_TOKEN_USER_";

	// 用户 id
	private Long userId;

	// 随机生成的 uuid
	private String token;

	// redis key
	private String redisKey;

	public TokenEntity(Long userId, String token) {
		this.userId = userId;
		this.token = token;
		this.redisKey = LOGIN_TOKEN_PREFIX + userId;
	}

	/**
	 * Getter method for property <tt>userId</tt>.
	 * 
	 * @return property value of userId
	 */
	public Long getUserId() {
		return userId;
	}

	/**
	 * Setter method for property <tt>userId</tt>.
	 * 
	 * @param userId value to be assigned to property userId
	 */
	public void setUserId(Long userId) {
		this.userId = userId;
	}

	/**
	 * Getter method for property <tt>token</tt>.
	 * 
	 * @return property value of token
	 */
	public String getToken() {
		return token;
	}

	/**
	 * Setter method for property <tt>token</tt>.
	 * 
	 * @param token value to be assigned to property token
	 */
	public void setToken(String token) {
		this.token = token;
	}

	/**
	 * Getter method for property <tt>redisKey</tt>.
	 * 
	 * @return property value of redisKey
	 */
	public String getRedisKey() {
		return redisKey;
	}

	/**
	 * Setter method for property <tt>redisKey</tt>.
	 * 
	 * @param redisKey value to be assigned to property redisKey
	 */
	public void setRedisKey(String redisKey) {
		this.redisKey = redisKey;
	}

}

token实现类 创建,校验,获取,删除

@Service
public class RedisTokenManager implements TokenManager {

	private static Logger logger = LoggerFactory.getLogger(RedisTokenManager.class);

	@Autowired
	private RedisUtils redisUtils;

	@Value("${token.timeout.second:1800}")
	private String timeout;

	/**
	 * @param userId
	 * @return
	 * @see com.anji.allways.business.user.service.TokenManager#createToken(java.lang.Long)
	 */
	@Override
	public TokenEntity createToken(Long userId, String userName) {
		logger.info("createToken开始");
		logger.info("入参:userId{}" + userId + ",userName{}" + userName);
		// 使用 uuid 作为源 token
		String token = userName + "_" + userId + "_" + UUIDUtil.getInstance().getUUID32();
		TokenEntity tokenModel = new TokenEntity(userId, token);
		// 存储到 redis 并设置过期时间
		logger.info("tokenModel{}" + tokenModel.toString());
		redisUtils.set(tokenModel.getRedisKey(), tokenModel.getToken(), Integer.parseInt(timeout));
		logger.info("createToken结束");
		return tokenModel;
	}

	/**
	 * @param token
	 * @return
	 * @see com.anji.allways.business.user.service.TokenManager#checkToken(com.anji.allways.business.user.entity.TokenEntity)
	 */
	@Override
	public boolean checkToken(TokenEntity tokenModel) {
		logger.info("checkTokne开始");
		if (tokenModel == null) {
			return false;
		}
		logger.info("tokenModel{}" + tokenModel.toString());
		String token = redisUtils.get(tokenModel.getRedisKey());
		logger.info("token{}" + token);
		if (token == null || !token.equals(tokenModel.getToken())) {
			return false;
		}
		// 如果验证成功,说明此用户进行了一次有效操作,延长 token 的过期时间
		logger.info("key{}" + tokenModel.getRedisKey() + ",value:{}" + redisUtils.get(tokenModel.getRedisKey()));
		redisUtils.set(tokenModel.getRedisKey(), redisUtils.get(tokenModel.getRedisKey()),
				Integer.parseInt(timeout));
		logger.info("checkTokne结束");
		return true;
	}

	/**
	 * @param authentication
	 * @param platform
	 * @return
	 * @see com.anji.allways.business.user.service.TokenManager#getToken(java.lang.String)
	 */
	@Override
	public TokenEntity getToken(String authentication) {
		if (null == authentication) {
			return null;
		}
		String[] param = authentication.split("_");
		if (param.length != 5 && param.length != 3) {
			return null;
		}
		// 使用 userId 和源 token 简单拼接成的 token,可以增加加密措施
		Long userId = Long.parseLong(param[1]);
		String token = authentication;
		return new TokenEntity(userId, token);
	}

	/**
	 * @param userId
	 * @see com.anji.allways.business.user.service.TokenManager#deleteToken(java.lang.Long)
	 */
	@Override
	public void deleteToken(TokenEntity tokenModel) {
		redisUtils.del(tokenModel.getRedisKey());
	}

	/**
	 * @param userVO
	 * @return
	 * @see com.anji.allways.business.auth.service.TokenManager#createToken(com.anji.allways.business.auth.vo.UserVO)
	 */
	@Override
	public TokenEntity createToken(UserVO userVO) {
		logger.info("createToken开始");
		// 使用 uuid 作为源 token
		String token = userVO.getUserName() + "_" + userVO.getId() + "_"+ userVO.getUserType() +"_"+ userVO.getBelongTo() +"_" +UUIDUtil.getInstance().getUUID32();
		TokenEntity tokenModel = new TokenEntity(userVO.getId(), token);
		logger.info("tokenModel{}",tokenModel.toString());
		// 存储到 redis 并设置过期时间
		redisUtils.set(tokenModel.getRedisKey(), tokenModel.getToken(), Integer.parseInt(timeout));
		logger.info("createToken:结束");
		return tokenModel;
	}

}

(3) 鉴权实现

 

  @RequestMapping("/validate/url")
    public boolean validateByUrl(Long userId, String authUrl) {
    	logger.info("userId={},authUrl={}",userId,authUrl);
        return authService.queryAuthByUrl(userId,authUrl);
    }
@Service
@Transactional
public class AuthServiceImpl extends AbstractService<AuthEntity, Long> implements AuthService {

	@Autowired
	private AuthMapper authMapper;

	@Override
	public CommonMapper<AuthEntity> getCommonMapper() {
		return authMapper;
	}
	
	@Override
	public boolean queryAuthByUrl(Long userId, String url) {
		List<AuthEntity> auths = authMapper.queryAuthByUrl(userId);
		if(CollectionUtils.isEmpty(auths)) {
			return false;
		}
		for (AuthEntity authEntity : auths) {
			if(url.contains(authEntity.getUrl())) {
				return true;
			}
		}
		return false;
	}
}

获取用户权限sql

<select id="queryAuthByUrl" parameterType="java.lang.Long" resultType="com.anji.allways.business.auth.entity.AuthEntity">
		SELECT
			a.*
		FROM
			csms_auth a
		WHERE
			1 = 1
		AND a.`status` = 1
		AND a.id IN (
			SELECT
				ma.auth_id
			FROM
				csms_menu_auth ma
			WHERE
				ma.menu_id IN (
					SELECT
						rm.menu_id
					FROM
						csms_role_menu rm
					INNER JOIN csms_menu m ON rm.menu_id = m.id
					WHERE
						1 = 1
					AND m.`status` = 1
					AND rm.role_id IN (
						SELECT
							ur.role_id
						FROM
							csms_user_role ur
						INNER JOIN csms_role r ON ur.role_id = r.id
						WHERE
							1 = 1
						AND ur.is_del = 0
						AND r.is_del = 0
						AND ur.user_id = #{userId}
					)
				)
				AND ma.is_del = 0
		)
	</select>

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值