「ADAM 筆記」.NET Walker: 一次搞懂OAuth與SSO在幹什麼?

    摘自: http://studyhost.blogspot.com/2017/01/oauthsso.html?m=1

<

iframe src=\”http://studyhost.blogspot.com/2017/01/oauthsso.html?m=1\” class=\”iframe-full-content\”>

2017年1月1日 星期日

一次搞懂OAuth與SSO在幹什麼?

最近的Line NotifyLine Login,以及前一陣子的Microsoft Graph API,全都使用到了OAuth作為用戶身分驗證以及資源存取的基礎。但很多讀者會卡在OAuth的運作流程上,根本的原因是不理解OAuth到底是幹嘛的?其存在的目的為何?以及如何應用?

因此,我想花一個篇幅,盡可能短的介紹一下OAuth與SSO,但,與坊間文章不同的是,我希望從應用情境的角度(而非技術)切入談這件事情,冀望能夠讓開發人員對OAuth有個最基本的認識。

OAuth的背景

我們回頭看Line LoginLine Notify中的例子,OAuth在這邊最簡單的應用情境,就是身分驗證。典型的情境中有幾個角色,分別是:

  1. 網站或App的開發單位 : 也就是各位開發人員
  2. OAuth服務的提供者(Provider) : 也就是Line(或Google、Microsoft…etc.)
  3. 終端用戶(End-User) : 網站使用者、Line使用者、消費者、客戶…etc.

上面這三者的關係是什麼?

當我們建立一個網站(例如Pc Home購物)、或App(例如一個手機遊戲),都非常有可能需要建立一組會員機制,這些機制包含:

  1. 登入(包含身分驗證,帳號、密碼保存…等)
  2. 個資管理(用戶名稱、地址、電話、暱稱、手機…等)

以往,幾乎都是每一個網站自己做一套,但這樣有很多麻煩事,首先用戶要記得很多組帳號密碼,而每一個網站都自己搞一套會員機制,網站開發人員自己也很辛苦,加上最近這幾年大家都很重視個資,網站儲存(保管)了很多帳號密碼與個人資料,總是會有被駭的風險。因此,這十年來,很多大廠開始提供登入(與身分驗證)機制服務。

也就是說,小網站你不用自己做登入和會員管理了,你連過來我這邊,我是大網站,我已經有幾百萬上億的用戶,(例如全台灣都用Line),而且早就做了超級安全的會員管理機制,你這小網站何必自己做會員管理呢?你跟我連結不就得了,我大網站來幫你管理個資,提供你登入的服務,你把會員資料通通存我這裡,用戶也不需要記得很多組帳密,只需要記得我大網站的帳密,一樣可以登入你小網站(或稱為第三方應用)來使用你提供的服務,這樣皆大歡喜。

因此,大家就這麼做了。

但提供這樣服務的大廠越來越多,Google、Microsoft、Yahoo…都提供了這樣的服務,導致小網站為了對使用者更貼心,可能要同時連結上很多這種提供身分服務的大網站,如果每一家連結方式都不同,就很煩。因此,業界就開了幾個會,共同決定了一套工業標準,就是OAuth了。

有哪些功能?

所以你會發現,基本上網站開發人員有兩種身分,一種是OAuth服務的提供者(像是Google、微軟、Line),另一種是OAuth服務的使用者,像是一般的小網站(trello)。而終端用戶只需要在大網站申請過帳號,就可以登入小網站來使用服務。

但,大網站當然不能給你(小網站)用戶的帳號和密碼,否則多麼不安全呢?因此OAuth工業標準讓服務提供者(大網站)透過一種標準的作法,在用戶驗證過身分之後,提供一組會過期的令牌給小網站,這就是token。

小網站拿著這個令牌,就可以跟大網站取得用戶的個資,或是其他需要的資料。小網站也可以拿著這個令牌,跟大網站確認該令牌是否已經到期。

所以,整個流程大概是底下這樣:

由於上述過程中的(2),登入畫面是大網站提供的,因此你小網站不會得知用戶的帳號密碼,大網站只會在登入成功後,把一個具有有效期限的Token傳給你小網站,一旦你需要存取用戶的資料,就拿這個token去跟大網站溝通。

當然,實際上的OAuth操作步驟又更複雜,如果你參考我們前面介紹的Line Login那篇,就會知道,用戶被引導到大網站完成登入之後,你小網站是無法直接取得token的,而是取得一個code,再去用這個code跟大網站換得一個token。為何要多這一道手續?因為,網際網路是個不安全的所在,在網路上傳遞的任何東西,都可能被路上經手的路由器或其他設備給擷取、偽造、變更,因此要確保安全,得更加小心一點。

