使用 OpenID Connect

2019独角兽企业重金招聘Python工程师标准>>> hot3.png

Using OpenID Connect

笔者按: 本文由 Visual Studio 和开发技术 MVP Shaun Luttin编写,作为_周二技术系列文章_的一部分,一级的到了 Visual Studio 和开发技术 MVP Kevin Chalet 的支持。

我们将会开发一个安全的单页应用(SPA)。Cookies 并不适用 SPAs,因为它们需要做防伪处理;因此我们将会通过 Authorization header 来使用 token 流。由于 OAuth2/OpenID Connect 现在已经是实现 token authentication 最流行的方式了,因此我们将会在 demo 中使用这种方式。

什么是 OpenID Connect?

OpenID Connect 通过 id_tokensaccess_tokens 在分布式应用程序安全(distributed application security)上发挥作用。id_token用于验证(authentication); 类似于驾照,护照,或者其他形式的 ID。access_token用于授权(authorization);类似于车钥匙,银行卡,或者门卡之类的。我们用 id_token 来确认我们是谁; 用 access_token 来访问我们的资源(或者谁谁谁的)。

使用 OpenID Connect_第1张图片

为什么不使用 Cookie 进行身份验证和授权

我们不在 APIs 中使用 cookie 的主要原因是为了防御伪造攻击。浏览器会自动发送 cookie 有伪造攻击的风险,并且使用 XSRF 对策在 APIs 也并不好。

Cookie 在使用单域(single domain)的 browser-based 应用上工作得很好。服务端给响应添加一个 cookie ,浏览器自动存储 cookie ,然后浏览器自动的将 cookie 加入到每个发送到 cookie 的域的请求中。

但当应用不使用浏览器,转而使用 native 客户端的时候,cookies 就不再适用了,因为它是浏览器技术。当我们的资源分部在多域(multiple domains)中的时候,cookies 也不适用了,因为它是域相关技术。而且,最重要的,我们需要防止跨站点请求伪造攻击(cross-site request forgery attacks)。这就是为什么我们需要 token 的原因。

一些历史: OAuth, OpenID, 和 OpenID Connect

OAuth 和 OpenID 都是互联网标准。OAuth 用于授权。OpenID 用于认证。OpenID Connect 则两者兼有:它通过指定客户端-服务端之间的协议,来请求,产生,和使用 access_tokens (授权)和 id_tokens (认证)。

  • 2006 – OpenID 1.0. 应用可以索要证明用户拥有身份认证(URL)的权力。
  • 2007 – OpenID 2.0. 向 OpenID 1.0 添加更多灵活性以及另一种格式的身份认证。
  • 2007 – OpenID Attribute Exchange. 应用可以获取和存储终端用户的配置文件信息。
  • 2010 – OAuth 1.0. 用户可以授权应用访问第三方资源服务器的有限权限。
  • 2012 – OAuth 2.0. 和 OAuth 1.0 一样,但是是新协议。
  • 2014 – OpenID Connect. 上述的所有功能都合并到单个协议中,它可以:
    • 验证终端用户的身份
    • 获取终端用户的配置文件信息,并
    • 授予有限权限来访问终端用户的资源f

详细请参阅: http://security.stackexchange.com/questions/44611/difference-between-oauth-openid-and-openid-connect-in-very-simple-term/130411#130411

OpenID Connect Demo

通过单页应用演示 OpenID Connect。OpenID Connect 允许用户通过单一身份登录,通过网站或原生应用。

  • 演示: https://zamboni-app.azurewebsites.net
  • 源码: https://github.com/openiddict/openiddict-samples/tree/master/samples/ImplicitFlow

演示使用了:

  • OpenID Connect, 可互动的身份验证协议
  • Aurelia, 单页应用的框架
  • oidc-client-js, JavaScript 库,用于浏览器中处理 OpenID Connect
  • OpenIddict, ASP.NET Core 库,用于服务端中处理 OpenID Connect,以及
  • NET Core Security, 用于 web 应用的安全和授权的中间件
  • NET Core Identity 用于本地授权

以上五点都是开源的并且发布在 GitHub 上。下面是演示的截屏。

使用 OpenID Connect_第2张图片

可以在 OpenID Connect 演示中尝试的点

  • 点击登录按钮。用于发现和请求授权服务器的授权终结点(authorization endpoint)。在授权服务器上,你(终端用户)可以注册,或者如果你之前有注册的话,你可以登录。授权服务器授权你之后,重定向回单页应用(SPA)。SPA 解析授权服务器的响应,存储 token 到本地存储,之后将响应内容显示到屏幕上。

  • 点击资源服务器 01 - 私有按钮。向资源服务器 01 发送一个被认证请求。请求的授权信息中包含有可用的 access_token。当接收到该请求,资源服务器开始自检,询问授权服务器该 access_token 是否有效。如果授权服务器确定该 access_token 有效,资源服务器则将被请求的受保护资源返回给 SPA。如果access_token 无效或并不存在,资源服务器则返回 401 的禁止响应。

  • 点击登出按钮。 请求授权服务器结束会话终结点。授权服务器将终端用户在服务器上登出之后,重定向到 SPA,并在 SPA 端将用户登出。

OpenID Connect 演示图示

该演示有 SPA,授权服务器,以及资源服务器(s),它们运行在不同的域和不同的服务器上。但是,它们同样也可以运行在单域。

使用 OpenID Connect_第3张图片

本地运行 OpenID Connect 演示

该示例值得你在本地运行一下,并用 Fiddler 来看看它的通信过程。下面说一下怎样在本地执行。

PowerShell

git clone https://github.com/openiddict/openiddict-samples.git
cd .\openiddict-samples\samples\ImplicitFlow
.\RunDemo.ps1

当你希望停止演示实例的时候,在启动它的 PowerShell 中执行 Kill-Demo。

启动该示例需要启动四个命令窗口: 一个 SPA,两个资源服务器,一个授权服务器。

使用 OpenID Connect_第4张图片

实例运行起来之后,访问 http://localhost:9000 查看 SPA ,它和你在 https://zamboni-app.azurewebsites.net 上看到的是一样的。

使用 OpenID Connect_第5张图片

通过 Fiddler 查看本地的 OpenID Connect 示例

设置 Fiddler 只显示 HTTP 请求,我们不希望看到很多噪音记录。

  1. 打开 Fiddler 然后点击过滤器(Filters)。
  2. 选择使用过滤器。
  3. 只显示下列主机(Show only the following Hosts) 添加 localhost
  4. 选择 隐藏包含这些的 URL (Hide if URL contains),添加 scripts browser-sync node_modules
  5. 然后点击 动作 > 执行过滤器(Actions > Run Filters)

使用 OpenID Connect_第6张图片

Fiddler 和本地示例执行起来之后,访问 http://localhost:9000 ,执行下列的步骤:

  • 点击登录. 浏览器跳转到本地的授权服务器。
  • 点击注册成为新用户? 浏览器跳转到注册页面。
  • 完成注册信息然后点击注册. 浏览器返回到 SPA。

执行完以上步骤之后,Fiddler 获取到了以下记录。Fiddler 会给它俘获的 HTTP 请求标记序号 (你的序号有可能不同)。

使用 OpenID Connect_第7张图片

下面是这些步骤的解析。

  • Capture #4

    • 我们点击登录We clicked log in.
    • SPA 通过 OpenID Connect Discovery 确定如何与授权服务器进行交互。
    • 授权服务器的响应中包含了它的终结点和特性。
  • Captures #5 to #7

    • 完成了发现搜索之后,SPA 请求授权终结点。
    • 请求启动 OpenID Connect Implicit flow,并且将下列 URL 参数包含在内:
      • client_id,
      • redirect_uri,
      • response_type,
      • scope,
      • state, and
      • nonce.
    • 授权服务器处理该请求,将 request_id 和请求细节对接,然后重定向回登录页面,保存 request_id 以便之后使用
  • Capture #9

    • 点击注册新用户
    • 由于认证服务器现在使用的是 ASP.NET Core Identity 来授权并/或注册新用户,这是一个简单的从登录页面到注册页面的 MVC 跳转。
    • 這一步的 OpenID Connect 部分關聯到之後的 request_id 使用。
  • Capture #15 to #17

    • 我們填寫完注冊信息,點擊註冊。
    • 授權服務器開始處理請求,重定向到授權端點,再之後跳轉到 SPA。OpenID Connect Implicit Flow 到此完成。

留給你的兩道 Fiddler 作業

  • 使用 Fiddler 查看向資源服務器 01 發送的授權請求。
  • 使用 Fiddler 查看從 SPA 登出的請求。

哦?聽說你在討論 JWTs

JSON Web Tokens (JWTs – 發音 jawt) 是 OpenID Connect 的一部分。 id_token 使用 JWT 格式,並且有時候 access_token 有時也使用 JWT 格式。JWT 格式採用 JavaScript 對象並將其轉換成簽名(signed)及/或加密散列(encrypted hash)。用點號分割為標識頭,負載,和簽名。

下面是一個有標識頭,負載,和簽名的 JavaScript Object (JSON)

{ alg: "HS256", typ: "JWT" }.

{ foo: "bar" }.

[signature]

轉換為有標識頭,負載,和簽名的 JWT 是這樣的:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIifQ.dtxWM6MIcgoeMgH87tGvsNDY6cHWL6MGW4LeYvnm1JA

練習:你可以在這裏 https://jwt.io 生成 JWT ,在這裏 http://jwt.calebb.net 對它解碼。

JWT 規範定義了 JWT 和 JSON 之間的轉化規則。The JWT specification arose for the compact encoding of claims. While the motivation for the JWT format is claims communication, the JWT is not intrinsically associated with authorization/authentication. Rather, it is a way of hashing, signing and/or encrypting JSON.

JWT claims are properties with values. While a JWT can contain any claim that we want it to have, there are a handful of non-mandatory registered claims. Here are three of the most important claims.

  • iss (發布者). 該 JWT 的發布者是誰?
  • sub (持有者). 該 JWT 的持有者是誰?
  • exp (有效期). JWT 什麼時候過期?

舉個駕照的例子,iss 是安大略,sub 是 JOHN DOE,exp 截止 2012/11/26。在一個 OpenID Connect 的 id_token 的上下文中,iss 是授權服務器,sub 是終端用戶(比如,一個郵件地址),exp 則規定了 id_token 的有效期。

使用 OpenID Connect_第8张图片

為 SPA 選擇 OpenID Connect Implicit flow

OpenID Connect flow 其實是一系列步驟,描述了應用如何從服務端申請用於代表終端用戶的令牌(token)。OpenID Connect flow 其實是 OAuth 授權方式的同義詞;也就是說 flow 會指定終端用戶如何獲取應用的授權。不同的 flow 有不同的授權方式。有三種最重要的方式供參考。

更多細節請參閱: http://kevinchalet.com/2016/07/13/creating-your-own-openid-connect-server-with-asos-choosing-the-right-flows/

客戶應用的不同類型

對於本文,我們將會定義三種客戶端類型。可對信息保密的機密客戶端(confidential client)不可對信息保密的公共客戶端(public client)。以及介於中間的高可信度客戶端(highly trusted client),我們相信它不會濫用終端用戶的保密信息。

Confidentiality and publicity exist on a continuum.舉例說,傳統的MVC web 應用運行在服務器上,通過將它的保密信息存儲在服務器上以提供高度機密性。原生應用運行在用戶的設備上,提供中等機密性;它存儲在用戶的設備上的保密信息通常來說比較難於提取。SPA 提供低機密性(也就是公共客戶端),因為它的保密信息都是保存在頁面瀏覽器上,並且很容易提取訪問。

雖然機密性是關於保存保密信息的能力,但是可信度是對客戶端不會共享或者盜用保密信息的期待。所以我們說的高可信度只存在那些我們能掌控的客戶端應用上。

https://tools.ietf.org/html/rfc6749#section-2.1 https://tools.ietf.org/html/rfc6749#section-4.3

Resource Owner Password Credential Grant/Flow

Resource Owner Password Credential (ROPC) 適用於高可信度的客戶端,不管是公開的或是私有的。該流程要求有高可信度,因為客戶端會看到終端用戶的帳號/密碼。所以我們說的高可信度只存在那些我們能掌控的客戶端上。因此允許我們授權服務器的客戶端應用使用 ROPC 是可行的,但是服務端絕對不會允許第三方客戶端使用改流程。此外,當我們在高可信度應用上執行該流程的時候,服務端應當儘可能強制執行客戶端認證。這樣可以防止終端用戶的信息泄露到不可信的應用上。

簡而言之,客戶端的 ROPC 流是這樣的:

  • 與授權服務器直接通訊
  • 通過授權服務器進行身份驗證
    • 客戶端密匙(client secret)(如果是機密客戶端)
    • 重定向鏈接(redirect_uri)(如果是公共客戶端)
  • 在驗證終端用戶的帳號/密碼之後,收到一個 access_token after
  • 看到終端用戶的帳號/密碼

該流程最主要的風險是泄露帳號/密碼。

https://tools.ietf.org/html/rfc6749#section-4.3 https://tools.ietf.org/html/rfc6749#section-10.7

Implicit Grant/Flow

Implicit 流適用於運行在瀏覽器上的公共客戶端。SPA 就是例子。SPA 不適用 authorization code 流,因為 SPA 不能隱蔽的保存它的客戶端密匙或者access_token。而且某些 SPA 並不可信。因此 SPA 也不適用 ROPC 流。

簡而言之,客戶端的 implicit 流是這樣的:

  • 通過瀏覽器與授權服務器通訊
  • 通過它的 redirect_uri 與授權服務器進行身份驗證
  • 在驗證終端用戶之後接收 access_token
  • 無法看到終端用戶的賬號/密碼

該流程的主要風險是客戶端偽造並且/或者泄露 access_token

https://tools.ietf.org/html/rfc6749#section-4.2

Authorization Code Grant/Flow

Authorization Code flow 流適用於可以使用瀏覽器的機密客戶端。此類能夠提供中等到高機密性的機密客戶端的例子是原生應用和 MVC 頁面應用。這使得它們可以利用 authoriation code 流程的兩個主要優點:通過客戶端密匙進行身份驗證並對頁面瀏覽器隱藏了寶貴的 access_token

簡而言之,客戶端的 authorization 流是這樣的:

  • 與授權服務器通訊
    • 通過瀏覽器獲取授權碼(authorization code)
    • 通過後台通道(backchannels)獲取 access_token
  • 通過它的客戶端密匙與授權服務器進行身份驗證
  • 在驗證終端用戶之後獲取授權碼
  • 在驗證授權碼之後獲取 access_token
  • 看不到終端用戶的賬戶/密碼

https://tools.ietf.org/html/rfc6749#section-4.1

適合 SPA 的流程

如果你同時擁有授權服務器和 SPA 的控制權,那麽 ROPC 流是最完美合理的,但是使用 Implicit 流會更安全,並且可以降低暴露終端用戶帳號/密碼的風險。

OpenID Connect 示例中的 ASP.NET Core Authentication

ASP.NET Core Security 和 ASP.NET Core Identity 為本地和第三方的授權提供了可用的中間件和類。在我們的示例授權服務器上,兩個庫我們都有使用。

ASP.NET Core Security 中間件支持使用 code grant/flow 進行第三方身份驗證。

  • OpenIdConnectAuthentication – 為第三方提供配置。
  • OAuthAuthentication – 為第三方提供預配置:
    • GoogleAuthentication
    • FacebookAuthentication
    • MicrosoftAccountAuthentication
    • TwitterAuthentication

ASP.NET Core Identity 提供了傳統的,本地身份驗證,在我們的應用中接受帳號/密碼驗證。許多網站為終端用戶提供了在本地認證和第三方認證的選擇,比如說:

使用 OpenID Connect_第9张图片

此外,ASP.NET Core Security 包含了 CookieAuthenticationJwtBearerAuthentication 中間件,用於將身份驗證狀態保存於頁面瀏覽器。也就是說,在服務端驗證了終端用戶的身份之後,服務端為客戶端提供返回安全 cookie 或者 JWT。客戶端將 cookie 或者 token 儲存起來用於之後的授權請求。CookieAuthentication 中間件生成並校驗 cookie ,而 JwtBearerAuthentication 則用於校驗別處生成的 cookie。

我們示例中的授權服務器利用了 ASP.NET Core Security 和 ASP.NET Core Identity。下圖看起來相當複雜;但是,它還是值得學習的。

在下圖中,我們的 SPA 發起 Implicit 流進行驗證,重定向到授權服務器。此時,授權服務器需要對終端用戶進行身份驗證。授權服務器可以通過本地驗證或者第三方驗證(比如 Twitter)。通常來說終端用戶都會有幾個選擇,但是我們的示例現在只提供本地驗證(嗯..現在)。如果終端用戶選擇通過 Twitter 登錄,授權服務器向 Twitter 會發起 Code Flow (利用 ASP.NET Security 中間件)。

使用 OpenID Connect_第10张图片

OpenID Connect 示例中的社區貢獻

ASP.NET Core Security 和 ASP.NET Core Identity 不會處理上圖中的 implicit 流部分。我們有下面兩個選擇。

  • Identity Server 3
  • OpenIddict (低層 AspNet.Security.OpenIdConnect.Server)

我們示例中使用的是 OpenIddict。OpenIddict 處理 SPA,授權服務器和資源服務器之間的交互。

OpenID Connect 解析/術語 的簡介

OpenID Connect 極大的繼承並擴展了 OAuth 和 OpenID。依賴方(relying party) (客戶端,客戶端應用)是依賴授權服務器進行身份驗證和授權的所有應用。在我們的示例中,SPA 是依賴方,以及資源服務器在使用內建認證的時候也是依賴方。一些依賴方擁有重定向 URL,授權服務器對終端用戶認證結束之後,會使用該URL。**用戶代理(user agent)**代表的是終端用戶的行為,它通常是指依賴方的環境。我們的例子中指的是頁面瀏覽器;它同時代表了終端用戶及 SPA 的環境。**資源服持有者(resource owner)**是一個代名詞,在我們的例子中,即是終端用戶;也就是說,終端用戶擁有在資源服務器上的資源。資源服務器(Resource Server),當然,就是裝載了終端用戶受保護資源(即用戶資源)的服務器。

一圖解釋 OpenID Connect Implicit Flow

使用 OpenID Connect_第11张图片

(4) 獲取終端用戶同意/授權。 在我們的例子中,我們選擇忽略該步驟,因為我們全權控制了 SPA,授權服務器,以及資源服務器。

(8) 校驗 access_token ,如果可用,處理該請求。 access_token 在資源服務器上進行校驗,並在授權服務器上進行確認。如果它是已簽名,加密的 JWT,或者其他專有格式,等等,校驗並檢查是否有效。我們的例子中,access_token 使用 SAML 格式而不是 JWT。我們使用內聯流程校驗它。所謂內聯,是說資源服務器發起 HTTP 請求到授權服務器,授權服務器響應判斷該 access_token 是否可用。為了使工作流程可用,資源服務器將它自己註冊到授權服務器上,並持有用於對其進行身份認證的神秘代碼。這就是我們需要添加到我們 controller 的 AuthorizeAttribute 當中的所有東西了。

FAQ

我們是否可以繼續使用 cookie,並將 access_token 和 id_token 保存到 cookie 當中?

不要在 cookie 中保存 bearer tokens :不要將 bearer token 存於 cookie,這將會導致以明文形式發送(這是 cookie 的默認傳輸方式)。如果將 bearer tokens 保存於 cookies 必須採取跨站點偽造請求防禦措施。(Bearer Token 用例, n.d.)

是否可以用單服務器和域托管授權服務器,SPA,和資源服務器?

當然。

授權服務器如何認證終端用戶是否很重要?

並不,授權服務器可以用視網膜掃描,賬號/密碼,第三方(Google, Facebook, Twitter…),或者其他形式的身份認證。

是否 JWT 是能用於授權/認證;我是否可以在其中存儲別的內容?

自便。

使用 OpenID Connect_第12张图片

关于作者

After receiving a Bachelor of Human Kinetics with honors from the University of British Columbia in 2008, Shaun sampled careers in health care, leisure administration, and academic research. The latter included co-publishing a paper on Motor Learning with Dr. Nicola Hodges.

He wrote his first line of code in 2009, and then enrolled in an intensive Software Systems Developer program at the BC Institute of Technology. Since receiving a Certificate of Technology with distinction in 2010, he has provided software development and technical support services to small, medium, and enterprise-scale businesses. He is a Microsoft Certified Professional Developer.

In 2011 he and his wife Kathryn moved from Vancouver to Salt Spring Island. In his spare time Shaun volunteers, practices sleight-of-hand, and is a prolific reader.

转载于:https://my.oschina.net/ilivebox/blog/868265

你可能感兴趣的:(java,json,javascript)