#项目中的拦截器:
在springboot中使用拦截器,要implementsHandlerInterceptor接口,实现preHandle、postHandle、afterCompletion方法。
preHandle
:该方法是进行处理器拦截用的,顾名思义,该方法将在Controller处理之前进行调用,SpringMVC中的Interceptor拦截器是链式的,可以同时存在多个Interceptor,然后SpringMVC会根据声明的前后顺序一个接一个的执行,而且所有的Interceptor中的preHandle方法都会在Controller方法调用之前调用。SpringMVC的这种Interceptor链式结构也是可以进行中断的,这种中断方式是令preHandle的返回值为false,当preHandle的返回值为false的时候整个请求就结束了。
postHandle
:这个方法只会在当前这个Interceptor的preHandle方法返回值为true的时候才会执行。postHandle是进行处理器拦截用的,它的执行时间是在处理器进行处理之后,也就是在Controller的方法调用之后执行,但是它会在DispatcherServlet进行视图的渲染之前执行,也就是说在这个方法中你可以对ModelAndView进行操作。这个方法的链式结构跟正常访问的方向是相反的,也就是说先声明的Interceptor拦截器该方法反而会后调用,这跟Struts2里面的拦截器的执行过程有点像,只是Struts2里面的intercept方法中要手动的调用ActionInvocation的invoke方法,Struts2中调用ActionInvocation的invoke方法就是调用下一个Interceptor * 或者是调用action,然后要在Interceptor之前调用的内容都写在调用invoke之前,要在Interceptor之后调用的内容都写在调用invoke方法之后。
afterCompletion
:该方法也是需要当前对应的Interceptor的preHandle方法的返回值为true时才会执行。该方法将在整个请求完成之后,也就是DispatcherServlet渲染了视图后, 这个方法的主要作用是用于清理资源的,当然这个方法也只能在当前这个Interceptor的preHandle方法的返回值为true时才会执行。
注册拦截器
springboot中光在Interceptor拦截器类上加@Component注解,只能说明拦截器在IOC容器里供用户使用,但是用户无法直接使用,所以必须注册才能生效,也就是下面重写的addInterceptors()方法。
@Component
//加入拦截器
public class ToutiaoWebConfig extends WebMvcConfigurerAdapter {
@Autowired
private PassportInterceptor passportInterceptor;
@Autowired
private LoginRequiredInterceptor loginRequiredInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
/**
* 错误写法,要体现spring的IOC
*/
//registry.addInterceptor(new PassportInterceptor());
/**
* 正确写法,通过自动配置
*/
registry.addInterceptor(passportInterceptor);
registry.addInterceptor(loginRequiredInterceptor).addPathPatterns("/setting*");
}
}
使用拦截器完成的功能
##页面访问(思想路线)
1.客户端:发送带token的HTTP请求
2.服务端:
- 根据token获取用户id
- 根据用户id获取用户的具体信息
- 检查该用户是否有相关页面访问权限
- 渲染页面/跳转页面。有,可以继续访问;没有跳转到主页
具体功能:
一、可以知道是哪个用户要登录。
在进入controller前,调用preHandle方法处理,它可以检查客户端提交的cookie中是否有服务器之前下发的ticket,如果有证明这个请求是已经登陆的用户了。把登陆的用户放到线程本地变量(ThreadLocal)。在此时进入controller,可以拿到具体的用户HostHolder类,进而可以根据登陆的用户进行个性化渲染,比如关注用户的动态,个人收藏等。
二、可以进行权限管理
比如,在浏览某些页面时,要保证用户登陆,或者用户是某个等级的。可以先在preHandler中判断,这个可以在设置一个特定范围的拦截器,如下的拦截器LoginRequiredInterceptor,在访问setting页面时才会进入到该拦截器,如果验证没有HostHolder,说明用户没登陆,就跳转到主页或者给出登陆页面。拦截器也先后执行顺序。
三、可以自动登陆
我们平时在浏览网页的时候会碰到这样的情况,昨天登陆了某个网站,关机,第二天再登陆,自动跳转到我的用户了。这里是服务器会把浏览器的sessionId和服务器的数据库关联,在提交请求的时候服务器会设置拦截器去找sessionId,如果这个sessionId和我服务器上的ticket关联了起来,并且设置的过期时间没有过期,那在登陆主页前我就可以知道是某个用户,在controller层中可以进行渲染。可以做到自动登陆的功能。
###自动登录(登录验证)
想要达到的目的就是将访问网站的用户所在的线程和ThreadLocal进行绑定,解决了线程安全的问题。
com.nowcoder.interceptor.PassportInterceptor:
@Component
public class PassportInterceptor implements HandlerInterceptor {
@Autowired
private LoginTicketDAO loginTicketDAO;
@Autowired
private UserDAO userDAO;
@Autowired
private HostHolder hostHolder;
//在preHandle中我们实现了自动登录,检查cookie中的ticket,通过找到的ticket在数据库中查找对应的LoginTicket对象,如果LoginTicket对象有效,然后通过LoginTicket对象中的userid字段,找到user对象
@Override
public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) throws Exception {
String ticket=null;
if (httpServletRequest.getCookies()!=null){
//在cook中找ticket
for (Cookie cookie:httpServletRequest.getCookies()){
if (cookie.getName().equals("ticket")){
ticket=cookie.getValue();
break;
}
}
}
//cookie可以伪造,所以光查看cookie不够
//如果客户端有ticket存在,查看与服务器后端的数据库中的值是否相等
if (ticket!=null){
LoginTicket loginTicket=loginTicketDAO.selectByTicket(ticket);
//ticket不相等、超过期限或者状态无效
if (loginTicket==null||loginTicket.getExpired().before(new Date())||loginTicket.getStatus()!=0)
{
return true;
}
//认证过后,将用户信息保存在hostHolder中,方便后面的流程使用
User user=userDAO.selectById(loginTicket.getUserId());
hostHolder.setUser(user);
}
return true;
}
//在controller结束的时候,通常会把结果返回给view视图,在拦截器中postHandle方法做
@Override
public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {
if (modelAndView != null && hostHolder.getUser() != null) {
modelAndView.addObject("user", hostHolder.getUser());
}
}
@Override
public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {
hostHolder.clear();
}
}
com.nowcoder.model.HostHolder:
/**
* ThreadLocal的用法
*
* 使用ThreadLocal把里面的值共享给所有的类
*/
@Component
public class HostHolder {
private static ThreadLocal<User> users = new ThreadLocal<User>();
public User getUser() {
return users.get();
}
public void setUser(User user) {
users.set(user);
}
public void clear() {
users.remove();;
}
}
###权限管理
拦截位置放在登陆验证PassportInterceptor的后面,通过登陆验证看HostHolder类中有没有线程本地变量user类,如果有,能确定是谁登陆的。如果没有,那没有权限访问/setting*相关的页面。通过PassportInterceptor的PreHandler()方法后,进入LoginRequiredInterceptor的PreHandler方法,如果没有拿到user类,返回false。不能进入controller层,重定向到首页,并且约定pop=1,弹出登陆框。
新增一个拦截器LoginRequiredInterceptor:
@Component
public class LoginRequiredInterceptor implements HandlerInterceptor {
@Autowired
private HostHolder hostHolder;
@Override
public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) throws Exception {
//歧视就是当前用户没有登录,直接重定向
if (hostHolder.getUser()==null){
httpServletResponse.sendRedirect("/?pop=1");////自己定义的,传到前端,假如pop=1,让登录框弹出
return false;
}
return true;
}
@Override
public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {
}
@Override
public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {
}
}
当用户证明没有登录,网址会重定向到:http://localhost:8080/?pop=1,重定向到这个url会发生什么呢?往下看
templates/home.html中:
#if ($pop)
<script>
window.loginpop = $!{pop};
</script>
<script type="text/javascript" src="/scripts/main/site/home.js"></script>
#end
将变量pop的值付给了window.loginpop,此时的window.loginpop=1
然后static/scripts/main/site/home.js中:
function fInitialize() {
if (window.loginpop > 0) {
fClickLogin();
}
}
function fClickLogin() {
var that = this;
PopupLogin.show({
listeners: {
login: function () {
//alert('login');
window.location.reload();
},
register: function () {
//alert('reg');
window.location.reload();
}
}
});
}
上面大致的意思就是:当window.loginpop > 0时,执行function fClickLogin(),在项目中就是跳出登录框,如图: