OAuth二、CAS單點登陸

1、Oauth 是一個關於受權(authorization)的開網絡標準(規範)前端

OAuth2: 解決的是不一樣的企業之間的登陸,本質是受權redis

要能訪問各類資源重點是要獲取令牌(token),但根據令牌的獲取方式不一樣,又會有四種受權方式spring

  1. 受權碼(authorization-code)
  2. 隱藏式(implicit)
  3. 密碼式(password)
  4. 客戶端憑證(client credentials)

受權碼這是最經常使用的一種方式,指的是第三方應用先申請一個受權碼,而後再用該碼獲取令牌,項目中用的就是這種後端

隱藏式:容許直接向前端頒發令牌。這種方式沒有受權碼這個中間步驟,因此稱爲(受權碼)"隱藏式"(implicit),通常應用於純前端項目跨域

密碼式:直接經過用戶名和密碼的方式申請令牌,這方式是最不安全的方式瀏覽器

憑證式這種方式的令牌是針對第三方應用,而不是針對用戶的,既某個第三方應用的全部用戶共用一個令牌,通常用於沒有前端的命令行應用tomcat

受權碼受權流程:安全

第一步,A 網站提供一個連接,用戶點擊後就會跳轉到 B 網站(權限驗證系統)服務器

http://b.com/oauth/authorize?cookie

  response_type=code&

  client_id=CLIENT_ID&

  redirect_uri=CALLBACK_URL&

  scope=read

用戶跳轉後,B 網站若是沒有登陸會要求用戶登陸,而後詢問是否贊成給予 A 網站受權。用戶表示贊成,這時 B 網站就會跳回redirect_uri參數指定的網址,並附加受權碼code

http://a.com/callback?code=AUTHORIZATION_CODE

第三步,A 網站拿到受權碼之後,在後端,向 B 網站請求令牌。

http://b.com/oauth/token?

 client_id=CLIENT_ID&

 client_secret=CLIENT_SECRET&

 grant_type=authorization_code&

 code=AUTHORIZATION_CODE&

 redirect_uri=CALLBACK_URL

上面 URL 中,client_id參數和client_secret參數用來讓 B 確認 A 的身份(client_secret參數是保密的,所以只能在後端發請求),grant_type參數的值是AUTHORIZATION_CODE,表示採用的受權方式是受權碼,code參數是上一步拿到的受權碼,redirect_uri參數是令牌頒發後的回調網址。

第四步,B 網站收到請求之後,就會頒發令牌。具體作法是向redirect_uri指定的網址,發送一段 JSON 數據。

{    

  "access_token":"ACCESS_TOKEN",

  "info":{...}

}

接下來用戶就能夠根據這個access_token來進行訪問了,

如A網站拿着token,申請獲取用戶信息,B網站確認令牌無誤,贊成向A網站開放資源。

 

2、單點: 是解決企業內部的一系列產品登陸問題,安全信任度要比oauth2高

(一)session-cookie機制

一、session-cookie機制出現的根源, http鏈接是無狀態的鏈接

-------- 同一瀏覽器向服務端發送屢次請求,服務器沒法識別,哪些請求是同一個瀏覽器發出的

二、爲了標識哪些請求是屬於同一我的 ---------- 須要在請求里加一個標識參數

方法1-----------直接在url里加一個標識參數(對前端開發有侵入性),如: token

方法2-----------http請求時,自動攜帶瀏覽器的cookie(對前端開發無知覺),如:jsessionid=XXXXXXX

三、瀏覽器標識在網絡上的傳輸,是明文的,不安全的

-----------安全措施:改https來保障

四、cookie的使用限制---依賴域名

-------------- 頂級域名下cookie,會被二級如下的域名請求,自動攜帶

-------------- 二級域名的cookie,不能攜帶被其它域名下的請求攜帶

五、在服務器後臺,經過解讀標識信息(token或jsessionid),來對應會話是哪一個session

--------------- 一個tomcat,被1000個用戶登錄,tomcat裏必定有1000個session -------》存儲格式map《sessionid,session對象》

--------------- 經過前端傳遞的jsessionid,來對應取的session ------ 動做發生時機request.getsession

(二)session共享方式,實現的單點登錄

一、多個應用共用同一個頂級域名,sessionid被種在頂級域名的cookie裏

二、後臺session經過redis實現共享(重寫httprequest、httpsession 或使用springsession框架),即每一個tomcat都在請求開始時,到redis查詢session;在請求返回時,將自身session對象存入redis

三、當請求到達服務器時,服務器直接解讀cookie中的sessionid,而後經過sessionid到redis中查找到對應會話session對象