因此一般的OAuth流程,其實應該長得像是底下這樣(這是微軟Graph API的OAuth Auth Authorization Code Flow流程) :

還有更複雜的、更進階的。

如果大網站除了提供用戶的個資之外,還要可以讓小網站有權限做一些額外的事情,像是變更用戶大頭照、取得用戶上傳的檔案、幫用戶book一個行事曆…這都是Office 365/Google Apps裡面典型的情境,如此一來,終端使用者(end-user)可能就要授權小網站,到底能夠使用該用戶在大網站中多少資料,也就是大網站的用戶要賦予小網站多大的權限,來存取該終端用戶的個資? 這部分,一般稱之為 Permission Scope。

所以,OAuth除了提供登入身分驗證之外,也逐漸開始負擔了網站合作之間的授權管理功能。

好,現在回過頭來看,請參考Line LoginLine Notify這兩篇中的例子,你會發現一開始我們都只是組出一個URL,來取得Authorization Code,這一段取得的code是明碼,走的是http get,透過瀏覽器網址列來傳遞,所以在網際網路上是可以被任何人擷取看到的(因此你當然應該加上SSL),但你會發現接下來小網站取得Authorization Code之後,要透過http post,從後端走另一個路徑去跟大網站換得token,這一段並不是走瀏覽器http get,而是在小網站的伺服器端走另一個https路徑,去跟大網站溝通。由於這一段往往是在背後做的(伺服器端對伺服器端,不會經過用戶端),因此安全性相對高(OAuth也有實作成在前端取token的implicit flow,但走後端相對安全點)。如果從後端換取Token,不管是瀏覽器或用戶本身都無法得知token,就算你的用戶被人在瀏覽器或電腦中安裝了木馬也無法得知,再加上Token還有期限,因此相對安全。

這也是我們前面說的,實務上小網站被導引到大網站完成登入之後,並非直接取得token而是取得一個Authorization Code的原因。

所以你也不難理解,既然Token會到期,就衍生出需要更新(refresh)token、判斷token的有效性、設定Token的生命長度…等相關議題,但在這邊就先不介紹了。

更進一步實現SSO

好的,假設網路世界的身分驗證,都是某一個大網站(例如Google)提供的,而其他服務的小網站(網站A、網站B、網站C…),都使用Google提供的身分驗證服務,那這世界就很單純了,一旦用戶登入了網站B,用著用著,連結到了網站A,還需要重新登入一次嗎? 不需要,因為在網站B已經登入過了,這就是SSO(Single Sign On)在internet上的實現。

一旦OAuth提供者和使用者(也就是大小網站),都有實作這樣的功能,那用戶翱翔於網際網路上時,就只需要記得一組帳號密碼了,這世界多麼美好…

當然,現實世界不是這樣的,你想想,當個大網站將會擁有所有人的個資耶,這意味著什麼呢? 不用大腦想也知道。 所以,只有你想做大網站? 不,每個人都想做。因此只要稍具規模網際網路服務提供者,都希望自己是最大的那個身分驗證提供者。

現在、連Line這個IM界的新玩家(相對What’s app、skype來說,真的算是新的),挾著在亞洲(其實也只有台灣、日本、和韓國…)的超人氣,都開始提供OAuth Provider服務了,你說,Line這家公司它還不夠任性嗎?
#搞懂了OAuth和SSO,不妨接著玩玩Line NotifyLine Login,很好玩唷… Smile
——————————————–
如果需要即時取得更多相關訊息,可按這裡加入FB專頁。若這篇文章對您有所幫助,請幫我們分享出去,謝謝您的支持。

3 則留言:

(function() {
var items = null;
var msgs = null;
var config = {};

// 0) {
cursor = parseInt(items[items.length – 1].timestamp) + 1;
}

var bodyFromEntry = function(entry) {
var text = (entry &&
((entry.content && entry.content.$t) ||
(entry.summary && entry.summary.$t))) ||
\’\’;
if (entry && entry.gd$extendedProperty) {
for (var k in entry.gd$extendedProperty) {
if (entry.gd$extendedProperty[k].name == \’blogger.contentRemoved\’) {
return \’\’ + text + \’\’;
}
}
}
return text;
}

var parse = function(data) {
cursor = null;
var comments = [];
if (data && data.feed && data.feed.entry) {
for (var i = 0, entry; entry = data.feed.entry[i]; i++) {
var comment = {};
// comment ID, parsed out of the original id format
var id = /blog-(\\d+).post-(\\d+)/.exec(entry.id.$t);
comment.id = id ? id[2] : null;
comment.body = bodyFromEntry(entry);
comment.timestamp = Date.parse(entry.published.$t) + \’\’;
if (entry.author && entry.author.constructor === Array) {
var auth = entry.author[0];
if (auth) {
comment.author = {
name: (auth.name ? auth.name.$t : undefined),
profileUrl: (auth.uri ? auth.uri.$t : undefined),
avatarUrl: (auth.gd$image ? auth.gd$image.src : undefined)
};
}
}
if (entry.link) {
if (entry.link[2]) {
comment.link = comment.permalink = entry.link[2].href;
}
if (entry.link[3]) {
var pid = /.*comments\\/default\\/(\\d+)\\?.*/.exec(entry.link[3].href);
if (pid && pid[1]) {
comment.parentId = pid[1];
}
}
}
comment.deleteclass = \’item-control blog-admin\’;
if (entry.gd$extendedProperty) {
for (var k in entry.gd$extendedProperty) {
if (entry.gd$extendedProperty[k].name == \’blogger.itemClass\’) {
comment.deleteclass += \’ \’ + entry.gd$extendedProperty[k].value;
} else if (entry.gd$extendedProperty[k].name == \’blogger.displayTime\’) {
comment.displayTime = entry.gd$extendedProperty[k].value;
}
}
}
comments.push(comment);
}
}
return comments;
};

var paginator = function(callback) {
if (hasMore()) {
var url = config.feed + \’?alt=json&v=2&orderby=published&reverse=false&max-results=50\’;
if (cursor) {
url += \’&published-min=\’ + new Date(cursor).toISOString();
}
window.bloggercomments = function(data) {
var parsed = parse(data);
cursor = parsed.length

  1. 單純用大網站的OAuth當自己的會員系統還是有很大的問題
    最明顯的是當使用者同時有Google 與 Facebook的帳號時
    兩個帳號上面的Email可能不是一樣的
    自己的網站很可能無法辨認這是同一個人的帳戶
    所以一般還是會需要自己的會員系統做整合
    回覆刪除

  2. I really enjoy your blog it\’s a nice post
    .Net Online Course

    回覆刪除

  3. OAuth主要功能是授權,不是認證,所以後來才延伸出OpenID Connect (OIDC)來提供認證用。

    回覆刪除

BLOG_CMT_createIframe(\’https://www.blogger.com/rpc_relay.html\’);

window.___gcfg = { \’lang\’: \’zh-TW\’ };

window.setTimeout(function() {
document.body.className = document.body.className.replace(\’loading\’, \’\’);
}, 10);

var BLOG_BASE_IMAGE_URL = \’https://img2.blogblog.com/img\’;var BLOG_LANG_DIR = \’ltr\’;window[\’__wavt\’] = \’AOuZoY41cF43Ay-h6w4rfJaRWTECbwnjxQ:1546051481705\’;_WidgetManager._Init(\’//www.blogger.com/rearrange?blogID\\x3d4291069679343025964\’,\’//studyhost.blogspot.com/2017/01/oauthsso.html?m\\x3d1\’,\’4291069679343025964\’);
_WidgetManager._SetDataContext([{\’name\’: \’blog\’, \’data\’: {\’blogId\’: \’4291069679343025964\’, \’title\’: \’.NET Walker\’, \’url\’: \’http://studyhost.blogspot.com/2017/01/oauthsso.html?m\\x3d1\’, \’canonicalUrl\’: \’http://studyhost.blogspot.com/2017/01/oauthsso.html\’, \’homepageUrl\’: \’http://studyhost.blogspot.com/?m\\x3d1\’, \’searchUrl\’: \’http://studyhost.blogspot.com/search\’, \’canonicalHomepageUrl\’: \’http://studyhost.blogspot.com/\’, \’blogspotFaviconUrl\’: \’http://studyhost.blogspot.com/favicon.ico\’, \’bloggerUrl\’: \’https://www.blogger.com\’, \’hasCustomDomain\’: false, \’httpsEnabled\’: true, \’enabledCommentProfileImages\’: true, \’gPlusViewType\’: \’FILTERED_POSTMOD\’, \’adultContent\’: false, \’analyticsAccountNumber\’: \’\’, \’encoding\’: \’UTF-8\’, \’locale\’: \’zh-TW\’, \’localeUnderscoreDelimited\’: \’zh_tw\’, \’languageDirection\’: \’ltr\’, \’isPrivate\’: false, \’isMobile\’: true, \’isMobileRequest\’: true, \’mobileClass\’: \’ mobile\’, \’isPrivateBlog\’: false, \’feedLinks\’: \’\\x3clink rel\\x3d\\x22alternate\\x22 type\\x3d\\x22application/atom+xml\\x22 title\\x3d\\x22.NET Walker – Atom\\x22 href\\x3d\\x22http://studyhost.blogspot.com/feeds/posts/default\\x22 /\\x3e\\n\\x3clink rel\\x3d\\x22alternate\\x22 type\\x3d\\x22application/rss+xml\\x22 title\\x3d\\x22.NET Walker – RSS\\x22 href\\x3d\\x22http://studyhost.blogspot.com/feeds/posts/default?alt\\x3drss\\x22 /\\x3e\\n\\x3clink rel\\x3d\\x22service.post\\x22 type\\x3d\\x22application/atom+xml\\x22 title\\x3d\\x22.NET Walker – Atom\\x22 href\\x3d\\x22https://www.blogger.com/feeds/4291069679343025964/posts/default\\x22 /\\x3e\\n\\n\\x3clink rel\\x3d\\x22alternate\\x22 type\\x3d\\x22application/atom+xml\\x22 title\\x3d\\x22.NET Walker – Atom\\x22 href\\x3d\\x22http://studyhost.blogspot.com/feeds/6738269321690393495/comments/default\\x22 /\\x3e\\n\’, \’meTag\’: \’\’, \’adsenseHostId\’: \’ca-host-pub-1556223355139109\’, \’adsenseHasAds\’: false, \’ieCssRetrofitLinks\’: \’\\x3c!–[if IE]\\x3e\\x3cscript type\\x3d\\x22text/javascript\\x22 src\\x3d\\x22https://www.blogger.com/static/v1/jsbin/864213505-ieretrofit.js\\x22\\x3e\\x3c/script\\x3e\\n\\x3c![endif]–\\x3e\’, \’view\’: \’\’, \’dynamicViewsCommentsSrc\’: \’//www.blogblog.com/dynamicviews/4224c15c4e7c9321/js/comments.js\’, \’dynamicViewsScriptSrc\’: \’//www.blogblog.com/dynamicviews/7a0cbfc92c51aced\’, \’plusOneApiSrc\’: \’https://apis.google.com/js/plusone.js\’, \’sharing\’: {\’platforms\’: [{\’name\’: \’取得連結\’, \’key\’: \’link\’, \’shareMessage\’: \’取得連結\’, \’target\’: \’\’}, {\’name\’: \’Facebook\’, \’key\’: \’facebook\’, \’shareMessage\’: \’分享到 Facebook\’, \’target\’: \’facebook\’}, {\’name\’: \’BlogThis!\’, \’key\’: \’blogThis\’, \’shareMessage\’: \’BlogThis!\’, \’target\’: \’blog\’}, {\’name\’: \’Twitter\’, \’key\’: \’twitter\’, \’shareMessage\’: \’分享到 Twitter\’, \’target\’: \’twitter\’}, {\’name\’: \’Pinterest\’, \’key\’: \’pinterest\’, \’shareMessage\’: \’分享到 Pinterest\’, \’target\’: \’pinterest\’}, {\’name\’: \’Google+\’, \’key\’: \’googlePlus\’, \’shareMessage\’: \’分享到 Google+\’, \’target\’: \’googleplus\’}, {\’name\’: \’以電子郵件傳送\’, \’key\’: \’email\’, \’shareMessage\’: \’以電子郵件傳送\’, \’target\’: \’email\’}], \’googlePlusShareButtonWidth\’: 300, \’googlePlusBootstrap\’: \’\\x3cscript type\\x3d\\x22text/javascript\\x22\\x3ewindow.___gcfg \\x3d {\\x27lang\\x27: \\x27zh_TW\\x27};\\x3c/script\\x3e\’}, \’hasCustomJumpLinkMessage\’: false, \’jumpLinkMessage\’: \’閱讀完整內容\’, \’pageType\’: \’item\’, \’postId\’: \’6738269321690393495\’, \’postImageThumbnailUrl\’: \’https://lh3.googleusercontent.com/-PywZ1rsFfXs/WG8BdWWP4vI/AAAAAAAAAzM/NHBmp-aCljg/s72-c/wlEmoticon-smile%25255B2%25255D.png?imgmax\\x3d800\’, \’postImageUrl\’: \’http://arock.blob.core.windows.net/blogdata201701/01-131813-359e594f-646a-44df-bc25-43e148243f08.png\’, \’pageName\’: \’一次搞懂OAuth與SSO在幹什麼?\’, \’pageTitle\’: \’.NET Walker: 一次搞懂OAuth與SSO在幹什麼?\’}}, {\’name\’: \’features\’, \’data\’: {\’sharing_get_link_dialog\’: \’true\’, \’sharing_native\’: \’false\’}}, {\’name\’: \’messages\’, \’data\’: {\’edit\’: \’編輯\’, \’linkCopiedToClipboard\’: \’已將連結複製到剪貼簿!\’, \’ok\’: \’確定\’, \’postLink\’: \’文章連結\’}}, {\’name\’: \’template\’, \’data\’: {\’name\’: \’Awesome Inc.\’, \’localizedName\’: \’頂尖企業\’, \’isResponsive\’: false, \’isAlternateRendering\’: true, \’isCustom\’: false, \’variant\’: \’light\’, \’variantId\’: \’light\’}}, {\’name\’: \’view\’, \’data\’: {\’classic\’: {\’name\’: \’classic\’, \’url\’: \’?view\\x3dclassic\’}, \’flipcard\’: {\’name\’: \’flipcard\’, \’url\’: \’?view\\x3dflipcard\’}, \’magazine\’: {\’name\’: \’magazine\’, \’url\’: \’?view\\x3dmagazine\’}, \’mosaic\’: {\’name\’: \’mosaic\’, \’url\’: \’?view\\x3dmosaic\’}, \’sidebar\’: {\’name\’: \’sidebar\’, \’url\’: \’?view\\x3dsidebar\’}, \’snapshot\’: {\’name\’: \’snapshot\’, \’url\’: \’?view\\x3dsnapshot\’}, \’timeslide\’: {\’name\’: \’timeslide\’, \’url\’: \’?view\\x3dtimeslide\’}, \’isMobile\’: true, \’title\’: \’一次搞懂OAuth與SSO在幹什麼?\’, \’description\’: \’最近的 Line Notify 、 Line Login ,以及前一陣子的 Microsoft Graph API ,全都使用到了OAuth作為用戶身分驗證以及資源存取的基礎。但很多讀者會卡在OAuth的運作流程上,根本的原因是不理解OAuth到底是幹嘛的?其存在的目的為何?以及…\’, \’featuredImage\’: \’https://lh3.googleusercontent.com/proxy/vvDdvGOWrEWYoHAI5uhoLSh44IbnOo76luSMBu2sqrXNcEuGoJrDDXEd_KGabDNKbae4Hr8Il8dVCZm6f3csj2j12zvjVFZuWcA1TpkYhEISq8YKMno86zHarsm4irq_48MptFpOaDKKKnmr2UZSs-SaRRNxyvVh\’, \’url\’: \’http://studyhost.blogspot.com/2017/01/oauthsso.html?m\\x3d1\’, \’type\’: \’item\’, \’isSingleItem\’: true, \’isMultipleItems\’: false, \’isError\’: false, \’isPage\’: false, \’isPost\’: true, \’isHomepage\’: false, \’isArchive\’: false, \’isLabelSearch\’: false, \’postId\’: 6738269321690393495}}]);
_WidgetManager._RegisterWidget(\’_HeaderView\’, new _WidgetInfo(\’Header1\’, \’header\’, document.getElementById(\’Header1\’), {}, \’displayModeFull\’));
_WidgetManager._RegisterWidget(\’_BlogView\’, new _WidgetInfo(\’Blog1\’, \’main\’, document.getElementById(\’Blog1\’), {\’cmtInteractionsEnabled\’: false, \’mobile\’: true}, \’displayModeFull\’));
_WidgetManager._RegisterWidget(\’_AttributionView\’, new _WidgetInfo(\’Attribution1\’, \’footer-3\’, document.getElementById(\’Attribution1\’), {}, \’displayModeFull\’));
_WidgetManager._RegisterWidget(\’_NavbarView\’, new _WidgetInfo(\’Navbar1\’, \’navbar\’, document.getElementById(\’Navbar1\’), {}, \’displayModeFull\’));

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *