做一个简单的鉴权
token鉴权,验证token
一般在zuul网关层面进行验证鉴权等操作,除了鉴权,还可以限流、加密等。
继承ZuulFilter,
ZuulFilter源码: 实现于IZuulFilter
* Copyright 2013 Netflix, Inc.
package com.netflix.zuul;
import com.netflix.config.DynamicBooleanProperty;
import com.netflix.config.DynamicPropertyFactory;
import com.netflix.zuul.monitoring.MonitoringHelper;
import com.netflix.zuul.monitoring.Tracer;
import com.netflix.zuul.monitoring.TracerFactory;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collections;
import java.util.concurrent.atomic.AtomicReference;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertSame;
import static org.mockito.Mockito.*;
/**
* Base abstract class for ZuulFilters. The base class defines abstract methods to define:
* filterType() - to classify a filter by type. Standard types in Zuul are "pre" for pre-routing filtering,
* "route" for routing to an origin, "post" for post-routing filters, "error" for error handling.
* We also support a "static" type for static responses see StaticResponseFilter.
* Any filterType made be created or added and run by calling FilterProcessor.runFilters(type)
* <p/>
* filterOrder() must also be defined for a filter. Filters may have the same filterOrder if precedence is not
* important for a filter. filterOrders do not need to be sequential.
* <p/>
* ZuulFilters may be disabled using Archius Properties.
* <p/>
* By default ZuulFilters are static; they don't carry state. This may be overridden by overriding the isStaticFilter() property to false
*
* @author Mikey Cohen
* Date: 10/26/11
* Time: 4:29 PM
*/
public abstract class ZuulFilter implements IZuulFilter, Comparable<ZuulFilter> {
private final AtomicReference<DynamicBooleanProperty> filterDisabledRef = new AtomicReference<>();
/**
* to classify a filter by type. Standard types in Zuul are "pre" for pre-routing filtering,
* "route" for routing to an origin, "post" for post-routing filters, "error" for error handling.
* We also support a "static" type for static responses see StaticResponseFilter.
* Any filterType made be created or added and run by calling FilterProcessor.runFilters(type)
*
* @return A String representing that type
*/
abstract public String filterType();
/**
* filterOrder() must also be defined for a filter. Filters may have the same filterOrder if precedence is not
* important for a filter. filterOrders do not need to be sequential.
*
* @return the int order of a filter
*/
abstract public int filterOrder();
/**
* By default ZuulFilters are static; they don't carry state. This may be overridden by overriding the isStaticFilter() property to false
*
* @return true by default
*/
public boolean isStaticFilter() {
return true;
}
/**
* The name of the Archaius property to disable this filter. by default it is zuul.[classname].[filtertype].disable
*
* @return
*/
public String disablePropertyName() {
return "zuul." + this.getClass().getSimpleName() + "." + filterType() + ".disable";
}
/**
* If true, the filter has been disabled by archaius and will not be run
*
* @return
*/
public boolean isFilterDisabled() {
filterDisabledRef.compareAndSet(null, DynamicPropertyFactory.getInstance().getBooleanProperty(disablePropertyName(), false));
return filterDisabledRef.get().get();
}
/**
* runFilter checks !isFilterDisabled() and shouldFilter(). The run() method is invoked if both are true.
*
* @return the return from ZuulFilterResult
*/
public ZuulFilterResult runFilter() {
ZuulFilterResult zr = new ZuulFilterResult();
if (!isFilterDisabled()) {
if (shouldFilter()) {
Tracer t = TracerFactory.instance().startMicroTracer("ZUUL::" + this.getClass().getSimpleName());
try {
Object res = run();
zr = new ZuulFilterResult(res, ExecutionStatus.SUCCESS);
} catch (Throwable e) {
t.setName("ZUUL::" + this.getClass().getSimpleName() + " failed");
zr = new ZuulFilterResult(ExecutionStatus.FAILED);
zr.setException(e);
} finally {
t.stopAndLog();
}
} else {
zr = new ZuulFilterResult(ExecutionStatus.SKIPPED);
}
}
return zr;
}
public int compareTo(ZuulFilter filter) {
return Integer.compare(this.filterOrder(), filter.filterOrder());
}
public static class TestUnit {
static Field field = null;
static {
try {
field = ZuulFilter.class.getDeclaredField("filterDisabledRef");
field.setAccessible(true);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Mock
private ZuulFilter f1;
@Mock
private ZuulFilter f2;
@Before
public void before() {
MockitoAnnotations.initMocks(this);
MonitoringHelper.initMocks();
}
@Test
public void testSort() {
when(f1.filterOrder()).thenReturn(1);
when(f2.filterOrder()).thenReturn(10);
when(f1.compareTo(any(ZuulFilter.class))).thenCallRealMethod();
when(f2.compareTo(any(ZuulFilter.class))).thenCallRealMethod();
ArrayList<ZuulFilter> list = new ArrayList<ZuulFilter>();
list.add(f2);
list.add(f1);
Collections.sort(list);
assertSame(f1, list.get(0));
}
@Test
public void testShouldFilter() {
class TestZuulFilter extends ZuulFilter {
@Override
public String filterType() {
return null;
}
@Override
public int filterOrder() {
return 0;
}
public boolean shouldFilter() {
return false;
}
public Object run() {
return null;
}
}
TestZuulFilter tf1 = spy(new TestZuulFilter());
TestZuulFilter tf2 = spy(new TestZuulFilter());
when(tf1.shouldFilter()).thenReturn(true);
when(tf2.shouldFilter()).thenReturn(false);
try {
tf1.runFilter();
tf2.runFilter();
verify(tf1, times(1)).run();
verify(tf2, times(0)).run();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
}
@Test
public void testIsFilterDisabled() {
class TestZuulFilter extends ZuulFilter {
@Override
public String filterType() {
return null;
}
@Override
public int filterOrder() {
return 0;
}
public boolean isFilterDisabled() {
return false;
}
public boolean shouldFilter() {
return true;
}
public Object run() {
return null;
}
}
TestZuulFilter tf1 = spy(new TestZuulFilter());
TestZuulFilter tf2 = spy(new TestZuulFilter());
when(tf1.isFilterDisabled()).thenReturn(false);
when(tf2.isFilterDisabled()).thenReturn(true);
try {
tf1.runFilter();
tf2.runFilter();
verify(tf1, times(1)).run();
verify(tf2, times(0)).run();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
}
@Test
public void testDisabledPropNameOnInit() throws Exception {
class TestZuulFilter extends ZuulFilter {
final String filterType;
public TestZuulFilter(String filterType) {
this.filterType = filterType;
}
@Override
public boolean shouldFilter() {
return false;
}
@Override
public Object run() {
return null;
}
@Override
public String filterType() {
return filterType;
}
@Override
public int filterOrder() {
return 0;
}
}
TestZuulFilter filter = new TestZuulFilter("pre");
assertFalse(filter.isFilterDisabled());
@SuppressWarnings("unchecked")
AtomicReference<DynamicBooleanProperty> filterDisabledRef = (AtomicReference<DynamicBooleanProperty>) field.get(filter);
String filterName = filterDisabledRef.get().getName();
assertEquals("zuul.TestZuulFilter.pre.disable", filterName);
}
}
}
关键的四个方法:
@Override
public Object run() throws ZuulException {
return null;
}
@Override
public boolean shouldFilter() {
return false;
}
@Override
public int filterOrder() {
return 0;
}
@Override
public String filterType() {
return null;
}
run() :具体执行的业务逻辑
shouldFilter() : 是否进行过滤,过滤器是否生效,true为需要过滤执行该过滤器
filterOrder() : 过滤器执行的优先级,越小优先级越高
filterType() : 过滤器的类型 ,有post、pre、route、error
PRE: 该类型的filters在Request routing到源web-service之前执行。用来实现Authentication、选择源服务地址等
ROUTING:该类型的filters用于把Request routing到源web-service,源web-service是实现业务逻辑的服务。这里使用HttpClient请求web-service。
POST:该类型的filters在ROUTING返回Response后执行。用来实现对Response结果进行修改,收集统计数据以及把Response传输会客户端。
ERROR:上面三个过程中任何一个出现错误都交由ERROR类型的filters进行处理。
主要关注 pre、post和error。分别代表前置过滤,后置过滤和异常过滤。
以上4项配置在FilterConstants这个工具类都是可以找到的
TokenAuthFilter:
package com.chwl.cn.token;
import java.util.Objects;
import java.util.UUID;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.http.HttpStatus;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants;
import org.springframework.stereotype.Component;
import com.chwl.cn.config.redis.RedisUtils;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
@Component
public class TokenAuthFilter extends ZuulFilter {
@Autowired
private RedisUtils redisUtils;
//排除过滤的 uri 地址
private static final String LOGIN_URI = "/user/login";
private static final String REGISTER_URI = "/user/register";
private static final String AUTH_TOKEN = "token";
@Override
public Object run() throws ZuulException {
RequestContext requestContext = RequestContext.getCurrentContext();
HttpServletRequest request = requestContext.getRequest();
HttpServletResponse response = requestContext.getResponse();
String token = request.getHeader(AUTH_TOKEN);
Object object = redisUtils.get(token);
//token存在,删除原来的token,重新生成token返回给客户端
if(!Objects.isNull(object)){
redisUtils.del(token);
String uuidToken = UUID.randomUUID().toString();
redisUtils.set(uuidToken,uuidToken);
response.setHeader("Access-Control-Expose-Headers",
"Cache-Control,Content-Type,Expires,Pragma,Content-Language,Last-Modified,token");
response.setHeader("token", uuidToken); // 设置响应头
response.setStatus(HttpStatus.SC_OK);
requestContext.setResponseBody("token验证成功!");
}else {
//不存在,直接返回验证失败,让其重新登录
requestContext.setSendZuulResponse(false);//不会被zuul路由转发,也就是不会请求到后端具体的服务。但是如果当前filter后面还有其他filter的话,其他filter依然会执行
// requestContext.setResponseStatusCode(HttpStatus.SC_UNAUTHORIZED);//和response.setStatus(HttpStatus.SC_UNAUTHORIZED);一样的效果
response.setStatus(HttpStatus.SC_UNAUTHORIZED);//401
requestContext.setResponseBody("token验证失败!");
}
return null;
}
@Override
public boolean shouldFilter() {
RequestContext requestContext = RequestContext.getCurrentContext();
HttpServletRequest request = requestContext.getRequest();
HttpServletResponse response = requestContext.getResponse();
String requestURI = request.getRequestURI();
//登录和注册放行
if(LOGIN_URI.equals(requestURI)||REGISTER_URI.equals(requestURI)){
String uuidToken = UUID.randomUUID().toString();
redisUtils.set(uuidToken,uuidToken);
response.setHeader("Access-Control-Expose-Headers",
"Cache-Control,Content-Type,Expires,Pragma,Content-Language,Last-Modified,token");
response.setHeader("token", uuidToken); // 设置响应头
return false;
}
return true;
}
@Override
public int filterOrder() {
return FilterConstants.SERVLET_DETECTION_FILTER_ORDER-1;
}
@Override
public String filterType() {
return FilterConstants.PRE_TYPE;
}
}
requestContext.setSendZuulResponse(false);这里要注意的一点是,这里设置为false表示不会进行路由请求后端服务器了。但是在这个filter后面如果还有filter的话,后面的filter还是照常会执行,所以这里如果验证都不通过,也就没必要继续在执行其他的filter了。在其他filter的shouldFilter()方法加入以下判断即可更好一点:
@Override
public boolean shouldFilter() {
RequestContext ctx = RequestContext.getCurrentContext();
if(!ctx.sendZuulResponse()){
return false;
}
return true;
}
先在redis放入一个token字符串,随意,用于测试
返回值报错了
token验证过了
再来试下不传token
401验证token没过