Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537
TTP 是一種"無狀態"協議,這意味著每次客戶端檢索網頁時,客戶端打開一個單獨的連接到 Web 服務器,服務器會自動不保留之前客戶端請求的任何記錄。
但是仍然有以下三種方式來維持 Web 客戶端和 Web 服務器之間的 session 會話:
Cookies
一個 Web 服務器可以分配一個唯一的 session 會話 ID 作為每個 Web 客戶端的 cookie,對于客戶端的后續請求可以使用接收到的 cookie 來識別。
這可能不是一個有效的方法,因為很多瀏覽器不支持 cookie,所以我們建議不要使用這種方式來維持 session 會話。
隱藏的表單字段
一個 Web 服務器可以發送一個隱藏的 HTML 表單字段,以及一個唯一的 session 會話 ID,如下所示:
<input type="hidden" name="sessionid" value="12345">
該條目意味著,當表單被提交時,指定的名稱和值會被自動包含在 GET 或 POST 數據中。每次當 Web 瀏覽器發送回請求時,session_id 值可以用于保持不同的 Web 瀏覽器的跟蹤。
這可能是一種保持 session 會話跟蹤的有效方式,但是點擊常規的超文本鏈接(<A HREF...>)不會導致表單提交,因此隱藏的表單字段也不支持常規的 session 會話跟蹤。
URL 重寫
您可以在每個 URL 末尾追加一些額外的數據來標識 session 會話,服務器會把該 session 會話標識符與已存儲的有關 session 會話的數據相關聯。
例如,http://w3cschool.cn/file.htm;sessionid=12345,session 會話標識符被附加為 sessionid=12345,標識符可被 Web 服務器訪問以識別客戶端。
URL 重寫是一種更好的維持 session 會話的方式,它在瀏覽器不支持 cookie 時能夠很好地工作,但是它的缺點是會動態生成每個 URL 來為頁面分配一個 session 會話 ID,即使是在很簡單的靜態 HTML 頁面中也會如此。
HttpSession 對象
除了上述的三種方式,Servlet 還提供了 HttpSession 接口,該接口提供了一種跨多個頁面請求或訪問網站時識別用戶以及存儲有關用戶信息的方式。
Servlet 容器使用這個接口來創建一個 HTTP 客戶端和 HTTP 服務器之間的 session 會話。會話持續一個指定的時間段,跨多個連接或頁面請求。
您會通過調用 HttpServletRequest 的公共方法 getSession() 來獲取 HttpSession 對象,如下所示:
HttpSession session = request.getSession();
你需要在向客戶端發送任何文檔內容之前調用 request.getSession()。下面總結了 HttpSession 對象中可用的幾個重要的方法:
Session 跟蹤實例
本實例說明了如何使用 HttpSession 對象獲取 session 會話創建時間和最后訪問時間。如果不存在 session 會話,我們將通過請求創建一個新的 session 會話。
// 導入必需的 java 庫 import java.io.*; import javax.servlet.*; import javax.servlet.http.*; import java.util.*; // 擴展 HttpServlet 類 public class SessionTrack extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 如果不存在 session 會話,則創建一個 session 對象 HttpSession session = request.getSession(true); // 獲取 session 創建時間 Date createTime = new Date(session.getCreationTime()); // 獲取該網頁的最后一次訪問時間 Date lastAccessTime = new Date(session.getLastAccessedTime()); String title = "歡迎回到我的網站"; Integer visitCount = new Integer(0); String visitCountKey = new String("visitCount"); String userIDKey = new String("userID"); String userID = new String("ABCD"); // 檢查網頁上是否有新的訪問者 if (session.isNew()){ title = "歡迎來到我的網站"; session.setAttribute(userIDKey, userID); } else { visitCount = (Integer)session.getAttribute(visitCountKey); visitCount = visitCount + 1; userID = (String)session.getAttribute(userIDKey); } session.setAttribute(visitCountKey, visitCount); // 設置響應內容類型 response.setContentType("text/html"); PrintWriter out = response.getWriter(); String docType = "<!doctype html public \"-//w3c//dtd html 4.0 " + "transitional//en\">\n"; out.println(docType + "<html>\n" + "<head><title>" + title + "</title></head>\n" + "<body bgcolor=\"#f0f0f0\">\n" + "<h1 align=\"center\">" + title + "</h1>\n" + "<h2 align=\"center\">Session 信息</h2>\n" + "<table border=\"1\" align=\"center\">\n" + "<tr bgcolor=\"#949494\">\n" + " <th>Session 信息</th><th>值</th></tr>\n" + "<tr>\n" + " <td>id</td>\n" + " <td>" + session.getId() + "</td></tr>\n" + "<tr>\n" + " <td>Creation Time</td>\n" + " <td>" + createTime + " </td></tr>\n" + "<tr>\n" + " <td>Time of Last Access</td>\n" + " <td>" + lastAccessTime + " </td></tr>\n" + "<tr>\n" + " <td>User ID</td>\n" + " <td>" + userID + " </td></tr>\n" + "<tr>\n" + " <td>Number of visits</td>\n" + " <td>" + visitCount + "</td></tr>\n" + "</table>\n" + "</body></html>"); } }
編譯上面的 Servlet SessionTrack,并在 web.xml 文件中創建適當的條目。在瀏覽器地址欄輸入 http://localhost:8080/SessionTrack,當您第一次運行時將顯示如下結果:
Session 信息
Session 信息值id0AE3EC93FF44E3C525B4351B77ABB2D5Creation TimeTue Jun 08 17:26:40 GMT+04:00 2014Time of Last AccessTue Jun 08 17:26:40 GMT+04:00 2014User IDABCDNumber of visits0
再次嘗試運行相同的 Servlet,它將顯示如下結果:
Session 信息
Session 信息值id0AE3EC93FF44E3C525B4351B77ABB2D5Creation TimeTue Jun 08 17:26:40 GMT+04:00 2014Time of Last AccessTue Jun 08 17:26:40 GMT+04:00 2014User IDABCDNumber of visits1
刪除 Session 會話數據
當您完成了一個用戶的 session 會話數據,您有以下幾種選擇:
<session-config> <session-timeout>15</session-timeout> </session-config>
上面實例中的超時時間是以分鐘為單位,將覆蓋 Tomcat 中默認的 30 分鐘超時時間。
在一個 Servlet 中的 getMaxInactiveInterval() 方法會返回 session 會話的超時時間,以秒為單位。所以,如果在 web.xml 中配置 session 會話超時時間為 15 分鐘,那么 getMaxInactiveInterval() 會返回 900。
Http協議是一種無狀態協議所以當服務端需要記錄用戶的狀態時,需要某種機制用于識別用戶,這個機制就是Session。服務器通過和用戶約定每一個請求攜帶一個id信息,用于統一用戶的請求有了管理,并且區分不同用戶。基于session方案,為讓用戶請求都攜帶同一個id,并且不妨礙用戶體驗的情況下,選擇cookie作為載體是一個不錯的選擇,用戶第一次訪問服務器的時候,沒有攜帶id,服務器端會生成sessionid:session鍵值對,并且發送sessionid給客戶端添加到cookie中。然后該用戶在之后的訪問中,每一次請求都會將sessionid放到cookie中,使得服務端可以很容易識別用戶。
但是有時候用戶為了保護個人信息或者安全考慮會禁用cookie,這時候cookie就無法使用。因此有時候服務還支持用url重寫來實現,比如:
http://www.baidu.com;jessionid=xxx
URL重寫原本是為了兼容禁用cookie的瀏覽器而設計的,但也容易被黑客利用。黑客只需訪問一 次系統,將系統生成的sessionId提取并拼湊在URL上,然后將該URL發給一些取得信任的用戶。只要 用戶在session有效期內通過此URL進行登錄,該sessionId就會綁定到用戶的身份,黑客便可以輕松享 有同樣的會話狀態,完全不需要用戶名和密碼,這就是典型的會話固定攻擊。
sessionManagement是一個會話管理的配置器,其中,防御會話固定攻擊的策略有四種:
http.sessionManagement().sessionFixation().changeSessionId();
除了防御會話固定攻擊,還可以通過SpringSecurity配置會話過期策略,比如會話過期跳轉到某個URL。在Springboot應用中有兩種會話超時設置的方式,當會話超時之后用戶需要重寫登錄才可以訪問應用:
方式1是springboot應用自帶的session超時設置,方式2是使用Spring Session之后。提供的session超時配置,方式2優先級高。
在Spring Boot中Session超時最短的時間是一分鐘,當你的設置小于一分鐘的時候,默認為一分鐘默認超時時長是30分鐘
默認情況下session失效以后會跳轉到認證頁面,我們可以自定義session失效后,響應結果,有以下兩種方式。
invalidSessionUrl作用是session失效后跳轉的url,配置如下,在安全配置中心的 configure(HttpSecurity http)方法中添加代碼如下:
<!--suppress ALL-->
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>springboot葵花寶典登錄頁面</title>
<!-- Tell the browser to be responsive to screen width -->
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<h1>springboot葵花寶典登錄頁面</h1>
<h2>session會話失效</h2>
</body>
</html>
@RequestMapping("/invalidSession")
public String invalidSession() {
return "invalidSession"; // classpath: /templates/login.html
}
http.sessionManagement().invalidSessionUrl("/invalidSession")
具體配置如圖
注意要以上路徑需要配置permitAll()權限,即無需授權即可訪問
啟動項目登錄后,再次登錄,結果如下
session失敗后的策略,配置如下:
public class CustomInvalidSessionStrategy implements InvalidSessionStrategy {
private static ObjectMapper objectMapper = new ObjectMapper();
@Override
public void onInvalidSessionDetected(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
Cookie cookie = new Cookie("JSESSIONID", null);
cookie.setMaxAge(0);
String contextPath = request.getContextPath();
String c= contextPath.length() > 0 ? contextPath : "/";
cookie.setPath(c);
response.addCookie(cookie);
// 當認證失敗后,響應 JSON 數據給前端
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write(objectMapper.writeValueAsString("策略失效"));
}
}
@Configuration
public class Myconfig {
@Bean
@ConditionalOnMissingBean(InvalidSessionStrategy.class)
public CustomInvalidSessionStrategy customInvalidSessionStrategy(){
return new CustomInvalidSessionStrategy();
}
}
在LearnSrpingSecurity的configure(HttpSecurity http)添加配置,代碼如下
啟動項目登錄后,再次登錄,結果如下
如果您覺得本文不錯,歡迎關注,點贊,收藏支持,您的關注是我堅持的動力!
公眾號 springboot葵花寶典 主要分享JAVA技術,主要包含SpringBoot、SpingCloud、Docker、中間件等技術
原創不易,轉載請注明出處,感謝支持!如果本文對您有用,歡迎轉發分享!
于Cookie和Session的功能與工作原理,在這里我就不再敘述了,大家想要了解可以看一下我的上一篇博客,講的還是很細致的。
但是之前講的Session是運行在一臺服務器上的,所有的訪問都會到達我們的唯一服務器上,這樣我們可以根據客戶端傳來的sessionID,來獲取session,或在對應Session不存在的情況下(session 生命周期到了/用戶第一次登錄),創建一個新的Session;但是,如果我們在集群環境下,假設我們有兩臺服務器A,B,用戶的請求會由Nginx服務器進行轉發(別的方案也是同理),用戶登錄時,Nginx將請求轉發至服務器A上,A創建了新的session,并將SessionID返回給客戶端,用戶在瀏覽其他頁面時,客戶端驗證登錄狀態,Nginx將請求轉發至服務器B,由于B上并沒有對應客戶端發來sessionId的session,所以會重新創建一個新的session,并且再將這個新的sessionID返回給客戶端,這樣,我們可以想象一下,用戶每一次操作都有1/2的概率進行再次的登錄,這樣不僅對用戶體驗特別差,還會讓服務器上的session激增,加大服務器的運行壓力。
為了解決集群環境下的seesion共享問題,共有4種解決方案:
1.粘性session
粘性session是指Ngnix每次都將同一用戶的所有請求轉發至同一臺服務器上,即將用戶與服務器綁定。
2.服務器session復制
即每次session發生變化時,創建或者修改,就廣播給所有集群中的服務器,使所有的服務器上的session相同。
3.session共享
緩存session,使用redis, memcached。
4.session持久化
將session存儲至數據庫中,像操作數據一樣才做session。
其實,最簡單的兩種方案,就是方案一和方案三,都不需要對session進行任何操作,只需要將Nginx和Tomcat上的配置文件修改一下即可。由于我們做集群,訪問量一定是比較大的了,對于第一種方案,如果某臺服務器發生故障,此服務器上的所有用戶的session都會丟失,所以今天我們采用第三種解決方案。
感謝開源項目tomcat-redis-session-manager,感謝項目的發起者jcoleman。
此方案最簡單之處就在于我們無需修改項目,只需要修改Tomcat的context.xml配置文件即可,并且,redis服務器同樣可以做分布式。
接下來,我們來講解一下如何配置tomcat,首先,講解一下注意事項,因為項目的原因,并沒能用maven來管理依賴的包,而在我配置的時候,很正常的發生了包版本間的沖突,從redis中取出的序列化后的session無法轉換為HttpSession,所以,大家做的時候一定要注意版本間的兼容問題。
把我使用的版本給大家做個參考吧,tomcat-redis-session-manager-1.1.jar,jedis-2.1.0.jar,commons-pool-1.6.jar
這三個包需放入 tomcat的安裝目錄下的lib文件夾下,IDE自帶的tomcat可以在配置中查看。
需注意的是,當redis的版本‘過高’時,需要依賴commons-pool2.jar,(具體版本不寫)。
接下來,我們就講一下最核心的配置,配置context.xml文件(文件路徑workspace\.metadata\.me_tcat7\conf,此處僅是myeclipse編輯器的路徑)
[html] view plain copy
這個里的class路徑具體看tomcat-redis-session-manager里的路徑。
可能你會遇到重啟tomcat配置文件會被還原的問題,那么你還需要修改另一處的context.xml文件,workspace\Servers\MyEclipse Tomcat v7.0-config文件夾中的,修改內容同上。
接下來,給大家看一下配置完成后的結果:
圖1為監聽到的redis服務器的狀態,可以看到我們將session序列化后以sessionID為key存入了redis中,圖2可以看到是客戶端header中的cookie信息(若有疑問可以查看我上一篇的博客),圖三是我在redis客戶端get key為sessionID的記錄,可以驗證成功,我們配置是成功了的,因為使用的自己的電腦,配置有限,并沒有開幾個tomcat來測試,不過上述還是很有說服力的(線上已經測試過了)。
希望對你有幫助!
*請認真填寫需求信息,我們會在24小時內與您取得聯系。