一. 為什么要使用Cookie,Session或者Token
- http協(xié)議是無狀態(tài)的,對于服務器而言,每次請求都是一個全新的請求
- 實際應用中,服務器要驗證請求對應的用戶信息,請求是否合法等
- 可以通過Cookie和Session驗證用戶信息
- 也可以通過token驗證用戶身份
二. Cookie和Session關聯(lián)
- Session保存在服務器中,用于記錄會話,而Cookie保存在瀏覽器中,用于跟蹤服務器的會話
- 每個session都有一個Id,新建session的時候,服務器會自動生成一個sessionid
- 服務器設置Set-Cookie響應頭,將sessionid返回給瀏覽器
- 瀏覽器根據(jù)Set-Cookie響應頭,新建一個Cookie,name為jsessionid(Java Servlet默認值,也可以自定義),value為session的id
- 瀏覽器的每次請求,都會攜帶Cookie值
- 服務器根據(jù)jsessionid這個Cookie找到對應的session,獲取用戶信息,判斷請求是否合法
- 如果瀏覽器禁用了Cookie,那么可以通過URL重定向的方式,將SessionID編碼到URL中,獲取服務器Session
1. Tomcat Session和Cookie源碼剖析
1.1 Tomcat Request類
主要是通過cookie獲取session,如果session不存在的話,則創(chuàng)建一個
//Tomcat Request類
public class Request implements HttpServletRequest {
/**
* 獲取請求對應的Session
* 如果session不存在,則創(chuàng)建一個
*/
@Override
public HttpSession getSession() {
Session session = doGetSession(true);
if (session == null) {
return null;
}
return session.getSession();
}
/**
* 獲取Session
* @param create 是否創(chuàng)建
*/
protected Session doGetSession(boolean create) {
//獲取請求上下文
Context context = getContext();
if (context == null) {
return null;
}
if ((session != null) && !session.isValid()) {
//session過期了
session = null;
}
if (session != null) {
return session;
}
Manager manager = context.getManager();
if (manager == null) {
return null;
}
if (requestedSessionId != null) {
//請求攜帶的requestedSessionId,也就是Cookie中的jsessionid
try {
//ManagerBase對象中獲取Session
session = manager.findSession(requestedSessionId);
} catch (IOException e) {
session = null;
}
if ((session != null) && !session.isValid()) {
session = null;
}
if (session != null) {
session.access();
return session;
}
}
if (!create) {
return null;
}
if (response != null
&& context.getServletContext()
.getEffectiveSessionTrackingModes()
.contains(SessionTrackingMode.COOKIE)
&& response.getResponse().isCommitted()) {
throw new IllegalStateException(
sm.getString("coyoteRequest.sessionCreateCommitted"));
}
String sessionId = getRequestedSessionId();
if (requestedSessionSSL) {
} else if (("/".equals(context.getSessionCookiePath())
&& isRequestedSessionIdFromCookie())) {
//從Cookie中取出SessionId
if (context.getValidateClientProvidedNewSessionId()) {
boolean found = false;
for (Container container : getHost().findChildren()) {
Manager m = ((Context) container).getManager();
if (m != null) {
try {
if (m.findSession(sessionId) != null) {
found = true;
break;
}
} catch (IOException e) {
}
}
}
if (!found) {
sessionId = null;
}
}
} else {
sessionId = null;
}
session = manager.createSession(sessionId);
//生成一個關聯(lián)Session的Cookie
if (session != null
&& context.getServletContext()
.getEffectiveSessionTrackingModes()
.contains(SessionTrackingMode.COOKIE)) {
Cookie cookie =
ApplicationSessionCookieConfig.createSessionCookie(
context, session.getIdInternal(), isSecure());
//將cookie添加到response中
response.addSessionCookieInternal(cookie);
}
if (session == null) {
return null;
}
session.access();
return session;
}
}
1.2 Tomcat ManagerBase類
維護了所有的session
public abstract class ManagerBase extends LifecycleMBeanBase implements Manager {
//保存sessiond的map
protected Map<String, Session> sessions = new ConcurrentHashMap<>();
/**
* 通過sessionid獲取session
*/
public HashMap<String, String> getSession(String sessionId) {
//從map中獲取
Session s = sessions.get(sessionId);
if (s == null) {
if (log.isInfoEnabled()) {
log.info(sm.getString("managerBase.sessionNotFound", sessionId));
}
return null;
}
Enumeration<String> ee = s.getSession().getAttributeNames();
if (ee == null || !ee.hasMoreElements()) {
return null;
}
//將session中的值放到另一個map中
//并返回新的map
HashMap<String, String> map = new HashMap<>();
while (ee.hasMoreElements()) {
String attrName = ee.nextElement();
map.put(attrName, getSessionAttribute(sessionId, attrName));
}
return map;
}
}
1.3 tomcat Response類
主要通過過Set-Cookie請求頭設置瀏覽器Cookie,還可以設置Cookie中維護的SessionId
/**
* tomcat Response類
*/
public class Response implements HttpServletResponse {
/**
* 添加cookie到瀏覽器
*/
@Override
public void addCookie(final Cookie cookie) {
if (included || isCommitted()) {
return;
}
cookies.add(cookie);
String header = generateCookieString(cookie);
//設置Set-Cookie響應頭
addHeader("Set-Cookie", header, getContext().getCookieProcessor().getCharset());
}
/**
* 將cookie添加到response中
*/
public void addSessionCookieInternal(final Cookie cookie) {
if (isCommitted()) {
return;
}
String name = cookie.getName();
//在Set-Cookie響應頭中設置cookie
final String headername = "Set-Cookie";
final String startsWith = name + "=";
String header = generateCookieString(cookie);
boolean set = false;
MimeHeaders headers = getCoyoteResponse().getMimeHeaders();
int n = headers.size();
for (int i = 0; i < n; i++) {
if (headers.getName(i).toString().equals(headername)) {
if (headers.getValue(i).toString().startsWith(startsWith)) {
headers.getValue(i).setString(header);
set = true;
}
}
}
if (!set) {
addHeader(headername, header);
}
}
//如果瀏覽器禁用了Cookie
//那么可以使用該方法,將sessionId編碼到URL中
@Override
public String encodeRedirectURL(String url) {
if (isEncodeable(toAbsolute(url))) {
return toEncoded(url, request.getSessionInternal().getIdInternal());
} else {
return url;
}
}
}
三. Token的使用
Token又被稱為令牌,主要用于校驗用戶身份,也是我們當前推薦使用的一種認證方式
Token相比于Cookie的優(yōu)勢
- 無狀態(tài),服務器上不需要存儲用戶對應的Session數(shù)據(jù),Token可以包含用戶信息
- 支持跨域訪問,Cookie默認是不支持跨域訪問,允許跨域可能會導致CSRF攻擊
- 解耦,將授權服務和應用服務解耦,只要token正確
- 安全性更高,可以采用各種加密方式來加密token,也可以有效避免CSRF攻擊
- 有利于前后端分離,H5,APP各個應用都可以使用同一的授權驗證模塊