拦截器[ 登录认证场景应用 ]
有两种方式: 在springmvc.xml中配置
方式1: 针对某一个HandlerMapping设置拦截[一般不推荐使用, 太麻烦]
1 <bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"> 2 <property name="interceptors"> 3 <list> 4 <ref bean="handlerInterceptor1"/> 5 <ref bean="handlerInterceptor2"/> 6 </list> 7 </property> 8 </bean> 9 <bean id="handlerInterceptor1" class="com.itcast.ssm.interceptor.HandlerInterceptor1"/> 10 <bean id="handlerInterceptor2" class="com.itcast.ssm.interceptor.HandlerInterceptor2"/>
方式2: 全局的拦截器[推荐使用]
1. 定义拦截器
拦截器1
1 package com.itcast.ssm.interceptor; 2 3 import javax.servlet.http.HttpServletRequest; 4 import javax.servlet.http.HttpServletResponse; 5 import org.springframework.web.servlet.HandlerInterceptor; 6 import org.springframework.web.servlet.ModelAndView; 7 8 /** 9 * 测试拦截器1 10 */ 11 public class HandlerInterceptor1 implements HandlerInterceptor { 12 13 /** 14 * 进入Handler方法之前执行, 用于身份认证/身份授权 15 */ 16 @Override 17 public boolean preHandle(HttpServletRequest request, 18 HttpServletResponse response, Object handler) throws Exception { 19 20 System.out.println("HandlerInterceptor1....preHandle()"); 21 return true; // return false 表示拦截, 不向下执行, return true 表示放行 22 } 23 24 /** 25 * 进入Handler方法之后, 返回ModelAndView之前执行 26 * 应用场景从modelAndView出发, 将一些公用的模型数据(比如菜单的导航)在这里传到视图, 也可以在这里统一制定视图 27 */ 28 @Override 29 public void postHandle(HttpServletRequest request, 30 HttpServletResponse response, Object handler, ModelAndView mv) 31 throws Exception { 32 33 System.out.println("HandlerInterceptor1....postHandle()"); 34 } 35 36 /** 37 * 执行Handler方法完成之后执行此方法 38 * 统一异常处理, 统一日志处理 39 */ 40 @Override 41 public void afterCompletion(HttpServletRequest request, 42 HttpServletResponse response, Object handler, Exception ex) 43 throws Exception { 44 45 System.out.println("HandlerInterceptor1....afterCompletion()"); 46 } 47 }
拦截器2
1 package com.itcast.ssm.interceptor; 2 3 import javax.servlet.http.HttpServletRequest; 4 import javax.servlet.http.HttpServletResponse; 5 import org.springframework.web.servlet.HandlerInterceptor; 6 import org.springframework.web.servlet.ModelAndView; 7 8 /** 9 * 测试拦截器2 10 */ 11 public class HandlerInterceptor2 implements HandlerInterceptor { 12 13 /** 14 * 进入Handler方法之前执行, 用于身份认证/身份授权 15 */ 16 @Override 17 public boolean preHandle(HttpServletRequest request, 18 HttpServletResponse response, Object handler) throws Exception { 19 20 System.out.println("HandlerInterceptor2....preHandle()"); 21 return true; // return false 表示拦截, 不向下执行, return true 表示放行 22 } 23 24 /** 25 * 进入Handler方法之后, 返回ModelAndView之前执行 26 * 应用场景从modelAndView出发, 将一些公用的模型数据(比如菜单的导航)在这里传到视图, 也可以在这里统一制定视图 27 */ 28 @Override 29 public void postHandle(HttpServletRequest request, 30 HttpServletResponse response, Object handler, ModelAndView mv) 31 throws Exception { 32 33 System.out.println("HandlerInterceptor2....postHandle()"); 34 } 35 36 /** 37 * 执行Handler方法完成之后执行此方法 38 * 统一异常处理, 统一日志处理 39 */ 40 @Override 41 public void afterCompletion(HttpServletRequest request, 42 HttpServletResponse response, Object handler, Exception ex) 43 throws Exception { 44 45 System.out.println("HandlerInterceptor2....afterCompletion()"); 46 } 47 }
2. 在springmvc.xml中配置拦截器
1 <!-- 配置全局拦截器 --> 2 <mvc:interceptors> 3 <!-- 多个拦截器, 按先后顺序执行 --> 4 <mvc:interceptor> 5 <!-- /**表示拦截所有URL, 包括其子URL路径 --> 6 <mvc:mapping path="/**"/> 7 <bean class="com.itcast.ssm.interceptor.HandlerInterceptor1"/> 8 </mvc:interceptor> 9 <mvc:interceptor> 10 <mvc:mapping path="/**"/> 11 <bean class="com.itcast.ssm.interceptor.HandlerInterceptor2"/> 12 </mvc:interceptor> 13 </mvc:interceptors>
测试1:[测试背景, springmvc+spring+mybatis整合项目demo]
http://localhost:8080/ssm_items/items/editItemsShow.action
后台打印:
HandlerInterceptor1....preHandle()
HandlerInterceptor2....preHandle()
HandlerInterceptor2....postHandle()
HandlerInterceptor1....postHandle()
HandlerInterceptor2....afterCompletion()
HandlerInterceptor1....afterCompletion()
总结: 1. preHandle()方法按照顺序执行 2. postHandle()和afterCompletion()方法逆向执行
----------------------------------------------------------------------
测试2:
把拦截器2的preHandle()方法返回值改成false, 再测试, 后台打印:
HandlerInterceptor1....preHandle()
HandlerInterceptor2....preHandle()
HandlerInterceptor1....afterCompletion()
总结:
拦截器1的preHandle放行, 拦截器2的proHandle才会执行
拦截器2的preHandle不放行, 拦截器2的postHandle和afterCompletion不会执行
只要有一个拦截器不放行, postHandle方法都不会执行
测试3: 后台打印
HandlerInterceptor1....preHandle()
拦截器1不放行, 后面的都不会执行
根据测试结果, 对拦截器的应用: 1. 统一日志处理拦截器 需要将该拦截器的preHandle方法设置为放行, 并且将该拦截器放在配置文件中的拦截器链的第一个位置, 因为这样,[中间可能设置有多个拦截器,
但不管中间的拦截器的preHandle方法怎么设置, 但是只要统一日志处理的拦截器的preHandle方法放行, 其afterCompletion方法就会被执行]
该拦截器的afterCompletion方法才会被执行(而这个方法就是用于写统一日志处理的) 2. 登录认证拦截器 将其放在拦截器链中的第一个位置(但是位置不能超过统一日志处理的拦截器) 3. 权限校验拦截器 放在登录认证拦截器之后, 因为只有登录通过之后才会校验权限
总结这三个拦截器在拦截器链中的放置顺序: 1. 统一日志处理拦截器 2. 登录认证拦截器 3. 权限校验拦截器
拦截器的应用: 登录认证
需求分析:
1. 用户请求URL
2. 拦截器进行拦截校验
* 如果请求的URL是公开地址(即: 无需登录即可访问的URL, 比如网站的首页), 放行
* 如果用户session不存在, 跳转到登录页面
* 如果用户session存在, 放行, 继续操作
实现:
1. 编写认证登录的handler: LoginController.java
1 package com.itcast.ssm.controller; 2 3 import javax.servlet.http.HttpSession; 4 import org.springframework.stereotype.Controller; 5 import org.springframework.web.bind.annotation.RequestMapping; 6 7 @Controller 8 public class LoginController { 9 10 // 登录 11 @RequestMapping("/login") 12 public String login(HttpSession session, String username, String password) throws Exception { 13 14 // 中间过程省略不写(这里要调用service方法进行用户身份验证) 15 16 // 将合法的用户身份信息保存到session中 17 session.setAttribute("username", username); 18 19 // 如果登录成功, 重定向到商品列表页面, 否则返回重新登录 20 return "redirect:items/queryItems.action"; 21 } 22 23 // 退出 24 @RequestMapping("/logout") 25 public String logout(HttpSession session) throws Exception { 26 27 // 清除session, 重定向到登录页面 28 session.invalidate(); 29 return "redirect:items/queryItems.action"; 30 } 31 }
2. 编写认证登录的拦截器LoginInterceptor.java
1 package com.itcast.ssm.interceptor; 2 3 import javax.servlet.http.HttpServletRequest; 4 import javax.servlet.http.HttpServletResponse; 5 import javax.servlet.http.HttpSession; 6 import org.springframework.web.servlet.HandlerInterceptor; 7 import org.springframework.web.servlet.ModelAndView; 8 9 /** 10 * 登录认证拦截器 11 */ 12 public class LoginInterceptor implements HandlerInterceptor { 13 14 @Override 15 public boolean preHandle(HttpServletRequest request, HttpServletResponse response, 16 Object handler) throws Exception { 17 18 // 获取请求的URL 19 String url = request.getRequestURI(); 20 21 // 判断URL是否是公开地址(实际使用时将公开地址配置在配置文件中) 22 if (url.indexOf("login.action") >= 0) { 23 // 如果是公开地址, 并且进行登录提交, 那么放行 24 return true; 25 } 26 27 // 判断session中是否存在用户信息 28 HttpSession session = request.getSession(); 29 String username = (String) session.getAttribute("username"); 30 if (username != null) { 31 // 身份存在, 放行 32 return true; 33 } 34 35 // 执行到这里表示用户身份不符合前面两个if判断, 那么用户身份需要验证 36 request.getRequestDispatcher("/WEB-INF/jsp/login.jsp").forward(request, response); 37 return false; 38 } 39 40 @Override 41 public void postHandle(HttpServletRequest request, HttpServletResponse response, 42 Object arg2, ModelAndView mv) throws Exception { 43 44 } 45 46 @Override 47 public void afterCompletion(HttpServletRequest request, 48 HttpServletResponse response, Object handler, Exception ex) 49 throws Exception { 50 51 } 52 }
3. 在springmvc.xml中配置认证登录的拦截器
1 <!-- 配置全局拦截器: 多个拦截器, 按先后顺序执行, /**表示拦截所有URL, 包括其子URL路径 --> 2 <mvc:interceptors> 3 <!-- 登录验证拦截器 --> 4 <mvc:interceptor> 5 <mvc:mapping path="/**"/> 6 <bean class="com.itcast.ssm.interceptor.LoginInterceptor"/> 7 </mvc:interceptor> 8 </mvc:interceptors>
4. 编写认证登录页面login.jsp
1 <%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%> 2 <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%> 3 <%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %> 4 <% 5 String path = request.getContextPath(); 6 String basePath = request.getScheme() + "://" 7 + request.getServerName() + ":" + request.getServerPort() 8 + path + "/"; 9 %> 10 11 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> 12 <html> 13 <head> 14 <base href="<%=basePath%>"> 15 <title>用户登录</title> 16 <meta http-equiv="pragma" content="no-cache"> 17 <meta http-equiv="cache-control" content="no-cache"> 18 <meta http-equiv="expires" content="0"> 19 <meta http-equiv="keywords" content="keyword1,keyword2,keyword3"> 20 <meta http-equiv="description" content="This is my page"> 21 </head> 22 23 <body> 24 <h3>用户登录</h3> 25 <form action="${pageContext.request.contextPath }/login.action" method="post"> 26 用户账号: <input type="text" name="username"/><br> 27 用户密码: <input type="password" name="password"/><br><br> 28 <input type="submit" value="登录"/> 29 </form> 30 </body> 31 </html>
5. 测试
(1) 直接输入http://localhost:8080/ssm_items/items/queryItems.action, 因为没有登录过, 而且session中没有用户身份信息,
所以直接被打回到login.jsp页面进行登录
(2) 输入张三, 123, 登录成功
(3) 重开一个页面, 直接输入http://localhost:8080/ssm_items/items/queryItems.action, 直接来到商品列表页面(因为session有数据)
(4) 退出后, 再次输入第3不的地址, 又被打回到登录页面, 因为退出后session数据被销毁
至此登录认证完成...