记录项目学习过程中的一个知识点——后端登录拦截。
并思考用一种方式——Servlet Filter实现该功能。
注:拦截器@Override的prehandle逻辑,在后期学习过程修改为了shiro验证,本文代码不再修改,详情见Vue + Spring Boot 项目实战(十四):用户认证方案与完善的访问拦截
项目还是那个项目:Vue + Spring Boot 项目实战(一):项目简介
还是第6节Vue + Spring Boot 项目实战(六):前端路由与登录拦截器。
文中有讲到一个需求——后端登录拦截,意思就是没有登录的用户,必须要求其登录之后才可以访localhost:8843/blog/index页面。
文中实现的方式使用拦截器,拦截器是什么?拦截器是SpringMVC的一种机制,类似于Servlet的过滤器Filter,主要实现对某个处理方法进行拦截,执行某些操作。有个链接可参考: Spring MVC拦截器(Interceptor )详解
- 如何使用拦截器实现登录拦截? (原文:Vue + Spring Boot 项目实战(六):前端路由与登录拦截器的实现方式)
- 首先自定义一个类实现
HandlerInterceptor
,比如public class LoginInterceptor implements HandlerInterceptor
,然后重写回调函数preHandle
。这里的逻辑就是:判断request.getServletPath()
是否是否是"/index"
,如果是,就判断是否登录(session.getAttribute("user")
是否为空),没有登录就重定向到"/login"
(response.sendRedirect(request.getContextPath() + "/login"
); - 然后自定义类实现
WebMvcConfigurer
,重写addInterceptors
,把上面的LoginInterceptor
注册到拦截器中。
贴一个我自己写的代码,可以对比着原文代码看看(注意不要忘了注解):1
2
3
4
5
6
7
8
9
10
11
12
13
14
15public class LoginInterceptor implements HandlerInterceptor {
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
HttpSession session = request.getSession();
String servletPath = request.getServletPath();
if (servletPath.startsWith("/index")) {
if (session.getAttribute("user") == null) {
System.out.println("Not Login");
response.sendRedirect(request.getContextPath() + "/login");
return false;
}
}
return true;
}
}下面是原博客中的LoginInterceptor的实现:1
2
3
4
5
6
7
8
9
10
11
12
13
public class MyWebConfigurer implements WebMvcConfigurer {
public LoginInterceptor getLoginInterceptor() {
return new LoginInterceptor();
}
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(getLoginInterceptor()).addPathPatterns("/**").excludePathPatterns("/index.html");
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36public class LoginInterceptor implements HandlerInterceptor{
public boolean preHandle (HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) throws Exception {
HttpSession session = httpServletRequest.getSession();
String contextPath=session.getServletContext().getContextPath();
String[] requireAuthPages = new String[]{
"index",
};
String uri = httpServletRequest.getRequestURI();
uri = StringUtils.remove(uri, contextPath+"/");
String page = uri;
if(begingWith(page, requireAuthPages)){
User user = (User) session.getAttribute("user");
if(user==null) {
httpServletResponse.sendRedirect("login");
return false;
}
}
return true;
}
private boolean begingWith(String page, String[] requiredAuthPages) {
boolean result = false;
for (String requiredAuthPage : requiredAuthPages) {
if(StringUtils.startsWith(page, requiredAuthPage)) {
result = true;
break;
}
}
return result;
}
}
- 既然一些博客在介绍SpringMVC的拦截器时说类似Servlet的Filter,那能否用Filter实现呢?
我做了做实验,应该是可以的。但因为博主还是小白,因此如果写的不对,还请读者指教。
逻辑和拦截器的代码差不多,就是对所有url先拦截,在判断ServletPath的前缀是不是"/index"
,是的话就判断用户是否登录,没有登录就重定向到"/login"
,已经登录就放行。
直接上代码吧,同样注意不要忘了注解。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public class LoginFilter implements Filter {
public void init(FilterConfig filterConfig) throws ServletException {
}
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
HttpSession session = request.getSession();
System.out.println("Servlet Path=" + request.getServletPath());
if (request.getServletPath().startsWith("/index")) {
User user = (User) session.getAttribute("user");
if (user == null) {
System.out.println("Not Login");
response.sendRedirect(request.getContextPath() + "/login");
} else {
//放行
filterChain.doFilter(request, response);
}
} else {
filterChain.doFilter(request, response);
}
}
public void destroy() {
}
} - 请仔细理解这两种方式的代码。如果您感觉我写的不对,请批评指正。我能写出来也是“麦芒掉进针眼里——凑巧了”。
如果您也在学习Vue + Spring Boot 项目实战(六):前端路由与登录拦截器,并且您实践了我说的这两种方式(前提我写对了,嘿嘿),请您继续往下看。
我说一个小细节吧。使用过滤器Filter可以对"/index"
和"/index.html"
都进行过滤,要求登录以后才能访问,但是使用拦截器的方式并不能对"/index.html"
进行拦截,即不要求登录就能访问(注:这个index.html
是Vue.js
单页面开发,直接访问是空白页面,由于不懂前端,我并不知道是否存在危害,或许可以直接访问并不存在危害,只是我感觉不太好)。
因为MyWebConfigurer·中的·addInterceptor()
方法有一句excludePathPatterns("/index.html")
。可是为什么要加这一句呢?原博客也有说明原因,是因为未登录状态下,"/index"
会重定向到"/login"
界面,然后又因为"/login"
使用了ErrorConfig
(这个请看原博客,属于项目内容),将HTTP 404
重定向到了"/index.html"
,"/index.html"
的前缀同样是"/index"
,未登录情况下再次重定向到"/login"
,然后这就造成了死循环,即重定向过多导致页面访问失败了。
那为什么Filter不会重定向过多呢?我认为和拦截器、过滤器的机制不同有关,拦截器是属于SpringMVC
规范,但过滤器是属于Servlet
规范。博客(蛮不错的)《spring boot 过滤器、拦截器的区别与使用》给了几个图,并指出“过滤器和拦截器触发时机不一样,过滤器是在请求进入容器后,但请求进入servlet之前进行预处理的。请求结束返回也是,是在servlet处理完后,返回给前端之前。”
这说明,过滤器Filter是在进入容器之后就起作用了,但是拦截器Interceptor是在进入了LoginController
之前才被调用,并且这个过程应该是在ErrorConfig
的后面(我感觉ErrorConfig
的重定向应该是属于dispatcher)。这还有个利用System.out.println()
输出Spring
组件加载顺序的图,
因此,如果MyWebConfigurer
中的addIntercepto
不写excludePathPatterns("/index.html")
,就会造成这样的重定向死循环顺序:"/index"(未登录) --> "/login" --> "/index.html" --> "/login" --> "/index.html" --> ...
,要把"/index.html"
排除在外。
截了个图可以看一下:
当然,加了excludePathPatterns("/index.html")
,也就如前面所说,意味着"/index.html"
不需要登录了,可以直接访问。
好了,说完了。欢迎批评指正。