單點登入的解決方案(Session)
以前,做過的專案中有單點登入的模組,以前做的模組沒仔細觀察,檢視資料發現單點登入的解決方案還挺多!以下就記錄單點的不同解決方案;
http協議的特性
無狀態的,就是使用者連線只要獲取響應,伺服器不記錄使用者狀態,就算使用者訪問100次,伺服器也不知道使用者具體資訊;
1.叢集環境下的session共享問題
通過cookie+session可以儲存使用者狀態;
2.關於負載均衡演算法分析
2.1)輪詢,加權輪詢
2.2)Hash演算法(可以解決session共享問題)
2.3)隨機
2.4)最小連線數
3.Session共享問題的解決辦法
3.1)Session replication Session複製 配置檔案 (tomcat中可以配置)
使得叢集中各個伺服器相互儲存各自節點的session資料,tomcat本身就可以實現session複製的功能,基於IP組播的方式,同步時候效能會很低;出現問題:
3.1.1)隨著叢集的數量越來越大,session帶來的頻寬影響網路開銷
3.1.2)每個節點都要儲存叢集中所有節點的session資料,很耗費記憶體
3.2) Cookie Based Token(JWT)(通行證)
基於服務的一定演算法,生成一個token給客戶端,客戶端每次清楚,都攜帶這個token伺服器驗證token是否有效,再對token的關鍵字進行處理(一種純cookie的方式);JWT強調的是伺服器端不對token進行儲存,而是通過簽名演算法進行解密.
3.3) Session的統一管理 Redis儲存或者mysql儲存(把jsessio儲存到同一個位置),無論哪個節點新增和修改了session,最終都發生在儲存的地方;需要雙擊熱備份,防止突然宕機 出現的問題:
3.3.1)session資料進行網路操作,存在延遲性
3.3.2) 如果session伺服器宕機,將大規模影響到應用
3.4) Session sticky (會話粘性) 利用hash演算法可以解決session sticky的問題。這種解決方案會出現的問題:
3.4.1)有一臺伺服器宕機,這臺機器上儲存的session會丟失
3.4.2)這種方式實現了session儲存,沒有辦法在四層進行網路轉發,只能在7層進行網路轉發
4.單點登入實現方案
利用jwt token的方式
Jwt token三部分組成,頭部(header),有效載荷(playload),簽名(signature)
附錄:
jwt 實際上就是定義了一套資料加密以及驗籤的演算法的規範,根據這個規範來實現單點登入,以及資料傳輸及驗籤功能。
問題:1)不能傳遞敏感問題
2)jwt中的部分內容可以解密破解
利用token來解決session共享問題解決方案的核心程式碼:
1.TokenIntercepter.java
import java.lang.reflect.Method;
public class TokenIntercepter extends HandlerInterceptorAdapter {
private final String ACCESS_TOKEN="access_token";
@Autowired
IUserCoreService iUserCoreService;
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
if(!(handler instanceof HandlerMethod)){
return true;
}
HandlerMethod handlerMethod=(HandlerMethod)handler;
Object bean=handlerMethod.getBean();
if(isAnoymous(handlerMethod)){
return true;
}
if(!(bean instanceof BaseController)){
throw new RuntimeException("must extend basecontroller");
}
// 利用CookieUitl工具類取出cookie的值
String token=CookieUtil.getCookieValue(request,ACCESS_TOKEN);
// 判斷request請求是不是Ajax
boolean isAjax=CookieUtil.isAjax(request);
if(StringUtils.isEmpty(token)){ //如果為空
if(isAjax){ // 設定返回值
response.setContentType("text/html;charset=UTF-8");
response.getWriter().write("{\"code\":\"-1\",\"msg\":\"error\"}");
return false;
}
response.sendRedirect(GpmallWebConstant.GPMALL_SSO_ACCESS_URL); //並重定向到sso_access_url連結
return false;
}
CheckAuthRequest checkAuthRequest=new CheckAuthRequest();
checkAuthRequest.setToken(token);
CheckAuthResponse checkAuthResponse=iUserCoreService.validToken(checkAuthRequest);
if("000000".equals(checkAuthResponse.getCode())){
BaseController baseController=(BaseController)bean;
baseController.setUid(checkAuthResponse.getUid());
return super.preHandle(request, response, handler);
}
if(isAjax){
response.setContentType("text/html;charset=UTF-8");
response.getWriter().write("{\"code\":\""+checkAuthResponse.getCode()+"\"" +
",\"msg\":\""+checkAuthResponse.getMsg()+"\"}");
return false;
}
response.sendRedirect(GpmallWebConstant.GPMALL_SSO_ACCESS_URL);
return false;
}
private boolean isAnoymous(HandlerMethod handlerMethod){
Object bean=handlerMethod.getBean();
Class clazz=bean.getClass();
if(clazz.getAnnotation(Anoymous.class)!=null){
return true;
}
Method method=handlerMethod.getMethod();
return method.getAnnotation(Anoymous.class)!=null;
}
}
2.BaseController.java
public class BaseController {
static ThreadLocal<String> uidThreadLocal=new ThreadLocal<>();
public void setUid(String uid){
uidThreadLocal.set(uid);
}
public String getUid(){
return uidThreadLocal.get();
}
}
3.UserContoller.java
@RestController
public class UserController extends BaseController{
@Autowired
IUserCoreService userCoreService;
@Autowired
KafkaTemplate kafkaTemplate;
@Anoymous
@PostMapping("/login")
public ResponseData doLogin(String username, String password,
HttpServletResponse response){
ResponseData data=new ResponseData();
UserLoginRequest request=new UserLoginRequest();
request.setPassword(password);
request.setUserName(username);
UserLoginResponse userLoginResponse=userCoreService.login(request);
response.addHeader("Set-Cookie",
"access_token="+userLoginResponse.getToken()+";Path=/;HttpOnly");
data.setMessage(userLoginResponse.getMsg());
data.setCode(userLoginResponse.getCode());
data.setData(GpmallWebConstant.GPMALL_ACTIVITY_ACCESS_URL);
return data;
}
@GetMapping("/register")
@Anoymous
public @ResponseBody
ResponseData register(String username, String password, String mobile){
ResponseData data=new ResponseData();
UserRegisterRequest request=new UserRegisterRequest();
request.setMobile(mobile);
request.setUsername(username);
request.setPassword(password);
try {
UserRegisterResponse response = userCoreService.register(request);
//非同步化解耦
kafkaTemplate.send("test",response.getUid());
data.setMessage(response.getMsg());
data.setCode(response.getCode());
}catch(Exception e) {
data.setMessage(ResponseEnum.FAILED.getMsg());
data.setCode(ResponseEnum.FAILED.getCode());
}
return data;
}
}