一,功能点
(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>