早期使用servlet进行网络开发时,没有拦截器这些内容,那时做请求拦截都是使用Filter过滤器实现的,配置Filter要对哪些请求路径处理,有权限或不需要拦截的路径放行,没有权限的路径直接拦截请求。
一、Filter直接进行拦截
下面就是模拟使用Filter做权限控制的场景,由于现在都是使用spring进行开发,这里定义Filter就使用注解方式实现注入,代码如下:
import org.example.pojo.ApiResult;
import org.example.pojo.StatusCode;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;/*** 过滤器处理* @Author xingo* @Date 2023/12/12*/
@Order(value = Ordered.HIGHEST_PRECEDENCE)
@Component
@WebFilter(filterName = "myFilter", urlPatterns = "/*")
public class MyFilter implements Filter {private ServletContext context;@Overridepublic void init(FilterConfig filterConfig) throws ServletException {Filter.super.init(filterConfig);context = filterConfig.getServletContext();}@Overridepublic void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain) throws IOException, ServletException {HttpServletRequest request = (HttpServletRequest) servletRequest;String uri = request.getRequestURI();// 静态资源直接放行if(uri.endsWith(".js") || uri.endsWith(".css") || uri.endsWith(".png") || uri.endsWith(".jpg")) {chain.doFilter(servletRequest, servletResponse);return;}// 登录请求或注册请求放行if(uri.startsWith("/login") || uri.startsWith("/register")) {chain.doFilter(servletRequest, servletResponse);return;}// 其他请求判断是否有权限拦截String userName = this.getLoginUserName(request);if(userName == null) {String sReqType = request.getHeader("X-Requested-With");String contentType = request.getHeader("Content-Type");if ("XMLHttpRequest".equalsIgnoreCase(sReqType) || (contentType != null && contentType.toLowerCase().contains("application/json"))) {servletResponse.setContentType("application/json; charset=utf-8");servletResponse.getWriter().print(JacksonUtils.toJSONString(ApiResult.fail(StatusCode.C_10001)));} else {servletResponse.setContentType("text/html;charset=utf-8");servletResponse.getWriter().print("用户未登录");}} else {// 执行调用链方法chain.doFilter(servletRequest, servletResponse);}}@Overridepublic void destroy() {Filter.super.destroy();}/*** 获取登录用户信息* @param request http请求体* @return*/private String getLoginUserName(HttpServletRequest request) {try {String token = "";if (request.getCookies() != null) {for (Cookie c : request.getCookies()) {if (null != c && "TOKEN".equals(c.getName())) {token = c.getValue();break;}}}// 简单通过cookie中的token做权限判断,如果有token且符合要求就返回用户名,否则表示用户身份认证失败if(token != null && token.length() > 10) {String userName = token.substring(10);return userName.equals("admin") ? userName : null;}} catch (Exception e) {e.printStackTrace();}return null;}
}
测试接口还是使用之前的那个controller:
import org.example.handler.AuthLogin;
import org.example.handler.BusinessException;
import org.example.handler.ManagerAuthInteceptor;
import org.example.pojo.ApiResult;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.TimeUnit;/*** @Author xingo* @Date 2023/12/7*/
@RestController
public class DemoController {@GetMapping("/demo1")public Object demo1() {int i = 1, j = 0;return i / j;}@GetMapping("/demo2")public Object demo2() {if(System.currentTimeMillis() > 1) {throw BusinessException.fail(88888, "业务数据不合法");}return System.currentTimeMillis();}@GetMapping("/demo3")public Map<String, Object> demo3() {Map<String, Object> map = new HashMap<>();map.put("key1", "Hello,world!");map.put("key2", new Date());return map;}@GetMapping("/demo4")public List<Object> demo4() {List<Object> list = new ArrayList<>();list.add(new Date());list.add("Hello,world!");list.add(Long.MAX_VALUE);list.add(Integer.MAX_VALUE);return list;}@GetMapping("/demo5")public Map<String, Object> demo5() throws InterruptedException {Map<String, Object> map = new HashMap<>();map.put("method", "demo5");map.put("start", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").format(new Date()));TimeUnit.SECONDS.sleep(1);map.put("end", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").format(new Date()));return map;}@GetMapping("/demo6")public ApiResult demo6() throws InterruptedException {Map<String, Object> map = new HashMap<>();map.put("method", "demo6");map.put("start", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").format(new Date()));TimeUnit.SECONDS.sleep(1);map.put("end", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").format(new Date()));return ApiResult.success(map);}@AuthLogin@GetMapping("/demo7")public Map<String, Object> demo7() {Map<String, Object> map = new HashMap<>();map.put("method", "demo7");map.put("userName", ManagerAuthInteceptor.LocalUserName.get());map.put("time", new Date());return map;}
}
使用/demo6接口进行测试,当有权限和无权限时的请求返回内容如下:



