Dva: oauth2认证样例

Created on 5 Sep 2016  ·  26Comments  ·  Source: dvajs/dva

有没有关于dva的OAuth2的样例,关于如何组织modal,如何进行权限校验?

Most helpful comment

至于 @u0x01 的回复,说了比较多混乱和无用的信息

其实总结起来就那么几点:

  1. 直接把 access_token 丢给前端而不用 Session 是因为需要跟 Client 模式共用接口,这种接口往往不支持 Session 验证,或者用在分布式系统上管理 Session 本身就不方便

  2. 对于防止 XSS 和 CSRF 攻击的安全性,要分开来分析

    2.1 对于防止 CSRF,前端把 access_token 放到 HTTP 头部发送,后端也仅仅支持头部验证,同时把接口设计好,就足够了。因为第三方网站仅仅通过表单是没办法发送额外的 HTTP 头部的,且第三方网站的 JS 没办法发送数据到自己的网站(CORS 正确设置的情况下)

    2.2 对于防止 XSS,前端存 access_token 确实是不安全的,可以被 JS 随意读取并跨域发送;而设置了 HttpOnly 的 Cookie 属于 Credentials 资源,现代浏览器对于 Credentials 的跨域限制非常严格几乎没有办法获取。但是说到底防止 XSS 不应该是防止注入脚本 + 避免引用不可信的外部脚本吗?这个 HttpOnly 只能说是亡羊补牢了

  3. 最安全的措施是后端做入口代理,设置 CORS 的 Access-Control-Allow-Origin 仅允许前端域名。用户登录成功后返回 HttpOnly 的 Session,同时加上一个头部字段比如 X-CSRF-TOKEN 的随机码,这个随机码和从 OAuth 接口获取到的 access_token 存在一起。前端用 Ajax 或 fetch 发送数据需要带上 Cookie 以及这个头部,后端验证完 Cookie 的有效性还要验证 X-CSRF 头部的有效性。这样就能基本防止 CSRF 和普通的 XSS 窃取权限了

总之,前端来管理 access_token 不能粗暴的认为是不安全的

Update 1:

中间件或者后端用 jwt 来加密 access_token,同时做好表单的注入检测和 CSP 来防止 XSS

All 26 comments

oauth2 校验不是通过 url 跳转来做的吗? 是否登录通过判断 cookie 就好了吧。

OAuht2有些是通过access_token来做的,不是url,纯粹RESTFul的格式

如果有 dva 和 Spring Boot Oauth2 集成的例子就更好了 :D

@soulmachine 我用的就是这个,有空的话会整理出来。

@WhatAKitty 通过 access_token 的话 access_token 存储在哪里?存储在 local/seesionStorage 会有 XSS 安全风险的。

@u0x01 是存储在localstorage里面的,其实也不会有太大风险,access_token是有时间限制的,超时后无法再次登录,而且没有不会有refresh_token的权限。而且,也没有其他方法了,查询了其他相关资料,都是存储在localstorage里面。

@WhatAKitty 现在黑客都是利用或者自己写自动化工具攻击的,access_token 两个小时的有效期足够做很多事情了,“也不会有太大风险”的结论怎么得出的?
其次, refresh_token 不放在 localstorage 那你放哪儿?
access_token 比较适合给 APP 用,纯 JS 端不应该使用 access_token 方案,依旧有跨站攻击风险。
现在最安全的方案还是 set-cookie 时加上 http-only。
检测登录的时候带上 cookie 去服务器拉一下seesion状态,失败就直接跳到登陆页。
安全也是生产力,重于泰山,不可以在这里一点上偷懒。

@u0x01 refresh_token的话,我不会赋予给js客户端的。所以一旦超期必须重新认证。之前可能没有讲清楚,因为我们的应用是这样的:access_token只是一个允许你访问的钥匙,如果想要访问到具体资源,必须经过用户认证,这个过程是有SSL保障的,所以也不会出现你说的各种风险。
至于你说的,最安全的方案,我也考虑过使用cookie,但是其一:我没做到服务端同时兼容OAUTH2和LOGIN PASSWORD的方案,因为没法在使用LOGIN PASSWORD 登录后给我返回一个access_token,可能有其他办法,但是我没找到。其二:由于项目时间需求问题,只能折中选择该种方案。所以,这个方案,看似欠妥当了点,但是实际上不会有安全问题。

