點(diǎn)擊上方藍(lán)色字體,選擇“標(biāo)星公眾號(hào)”
優(yōu)質(zhì)文章,第一時(shí)間送達(dá)
作者 | 一頭磕在鍵盤(pán)上
來(lái)源 | urlify.cn/UV7Vvu
這里說(shuō)的前后端分離,是指部署也分離的項(xiàng)目.
關(guān)于前后端分離的項(xiàng)目如何集成CAS,很少有資料博客來(lái)說(shuō)如何做.
本文先給出一個(gè)解決流程思路,再?gòu)脑韺用鎭?lái)進(jìn)行一定的剖析.
采用環(huán)境:JDK1.8,Tomcat 8.5,cas 3.4.1.
實(shí)際本文內(nèi)容和JDK、Tomcat版本無(wú)關(guān),理論上和cas版本也無(wú)關(guān).
主要涉及以下知識(shí):
cas認(rèn)證原理,及客戶(hù)端的認(rèn)證源碼分析.
session、cookie相關(guān)知識(shí)
主要解決如下兩個(gè)問(wèn)題:
前后端分離項(xiàng)目如何接入cas.
如何和項(xiàng)目原有的登錄做集成.
問(wèn)題一、前后端分離項(xiàng)目如何集成CAS
先說(shuō)解決方案.
關(guān)鍵就是這兩點(diǎn):
登錄成功后跳轉(zhuǎn)的第一個(gè)地址必須是被后端CAS Filter攔截的地址(主要用來(lái)重定向到前端,可以是一個(gè)接口,也可以是一個(gè).jsp界面),保證cas可以把認(rèn)證信息寫(xiě)入session.
前端發(fā)起的后續(xù)請(qǐng)求必須和第一個(gè)請(qǐng)求(即前面的那個(gè)跳轉(zhuǎn)后端的那個(gè)請(qǐng)求)是屬于同一個(gè)session.
只要能保證這兩點(diǎn),那就絕對(duì)沒(méi)問(wèn)題.下面這是我的解決思路:
假設(shè):
cas服務(wù)端地址為192.168.0.90:8080/cas
后端服務(wù)地址為192.168.0.100:8080/api-server
前端界面地址為192.168.0.120
后端webapp根目錄下增加一個(gè)cas.jsp來(lái)做重定向,這個(gè)jsp的訪問(wèn)地址為192.168.0.100:8080/api-server/cas.jsp
具體步驟:
1. 修改web.xml增加對(duì)cas.jsp的攔截
隨手建一個(gè)cas.jsp,放到后端webapp的根目錄下,里面暫時(shí)不用寫(xiě)什么代碼,后面再加.
修改web.xml中的CAS Filter的配置,額外增加對(duì)cas.jsp的攔截.
<filter-mapping>
<filter-name>CAS Filter</filter-name>
<url-pattern>/cas.jsp</url-pattern>
</filter-mapping>
2.未登錄時(shí),前端重定向后端cas.jsp
調(diào)整下前端的js邏輯,在發(fā)現(xiàn)未登錄時(shí)跳轉(zhuǎn)cas.jsp(準(zhǔn)確的說(shuō),最終目的是保證登錄成功后第一個(gè)跳轉(zhuǎn)的界面是cas.jsp).
// 沒(méi)有登錄,跳轉(zhuǎn)后端cas.jsp
if(notLogin){
window.location.href='192.168.0.100:8080/api-server/cas.jsp';
}
cas.jsp被CAS Filter攔截,那么會(huì)再自動(dòng)重定向到cas登錄界面:192.168.0.90:8080/cas/login?service=192.168.0.100:8080/api-server/cas.jsp.
如果想節(jié)省一次重定向的開(kāi)銷(xiāo),前端也可以直接重定向到登錄界面,注意加上service參數(shù)即可
//和上面代碼結(jié)果一樣,但是節(jié)省一次重定向開(kāi)銷(xiāo).參考代碼如下
if(notLogin){
window.location.href=`192.168.0.90:8080/cas/login?service=192.168.0.100:8080/api-server/cas.jsp`;
}
如果一切順利的話(huà),這個(gè)時(shí)候登錄成功,那么就會(huì)進(jìn)入cas.jsp界面.這個(gè)進(jìn)入cas.jsp的請(qǐng)求的session,是通過(guò)了cas認(rèn)證的.
3. cas.jsp重定向到前端
修改cas.jsp,加入如下代碼:
// 重定向到前端地址.
String url = "192.168.0.120";
response.sendRedirect(url);
那么此時(shí),登錄成功后最終會(huì)進(jìn)入前端界面.下面我們只需要保證,后續(xù)前端發(fā)起的請(qǐng)求,和剛剛這個(gè)經(jīng)過(guò)了cas.jsp的請(qǐng)求,是屬于同一個(gè)session就行了,或者說(shuō),JSEESIONID這個(gè)cookie能正常寫(xiě)入前端的域下.
4. 保證將JSESSIONID寫(xiě)入前端cookie中
在創(chuàng)建一個(gè)新的session后,Tomcat的Session Manager在response中會(huì)加上Set-Cookie:JSESSIONID=123123這個(gè)頭,來(lái)讓瀏覽器寫(xiě)入名為JSESSIONID的cookie.但是這個(gè)是寫(xiě)在后端的域下面而不是前端的.而一般情況下,cookie是沒(méi)法跨域讀寫(xiě)的.
后果就是,登錄成功了也跳轉(zhuǎn)回了前端界面,但是后續(xù)的請(qǐng)求因?yàn)檎?qǐng)求中沒(méi)有JSESSIONID這個(gè)cookie(實(shí)際上也不是必須放在cookie里),導(dǎo)致后續(xù)的每個(gè)請(qǐng)求,對(duì)于Tomcat來(lái)說(shuō),都是一個(gè)新的請(qǐng)求,進(jìn)而創(chuàng)建新的session–和第一個(gè)通過(guò)了認(rèn)證的請(qǐng)求半毛錢(qián)關(guān)系都沒(méi),那CAS Filter自然也就不會(huì)放行.所以后續(xù)的請(qǐng)求依舊是調(diào)用不成功.
PS:如果這部分沒(méi)看懂,那么建議還是看看后文中的CAS認(rèn)證原理的章節(jié).
那么這一步要解決的問(wèn)題就是如何將JSESSIONID寫(xiě)入前端的域下的問(wèn)題.
那大體上的方案,也就如下三種:
允許跨域?qū)?/span>cookie.大體上就是ajax請(qǐng)求加上{crossDomain: true, xhrFields: {withCredentials: true}},后端響應(yīng)頭加上response.addHeader("Access-Control-Allow-Credentials", "true").
讓前后端"不跨域".具體方法就是,用nginx將前后端反向代理到同一個(gè)域下,無(wú)論是訪問(wèn)前端界面還是調(diào)用后端接口亦或是后端CAS Filter中的配置都是用這個(gè)代理后的地址.
前端手動(dòng)寫(xiě)入JSESSIONID.比如,cas.jsp在重定向回前端時(shí),在url后面附帶上JSESSIONID的值,前端js獲取到地址欄里的值,再手動(dòng)寫(xiě)入cookie中.
就以第三種–讓前端手動(dòng)寫(xiě)入JSESSIONID為例:
修改cas.jsp.
//獲取sessionid
String jsessionid = session.getId();
// 前端地址.
String url = "192.168.0.120";
response.sendRedirect(url + "?jsessionid=" + jsessionid);
前端js:
// getJsessionIdFromUrl() 從地址欄里獲取jseesionid參數(shù)的值,具體邏輯自行實(shí)現(xiàn)
var jsessionid = getJsessionIdFromUrl();
// setCookie() 寫(xiě)入cookie,具體邏輯自行實(shí)現(xiàn)
setCookie('jsessionid',jsessionid);
前端后續(xù)再發(fā)送的請(qǐng)求,帶上JSESSIONID這個(gè)cookie,就沒(méi)事了.
當(dāng)然細(xì)節(jié)上還有很多可優(yōu)化可完善的地方,但大致上就是這個(gè)流程思路.
方案流程
上述方案的總體的流程就是:
訪問(wèn)前端192.168.0.120
前端判斷沒(méi)登錄,跳轉(zhuǎn)192.168.0.100:8080/api-server/cas.jsp
訪問(wèn)192.168.0.100:8080/api-server/cas.jsp的請(qǐng)求到達(dá)Tomcat的Session Manager,發(fā)現(xiàn)沒(méi)有session,創(chuàng)建新的session
請(qǐng)求到達(dá)后端的CAS Filter,CAS Filter發(fā)現(xiàn)session未認(rèn)證且沒(méi)有ST參數(shù),302讓瀏覽器重定向到cas服務(wù)端登錄界面192.168.0.90:8080/cas/login?service=192.168.0.100:8080/api-server/cas.jsp
請(qǐng)求到到達(dá)cas服務(wù)端,服務(wù)端沒(méi)有在自己的域(即192.168.0.90:8080)下找到名為TGC的cookie,那么認(rèn)定是用戶(hù)之前沒(méi)有登錄過(guò),展示登錄界面.
在cas服務(wù)端登錄界面輸入用戶(hù)名密碼,進(jìn)行登錄.
登錄成功,cas服務(wù)端根據(jù)service參數(shù),302讓瀏覽器重定向到192.168.0.100:8080/api-server/cas.jsp?ticket=ST-ABC1234567,注意此時(shí)是附帶上了ST參數(shù),并且在服務(wù)端的域(192.168.0.90:8080)下寫(xiě)入名為TGC的cookie.
訪問(wèn)192.168.0.100:8080/api-server/cas.jsp?ticket=ST-ABC1234567到達(dá)Tomcat的Session Manager,發(fā)現(xiàn)沒(méi)有session,創(chuàng)建新的session.
請(qǐng)求進(jìn)入后端CAS Filter,發(fā)現(xiàn)session未認(rèn)證但是有ST參數(shù),予以放行
請(qǐng)求再進(jìn)入后端的CAS Validation Filter,向cas服務(wù)端校驗(yàn)ST,校驗(yàn)通過(guò),將校驗(yàn)結(jié)果聲明(內(nèi)含用戶(hù)信息)寫(xiě)入此session.
請(qǐng)求到達(dá)192.168.0.100:8080/api-server/cas.jsp界面
cas.jsp做一系列處理(比如二次登錄)后,將JSESSIONID附帶在url參數(shù)中,再重定向回前端
前端從地址欄url中獲取到JSESSIONID參數(shù),寫(xiě)入cookie中.
后續(xù)請(qǐng)求,帶上JSESSIONID這個(gè)cookie.比如再去請(qǐng)求192.168.0.100:8080/api-server/a/b/c-api.
請(qǐng)求到達(dá)Tomcat的Session Manager,發(fā)現(xiàn)有JSESSIONID,并且存在,不再創(chuàng)建新的session.
請(qǐng)求到達(dá)后端CAS Filter,CAS Filter發(fā)現(xiàn)session中有認(rèn)證信息.予以放行.
請(qǐng)求到達(dá)192.168.0.100:8080/api-server/a/b/c-api
問(wèn)題二、CAS如何和原有的登錄認(rèn)證做對(duì)接
常見(jiàn)的一個(gè)問(wèn)題就是,我這個(gè)項(xiàng)目本身已經(jīng)有了一套登錄體系,現(xiàn)在要集成cas,應(yīng)當(dāng)如何去做.
尤其是對(duì)接第三方的cas,cas服務(wù)端采用的用戶(hù)庫(kù)都和我們自己的不一樣,這個(gè)該怎么去做.
大體思路依舊是通過(guò)前面我們?cè)黾拥倪@個(gè)cas.jsp.在cas.jsp中根據(jù)cas返回的用戶(hù)信息(一般都有用戶(hù)名,但是不會(huì)有也不應(yīng)當(dāng)有密碼),做二次登錄,二次登錄成功后再跳轉(zhuǎn)前端.
參考代碼如下:
// cas.jsp 內(nèi)容
// cas會(huì)將用戶(hù)信息寫(xiě)入session中. request.getAttribute("_const_cas_assertion_") 也可以拿到.
Object object = request.getSession().getAttribute("_const_cas_assertion_");
// org.jasig.cas.client.validation.Assertion
Assertion assertion = (Assertion) object;
// 獲取到用戶(hù)名
String userName = assertion.getPrincipal().getName()
/*
假設(shè)原有登錄是調(diào)用的 loginService.login(userName,password);方法
那我們?cè)黾右粋€(gè)免密登錄方法 loginService.loginWithOutPWD(userName),
里面的處理邏輯和返回值 同loginService.login(userName,password)基本一致,唯獨(dú)不再需要密碼.
*/
Object obj = loginService.loginWithOutPWD(userName);
// isLogin判斷是否二次登錄成功
if(isLogin(obj)){
// 二次登錄成功,調(diào)整前端. 如果原有登錄有其它參數(shù)需要給前端,也可以附帶在url后面.
//獲取sessionid
String jsessionid = session.getId();
// 前端地址.
String url = "192.168.0.120";
response.sendRedirect(url + "?jsessionid=" + jsessionid);
}else{
// 二次登錄失敗
//...
}
CAS認(rèn)證原理
CAS基本概念
1.體系結(jié)構(gòu)
從總體上看,CAS由兩大部分組成:一個(gè)CAS Server 和多個(gè)CAS Client.
CAS Server(服務(wù)端)負(fù)責(zé)提供登錄認(rèn)證服務(wù),單獨(dú)部署,會(huì)給用戶(hù)頒發(fā)兩個(gè)核心票據(jù):TGT(登錄票據(jù),服務(wù)端使用)和ST(服務(wù)票據(jù),客戶(hù)端使用).
CAS Client(客戶(hù)端)負(fù)責(zé)處理對(duì)客戶(hù)端受保護(hù)資源的訪問(wèn)請(qǐng)求.一般通過(guò)是在web.xml中配置了CAS過(guò)濾器,和應(yīng)用系統(tǒng)部署在一起,可以有多個(gè).
2. 核心票據(jù)
CAS的核心就是其Ticket,及其在Ticket之上的一系列處理操作.CAS的主要票據(jù)有TGT、ST、PGT、PGTIOU、PT,其中TGT、ST是CAS1.0(基礎(chǔ)模式)協(xié)議中就有的票據(jù),PGT、PGTIOU、PT是CAS2.0(代理模式)協(xié)議中有的票據(jù).這里主要介紹CAS1.0—基礎(chǔ)模式中的幾種票據(jù).
TGT(Ticket Grangting Ticket)
TGT是CAS為用戶(hù)簽發(fā)的登錄票據(jù),擁有了TGT,用戶(hù)就可以證明自己在CAS成功登錄過(guò).TGT封裝了Cookie值以及此Cookie值對(duì)應(yīng)的用戶(hù)信息.用戶(hù)在CAS認(rèn)證成功后,生成一個(gè)TGT對(duì)象,放入自己的session;同時(shí),CAS生成TGC(一個(gè)cookie,官方文檔說(shuō)是叫TGC,但是我這邊看到的名稱(chēng)是CASTGC,可能是我理解問(wèn)題或者是版本差異,下文統(tǒng)稱(chēng)TGC),寫(xiě)入瀏覽器.
TGT對(duì)象的id就是TGC cookie的值,當(dāng)HTTP再次請(qǐng)求到來(lái)時(shí),如果傳過(guò)來(lái)的有TGC這個(gè)cookie,并且CAS能找到對(duì)應(yīng)的TGT,則說(shuō)明用戶(hù)之前登錄過(guò);如果沒(méi)有,則用戶(hù)需要重新登錄.
TGC(Ticket-granting cookie)
上面提到,CAS Server會(huì)生成TGT,而TGC就是將TGT的id以cookie形式放到瀏覽器端,是CAS Server用來(lái)明確用戶(hù)身份的憑證.
ST(ServiceTicket)
ST是CAS為用戶(hù)簽發(fā)的訪問(wèn)某一服務(wù)票據(jù).在登錄成功后重定向回客戶(hù)端的時(shí)候,會(huì)給客戶(hù)端頒發(fā)ST,客戶(hù)端的CAS Validation Filter會(huì)根據(jù)ST去服務(wù)端再次做校驗(yàn),獲取用戶(hù)信息.
為了保證ST的安全性:ST 是基于隨機(jī)生成的,沒(méi)有規(guī)律性.而且,CAS規(guī)定 ST 只能存活一定的時(shí)間,然后 CAS Server 會(huì)讓它失效.而且,CAS 協(xié)議規(guī)定ST只能使用一次,無(wú)論 Service Ticket 驗(yàn)證是否成功, CASServer 都會(huì)清除服務(wù)端緩存中的該 Ticket ,從而可以確保一個(gè) Service Ticket 不被使用兩次.
概括下就是,ST是服務(wù)端提供給客戶(hù)端的用來(lái)獲取用戶(hù)信息的只能用一次的憑證.
3. 核心過(guò)濾器
CAS Client使用的核心過(guò)濾器主要由兩個(gè):負(fù)責(zé)判斷請(qǐng)求是否已認(rèn)證的AuthenticationFilter和負(fù)責(zé)校驗(yàn)ST的TicketValidationFilter.
CAS認(rèn)證原理
直接上官方提供的認(rèn)證序列圖(一定要看懂).
[外鏈圖片轉(zhuǎn)存中…(img-NJvC5iEI-1569813048856)]
其中的ST就是所謂的唯一令牌,用過(guò)即失效.這個(gè)令牌也可能是采用JWT來(lái)生成的.
如果ST采用的是JWT來(lái)進(jìn)行生成,那么流程上稍微有些不同,序列圖如下(這個(gè)了解就行):
[外鏈圖片轉(zhuǎn)存中…(img-OuyOnH3A-1569813048857)]
可以打開(kāi)Chrome的控制臺(tái)或者是直接采用Filder進(jìn)行抓包,對(duì)照著進(jìn)行分析確認(rèn).
CAS客戶(hù)端核心過(guò)濾器
對(duì)著源碼來(lái)看下cas客戶(hù)端的是如何判斷一個(gè)session是否已通過(guò)認(rèn)證.
前面我們提到過(guò),CAS客戶(hù)端使用的核心過(guò)濾器主要有兩個(gè):負(fù)責(zé)判斷請(qǐng)求是否已認(rèn)證的AuthenticationFilter和負(fù)責(zé)校驗(yàn)ST的TicketValidationFilter.
這兩個(gè)過(guò)濾器都有多種不同的實(shí)現(xiàn),下文只以具體的某個(gè)實(shí)現(xiàn)類(lèi)為例來(lái)說(shuō)明.
AuthenticationFilter
這個(gè)過(guò)濾器就是用來(lái)負(fù)責(zé)校驗(yàn)每個(gè)請(qǐng)求是否是認(rèn)證的請(qǐng)求,如果請(qǐng)求未通過(guò)認(rèn)證且不包含ST,那么就重定向到服務(wù)端登錄界面.
對(duì)應(yīng)客戶(hù)端web.xml中配置的CAS Filter,大致是這樣的:
<filter>
<filter-name>CAS Filter</filter-name>
<filter-class>org.jasig.cas.client.authentication.AuthenticationFilter</filter-class>
<init-param>
<param-name>casServerLoginUrl</param-name>
<param-value>${cas.serverUrl}/login</param-value>
</init-param>
<init-param>
<param-name>serverName</param-name>
<param-value>${cas.clientUrl}</param-value>
</init-param>
<init-param>
<param-name>ignorePattern</param-name>
<param-value>.*/login|.*/unsafe|.*/api/app/token/*|.*\.ico|.*\.js(?!p)|.*\.css|</param-value>
</init-param>
<init-param>
<param-name>ignoreUrlPatternType</param-name>
<param-value>REGEX</param-value>
</init-param>
...
</filter>
<filter-mapping>
<filter-name>CAS Filter</filter-name>
<url-pattern>/cas.jsp</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>CAS Filter</filter-name>
<url-pattern>/api/*</url-pattern>
</filter-mapping>
看下對(duì)應(yīng)的實(shí)現(xiàn).
public final void doFilter(final ServletRequest servletRequest, final ServletResponse servletResponse,
final FilterChain filterChain) throws IOException, ServletException {
final HttpServletRequest request = (HttpServletRequest) servletRequest;
final HttpServletResponse response = (HttpServletResponse) servletResponse;
// 判斷請(qǐng)求是否不需要過(guò)濾
if (isRequestUrlExcluded(request)) {
logger.debug("Request is ignored.");
filterChain.doFilter(request, response);
return;
}
final HttpSession session = request.getSession(false);
// CONST_CAS_ASSERTION = "_const_cas_assertion_"
final Assertion assertion = session != null ? (Assertion) session.getAttribute(CONST_CAS_ASSERTION) : null;
// 存在assertion,即認(rèn)為這是一個(gè)已通過(guò)認(rèn)證的請(qǐng)求.予以放行
if (assertion != null) {
filterChain.doFilter(request, response);
return;
}
// 不存在 assertion,那么就來(lái)判斷這個(gè)請(qǐng)求是否是用來(lái)校驗(yàn)ST的(校驗(yàn)通過(guò)后會(huì)將信息寫(xiě)入assertion)
final String serviceUrl = constructServiceUrl(request, response);
final String ticket = retrieveTicketFromRequest(request);
final boolean wasGatewayed = this.gateway && this.gatewayStorage.hasGatewayedAlready(request, serviceUrl);
// 是校驗(yàn)ST的請(qǐng)求,予以放行
if (CommonUtils.isNotBlank(ticket) || wasGatewayed) {
filterChain.doFilter(request, response);
return;
}
final String modifiedServiceUrl;
logger.debug("no ticket and no assertion found");
if (this.gateway) {
logger.debug("setting gateway attribute in session");
modifiedServiceUrl = this.gatewayStorage.storeGatewayInformation(request, serviceUrl);
} else {
modifiedServiceUrl = serviceUrl;
}
logger.debug("Constructed service url: {}", modifiedServiceUrl);
// 要重定向界面地址(cas服務(wù)端登錄界面).
final String urlToRedirectTo = CommonUtils.constructRedirectUrl(this.casServerLoginUrl,
getProtocol().getServiceParameterName(), modifiedServiceUrl, this.renew, this.gateway);
logger.debug("redirecting to \"{}\"", urlToRedirectTo);
this.authenticationRedirectStrategy.redirect(request, response, urlToRedirectTo);
}
可以看到,cas正是通過(guò)session中是否有assertion的信息來(lái)判斷一個(gè)請(qǐng)求是否合法.
而這個(gè)assertion信息,又是在客戶(hù)端校驗(yàn)ST之后(登錄成功后重定向回客戶(hù)端的請(qǐng)求附帶有ST參數(shù))寫(xiě)入session中的.具體見(jiàn)下文TicketValidationFilter
TicketValidationFilter
這個(gè)過(guò)濾器用來(lái)校驗(yàn)ST,并在session中寫(xiě)入認(rèn)證聲明信息assertion.
對(duì)應(yīng)客戶(hù)端web.xml中配置的CAS Validation Filter,大致是這樣的:
<filter>
<filter-name>CAS Validation Filter</filter-name>
<filter-class>
org.jasig.cas.client.validation.Cas20ProxyReceivingTicketValidationFilter
</filter-class>
</filter>
查看Cas20ProxyReceivingTicketValidationFilter源碼,可以在其父類(lèi)AbstractTicketValidationFilter的doFilter()方法中找到對(duì)應(yīng)的代碼,如下:
final Assertion assertion = this.ticketValidator.validate(ticket,
constructServiceUrl(request, response));
logger.debug("Successfully authenticated user: {}", assertion.getPrincipal().getName());
// CONST_CAS_ASSERTION = "_const_cas_assertion_"
request.setAttribute(CONST_CAS_ASSERTION, assertion);
// useSession 對(duì)應(yīng)配置項(xiàng)中的useSession參數(shù),缺省值為true.但這個(gè)配置在3.4版本之后是棄用的,后續(xù)隨時(shí)可能會(huì)被移除.
if (this.useSession) {
request.getSession().setAttribute(CONST_CAS_ASSERTION, assertion);
}
那么通過(guò)這兩個(gè)過(guò)濾器的源碼分析,可以證明這兩點(diǎn):
cas客戶(hù)端是根據(jù)session中是否有assertion信息來(lái)判斷是否已認(rèn)證.
assertion是在登錄成功后重定向回后端的時(shí)候?qū)懭?/span>session的.
那么進(jìn)而就會(huì)得出我們前文中提到的解決思路的關(guān)鍵性?xún)牲c(diǎn):
登錄成功后跳轉(zhuǎn)的第一個(gè)地址必須是被后端CAS Filter攔截的地址(主要用來(lái)重定向到前端,可以是一個(gè)接口,也可以是一個(gè).jsp界面),保證cas可以把認(rèn)證聲明信息assertion寫(xiě)入session.
前端發(fā)起的后續(xù)請(qǐng)求必須和第一個(gè)請(qǐng)求(即前面的那個(gè)跳轉(zhuǎn)后端的那個(gè)請(qǐng)求)是屬于同一個(gè)session(因?yàn)橛?/span>assertion,一定可以通過(guò)認(rèn)證).
關(guān)于第一點(diǎn)不再多說(shuō).
關(guān)于第二點(diǎn),如何保證前端發(fā)起的后續(xù)請(qǐng)求和第一個(gè)請(qǐng)求是同一個(gè)session,答案就是保證JSEESIONID一致就行.
關(guān)于session、cookie及JSESSIONID在其中起到的作用
眾所周知,Http協(xié)議是一種無(wú)狀態(tài)協(xié)議,每次服務(wù)端接收到客戶(hù)端的請(qǐng)求時(shí),都是一個(gè)全新的請(qǐng)求,服務(wù)器并不知道客戶(hù)端的歷史請(qǐng)求記錄;
為了彌補(bǔ)Http的無(wú)狀態(tài)特性,session應(yīng)運(yùn)而生.服務(wù)器可以利用session存儲(chǔ)客戶(hù)端在同一個(gè)會(huì)話(huà)期間的一些操作記錄,而服務(wù)端的這個(gè)session,對(duì)應(yīng)到瀏覽器端,則是名為JSESSIONID的cookie,JSESSIONID的值就是session的id.
那么再看這兩個(gè)問(wèn)題:
服務(wù)器如何判斷客戶(hù)端發(fā)送過(guò)來(lái)的請(qǐng)求是屬于同一個(gè)seesion?
答:用session的id來(lái)進(jìn)行區(qū)分,如果id相同,那就認(rèn)為是同一個(gè)會(huì)話(huà).在Tomcat中,session的id的默認(rèn)名字是JSESSIONID.對(duì)應(yīng)到前端就是名為JSESSIONID的cookie.
session的id是在什么時(shí)候創(chuàng)建,又是怎樣在前后端傳輸?shù)?
答:tomcat在第一次接收到一個(gè)請(qǐng)求時(shí),會(huì)創(chuàng)建一個(gè)session對(duì)象,同時(shí)生成一個(gè)session id,并通過(guò)響應(yīng)頭的Set-Cookie:"JSESSIONID=XXXXXXX"命令,向客戶(hù)端發(fā)送要求設(shè)置Cookie的響應(yīng).
前端在后續(xù)的每次請(qǐng)求時(shí),會(huì)帶上所有cookie信息,自然也就包含了JSESSIONID這個(gè)cookie.Tomcat據(jù)此來(lái)查找到對(duì)應(yīng)的session.如果指定session不存在(比如我們隨手編一個(gè)JSESSIONID,那對(duì)應(yīng)的session肯定不存在),那么就會(huì)創(chuàng)建一個(gè)新的session,其id的值就是請(qǐng)求中的JSESSIONID的值.
這樣我們?cè)诖a中就可以通過(guò)request.getSession()來(lái)獲取到當(dāng)前的會(huì)話(huà)信息.
PS:
實(shí)際上,JSESSIONID傳給后端的方式,還可以直接在url后面加上;jsessionid=xxxx來(lái)傳遞.但是會(huì)被cookie中的JSESSIONID覆蓋.
那么具體到cas對(duì)接上,第一次重定向回客戶(hù)端的請(qǐng)求肯定是可以通過(guò)cas的認(rèn)證的,那么只要后續(xù)的請(qǐng)求和第一個(gè)是同一個(gè)session,那就一定也能通過(guò)cas的過(guò)濾.
前面我們也說(shuō)了,只要請(qǐng)求中的JSESSIONID是一致的,那就會(huì)被認(rèn)定是同一個(gè)session.
前文中給出的解決思路,也正是基于此進(jìn)行.
原理部分基本上就到這里.
如果覺(jué)得看懂了原理,那我最后提個(gè)問(wèn)題:
如果通過(guò)nginx將前后端反向代理到同一個(gè)域下,登錄成功后直接跳轉(zhuǎn)前端地址,那是否可行?原因是什么?
