簡體   English   中英

在OAuth2中使用Access Tokens with JavaScript

[英]Using Access Tokens with JavaScript in OAuth2

這是我以前嘗試獲取一些信息但我從未找到問題的實際答案或解決方案。 所以希望有人可以澄清並指出我正確的方向。

我把問題分成了底部的3個問題,所以如果它們可以被回答為1,2,3那會讓事情變得更容易消化並幫助我理解這個問題。

基本上,我使用CakePHP進行OAuth2服務器設置,以下JavaScript可以與之通信以允許用戶登錄並獲取訪問令牌,然后使用此令牌向不同的端點發出各種請求以發送和接收數據。

var access_token,
     refresh_token;

var App = {
    init: function() {
        $(document).ready(function(){
            Users.checkAuthenticated();
        });
    }(),
    splash: function() {
        var contentLogin = '<input id="Username" type="text"> <input id="Password" type="password"> <button id="login">Log in</button>';
        $('#app').html(contentLogin);
    },
    home: function() {  
        var contentHome = '<h1>Welcome</h1> <a id="logout">Log out</a>';
        $('#app').html(contentHome);
    }
};

var Users = {
    init: function(){
        $(document).ready(function() {
            $('#login').live('click', function(e){
                e.preventDefault();
                Users.login();
            }); 
            $('#logout').live('click', function(e){
                e.preventDefault();
                Users.logout();
            });
        });
    }(),

    // Check that if user is logged in (has an access token)

    checkAuthenticated: function() {
        access_token = window.localStorage.getItem('access_token');
        if( access_token == null ) {
            Users.logout();
        }
        else {
            Users.checkTokenValid(access_token);
        }
    },

    // Check the token is still valid on the server for access (also get User info)

    checkTokenValid: function(access_token){

        $.ajax({
            type: 'GET',
            url: 'http://domain.com/api/oauth/userinfo',
            data: {
                access_token: access_token
            },
            dataType: 'json',
            success: function(data) {

                console.log('success');

                console.log(data);

                if( data.error ) {
                    refresh_token = window.localStorage.getItem('refresh_token');
                     if( refresh_token == null ) {
                         Users.logout();
                     } else {
                         Users.refreshToken(refresh_token);
                    }
                } else {
                    App.home();
                }
            },
            error: function(a,b,c) {

                console.log('error');

                console.log(a);

                refresh_token = window.localStorage.getItem('refresh_token');
                if( refresh_token == null ) {
                     Users.logout();
                 } else {
                     Users.refreshToken(refresh_token);
                }
            }
        });

    },

    // Request a new access token using the refresh token

    refreshToken: function(refresh_token){

        $.ajax({
            type: 'GET',
            url: 'http://domain.com/api/oauth/token',
            data: {
                grant_type: 'refresh_token',
                refresh_token: refresh_token,
                client_id: 'NTEzN2FjNzZlYzU4ZGM2'
            },
            dataType: 'json',
            success: function(data) {
                if( data.error ) {
                    alert(data.error);
                } else {
                    window.localStorage.setItem('access_token', data.access_token);
                    window.localStorage.setItem('refresh_token', data.refresh_token);
                    access_token = window.localStorage.getItem('access_token');
                    refresh_token = window.localStorage.getItem('refresh_token');
                    App.home();
                }
            },
            error: function(a,b,c) {
                console.log(a,b,c);
                Users.logout();
            }
        });

    },

    // send login credentials and store tokens in localStorage and in variables

    login: function() {
        $.ajax({
            type: 'GET',
            url: 'http://domain.com/api/oauth/token',
            data: {
                grant_type: 'password',
                username: $('#Username').val(),
                password: $('#Password').val(),
                client_id: 'NTEzN2FjNzZlYzU4ZGM2'
            },
            dataType: 'json',
            success: function(data) {
                if( data.error ) {
                    alert(data.error);
                } else {
                    window.localStorage.setItem('access_token', data.access_token);
                    window.localStorage.setItem('refresh_token', data.refresh_token);
                    access_token = window.localStorage.getItem('access_token');
                    refresh_token = window.localStorage.getItem('refresh_token');
                    App.home();
                }
            },
            error: function(a,b,c) {
                console.log(a,b,c);
            }
        });
    },

    // Clear the localStorage and token variables and load the login (splash page)

    logout: function() {
        localStorage.removeItem('access_token');
        localStorage.removeItem('refresh_token');
        access_token = window.localStorage.getItem('access_token');
        refresh_token = window.localStorage.getItem('refresh_token');
        App.splash();
    }
};