@WhatAKitty 没听说过SSL“能保障网站不被XSS/CSRF攻击”,两者之间没有什么直接关系。
如果你能保证自己的网站系统100%没有XSS/CSRF漏洞,那么你可以存储到 localStorage 直接让JS访问凭据。(但我没见过哪家公司敢打包票自己系统没有安全漏洞的)

而且我不理解什么叫 想要访问到具体资源,必须需要经过用户认证。在 OAuth2 规范中,获得了 access_token 就代表用户(或 Provider)已经认可了这项授权。

另外你提到的:
因为没法在使用 LOGIN PASSWORD 登录后给我返回一个access_token
是对 OAUth2 规范的错误理解,也是错误的用法。

LOGIN PASSWORD 方式实质上就是 UA 直接在向 OAUth2 Provider 请求鉴权的过程,参见RFC6749。这时候得到的应该是 authorization_code,UA 拿到 authorization_code 后,再送给 Client 去 OAUth2 Provider 验证有效性,如果验证通过,Client 会从 OAUth2 Provider 拿到 access_token。这时候 Client 才可以使用 access_token 访问资源。Client 再给 UA 颁发 SessionID。
(这里的 Client 是指业务方的服务器或 APP,不是终端用户,终端用户是 Resource Owner,UA 是浏览器)

总而言之,应该使用 SessionID 来鉴别用户,就算是不使用 SessionID 也应该把 access_token 放在 http only 的 cookie 中:

1. Don't use local storage for session identifiers. Stick with cookies and use the HTTPOnly and Secure flags.
2. If cookies won't work for some reason, then use session storage which will be cleared when the user closes the browser window.
3. Be cautious with storing sensitive data in local storage. Just like any other client side storage options this data can be viewed and modified by the user. 

如果你真的想要把 access_token 暴露给客户端,折中的方法是:

// GET /seesion
    access_token := ctx.Session().GetString("access_token")

    if access_token != "" {
        ctx.JSON(200, {"signed": true, "access_token": access_token})
    } else {
        ctx.Redirect("//yoursite.com/login")
    }

但我依旧推荐只放到 cookie 中,JS 使用 GET /seesion 的方法判断是否已经登录就可以了,其他的资源照常访问,服务器发现 access_token 过期后直接跳转到登录页:

// GET /seesion
    access_token := ctx.Session().GetString("access_token")
    if signed != "" {
        ctx.JSON(200, {"signed": true})
    } else {
        ctx.Redirect("//yoursite.com/login", "you need login first.")
    }

// GET /posts/:postid
    access_token := ctx.Session().GetString("access_token")

    if access_token != "" && isValidAccessToken(access_token) {
        ctx.JSON(200, getPostWithAccessToken(access_token, postid))
    } else {
        ctx.Redirect("//yoursite.com/login", "you need login first.")
    }

当然,如果你认为你们网站系统没什么攻击价值的话,当我什么都没说过。

这种“看似欠妥当了点,但是实际上不会有安全问题。”的想法,不知道今后会给你的雇主带来多大的损失:
盘点企业信息安全泄漏
某通百万客户信息被实时兜售(这家公司信息安全泄漏问题到现在都没有彻底解决,前段时间被某品会等多家大型电商点名禁用)

信息安全任重道远。

@u0x01
首先说一下之前我回复的,可能没有说清楚,在获取access_token的过程中,不仅需要client_id和client_secret(这个也可能不需要)的同时,还需要提供用户名和密码。

能保障网站不被XSS/CSRF攻击

还有就是CSRF已被禁用。至于XSS,所有存储至数据库的内容,都会被自动转移,过滤非法字段,虽然说可能有遗漏,但是不可能完全杜绝,信息安全没有绝对安全,就说微软、苹果也一样,我就不信你的系统能达到百分百安全。

就算是不使用 SessionID 也应该把 access_token 放在 http only 的 cookie 中

Http only也不是绝对安全。

是对 OAUth2 规范的错误理解,也是错误的用法。

我不知道这种算不算错误用法,但是自己的应用难不成还需要请求一个授权页面,用户登录后,获取授权页面,然后同意才可访问?是否过于麻烦?就目前来说,没有网站对于自己的应用这么做吧?

另外,我理想中的认证方式可能跟你说的不同,因为是我自己的服务器提供OAuth2的认证服务。所以我认为对与自己的浏览器客户端,是这样的:UA请求AS,AS发现未认证,跳转至LOGIN,然后用户登录后,成功获取访问服务器的权限(在这里可能是access_token,也可能可以直接开放资源服务访问权限),之后,用户在js端可以直接/间接(access_token)请求资源服务器内的资源。

也就说,我理想中的认证方式,是针对于我们自己的UA来说的,我认为如果是第三方才可能需要通过你说的authorize_code来获取用户是否可以访问的授权页面。

以上是我的理想认证方式,可惜本人水平有限,无法通过Spring的OAuth2来取得我想要做的效果。

不知道今后会给你的雇主带来多大的损失

首先,想说明点,我们的项目非常紧迫,架构是近期才赶上架的,如果可以的话,我难道不会做到进益求精?但是,时间不允许,你的老板允许你拖着客户的项目,在一边玩技术安全性?现实往往是残酷的,你的老板可能压根不关心客户的安全性问题,他们更关注的是利益。 我能做到的,只能是尽可能保证安全性。

@u0x01 其实对于我自己来说,我也想要做到我设计的架构没有任何安全性问题,但是现实是:

  1. 个人技术水平达不到这个要求,我自己并不是什么技术大牛
  2. 缺少一个真正技术大牛来引导,如果有人愿意引导我,我想我会很乐意

目前为止,我所有的技术,都是一个人探索,没有人引导,有些欠缺的方案的确是我的不足。

@WhatAKitty
既然你项目赶,老板也不在乎那就现按你的思路做吧。

但是自己的应用难不成还需要请求一个授权页面,用户登录后,获取授权页面,然后同意才可访问?是否过于麻烦?就目前来说,没有网站对于自己的应用这么做吧?

请参看 OAuth2 的相关规范,OAuth2 有隐式许可这个概念。

也就说,我理想中的认证方式,是针对于我们自己的UA来说的,我认为如果是第三方才可能需要通过你说的authorize_code来获取用户是否可以访问的授权页面。

OAuth2 似乎没有第三方这个概念。

另外,你把 CSRF 防护禁用掉干什么… 那不是给自己挖坑么。
我也没有说 http only 是绝对安全的,http only 主要解决的安全问题是阻止 js 读取 cookie。配合 SSL 就可以阻止第三方监听到 cookie(非中间人攻击的情况下)。

@WhatAKitty
我之前都是站在老板的角度,但其实出一些安全问题也是好的。一方面你们老板会重视起安全问题,另一方面也给信息安全工程师留口饭吃。

信息安全工程师应该感谢故意留下bug的程序员们 :)

@u0x01 看来你对于OAuth2很了解?有没有可能创建一个群,这方面的应用资料我找到的不是很多,也没有特别多时间去搜集,有些问题可以请教你下。

@u0x01 后端采用无状态,根本就没session,需要用token去redis判断是否过期。请问大神token到底放在哪里才安全

@longzb stateless ≠ session none,你提到的

用token去redis判断是否过期

其实就是把 access_token 当作 session 在使用。建议去了解一下 session 的定义和工作方式。

token到底放在哪里才安全

放在 http_only 的 cookie 中可以防止 JS 直接获取 Cookie,从而减少 XSS/CSRF 攻击的价值及可能性。

@u0x01 嗯。昨天去百度了。打算放cookie了,能安全点也好。

这篇文章,关于隐式的分析不错。
今天正在整理SSM+ANTD的样例,看了这篇文章后,感觉最安全的做法还是使用implicit服务端请求认证服务器,获取token,implicit则请求implicit服务器获取资源服务器的资源。

因为就如这篇文章所说,隐式授权都是有风险的,比如钓鱼,很容易让haker伪装安全客户端请求到资源。

不过需要做到这个,则需要dva支持服务端渲染,现在antd已经支持,dva是否已经支持了呢?@sorrycc

You are not worrying about the right things

Protecting information located on the client?

You don't need to do anything once the information has reached your client (ex.: your browser) in order to protect it. You could store the access token in a cookie, in a hidden field on your webpage, in html5 local cache or plainly visible directly in the middle of the page and it doesn't change anything (except shoulder surfing...).

Worrying about the access token once it's on the client is like opening notepad to write your email password and then worry that an attacker might be able to steal that information from a remote location. It doesn't happen. Unless your computer is already compromised but at this point you have already lost.

Where it makes sense to worry?

Usually, your information is vulnerable when it is in transit. In the case of OAuth2 (implicit flow), the access token will be in transit at two places :

from the authorization server to your browser
from your browser to the resource server
Protecting the information while it is in transit is as easy as using TLS everywhere. Which you should already be doing since you are using OAuth2 and it's required by the protocol.

Now the real problem

The way you intend to use OAuth2 is most likely not the way that you should be using it.

To understand why you will probably misuse OAuth2, you have to know about the flows. OAuth2 define 4 authorization flow

Authorization Code (only good one but... keep reading)
Implicit (false sense of security)
Resource Owner Password Credentials (horrible idea)
Client Credentials (not applicable to your case)
Since you using a javascript client, the only flow that works for you is the implicit flow and now starts the issues.

Implicit flow problems

There are many but let's just talk about the most critical one. Access token are not bound to a specific client! From the specification section 10.16 :

For public clients using implicit flows, this specification does not provide any method for the client to determine what client an access token was issued to.
This open the doors for attacker to impersonate you, the resource owner, and then gain access to the resource server. Let's keep reading section 10.16 :

A resource owner may willingly delegate access to a resource by granting an access token to an attacker's malicious client. This may be due to phishing or some other pretext. An attacker may also steal a token via some other mechanism. An attacker may then attempt to impersonate the resource owner by providing the access token to a legitimate public client.

In the implicit flow (response_type=token), the attacker can easily switch the token in the response from the authorization server, replacing the real access token with the one previously issued to the attacker.

Servers communicating with native applications that rely on being passed an access token in the back channel to identify the user of the client may be similarly compromised by an attacker creating a compromised application that can inject arbitrary stolen access tokens.

Any public client that makes the assumption that only the resource owner can present it with a valid access token for the resource is vulnerable to this type of attack.
That first attack is actually not even an attack but rather just a "flaw" in the implicit flow...

The next attack

Now starts the big troubles. You seem to be trying to use OAuth2 implicit flow as a form of delegated end-user authentication which it is not meant to provide. Back to the specification section 10.16

Authenticating resource owners to clients is out of scope for this specification. Any specification that uses the authorization process as a form of delegated end-user authentication to the client (e.g., third-party sign-in service) MUST NOT use the implicit flow without additional security mechanisms that would enable the client to determine if the access token was issued for its use (e.g., audience-restricting the access token).
At this point it's mostly game over for you.

How to mount that attack?

It's pretty simple. Let's say your REST service required an access token from facebook. All an attacker need to do is to host a service, for example stackoverflow, and require an access token from facebook. When you give the facebook access token to stackoverflow, stackoverflow (our attacker) can now impersonate you with your REST service.

All that because access tokens are not bound to a specific client.

A solution

Don't use the implicit flow and instead use the authorization code flow. Which means that your 100% client side app will need to no longer be a 100% client side app.

Why are you not using the server that is serving the angularjs client to your user to handle the OAuth2 flow?

Reference : http://tools.ietf.org/html/rfc6749

