HTML5 Web 应用程序中 OAuth2 的本地存储与 cookie

2024-03-26

我目前正在尝试使用 OAuth2 来开发一个完全用 JavaScript 构建的移动应用程序,该应用程序与 CakePHP API 进行通信。查看以下代码以了解我的应用程序当前的外观(请注意,这是一个实验,因此代码混乱,区域缺乏结构等......)

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();
            });
        });
    }(),
    checkAuthenticated: function() {
        access_token = window.localStorage.getItem('access_token');
        if( access_token == null ) {
            App.splash();
        }
        else {
            Users.checkTokenValid(access_token);
        }
    },
    checkTokenValid: function(access_token){

        $.ajax({
            type: 'GET',
            url: 'http://domain.example/api/oauth/userinfo',
            data: {
                access_token: access_token
            },
            dataType: 'jsonp',
            success: function(data) {
                console.log('success');
                if( data.error ) {
                    refresh_token = window.localStorage.getItem('refresh_token');
                     if( refresh_token == null ) {
                         App.splash();
                     } else {
                         Users.refreshToken(refresh_token);
                    }
                } else {
                    App.home();
                }
            },
            error: function(a,b,c) {
                console.log('error');
                console.log(a,b,c);
                refresh_token = window.localStorage.getItem('refresh_token');
                 if( refresh_token == null ) {
                     App.splash();
                 } else {
                     Users.refreshToken(refresh_token);
                }
            }
        });

    },
    refreshToken: function(refreshToken){

        $.ajax({
            type: 'GET',
            url: 'http://domain.example/api/oauth/token',
            data: {
                grant_type: 'refresh_token',
                refresh_token: refreshToken,
                client_id: 'NTEzN2FjNzZlYzU4ZGM2'
            },
            dataType: 'jsonp',
            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);
            }
        });

    },
    login: function() {
        $.ajax({
            type: 'GET',
            url: 'http://domain.example/api/oauth/token',
            data: {
                grant_type: 'password',
                username: $('#Username').val(),
                password: $('#Password').val(),
                client_id: 'NTEzN2FjNzZlYzU4ZGM2'
            },
            dataType: 'jsonp',
            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);
            }
        });
    },
    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();
    }
};

我有一些与 OAuth 实施相关的问题:

  1. 显然,将 access_token 存储在 localStorage 中是不好的做法,我应该使用 cookie。谁能解释为什么?据我所知,这不再安全或不太安全,因为 cookie 数据不会被加密。

    根据这个问题:本地存储与 Cookie https://stackoverflow.com/questions/3220660/local-storage-vs-cookies无论如何,将数据存储在 localStorage 中仅在客户端可用,并且不会像 cookie 那样执行任何 HTTP 请求,因此对我来说似乎更安全,或者至少据我所知似乎没有任何问题!* *

  2. 关于问题1,使用cookie来确定过期时间,对我来说同样毫无意义,就好像你看代码一样,在应用程序启动时发出请求来获取用户信息,如果它已过期,则会返回错误在服务器端,并且需要一个refresh_token。因此,不确定在客户端和服务器上都有过期时间的好处,而服务器才是真正重要的。

  3. 如何获取刷新令牌,无需 A、将其与原始 access_token 一起存储以供稍后使用,以及 B) 还存储 client_id?我被告知这是一个安全问题,但我以后如何使用这些,但在纯 JS 应用程序中保护它们呢?再次查看上面的代码,了解到目前为止我是如何实现的。


您似乎正在使用资源所有者密码凭证 https://www.rfc-editor.org/rfc/rfc6749#section-4.3OAuth 2.0 流程例如提交用户名/密码以取回访问令牌和刷新令牌。

  • The 访问令牌可以在 JavaScript 中公开,访问令牌被公开的风险因其较短的生命周期而得到缓解。
  • The 刷新令牌不应暴露给客户端 JavaScript。它用于获取更多访问令牌(正如您在上面所做的那样),但如果攻击者能够获取刷新令牌,他们就能够随意获取更多访问令牌,直到 OAuth 服务器撤销对客户的刷新令牌发出。

