1. 程式人生 > >單點登入的解決方案(Session)

單點登入的解決方案(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;
    }
}