四、後臺判斷請求是否已登錄,主要校驗session對象中,是否存在登錄用戶信息

五、整個校驗過程,經過filter過濾器來攔截切入,以下圖:

六、登錄成功時,後臺須要給頁面種cookie方法以下:

response裏,反映的種cookie效果以下:

七、爲了request.getsession時,自動能拿到redis中共享的session,

    咱們須要重寫request的getsession方法(使用HttpServletRequestWrapper包裝原request)

(三)cas單點登錄方案

一、對於徹底不一樣域名的系統,cookie是沒法跨域名共享的

二、cas方案,直接啓用一個專業的用來登錄的域名(好比:cas.com)來供全部的系統登錄。

三、當業務系統(如b.com)被打開時,藉助cas系統來登錄,過程以下:

cas登錄的全過程:

(1)、b.com打開時,發現本身未登錄 ----》 因而跳轉到cas.com去登錄

(2)、cas.com登錄頁面被打開,用戶輸入賬戶/密碼登錄成功

(3)、cas.com登錄成功,種cookie到cas.com域名下 -----------》把sessionid放入後臺redis《ticket,sesssionid》---頁面跳回b.com

String ticket = UUID.randomUUID().toString();
redisTemplate.opsForValue().set(ticket,request.getSession().getId(),20, TimeUnit.SECONDS);//必定要設置過時時間
CookieBasedSession.onNewSession(request,response);
response.sendRedirect(user.getBackurl()+"?ticket="+ticket);

(4)、b.com從新被打開,發現仍然是未登錄,可是有了一個ticket值

(5)、b.com用ticket值,到redis裏查到sessionid,並作session同步 ------ 》種cookie給本身,頁面原地重跳

(6)、b.com打開本身頁面,此時有了cookie,後臺校驗登錄狀態,成功

(7)整個過程交互,列圖以下:

四、cas.com的登錄頁面被打開時,若是此時cas.com原本就是登錄狀態的,則自動返回生成ticket給業務系統

整個單點登錄的關鍵部位,是利用cas.com的cookie保持cas.com是登錄狀態,此後任何第三個系統跳入,都將自動完成登錄過程

5,本示例中,使用了redis來作cas的服務接口,請根據工做狀況,自行替換爲合適的服務接口(主要是根據sessionid來判斷用戶是否已登錄)

6,爲提升安全性,ticket應該使用過即做廢(本例中,會用有效期機制)

public void doFilter(ServletRequest servletRequest,ServletResponse servletResponse, FilterChain filterChain)
        throws IOException, ServletException {
    HttpServletRequest request = (HttpServletRequest) servletRequest;
    MyRequestWrapper myRequestWrapper = new MyRequestWrapper(request,redisTemplate);
    //若是未登錄狀態,進入下面邏輯
    String requestUrl = request.getServletPath();
    if (!"/toLogin".equals(requestUrl) && !requestUrl.startsWith("/login")  && !myRequestWrapper.isLogin()) {
        /**
         * ticket爲空,或無對應sessionid爲空
         * --- 代表不是自動登錄請求--直接強制到登錄頁面
         */
        String ticket = request.getParameter("ticket");
        if (null == ticket || null == redisTemplate.opsForValue().get(ticket)){
            HttpServletResponse response = (HttpServletResponse)servletResponse;
            response.sendRedirect("http://cas.com:8090/toLogin?url="+request.getRequestURL().toString());
            return ;
        }
        /**
         * 是自動登錄請求,則種cookie值進去---本次請求是302重定向
         * 重定向後的下次請求,自帶本cookie,將直接是登錄狀態
         */
        myRequestWrapper.setSessionId((String) redisTemplate.opsForValue().get(ticket));
        myRequestWrapper.createSession();
        //種cookie
        CookieBasedSession.onNewSession(myRequestWrapper,(HttpServletResponse)servletResponse);
        //重定向自流轉一次,原地跳轉重向一次
        HttpServletResponse response = (HttpServletResponse)servletResponse;
        response.sendRedirect(request.getRequestURL().toString());
        return;
    }
    try {
        filterChain.doFilter(myRequestWrapper,servletResponse);
    } finally {
        myRequestWrapper.commitSession();
    }
}
public static void onNewSession(HttpServletRequest request, HttpServletResponse response) {
    HttpSession session = request.getSession();
    String sessionId = session.getId();
    Cookie cookie = new Cookie(COOKIE_NAME_SESSION, sessionId);
    cookie.setHttpOnly(true);
    cookie.setPath(request.getContextPath() + "/");
    cookie.setMaxAge(Integer.MAX_VALUE);
    response.addCookie(cookie);
}
相關文章
相關標籤/搜索