二、Filter配合注解实现拦截
仿造前面的拦截器实现,在过滤器中配合注解实现拦截,这种方式更加灵活,不用在代码中编写拦截和放行的请求路径,它的实现原理与拦截器类似:
- 定义注解
import java.lang.annotation.*;/*** 登录检查注解注解* @author xingo* @date 2023/12/12**/
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface AuthLogin {/*** 检查是否已经登录* @return*/boolean check() default false;}
- 使用过滤器对请求进行拦截处理
import org.example.pojo.ApiResult;
import org.example.pojo.StatusCode;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerMapping;
import org.springframework.web.servlet.mvc.condition.PathPatternsRequestCondition;
import org.springframework.web.servlet.mvc.condition.RequestMethodsRequestCondition;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;/*** 过滤器处理* @Author xingo* @Date 2023/12/12*/
@Order(value = Ordered.HIGHEST_PRECEDENCE)
@Component
@WebFilter(filterName = "authFilter", urlPatterns = "/*")
public class AuthFilter extends OncePerRequestFilter {// //获取WebApplicationContext,用于获取Bean
// WebApplicationContext webApplicationContext = WebApplicationContextUtils.getWebApplicationContext(this.getServletContext());
// //获取spring容器中的RequestMappingHandlerMapping
// RequestMappingHandlerMapping requestMappingHandlerMapping = (RequestMappingHandlerMapping) webApplicationContext.getBean("requestMappingHandlerMapping");@Autowiredprivate RequestMappingHandlerMapping requestMappingHandlerMapping;@Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {// 在 chain.doFilter(request, response) 执行前调用返回nullObject handler1 = request.getAttribute(HandlerMapping.BEST_MATCHING_HANDLER_ATTRIBUTE);System.out.println(handler1);// 获取处理请求HandlerMethod handler = getHandlerMethod(request);if(handler != null) {Method method = handler.getMethod();Class<?> clazz = handler.getBeanType();// 判断添加了注解的方法或类才进行拦截boolean annotationType = clazz.isAnnotationPresent(AuthLogin.class);boolean annotationMethod = method.isAnnotationPresent(AuthLogin.class);if(annotationType || annotationMethod) {// 拦截到的请求需要判断用户名是否为空,如果为空表示用户身份验证失败,表示用户无访问权限String userName = this.getLoginUserName(request);if(userName == null) {String sReqType = request.getHeader("X-Requested-With");String contentType = request.getHeader("Content-Type");if ("XMLHttpRequest".equalsIgnoreCase(sReqType) || (contentType != null && contentType.toLowerCase().contains("application/json"))) {response.setContentType("application/json; charset=utf-8");response.getWriter().print(JacksonUtils.toJSONString(ApiResult.fail(StatusCode.C_10001)));} else {response.setContentType("text/html;charset=utf-8");response.getWriter().print("用户未登录");}return;} else {// 执行调用链方法chain.doFilter(request, response);}} else {// 没有添加注解的请求直接放行chain.doFilter(request, response);}}// 在 chain.doFilter(request, response) 执行后调用可以返回值Object handler4 = request.getAttribute(HandlerMapping.BEST_MATCHING_HANDLER_ATTRIBUTE);System.out.println(handler4);}/*** 获取登录用户信息* @param request http请求体* @return*/private String getLoginUserName(HttpServletRequest request) {try {String token = "";if (request.getCookies() != null) {for (Cookie c : request.getCookies()) {if (null != c && "TOKEN".equals(c.getName())) {token = c.getValue();break;}}}// 简单通过cookie中的token做权限判断,如果有token且符合要求就返回用户名,否则表示用户身份认证失败if(token != null && token.length() > 10) {String userName = token.substring(10);return userName.equals("admin") ? userName : null;}} catch (Exception e) {e.printStackTrace();}return null;}/*** 缓存handler*/public static final ConcurrentHashMap<String, HandlerMethod> handlerMap = new ConcurrentHashMap<>();/*** 获取请求对应的handler方法* @param request* @return*/private HandlerMethod getHandlerMethod(HttpServletRequest request) {String uri = request.getRequestURI();String method = request.getMethod();String key = (method + ":" + uri).intern();HandlerMethod handler = handlerMap.get(key);if(handler == null) {//获取应用中所有的请求Map<RequestMappingInfo, HandlerMethod> handlerMethods = requestMappingHandlerMapping.getHandlerMethods();Set<Map.Entry<RequestMappingInfo, HandlerMethod>> entries = handlerMethods.entrySet();for(Map.Entry<RequestMappingInfo, HandlerMethod> entry : entries) {RequestMethodsRequestCondition methodsCondition = entry.getKey().getMethodsCondition();boolean checkMethod = false;if(methodsCondition.getMethods().isEmpty()) {checkMethod = true;} else {for(RequestMethod reqMethod : methodsCondition.getMethods()) {checkMethod = reqMethod.name().equals(method);if(checkMethod) {break;}}}PathPatternsRequestCondition pathCondition = entry.getKey().getPathPatternsCondition();if(checkMethod && pathCondition.getDirectPaths().contains(uri)) {handler = entry.getValue();}}if(handler != null) {handlerMap.put(key, handler);}}return handler;}
}
这里也可以使用Filter进行处理,但是我选择继承spring实现的OncePerRequestFilter,在每次请求时过滤器只会执行一次,这里面最主要的处理逻辑是获取到HandlerMethod,获取到这个对象后剩余的处理逻辑跟拦截器基本一致了,只不过拦截器是通过返回布尔值true或false来进行放行或拦截。而在Filter中是通过是否调用 chain.doFilter(request, response) 这个方法来进行放行和拦截的。
还是用上面的controller进行测试,这次只对添加了注解的方法进行拦截,没有添加注解的请求直接放行。