希望代碼都有意義......但簡而言之,它會向API發送一個用戶名和密碼,然后發回一個access_token和refresh_token,然后我使用HTML5中的localStorage存儲。 一旦access_token不再起作用,refresh_token用於獲取新的access_token,因此用戶無需繼續登錄即可獲得無縫體驗(除非他們實際注銷!)。 這是由checkTokenValid函數處理的,我調用它來檢查它是否仍然有效,並且如果refresh_token不存在(或者也是無效的),請求新的令牌或讓用戶再次登錄。

  1. 第一個問題是必須存儲refresh_token。 這通常不是問題,因為它存儲在服務器端,但是因為它是客戶端,所以客戶端ID被公開,因此如果有人要訪問用戶瀏覽器,他們可以請求新的令牌。 那么如何在不使用刷新令牌的情況下讓用戶登錄(即自動請求新的access_token)? 這是一個問題,因為它只在用戶機器上!

  2. 第二個問題,是我被告知我不應該使用這種類型的授權類型(密碼/資源所有者密碼憑證),因為它是客戶端,因此客戶端ID和秘密之類的東西無法得到保護。 而我應該使用Implicit。 但是我無法看到這將如何幫助我解決第一個問題。 有人能舉例說明嗎? 以及如何解決上面的refresh_token問題。 從我讀到的有關隱式授權類型的內容來看,它所做的只是簡化令牌進程(通過刪除對客戶端ID的需要)並且實際上並沒有做任何不同的事情。

  3. 最后,因為應用程序將始終是使用API​​的唯一應用程序,所以用戶無需通過令牌授權類型進程,因此整個客戶端ID的設置似乎有點過分,因為只是一些JavaScript與之交談一個API。 我還有其他選擇嗎? 我已經考慮過解雇OAuth而只是改用Basic Auth ......但是會話呢? 因為我不會有令牌! 思考?

1.如何防止用戶登錄每個請求

沒有人強迫您發出刷新令牌,或者在每次請求時使訪問令牌失效。 如果您發出有效一小時或兩小時的訪問令牌,那么用戶應該有足夠的時間使用該網站而不允許“無限”刷新鏈刷新令牌提供(通常使用刷新令牌將導致發出新的訪問和刷新令牌)。 選擇允許典型用戶操作期限的有效期,允許一些額外的期限,並且您可以完全避免發出刷新令牌。

當然,存在一些用戶超過該時間跨度並被迫再次登錄的風險。 我認為通常如果發生這種情況,如果向他們解釋說這是出於安全原因以及簡單,非技術性條款發生的事情,那么他們就可以了。 最后,它總是在最佳安全性和最佳可用性之間進行權衡:顯然,更好的可用性將使用刷新令牌 - 您的用戶在使用頁面時將永遠不會被迫重新登錄 - 但如果您這樣做,你必須忍受存在他們受到損害的風險。

本地存儲受同一原始策略的保護,因此它與瀏覽器為存儲內容提供的任何內容一樣安全。 就個人而言,我喜歡使用會話cookie存儲令牌的想法 - 它可以跨瀏覽器選項卡工作,當瀏覽器關閉時被清除,當用戶手動清除他們的cookie時,他們會得到被你忘記的預期行為頁面(他們不會使用瀏覽器本地/會話存儲)。 如果您這樣做,請確保使用僅安全的cookie。 將cookie路徑設置為不存在的值將使其不會在每個請求上傳輸,盡管這純粹是消息大小的考慮因素,因為它將在每個請求上傳輸(在Authorization標頭中)。 無論您使用何種存儲類型,您總是容易受到XSS攻擊,因此請務必謹慎對待它。

