Do a simple authentication
token authentication, verification token
Usually verify operations such as authentication zuul gateway level, in addition to authentication, it may also be limiting, encryption.
Inheritance ZuulFilter,
ZuulFilter Source: implemented in 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);
}
}
}
The key of four methods:
@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 (): the specific implementation of the business logic
shouldFilter (): whether the filter, the filter is in effect, true to execute the filter needs to be filtered
filterOrder (): execution priority filter, are higher priority
filterType (): type of filter, there are post, pre, route, error
PRE: This type of filters in Request routing performed prior to the source web-service. For implementing Authentication, select the source service address
ROUTING: the type of filters used in the Request routing to a web-service source, the source is a web-service business logic services. As used herein, HttpClient requested web-service.
POST: The type of the filters is performed ROUTING return Response. Response to achieve the results to be modified, as well as the collection of statistical data will be transmitted Response clients.
ERROR: three processes above any error ERROR types of filters are handed over for processing.
The main concern pre, post and error. Represent the pre-filter, post-filter and filter abnormalities.
More than four in FilterConstants this configuration tool can be found in all classes
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); to note here is that this set does not route false to request the back-end server. But if there is, then filter behind the filter performs as usual in this rear filter, so here if the verification is not passed, but also no need to continue the implementation of the other filter. In other shouldFilter filter () method is determined by adding the following to a little better:
@Override
public boolean shouldFilter() {
RequestContext ctx = RequestContext.getCurrentContext();
if(!ctx.sendZuulResponse()){
return false;
}
return true;
}
First into a token string Redis, random, for testing
The return value being given
After a token verification
Try it again does not pass the token
401 verification token you have not seen