@WhatAKitty 如果每个用户的浏览器的某个标识是唯一的,用户发送请求时自动带上这个标识,不可更改,初次申请token的时候后端可以将token跟这个标识绑定。下次请求跟token预存的浏览器标识一旦对应不上就作为非法请求。。
所以想问下,,浏览器请求时有没有这种标识??

@longzb 一般用的是JSESSIONID,或者也可以自定义,比如oschina用的是他自己的一个id

@WhatAKitty @soulmachine @longzb

这种同时要用到 OAuth2.0 的 Password 和 Client 模式的时候,可以在后端服务器加个入口代理去存储 access_token,同时这个代理还能做前端的 CORS(Spring Boot Oauth2 貌似会拦截 CORS 的 preflight )

前端用 Password 模式只需要发送用户名和密码给代理,代理去获取授权并存储 access_token,然后返回给前端的是代理生成的 SESSION_ID,并且代理需要去管理 access_token 的过期和刷新,暴露给前端的只是 Remember-Me 等体现在 Set-Cookie: Expires= 头部的不同而已

客户端的 Client 模式就只不需要访问代理了,直接到 OAuth 接口去拿授权就行了

至于 @u0x01 的回复,说了比较多混乱和无用的信息

其实总结起来就那么几点:

  1. 直接把 access_token 丢给前端而不用 Session 是因为需要跟 Client 模式共用接口,这种接口往往不支持 Session 验证,或者用在分布式系统上管理 Session 本身就不方便

  2. 对于防止 XSS 和 CSRF 攻击的安全性,要分开来分析

    2.1 对于防止 CSRF,前端把 access_token 放到 HTTP 头部发送,后端也仅仅支持头部验证,同时把接口设计好,就足够了。因为第三方网站仅仅通过表单是没办法发送额外的 HTTP 头部的,且第三方网站的 JS 没办法发送数据到自己的网站(CORS 正确设置的情况下)

    2.2 对于防止 XSS,前端存 access_token 确实是不安全的,可以被 JS 随意读取并跨域发送;而设置了 HttpOnly 的 Cookie 属于 Credentials 资源,现代浏览器对于 Credentials 的跨域限制非常严格几乎没有办法获取。但是说到底防止 XSS 不应该是防止注入脚本 + 避免引用不可信的外部脚本吗?这个 HttpOnly 只能说是亡羊补牢了

  3. 最安全的措施是后端做入口代理,设置 CORS 的 Access-Control-Allow-Origin 仅允许前端域名。用户登录成功后返回 HttpOnly 的 Session,同时加上一个头部字段比如 X-CSRF-TOKEN 的随机码,这个随机码和从 OAuth 接口获取到的 access_token 存在一起。前端用 Ajax 或 fetch 发送数据需要带上 Cookie 以及这个头部,后端验证完 Cookie 的有效性还要验证 X-CSRF 头部的有效性。这样就能基本防止 CSRF 和普通的 XSS 窃取权限了

总之,前端来管理 access_token 不能粗暴的认为是不安全的

Update 1:

中间件或者后端用 jwt 来加密 access_token,同时做好表单的注入检测和 CSP 来防止 XSS

@mdluo 思路清晰,赞👍

@mdluo Sorry,我是一个前段的新手,您说的第3种方法中,后端的部分,我用 Spring boot+spring security基本可以实现,但前端fetch这里如何带上Cookie?因为涉及到跨域,网上有的方案是利用credentials: "include"这个参数解决,但这个参数只能解决GET请求,POST和其他的都有问题。因为第一次用,可能自己写的比较笨。环境是:
前端:DVA 端口8000
后端:Spring boot + Spring security,有X-CSRF-TOKEN也有Cookie

@yoster0520 后端配置 CORS,如果前端有多个地址,后端要动态判断,根据白名单返回唯一的 Access-Control-Allow-Origin,这样才能带 Cookie

Was this page helpful?
0 / 5 - 0 ratings

Related issues

mclouvem picture mclouvem  ·  4Comments

oldfeel picture oldfeel  ·  3Comments

kpaxqin picture kpaxqin  ·  3Comments

yaeSakuras picture yaeSakuras  ·  3Comments

ljjsimon picture ljjsimon  ·  3Comments