早期使用servlet进行网络开发时,没有拦截器这些内容,那时做请求拦截都是使用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;
@Override
public void init(FilterConfig filterConfig) throws ServletException {
Filter.super.init(filterConfig);
context = filterConfig.getServletContext();
}
@Override
public 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);
}
}
@Override
public 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接口进行测试,当有权限和无权限时的请求返回内容如下:
仿造前面的拦截器实现,在过滤器中配合注解实现拦截,这种方式更加灵活,不用在代码中编写拦截和放行的请求路径,它的实现原理与拦截器类似:
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");
@Autowired
private RequestMappingHandlerMapping requestMappingHandlerMapping;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
// 在 chain.doFilter(request, response) 执行前调用返回null
Object 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进行测试,这次只对添加了注解的方法进行拦截,没有添加注解的请求直接放行。