考虑到这一背景,让我回答您的问题:

  1. cookie 或本地存储将为您提供跨页面刷新的本地持久性。将访问令牌存储在本地存储中可以为您提供更多针对 CSRF 攻击的保护,因为它不会像 cookie 那样自动发送到服务器。您的客户端 JavaScript 需要将其从本地存储中取出并在每个请求时传输它。我正在开发一个 OAuth 2 应用程序,因为它是单页方法,所以我两者都不做;相反,我只是将其保留在记忆中。
  2. 我同意...如果您存储在 cookie 中只是为了持久性而不是为了过期,那么当令牌过期时,服务器将响应错误。我认为您可能创建具有过期时间的 cookie 的唯一原因是,您可以检测它是否已过期,而无需先发出请求并等待错误响应。当然,您可以通过保存已知的过期时间来对本地存储执行相同的操作。
  3. 我认为这是整个问题的关键......“我如何获得刷新令牌,没有A,将其与原始access_token一起存储以供以后使用,B)还存储client_id”。不幸的是你真的不能......正如介绍性评论中所指出的,有刷新令牌客户端否定了所提供的安全性访问令牌的寿命有限。我在我的应用程序中所做的事情(我没有使用任何持久的服务器端会话状态)如下:
  • 用户向服务器提交用户名和密码
  • The server然后将用户名和密码转发到 OAuth 端点,如上面的示例所示http://domain.example/api/oauth/token,并接收到访问令牌和刷新令牌.
  • 服务器加密刷新令牌并将其设置在 cookie 中(应该仅限 HTTP)
  • 服务器响应仅访问令牌以明文形式(在 JSON 响应中)和加密的仅 HTTP cookie
  • 客户端 JavaScript 现在可以读取和使用访问令牌(存储在本地存储或其他任何位置)
  • 当访问令牌过期时,客户端向服务器(不是 OAuth 服务器,而是托管应用程序的服务器)提交新令牌的请求
  • 服务器接收它创建的加密的仅 HTTP cookie,对其进行解密以获取刷新令牌,请求新的访问令牌并最终返回新的访问令牌在回应中。

不可否认,这确实违反了您正在寻找的“仅 JS”约束。但是,a)您确实不应该在 JavaScript 中使用刷新令牌,b)它在登录/注销时需要非常少的服务器端逻辑,并且不需要持久的服务器端存储。

关于 CSRF 的说明:正如评论中指出的,该解决方案没有解决跨站请求伪造 http://en.wikipedia.org/wiki/Cross-site_request_forgery;看到OWASP CSRF 预防备忘单 https://www.owasp.org/index.php/Cross-Site_Request_Forgery_(CSRF)_Prevention_Cheat_Sheet了解解决这些形式的攻击的更多想法。

另一种选择是根本不请求刷新令牌(不确定这是否是您正在处理的 OAuth 2 实现的一个选项;刷新令牌是可选的根据规格 https://www.rfc-editor.org/rfc/rfc6749#section-5.1)并在过期时不断重新进行身份验证。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

HTML5 Web 应用程序中 OAuth2 的本地存储与 cookie 的相关文章

随机推荐

  • JPA,外部化列、表或模式名称?

    我将 Spring Hibernate Tomcat Oracle 应用程序改编为分布式形式 并且需要在 JPA 注释中自定义模式名称 类似于 Spring EL Entity Table name LOSS schema app data
  • 重写java中的方法,然后将对象转换为父类行为

    我有一个父类 A 和一个子类 B B 重写了 A 中的方法 f public class A public String f return A public class B extends A public String f return
  • Monotouch/WCF:为什么无法覆盖wcf绑定超时设置

    Monotouch WCF 为什么无法覆盖 wcf 绑定默认超时设置 public class MyServiceClient ClientBase
  • Jade/Pug if else 条件用法

    我正在使用 js 文件将日期发送到 jade 文件Node js 当 的时候 date 场是false 它执行 else 并打印man因为它的答案 可能出了什么问题 if date false date else man 如果 date 为
  • ActiveRecord什么时候保存关联?

    我知道它会保存关联autosave true as per https api rubyonrails org classes ActiveRecord AutosaveAssociation html https api rubyonra
  • 按照这些规范用Python制作一个空心盒子?

    我要 编写一个 python 程序 提示用户输入一个正整数 n 然后该程序打印一个包含 n 行和 2 n 列的空心矩形 例如 输入 3 将输出 我的代码是 n int input Please enter a positive intege
  • 对片段之间的过渡进行动画处理

    我正在尝试为片段之间的过渡设置动画 我从以下得到了答案 Android 片段和动画 https stackoverflow com questions 4817900 android fragments and animation Frag
  • 将匿名对象传递给视图

    我想将两个值从控制器操作传递到 asp net MVC 3 Razor 视图 我在操作方法中这样做 var model new reportid rid refno refernumber return View model 但是当我尝试像
  • 在 FB 和 LINKEDIN 上共享文本与页面文本?

    我刚刚偶然发现在 Facebook LinkedIn 上将文本 主题标签 与文本一起共享 这是我正在尝试做的事情 标签 STUFF li class facebook a target blank href facebook a li 不幸
  • SSIS 脚本任务在计划时不工作

    我找不到类似的问题 我有一个 SSIS 包 其中包含一个 Visual Basic 脚本任务 其中包含以下行 msgbox some text 它从 BIDS 运行良好 并从 MSDB 手动执行 但当我在 SQL Server 代理中安排它
  • 了解缓存?

    昨天 我们家里没有电 因此无法上网 所以我认为我无法让我的网络应用程序在本地工作 因为在 index html 末尾我有
  • 如何在 macOS 上的 Sublime Text 中在同一窗口中打开文件和文件夹?

    如果我打开 foo txt然后打开 baz bar txt从终端 Sublime Text 总是打开 2 个不同的窗口 打开所有这些不同的窗户真是令人烦恼 我尝试过更改 ST 的一些设置 但似乎没有任何办法可以解决这个问题 有没有办法让所有
  • 插件包的 Xcode 单元测试

    我想将单元测试添加到我的应用程序中 不幸的是 目前大部分功能都位于插件中 我的插件基础设施由一个带有插件基类和一些共享帮助器类和资源的框架组成 现在我想添加一个能够测试我的插件的单元测试 我想创建一个 测试 应用程序来加载该包并将所有调用重
  • Ruby BCrypt 哈希比较

    我正在尝试使用 Sinatra 和 BCrypt 实现看似非常简单的身份验证方法 但显然我错过了一些东西 用户预先分配了一个临时密码 该密码以明文形式存储在数据库中 我根据临时密码进行身份验证 然后创建 salt 和 password ha
  • 使用 strtotime() 从字符串(各种时间单位)中减去间隔;

    我正在创建一个票务系统 我已经存储了票证创建日期和当前日期之间的间隔 每张票证都有一个存储为字符串的解决时间限制 targetTime 15 分钟 4 小时等 我试图通过从时间限制字符串中减去间隔来计算剩余时间 我尝试在 targetTim
  • 将 NULL 值插入双精度数据类型 MySQL Python

    我有一张桌子 这是创建语句 CREATE TABLE runsettings runnumber mediumint 9 NOT NULL equipment varchar 45 NOT NULL wafer varchar 45 NOT
  • 将 Unix 纪元时间戳转换为 JavaScript 日期时间戳

    我有一个带有 Unix 纪元时间戳的数据库 我想在 Highcharts 图表中绘制这些值 这需要 JavaScript 日期时间戳 自 1970 年 1 月 1 日以来的毫秒数 数据库数组当前如下所示 data 1519395624 10
  • 无法在 Anko 中调用 StartActivityForResult

    我对 android 还很陌生 我正在尝试用 kotlin 来学习它 在这段代码中 mHelp setOnClickListener context startActivity
  • python中连接数据点的线的箱线图

    我试图根据与点相关的特定关系来连接线 在此示例中 线条将连接玩家所在的球场 我可以创建基本结构 但还没有找到一种相当简单的方法来创建此附加功能 import pandas as pd import numpy as np import ma
  • HTML5 Web 应用程序中 OAuth2 的本地存储与 cookie

    我目前正在尝试使用 OAuth2 来开发一个完全用 JavaScript 构建的移动应用程序 该应用程序与 CakePHP API 进行通信 查看以下代码以了解我的应用程序当前的外观 请注意 这是一个实验 因此代码混乱 区域缺乏结构等 va