整理思路:
1、用户登录或者注册后返回token给前台,并同时以userId为key,token为value存入redis
2、通过实现zuulFilter接口拦截所有通过的路由接口(单独放行登录和注册),获取请求的cookie或者请求头有没有的userId和token值跟redis预存的键值对比,相同则放行,不同则鉴权失败
主要代码实现如下:
filterType()
: Filter 的类型,前置过滤器返回 PRE_TYPE
filterOrder()
: Filter 的顺序,值越小越先执行。这里的写法是 PRE_DECORATION_FILTER_ORDER - 1
, 也是官方建议的写法。
shouldFilter()
: 是否应该过滤。返回 true 表示过滤,false 不过滤。可以在这个方法里判断哪些接口不需要过滤,本例排除了注册和登录接口,除了这两个接口,其他的都需要过滤。
run()
: 过滤器的具体逻辑
1、登录测试类
@Api("登录与注册")
@RequestMapping("/user")
@RestController
public class UserController {
@Autowired
private StringRedisTemplate stringRedisTemplate;
@GetMapping("/login")
public ApiMessage login(){
stringRedisTemplate.opsForValue().set("22","bb");
return ApiMessage.success("ok","bb");
}
}
2、zuul项目的过滤类
package com.njwd.zuul.filter;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import com.netflix.zuul.http.ServletInputStreamWrapper;
import com.njwd.zuul.constant.ErrorCode;
import com.njwd.zuul.entity.ApiMessage;
import com.njwd.zuul.util.CookiesUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.util.StreamUtils;
import org.springframework.util.StringUtils;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
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_DECORATION_FILTER_ORDER;
import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.PRE_TYPE;
/**
* 权限验证 Filter
* 注册和登录接口不过滤
*
* 验证权限需要前端在 Cookie 或 Header 中(二选一即可)设置用户的 userId 和 token
* 因为 token 是存在 Redis 中的,Redis 的键由 userId 构成,值是 token
* 在两个地方都没有找打 userId 或 token其中之一,就会返回 400 无权限,并给与文字提示
*/
@Component
public class AuthFilter extends ZuulFilter {
private Logger logger = LoggerFactory.getLogger(AuthFilter.class);
@Autowired
StringRedisTemplate stringRedisTemplate;
//排除过滤的 uri 地址
private static final String LOGIN_URI = "/common/user/login";
private static final String REGISTER_URI = "/common/user/register";
//无权限时的提示语
private static final String INVALID_TOKEN = "invalid token";
private static final String INVALID_USERID = "invalid userId";
@Override
public String filterType() {
return PRE_TYPE;
}
@Override
public int filterOrder() {
return PRE_DECORATION_FILTER_ORDER - 1;
}
@Override
public boolean shouldFilter() {
RequestContext requestContext = RequestContext.getCurrentContext();
HttpServletRequest request = requestContext.getRequest();
logger.info("===uri===", request.getRequestURI());
//注册和登录接口不拦截,其他接口都要拦截校验 token
if (LOGIN_URI.equals(request.getRequestURI()) ||
REGISTER_URI.equals(request.getRequestURI())) {
return false;
}
return true;
}
@Override
public Object run() throws ZuulException {
RequestContext requestContext = RequestContext.getCurrentContext();
HttpServletRequest request = requestContext.getRequest();
//先从 cookie 中取 token,cookie 中取失败再从 header 中取,两重校验
//通过工具类从 Cookie 中取出 token
String token = CookiesUtil.getCookies(request, "token");
System.out.println(token+"===========cookie===================");
//不验证token时注调该代码
if (token == null || StringUtils.isEmpty(token)) {
readTokenFromHeader(requestContext, request);
} else {
verifyToken(requestContext, request, token);
}
return null;
}
/**
* 从 header 中读取 token 并校验
*/
private void readTokenFromHeader(RequestContext requestContext, HttpServletRequest request) {
//从 header 中读取
String headerToken = request.getHeader("token");
if (StringUtils.isEmpty(headerToken)) {
setUnauthorizedResponse(requestContext, INVALID_TOKEN);
} else {
verifyToken(requestContext, request, headerToken);
}
}
/**
* 从Redis中校验token
*/
private void verifyToken(RequestContext requestContext, HttpServletRequest request, String token) {
//需要从cookie或header 中取出 userId 来校验 token 的有效性,因为每个用户对应一个token,在Redis中是以 TOKEN_userId 为键的
String userIdCookie = CookiesUtil.getCookies(request, "userId");
String rootEnterpriseId = CookiesUtil.getCookies(request, "rootEnterpriseId");
String type = request.getContentType();
if (userIdCookie == null || StringUtils.isEmpty(userIdCookie)) {
//从header中取userId
String userId = request.getHeader("userId");
rootEnterpriseId = request.getHeader("rootEnterpriseId");
if (StringUtils.isEmpty(userId)) {
setUnauthorizedResponse(requestContext, INVALID_USERID);
} else {
String redisToken = stringRedisTemplate.opsForValue().get(userId);
if (StringUtils.isEmpty(redisToken) || !redisToken.equals(token)) {
setUnauthorizedResponse(requestContext, INVALID_TOKEN);
}else{
//针对post请求
if (type != null && type.startsWith("application/json")){
// 在json参数中添加 userId
try {
InputStream in = requestContext.getRequest().getInputStream();
String body = StreamUtils.copyToString(in, Charset.forName("UTF-8"));
System.out.println("body:" + body);
JSONObject json = JSONObject.parseObject(body);
json.put("userId", userIdCookie);
json.put("rootEnterpriseId", rootEnterpriseId);
String newBody = json.toString();
System.out.println("newBody:" + newBody);
final byte[] reqBodyBytes = newBody.getBytes();
requestContext.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;
}
});
} catch (IOException e) {
e.printStackTrace();
}
}else{ //针对get请求
//将转换后的数据放入请求参数中
Map> requestQueryParams = requestContext.getRequestQueryParams();
if (requestQueryParams==null) requestQueryParams=new HashMap<>();
//将要新增的参数添加进去,被调用的微服务可以直接 去取,就想普通的一样,框架会直接注入进去
ArrayList paramsList = new ArrayList<>();
paramsList.add(rootEnterpriseId);
ArrayList paramsList1 = new ArrayList<>();
paramsList1.add(userIdCookie);
requestQueryParams.put("rootEnterpriseId", paramsList);
requestQueryParams.put("userId", paramsList1);
requestContext.setRequestQueryParams(requestQueryParams);
}
}
}
} else {
String redisToken = stringRedisTemplate.opsForValue().get(userIdCookie);
if (StringUtils.isEmpty(redisToken) || !redisToken.equals(token)) {
setUnauthorizedResponse(requestContext, INVALID_TOKEN);
}else{
//针对post请求
if (type != null && type.startsWith("application/json")){
// 在json参数中添加 userId
try {
InputStream in = requestContext.getRequest().getInputStream();
String body = StreamUtils.copyToString(in, Charset.forName("UTF-8"));
System.out.println("body:" + body);
JSONObject json = JSONObject.parseObject(body);
json.put("userId", userIdCookie);
json.put("rootEnterpriseId", rootEnterpriseId);
String newBody = json.toString();
System.out.println("newBody:" + newBody);
final byte[] reqBodyBytes = newBody.getBytes();
requestContext.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;
}
});
} catch (IOException e) {
e.printStackTrace();
}
}else{ //针对get请求
//将转换后的数据放入请求参数中
Map> requestQueryParams = requestContext.getRequestQueryParams();
if (requestQueryParams==null) requestQueryParams=new HashMap<>();
//将要新增的参数添加进去,被调用的微服务可以直接 去取,就想普通的一样,框架会直接注入进去
ArrayList paramsList = new ArrayList<>();
paramsList.add(rootEnterpriseId);
ArrayList paramsList1 = new ArrayList<>();
paramsList1.add(userIdCookie);
requestQueryParams.put("rootEnterpriseId", paramsList);
requestQueryParams.put("userId", paramsList1);
requestContext.setRequestQueryParams(requestQueryParams);
}
}
}
}
/**
* 设置 400 无权限状态
*/
private void setUnauthorizedResponse(RequestContext requestContext, String msg) {
requestContext.setSendZuulResponse(false);
requestContext.setResponseStatusCode(HttpStatus.UNAUTHORIZED.value());
String result = JSON.toJSONString(ApiMessage.error(ErrorCode.SIGN_ERROR_MESSAGE));
requestContext.setResponseBody(result);
}
}