我的觀點簡而言之:您不需要在每次請求時發出刷新令牌或過期訪問令牌。 如果您想避免使用刷新令牌,請讓您的訪問權限更長久 - 但您必須忍受用戶在使用您的網站時必須重新進行身份驗證的可能性。

2.在資源所有者密碼憑據流上使用隱式流

您不鼓勵使用資源所有者密碼憑據流的原因通常是因為它強制用戶公開其用於對其進行身份驗證的其他身份提供程序的憑據。 即如果您允許您的用戶使用他們的Google / Facebook登錄,則不應使用該流程,因為用戶必須將您的用戶名和密碼提供給您的應用/網站,並且您可以使用他們的各種惡作劇他們。

另一方面,由於您了解我的問題,讓您自己的OAuth2服務器啟動並運行,您就是自己的身份提供商 - 您已經可以訪問用戶的憑據(用戶名和散列密碼),因此避免使用資源所有者流沒有額外的安全性好處。 RFC 6749實際上沒有指定客戶端ID和秘密用於此流程(並且它也沒有多大意義),因此,不需要保護您不必提供的內容。

我的觀點簡而言之:因為您是自己的身份提供者,使用資源所有者密碼憑據流程是可以的。 您不需要客戶端ID和/或密碼。 Autorization Grant流程和隱式流程適用於您針對其他身份提供商(例如Google或Facebook)進行身份驗證時的情況

3.保持或不保持基於令牌的身份驗證

可能最常用的身份驗證機制仍然是基於會話的,即服務器,當您登錄時,會向您發出會話ID,通過該會話ID跟蹤您已成功通過身份驗證的事實。 通常,此會話ID存儲為cookie。 美妙的是你幾乎沒有任何實現問題 - 設置cookie,瀏覽器將為你處理幾乎所有細節。 在服務器上,您所要做的就是檢查會話是否有效。 但是,這種機制仍然容易受到XSS攻擊,更糟糕的是XSRF(跨站點請求偽造)。 雖然不是不可能防范,但檢測並防止這種情況發生有點痛苦。 使用基於令牌的身份驗證系統,您可以獲得內置的XSRF保護。

我的觀點簡而言之:既然您已經啟動並運行了OAuth2服務器,我會堅持下去。 如果使用資源所有者密碼憑據流,則不需要使用客戶端ID和密碼。

  1. 刷新令牌對於您使用的流類型是可選的。 您可以在需要時使用密碼流來獲取另一個訪問令牌並省去刷新令牌。
  2. 資源所有者授予類型是您的案例的正確流類型,因為它是您的客戶端使用您的oauth服務器。 由於您的客戶端是基於瀏覽器的客戶端,因此不需要使用客戶端ID。 為了感受它,想象一下你使用第三方oauth服務器,它將是他們的瀏覽器客戶端代碼,要求輸入密碼或檢查一些cookie或localstorage狀態。 要求用戶授權操作並提供用戶名和密碼的骯臟工作必須在某個地方完成,因為在您的情況下它不是第三方,它的oauth服務器和Web客戶端正在進行詢問,這是不可避免的,必須完成。
  3. 堅持使用資源所有者授予類型來生成訪問令牌。 如果您想將其實際存儲在localStorage中,它將取決於您,它不會像用戶友好,並且每次刷新硬盤時都需要重新登錄,但如果您擔心可以訪問令牌,則更安全客戶。 雖然具有該級別訪問權限的攻擊者可以發起許多其他攻擊,但請保持透視。

為了獲得刷新令牌,您需要使用代碼流,而不是隱式授權流。

您無法在隱式流中安全地刷新令牌。

顯然,您可以在客戶端中實現授權代碼流(即 - 就像它是服務器一樣),但這會導致2個問題:

1)如果您的IDP(身份提供商)位於您的客戶端以外的其他域中,您的瀏覽器將阻止您進行HTTP調用以從用戶生成的代碼(或使用refresh_token)生成令牌。

2)您的客戶秘密將在客戶內部提供 - 一個安全漏洞。 刷新令牌也將可用 - 另一個安全漏洞。

簡而言之 - 使用隱式oAuth流時,無法實現“無縫刷新體驗”。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM