攔截一些請(qǐng)求進(jìn)行處理零抬,比如通過(guò)它來(lái)進(jìn)行權(quán)限驗(yàn)證,或者是來(lái)判斷用戶是否登陸宽涌,日志記錄平夜,編碼,或者限制時(shí)間點(diǎn)訪問(wèn)等等卸亮,是非常有必要的忽妒。所以就有了此篇文章啦。
文章結(jié)構(gòu):(1)Servlet過(guò)濾器Filter兼贸;(2)SpringMVC的HandlerInterceptor段直;(3)對(duì)比認(rèn)知。
一溶诞、Servlet過(guò)濾器Filter:
此部分是從趙四大佬那里學(xué)來(lái)的鸯檬,并補(bǔ)充自己的認(rèn)知
(1)概念:
能夠?qū)ervlet容器的請(qǐng)求和響應(yīng)對(duì)象進(jìn)行檢查和修改。
Servlet過(guò)濾器本身并不產(chǎn)生請(qǐng)求和響應(yīng)對(duì)象螺垢,它只能提供過(guò)濾作用喧务。Servlet過(guò)期能夠在Servlet被調(diào)用之前檢查Request對(duì)象赖歌,修改Request Header和Request內(nèi)容;在Servlet被調(diào)用之后檢查Response對(duì)象功茴,修改Response Header和Response內(nèi)容庐冯。
Servlet過(guò)期負(fù)責(zé)過(guò)濾的Web組件可以是Servlet、JSP或者HTML文件痊土。
總之就是:實(shí)現(xiàn)用戶在訪問(wèn)某個(gè)目標(biāo)資源之前肄扎,對(duì)訪問(wèn)的請(qǐng)求和響應(yīng)進(jìn)行攔截。
(2)特點(diǎn):
A.Servlet過(guò)濾器可以檢查和修改ServletRequest和ServletResponse對(duì)象
B.Servlet過(guò)濾器可以被指定和特定的URL關(guān)聯(lián)赁酝,只有當(dāng)客戶請(qǐng)求訪問(wèn)該URL時(shí),才會(huì)觸發(fā)過(guò)濾器
C.Servlet過(guò)濾器可以被串聯(lián)在一起旭等,形成管道效應(yīng)酌呆,協(xié)同修改請(qǐng)求和響應(yīng)對(duì)象
(3)作用:
A.查詢請(qǐng)求并作出相應(yīng)的行動(dòng)。
B.阻塞請(qǐng)求-響應(yīng)對(duì)搔耕,使其不能進(jìn)一步傳遞隙袁。
C.修改請(qǐng)求的頭部和數(shù)據(jù)。用戶可以提供自定義的請(qǐng)求弃榨。
D.修改響應(yīng)的頭部和數(shù)據(jù)菩收。用戶可以通過(guò)提供定制的響應(yīng)版本實(shí)現(xiàn)。
E.與外部資源進(jìn)行交互鲸睛。
(3)適用場(chǎng)合:
A.認(rèn)證過(guò)濾
B.登錄和審核過(guò)濾
C.圖像轉(zhuǎn)換過(guò)濾
D.?dāng)?shù)據(jù)壓縮過(guò)濾
E.加密過(guò)濾
F.令牌過(guò)濾
G.資源訪問(wèn)觸發(fā)事件過(guò)濾
H.XSL/T過(guò)濾
I.Mime-type過(guò)濾
(4)認(rèn)知Filter接口源碼:
public interface Filter {
//Servlet過(guò)濾器的初始化方法娜饵,Servlet容器創(chuàng)建Servlet過(guò)濾器實(shí)例后將調(diào)用這個(gè)方法。在這個(gè)方法中可以讀取web.xml文件中Servlet過(guò)濾器的初始化參數(shù)
public void init(FilterConfig filterConfig) throws ServletException;
//完成實(shí)際的過(guò)濾操作官辈,當(dāng)客戶請(qǐng)求訪問(wèn)于過(guò)濾器關(guān)聯(lián)的URL時(shí)箱舞,Servlet容器將先調(diào)用過(guò)濾器的doFilter方法。FilterChain參數(shù)用于訪問(wèn)后續(xù)過(guò)濾器
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException;
//Servlet容器在銷毀過(guò)濾器實(shí)例前調(diào)用該方法拳亿,這個(gè)方法中可以釋放Servlet過(guò)濾器占用的資源
public void destroy();
}
(5)認(rèn)知Filter為何可以攔截請(qǐng)求:(本博主找不到具體的代碼調(diào)用晴股,只能找到相關(guān)的幾個(gè)接口估測(cè))
FilterChain
public interface FilterChain {
//此方法是由Servlet容器提供給開(kāi)發(fā)者的,用于對(duì)資源請(qǐng)求過(guò)濾鏈的依次調(diào)用肺魁,通過(guò)FilterChain調(diào)用過(guò)濾鏈中的下一個(gè)過(guò)濾 器电湘,如果是最后一個(gè)過(guò)濾器,則下一個(gè)就調(diào)用目標(biāo)資源鹅经。
public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException;
}
FilterConfig寂呛。FilterConfig接口檢索過(guò)濾器名、初始化參數(shù)以及活動(dòng)的Servlet上下文瞬雹。
public interface FilterConfig {
//返回web.xml部署文件中定義的該過(guò)濾器的名稱
String getFilterName();
//返回調(diào)用者所處的servlet上下文
ServletContext getServletContext();
//返回過(guò)濾器初始化參數(shù)值的字符串形式昧谊,當(dāng)參數(shù)不存在時(shí),返回nul1.name是初始化參數(shù)名
String getInitParameter(String var1);
//以Enumeration形式返回過(guò)濾器所有初始化參數(shù)值酗捌,如果沒(méi)有初始化參數(shù)呢诬,返回為空
Enumeration<String> getInitParameterNames();
}
博主猜測(cè)是:web服務(wù)器的底層源碼機(jī)制的模板方法模式涌哲,默認(rèn)調(diào)用doFilter時(shí),先去執(zhí)行filterClain的doFilter尚镰。從而進(jìn)行攔截
(6)Servlet過(guò)濾器對(duì)響應(yīng)的過(guò)濾過(guò)程:
A.過(guò)濾器截獲客戶端的請(qǐng)求
B.重新封裝ServletResponse阀圾,在封裝后的ServletResponse中提供用戶自定義的輸出流
C.將請(qǐng)求向后續(xù)傳遞
D.Web組件產(chǎn)生響應(yīng)
E.從封裝后的ServletResponse中獲取用戶自定義的輸出流
F.將響應(yīng)內(nèi)容通過(guò)用戶自定義的輸出流寫(xiě)入到緩沖流中
G.在緩沖流中修改響應(yīng)的內(nèi)容后清空緩沖流,輸出響應(yīng)內(nèi)容
(7)實(shí)例:(取自上文介紹的趙四大佬那里)
1. 注冊(cè):在web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" id="WebApp_ID" version="3.0">
<display-name>testFilter</display-name>
<!-- 請(qǐng)求url日志記錄過(guò)濾器 -->
<filter>
<filter-name>logfilter</filter-name>
<filter-class>com.fuzhu.LogFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>logfilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- 編碼過(guò)濾器 -->
<filter>
<filter-name>setCharacterEncoding</filter-name>
<filter-class>com.fuzhu.EncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>utf-8</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>setCharacterEncoding</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>
既然注冊(cè)了狗唉,那實(shí)現(xiàn)呢初烘?
package com.fuzhu;
import java.io.IOException;
import java.util.Enumeration;
import java.util.HashMap;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
public class EncodingFilter implements Filter {
private String encoding;
private HashMap<String,String> params = new HashMap<String,String>();
// 項(xiàng)目結(jié)束時(shí)就已經(jīng)進(jìn)行銷毀
public void destroy() {
System.out.println("end do the encoding filter!");
params=null;
encoding=null;
}
public void doFilter(ServletRequest req, ServletResponse resp,FilterChain chain) throws IOException, ServletException {
System.out.println("before encoding " + encoding + " filter!");
req.setCharacterEncoding(encoding);
chain.doFilter(req, resp);
System.out.println("after encoding " + encoding + " filter分俯!");
System.err.println("----------------------------------------");
}
// 項(xiàng)目啟動(dòng)時(shí)就已經(jīng)進(jìn)行讀取
public void init(FilterConfig config) throws ServletException {
System.out.println("begin do the encoding filter!");
encoding = config.getInitParameter("encoding");
for (Enumeration<?> e = config.getInitParameterNames(); e.hasMoreElements();) {
String name = (String) e.nextElement();
String value = config.getInitParameter(name);
params.put(name, value);
}
}
}
package com.fuzhu;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
public class LogFilter implements Filter {
public FilterConfig config;
public void destroy() {
this.config = null;
System.out.println("end do the logging filter!");
}
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
System.out.println("before the log filter!");
// 將請(qǐng)求轉(zhuǎn)換成HttpServletRequest 請(qǐng)求
HttpServletRequest hreq = (HttpServletRequest) req;
// 記錄日志
System.out.println("Log Filter已經(jīng)截獲到用戶的請(qǐng)求的地址:"+hreq.getServletPath() );
try {
// Filter 只是鏈?zhǔn)教幚砩隹穑?qǐng)求依然轉(zhuǎn)發(fā)到目的地址。
chain.doFilter(req, res);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("after the log filter!");
}
public void init(FilterConfig config) throws ServletException {
System.out.println("begin do the log filter!");
this.config = config;
}
}
接下來(lái)就是測(cè)試?yán)玻?/h3>
package com.fuzhu;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebServlet("/FilterServlet")
public class FilterServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setDateHeader("expires", -1);
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
}
}
再著就是發(fā)布啦缸剪。注意下吗铐,我們先注冊(cè)日志過(guò)濾器,然后注冊(cè)編碼器
這里寫(xiě)圖片描述
(8)我們來(lái)看下注意事項(xiàng):
A.由于Filter杏节、FilterConfig唬渗、FilterChain都是位于javax.servlet包下,并非HTTP包所特有的奋渔,所以其中所用到的請(qǐng)求镊逝、響應(yīng)對(duì)象ServletRequest、ServletResponse在使用前都必須先轉(zhuǎn)換成HttpServletRequest嫉鲸、HttpServletResponse再進(jìn)行下一步操作撑蒜。
B.在web.xml中配置Servlet和Servlet過(guò)濾器,應(yīng)該先聲明過(guò)濾器元素充坑,再聲明Servlet元素
C.如果要在Servlet中觀察過(guò)濾器生成的日志减江,應(yīng)該確保在server.xml的localhost對(duì)應(yīng)的<host>元素中配置如下<logger>元素:
<Logger className = “org.apache.catalina.logger.FileLogger”
directory = “l(fā)ogs”prefix = “l(fā)ocalhost_log.”suffix=”.txt”
timestamp = “true”/>
二、SpringMVC的HandlerInterceptor(給出一個(gè)登錄功能的攔截Demo)
(1)springMVC攔截器的實(shí)現(xiàn)一般有兩種方式:
第一種方式是要定義的Interceptor類要實(shí)現(xiàn)了Spring的HandlerInterceptor 接口
第二種方式是繼承實(shí)現(xiàn)了HandlerInterceptor接口的類捻爷,比如Spring已經(jīng)提供的實(shí)現(xiàn)了HandlerInterceptor接口的抽象類HandlerInterceptorAdapter
(2)概念及作用:
概念:Spring MVC允許你通過(guò)處理攔截?cái)r截web請(qǐng)求辈灼,進(jìn)行前置處理和后置處理。處理攔截是在Spring的web應(yīng)用程序上下文中配置的也榄,因此它們可以利用各種容器特性巡莹,并引用容器中聲明的任何Bean。處理攔截是針對(duì)特殊的處理程序映射進(jìn)行注冊(cè)的甜紫,因此它只攔截通過(guò)這些處理程序映射的請(qǐng)求降宅。
主要作用是攔截用戶的請(qǐng)求并進(jìn)行相應(yīng)的處理,其他的作用比如通過(guò)它來(lái)進(jìn)行權(quán)限驗(yàn)證囚霸,或者是來(lái)判斷用戶是否登陸腰根,日志記錄,或者限制時(shí)間點(diǎn)訪問(wèn)拓型。
(3)認(rèn)識(shí)HandlerInterceptor接口:
public interface HandlerInterceptor {
//該方法將在請(qǐng)求處理之前進(jìn)行調(diào)用额嘿。該方法將在請(qǐng)求處理之前進(jìn)行調(diào)用瘸恼,只有該方法返回true,才會(huì)繼續(xù)執(zhí)行后續(xù)的Interceptor和Controller
boolean preHandle(HttpServletRequest var1, HttpServletResponse var2, Object var3) throws Exception;
//在當(dāng)前請(qǐng)求進(jìn)行處理之后册养,也就是Controller 方法調(diào)用之后執(zhí)行
void postHandle(HttpServletRequest var1, HttpServletResponse var2, Object var3, ModelAndView var4) throws Exception;
//該方法將在整個(gè)請(qǐng)求結(jié)束之后东帅,也就是在DispatcherServlet 渲染了對(duì)應(yīng)的視圖之后執(zhí)行。
void afterCompletion(HttpServletRequest var1, HttpServletResponse var2, Object var3, Exception var4) throws Exception;
}
preHandle:
SpringMVC 中的Interceptor 是鏈?zhǔn)降恼{(diào)用的球拦,在一個(gè)應(yīng)用中或者說(shuō)是在一個(gè)請(qǐng)求中可以同時(shí)存在多個(gè)Interceptor 靠闭。每個(gè)Interceptor 的調(diào)用會(huì)依據(jù)它的聲明順序依次執(zhí)行,而且最先執(zhí)行的都是Interceptor 中的preHandle 方法坎炼,所以可以在這個(gè)方法中進(jìn)行一些前置初始化操作或者是對(duì)當(dāng)前請(qǐng)求的一個(gè)預(yù)處理愧膀,也可以在這個(gè)方法中進(jìn)行一些判斷來(lái)決定請(qǐng)求是否要繼續(xù)進(jìn)行下去。該方法的返回值是布爾值Boolean 類型的点弯,當(dāng)它返回為false 時(shí)扇调,表示請(qǐng)求結(jié)束,后續(xù)的Interceptor 和Controller 都不會(huì)再執(zhí)行抢肛;當(dāng)返回值為true 時(shí)就會(huì)繼續(xù)調(diào)用下一個(gè)Interceptor 的preHandle 方法,如果已經(jīng)是最后一個(gè)Interceptor 的時(shí)候就會(huì)是調(diào)用當(dāng)前請(qǐng)求的Controller 方法碳柱。
postHandle:
只能是在當(dāng)前所屬的Interceptor 的preHandle 方法的返回值為true 時(shí)才能被調(diào)用捡絮。Controller 方法調(diào)用之后執(zhí)行,但是它會(huì)在DispatcherServlet 進(jìn)行視圖返回渲染之前被調(diào)用莲镣,所以我們可以在這個(gè)方法中對(duì)Controller 處理之后的ModelAndView 對(duì)象進(jìn)行操作福稳。postHandle 方法被調(diào)用的方向跟preHandle 是相反的,也就是說(shuō)先聲明的Interceptor 的postHandle 方法反而會(huì)后執(zhí)行瑞侮。
afterCompletion:
該方法也是需要當(dāng)前對(duì)應(yīng)的Interceptor 的preHandle 方法的返回值為true 時(shí)才會(huì)執(zhí)行的圆。顧名思義,該方法將在整個(gè)請(qǐng)求結(jié)束之后半火,也就是在DispatcherServlet 渲染了對(duì)應(yīng)的視圖之后執(zhí)行越妈。這個(gè)方法的主要作用是用于進(jìn)行資源清理工作的。 我們的系統(tǒng)日志的攔截在這個(gè)方法中钮糖,可以記錄日志的相關(guān)的參數(shù)梅掠,檢測(cè)方法的執(zhí)行。
第一個(gè)和第二個(gè)方法分別是在處理程序處理請(qǐng)求之前和之后被調(diào)用的店归。第二個(gè)方法還允許訪問(wèn)返回的ModelAndView對(duì)象阎抒,因此可以在它里面操作模型屬性。最后一個(gè)方法是在所有請(qǐng)求處理完成之后被調(diào)用的(如視圖呈現(xiàn)之后).
(4)先來(lái)個(gè)Helloworld:
在springmvc配置文件那里配置攔截器消痛,攔截的請(qǐng)求且叁。
可以看到我一會(huì)實(shí)現(xiàn)的一個(gè)功能啦,登錄的認(rèn)證秩伞。
<!-- 配置攔截器 -->
<mvc:interceptors>
<!-- 配置登陸攔截器 -->
<mvc:interceptor>
<!--攔截后臺(tái)頁(yè)面的請(qǐng)求-->
<!--<mvc:mapping path="/backend/**"/>-->
<mvc:mapping path="/test/testMethod"/>
<!--不攔截登錄頁(yè)和登錄的請(qǐng)求-->
<!--<mvc:exclude-mapping path="/backend/loginPage"/>-->
<!--<mvc:exclude-mapping path="/backend/login"/>-->
<bean class="com.fuzhu.Interceptor.Myinterceptor"></bean>
</mvc:interceptor>
</mvc:interceptors>
請(qǐng)求的實(shí)現(xiàn):
@RestController
@RequestMapping("/test")
public class TestController {
@RequestMapping(value = "/testMethod",produces="text/html;charset=UTF-8", method = {RequestMethod.GET,RequestMethod.GET})
public String test() {
Score score = new Score();
// score.setChangeType("玩游戲");
// score.setScore(10);
// scoreService.insertScore(score);
return JSON.toJSONString(score);
}
}
這里寫(xiě)圖片描述
這里寫(xiě)圖片描述
(5)實(shí)現(xiàn)后臺(tái)管理頁(yè)面的登錄攔截(其實(shí)就是面向前端的攔截)
作用是:就是在后臺(tái)管理者登錄之前逞带,都必須經(jīng)過(guò)登錄才可進(jìn)入管理界面欺矫,其他侵入性的接口也不可訪問(wèn)。
(一)注冊(cè)我們的攔截器先:
<!-- 配置攔截器 -->
<mvc:interceptors>
<!-- 配置登陸攔截器 -->
<mvc:interceptor>
<!--攔截后臺(tái)頁(yè)面的請(qǐng)求-->
<mvc:mapping path="/backend/**"/>
<!--<mvc:mapping path="/test/testMethod"/>-->
<!--不攔截登錄頁(yè)和登錄的請(qǐng)求-->
<mvc:exclude-mapping path="/backend/loginPage"/>
<mvc:exclude-mapping path="/backend/login"/>
<!--<bean class="com.fuzhu.Interceptor.Myinterceptor"></bean>-->
<bean class="com.fuzhu.Interceptor.LoginInterceptor"></bean>
</mvc:interceptor>
</mvc:interceptors>
(二)實(shí)現(xiàn)攔截器:針對(duì)后臺(tái)管理系統(tǒng)的需要掰担,不能讓別人隨便用url直接訪問(wèn)到別的服務(wù)器應(yīng)用請(qǐng)求汇陆,必須先登錄。所以我們做一個(gè)登錄認(rèn)證的攔截器带饱。而且需要認(rèn)證一系列的cookie毡代,session
public class LoginInterceptor implements HandlerInterceptor {
@Override
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response, Object obj, Exception err)
throws Exception {
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response,
Object obj, ModelAndView mav) throws Exception {
}
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
Object obj) throws Exception {
//拿到cookie
//也就是獲取session里的登錄狀態(tài)值
String cookie= CookieUtil.getByName(request,"isLogin");
if (cookie!=null){
//session解密
Map<String,Object> map= AuthUtil.decodeSession(cookie);
String loginStatus= (String) map.get("isLogin");
Long timestamp= (Long) map.get("timestamp");
if (loginStatus!=null&×tamp!=null&&new Date().getTime()-timestamp<1000*60*60*24*10){
return true;
}
}
//沒(méi)有找到登錄狀態(tài)則重定向到登錄頁(yè),返回false勺疼,不執(zhí)行原來(lái)controller的方法
response.sendRedirect("/backend/loginPage");
return false;
}
}
(三)為了充分去了解Session和cookie機(jī)制教寂,本博主再做了兩個(gè)工具類,一個(gè)是session的工具類执庐,一個(gè)是cookie的工具類酪耕,還有另外一篇解析博客(附帶面向客戶端的Demo)--也是一個(gè)目前最流行的認(rèn)證方式--Token認(rèn)證。
WEB后臺(tái)--基于Token的WEB后臺(tái)登錄認(rèn)證機(jī)制(并講解其他認(rèn)證機(jī)制以及cookie和session機(jī)制)
//session工具類
public class AuthUtil {
//這個(gè)類方法是面向手機(jī)客戶端的轨淌,從而實(shí)現(xiàn)的Token機(jī)制迂烁。實(shí)現(xiàn)請(qǐng)見(jiàn)上述文章:
private static Map<String, Object> getClientLoginInfo(HttpServletRequest request) throws Exception {
Map<String, Object> r = new HashMap<>();
String sessionId = request.getHeader("sessionId");
if (sessionId != null) {
r = decodeSession(sessionId);
return r;
}
throw new Exception("session解析錯(cuò)誤");
}
//根據(jù)token拿去用戶id
public static Long getUserId(HttpServletRequest request) throws Exception {
return Long.valueOf((Integer)getClientLoginInfo(request).get("userId"));
}
/**
* session解密
*/
public static Map<String, Object> decodeSession(String sessionId) {
try {
return verifyJavaWebToken(sessionId);
} catch (Exception e) {
System.err.println("");
return null;
}
}
}
cookie工具類:
public class CookieUtil {
public static final int TIME = 60 * 60 * 24 * 10; //10天存活時(shí)間
//添加cookie
public static void addCookie(HttpServletResponse response,
String cookieName, String value) {
Cookie cookie = new Cookie(cookieName, value);
cookie.setPath("/");
cookie.setMaxAge(TIME);
response.addCookie(cookie);
}
//刪除cookie
public static void deleteCookie(HttpServletResponse response,
String cookieName) {
Cookie cookie = new Cookie(cookieName, null);
cookie.setPath("/");
cookie.setMaxAge(0);
response.addCookie(cookie);
}
//獲取用戶的cookie名字
public static String getByName(HttpServletRequest request, String cookieName) {
String value = null;
Cookie[] cookies = request.getCookies();
if (cookies != null) {
for (Cookie cookie : cookies) {
if (cookieName.equals(cookie.getName())) {
value = cookie.getValue();
}
}
}
return value;
}
}
(四)怎么去使用這個(gè)機(jī)制以及攔截器呢?递鹉?
@Controller
@RequestMapping("/backend")
public class BackstageController {
//首先是登錄頁(yè)面
@RequestMapping(value = "/loginPage", method = {RequestMethod.GET})
public String loginPage(HttpServletRequest request, String account, String password) {
return "login";
}
//登錄的接口邏輯
@RequestMapping(value = "/login", method = {RequestMethod.POST})
public String login(HttpServletRequest request, HttpServletResponse response, RedirectAttributes model, String account, String password) {
//后臺(tái)管理者的賬號(hào)密碼
if ("fuzhu".equals(account) && "fuzhucheng".equals(password)) {
Map<String, Object> loginInfo = new HashMap<>();
loginInfo.put("isLogin", "yes!");
loginInfo.put("timestamp", new Date());
String sessionId = JavaWebToken.createJavaWebToken(loginInfo);//token機(jī)制盟步,詳情請(qǐng)看上文所說(shuō)的文章
CookieUtil.addCookie(response,"isLogin",sessionId);//加cookie
return "redirect:loginSuccess";//重定向
} else {
model.addFlashAttribute("error", "密碼錯(cuò)誤");
return "redirect:loginPage";
}
}
@RequestMapping(value = "/loginSuccess", method = {RequestMethod.GET})
public String accusationPage(HttpServletRequest request) {
return "success";
}
//主動(dòng)登出的時(shí)候使用
@RequestMapping(value = "/logOut", method = {RequestMethod.GET})
public String loginOut(HttpServletRequest request, HttpServletResponse response) {
CookieUtil.deleteCookie(response,"isLogin");
return "redirect:loginPage";
}
}
(五)演示:
這里寫(xiě)圖片描述
登錄成功
這里寫(xiě)圖片描述
如果我們不登錄,直接訪問(wèn)登錄成功的url躏结。結(jié)果如下:默認(rèn)返回登錄頁(yè)面(因?yàn)槲覀償r截重定向了)
這里寫(xiě)圖片描述
三却盘、對(duì)比認(rèn)知:閱讀上文所述的接口并參考此文
(1)filter基于filter接口中的doFilter回調(diào)函數(shù),interceptor則基于Java本身的反射機(jī)制媳拴;
比如HandlerInterceptor是交給spring bean工廠去反射生成的黄橘。
(2)filter是依賴于servlet容器的,沒(méi)有servlet容器就無(wú)法回調(diào)doFilter方法屈溉,而interceptor與servlet無(wú)關(guān)塞关;
(3)filter的過(guò)濾范圍比interceptor大,filter除了過(guò)濾請(qǐng)求外通過(guò)通配符可以保護(hù)頁(yè)面语婴、圖片描孟、文件等,而interceptor只能過(guò)濾請(qǐng)求砰左,只對(duì)controller起作用匿醒,在controller之前開(kāi)始,在controller完成后結(jié)束(如被攔截缠导,不執(zhí)行action)廉羔;
(4)filter的過(guò)濾一般在加載的時(shí)候在init方法聲明,而interceptor可以通過(guò)在xml聲明是guest請(qǐng)求還是user請(qǐng)求來(lái)辨別是否過(guò)濾僻造;
(5)interceptor可以訪問(wèn)controller上下文憋他、值棧里的對(duì)象孩饼,而filter不能;
(6)在controller的生命周期中竹挡,攔截器可以被多次調(diào)用镀娶,而過(guò)濾器只能在容器初始化時(shí)被調(diào)用一次。
源碼下載:SpringMVC的HandlerInterceptor(Session和Cookie登錄認(rèn)證)
好了揪罕,JavaWeb--Servlet過(guò)濾器Filter和SpringMVC的HandlerInterceptor(Session和Cookie登錄認(rèn)證)講完了梯码,這是項(xiàng)目過(guò)程中一個(gè)必須登錄認(rèn)證機(jī)制,現(xiàn)在羅列給大家好啰,這是積累的必經(jīng)一步轩娶,我會(huì)繼續(xù)出這個(gè)系列文章,分享經(jīng)驗(yàn)給大家框往。歡迎在下面指出錯(cuò)誤鳄抒,共同學(xué)習(xí)!椰弊!你的點(diǎn)贊是對(duì)我最好的支持P斫Α!
更多內(nèi)容秉版,可以訪問(wèn)JackFrost的博客
package com.fuzhu;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebServlet("/FilterServlet")
public class FilterServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setDateHeader("expires", -1);
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
}
}
這里寫(xiě)圖片描述
public interface HandlerInterceptor {
//該方法將在請(qǐng)求處理之前進(jìn)行調(diào)用额嘿。該方法將在請(qǐng)求處理之前進(jìn)行調(diào)用瘸恼,只有該方法返回true,才會(huì)繼續(xù)執(zhí)行后續(xù)的Interceptor和Controller
boolean preHandle(HttpServletRequest var1, HttpServletResponse var2, Object var3) throws Exception;
//在當(dāng)前請(qǐng)求進(jìn)行處理之后册养,也就是Controller 方法調(diào)用之后執(zhí)行
void postHandle(HttpServletRequest var1, HttpServletResponse var2, Object var3, ModelAndView var4) throws Exception;
//該方法將在整個(gè)請(qǐng)求結(jié)束之后东帅,也就是在DispatcherServlet 渲染了對(duì)應(yīng)的視圖之后執(zhí)行。
void afterCompletion(HttpServletRequest var1, HttpServletResponse var2, Object var3, Exception var4) throws Exception;
}
<!-- 配置攔截器 -->
<mvc:interceptors>
<!-- 配置登陸攔截器 -->
<mvc:interceptor>
<!--攔截后臺(tái)頁(yè)面的請(qǐng)求-->
<!--<mvc:mapping path="/backend/**"/>-->
<mvc:mapping path="/test/testMethod"/>
<!--不攔截登錄頁(yè)和登錄的請(qǐng)求-->
<!--<mvc:exclude-mapping path="/backend/loginPage"/>-->
<!--<mvc:exclude-mapping path="/backend/login"/>-->
<bean class="com.fuzhu.Interceptor.Myinterceptor"></bean>
</mvc:interceptor>
</mvc:interceptors>
@RestController
@RequestMapping("/test")
public class TestController {
@RequestMapping(value = "/testMethod",produces="text/html;charset=UTF-8", method = {RequestMethod.GET,RequestMethod.GET})
public String test() {
Score score = new Score();
// score.setChangeType("玩游戲");
// score.setScore(10);
// scoreService.insertScore(score);
return JSON.toJSONString(score);
}
}
這里寫(xiě)圖片描述
這里寫(xiě)圖片描述
<!-- 配置攔截器 -->
<mvc:interceptors>
<!-- 配置登陸攔截器 -->
<mvc:interceptor>
<!--攔截后臺(tái)頁(yè)面的請(qǐng)求-->
<mvc:mapping path="/backend/**"/>
<!--<mvc:mapping path="/test/testMethod"/>-->
<!--不攔截登錄頁(yè)和登錄的請(qǐng)求-->
<mvc:exclude-mapping path="/backend/loginPage"/>
<mvc:exclude-mapping path="/backend/login"/>
<!--<bean class="com.fuzhu.Interceptor.Myinterceptor"></bean>-->
<bean class="com.fuzhu.Interceptor.LoginInterceptor"></bean>
</mvc:interceptor>
</mvc:interceptors>
public class LoginInterceptor implements HandlerInterceptor {
@Override
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response, Object obj, Exception err)
throws Exception {
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response,
Object obj, ModelAndView mav) throws Exception {
}
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
Object obj) throws Exception {
//拿到cookie
//也就是獲取session里的登錄狀態(tài)值
String cookie= CookieUtil.getByName(request,"isLogin");
if (cookie!=null){
//session解密
Map<String,Object> map= AuthUtil.decodeSession(cookie);
String loginStatus= (String) map.get("isLogin");
Long timestamp= (Long) map.get("timestamp");
if (loginStatus!=null&×tamp!=null&&new Date().getTime()-timestamp<1000*60*60*24*10){
return true;
}
}
//沒(méi)有找到登錄狀態(tài)則重定向到登錄頁(yè),返回false勺疼,不執(zhí)行原來(lái)controller的方法
response.sendRedirect("/backend/loginPage");
return false;
}
}
//session工具類
public class AuthUtil {
//這個(gè)類方法是面向手機(jī)客戶端的轨淌,從而實(shí)現(xiàn)的Token機(jī)制迂烁。實(shí)現(xiàn)請(qǐng)見(jiàn)上述文章:
private static Map<String, Object> getClientLoginInfo(HttpServletRequest request) throws Exception {
Map<String, Object> r = new HashMap<>();
String sessionId = request.getHeader("sessionId");
if (sessionId != null) {
r = decodeSession(sessionId);
return r;
}
throw new Exception("session解析錯(cuò)誤");
}
//根據(jù)token拿去用戶id
public static Long getUserId(HttpServletRequest request) throws Exception {
return Long.valueOf((Integer)getClientLoginInfo(request).get("userId"));
}
/**
* session解密
*/
public static Map<String, Object> decodeSession(String sessionId) {
try {
return verifyJavaWebToken(sessionId);
} catch (Exception e) {
System.err.println("");
return null;
}
}
}
public class CookieUtil {
public static final int TIME = 60 * 60 * 24 * 10; //10天存活時(shí)間
//添加cookie
public static void addCookie(HttpServletResponse response,
String cookieName, String value) {
Cookie cookie = new Cookie(cookieName, value);
cookie.setPath("/");
cookie.setMaxAge(TIME);
response.addCookie(cookie);
}
//刪除cookie
public static void deleteCookie(HttpServletResponse response,
String cookieName) {
Cookie cookie = new Cookie(cookieName, null);
cookie.setPath("/");
cookie.setMaxAge(0);
response.addCookie(cookie);
}
//獲取用戶的cookie名字
public static String getByName(HttpServletRequest request, String cookieName) {
String value = null;
Cookie[] cookies = request.getCookies();
if (cookies != null) {
for (Cookie cookie : cookies) {
if (cookieName.equals(cookie.getName())) {
value = cookie.getValue();
}
}
}
return value;
}
}
@Controller
@RequestMapping("/backend")
public class BackstageController {
//首先是登錄頁(yè)面
@RequestMapping(value = "/loginPage", method = {RequestMethod.GET})
public String loginPage(HttpServletRequest request, String account, String password) {
return "login";
}
//登錄的接口邏輯
@RequestMapping(value = "/login", method = {RequestMethod.POST})
public String login(HttpServletRequest request, HttpServletResponse response, RedirectAttributes model, String account, String password) {
//后臺(tái)管理者的賬號(hào)密碼
if ("fuzhu".equals(account) && "fuzhucheng".equals(password)) {
Map<String, Object> loginInfo = new HashMap<>();
loginInfo.put("isLogin", "yes!");
loginInfo.put("timestamp", new Date());
String sessionId = JavaWebToken.createJavaWebToken(loginInfo);//token機(jī)制盟步,詳情請(qǐng)看上文所說(shuō)的文章
CookieUtil.addCookie(response,"isLogin",sessionId);//加cookie
return "redirect:loginSuccess";//重定向
} else {
model.addFlashAttribute("error", "密碼錯(cuò)誤");
return "redirect:loginPage";
}
}
@RequestMapping(value = "/loginSuccess", method = {RequestMethod.GET})
public String accusationPage(HttpServletRequest request) {
return "success";
}
//主動(dòng)登出的時(shí)候使用
@RequestMapping(value = "/logOut", method = {RequestMethod.GET})
public String loginOut(HttpServletRequest request, HttpServletResponse response) {
CookieUtil.deleteCookie(response,"isLogin");
return "redirect:loginPage";
}
}
這里寫(xiě)圖片描述
這里寫(xiě)圖片描述
這里寫(xiě)圖片描述