美文网首页
OAuth & OpenID & SAML 工作流程梳理对比

OAuth & OpenID & SAML 工作流程梳理对比

作者: 蒋扬海 | 来源:发表于2019-04-01 23:21 被阅读0次

    我们经常会提到到SSO,OAuth,OpenID,SAML,一时间会让人摸不清他们之间的关系和区别,最近简单粗浅的研究了一下,分享出来。还有很多问题没来得及搞清楚,希望与大家一起讨论学习!

    OAuth

    OAuth 是由 Google 和 Twitter 合作开发的相对较新的认证授权标准(相比SAML),到现在被广泛使用的是OAuth2.0 版本。

    使用场景:

    我们在使用一些网站和手机应用的时候,往往需要填写各种信息和资料注册用户,这让很多人望而却步。利用第三方进行认证授权会让用户体验大大提升,并且也能降低系统的开发难度,减少开发成本。如今有很多互联网产品,比如,微信,QQ,新浪等拥有大量的用户,它们顺理成章的成为了第三方认证服务提供者。

    下面的这种界面你一定不陌生:

    20180330104453911.jpg qq_login.png

    这里使用 QQ 登录 CSDN,并且 CSDN 被授权获取用户的QQ昵称,头像和性别。

    等等,在近一步讲解之前,我们先要知道OAuth的几个基本概念:

    • 资源所有人 (Resource Owner)

      可以认为就是上面的QQ账户的所有者。

    • 客户端 (Client)

      客户端是指要使用第三方认证授权服务的一方,也就是上面的 CSDN

    • 资源服务器 (Resource Server)

      这里的资源就是 QQ 昵称,头像和性别等,资源服务器自然是提供这些资源的腾讯的服务器咯!

    • 授权服务器 (Authorization Server)

      这里使用的是 QQ 登录,那么自然我们可以认为 QQ 的登录服务器就是授权服务器了。

    四种模式

    英文中用的是Flow,每一种Flow对应的不同的工作流程,不知道为啥被翻译成模式!

    • 授权码模式 (Authorization Code,最常用也最安全)
    auth_code_flow.png
    • 第一步的请求如下

      https://AUTHORIZATION_SERVER_URL/v1/oauth/authorize?
          response_type=code&
          client_id=CLIENT_ID&
          redirect_uri=CALLBACK_URL&
          scope=read 
      
      • response_type=code 表示我要请求一个授权码

      • client_id 是个什么东东?

        上面的例子中,CSDN 要使用 QQ 登录,那 QQ 这边要求 CSDN 到 QQ 这边的授权服务器上注册一下,并且会給 CSDN 一个 CLIENT_ID 作为 CSDN 的唯一标识符,不然QQ咋知道是谁要使用QQ进行第三方登录呢?QQ总不能随随便便就让别人用自己的授权功能是吧?

      • redirect_url 这个就不多说了,就是 QQ 认证成功后接下来要跳转的URL,由 CSDN 提供。

      • scope 是指用户认证成功以后具有的权限范围,在上图第四步拿到的 Token 就仅仅具有这么点儿权限!

    • 第二步就是用户点一下“授权并登录”那个按钮

    • 第三步认证服务器返回授权码,并且会跳转到第一步中提供的 callback地址,大概长这样: https://CLIENT_URL/callback?code=AUTHORIZATION_CODE

    • 第四步,客户端(其实就是指 CSDN,这个翻译真的很 confusing!!!)利用上一步的 AUTHORIZATION_CODE 去找认证服务器换一个 Access Token,请求如下:

      https://AUTHORIZATION_SERVER_URL/v1/oauth/token?
          client_id=CLIENT_ID&
          client_secret=CLIENT_SECRET&
          grant_type=authorization_code&
          code=AUTHORIZATION_CODE&
          redirect_uri=CALLBACK_URL
      
      • grant_type 它表示我使用的是授权码模式,授权服务器会返回

        {
          "access_token":"ACCESS_TOKEN",
          "token_type":"bearer",
          "expires_in":2592000,
          "refresh_token":"REFRESH_TOKEN"
        }
        

        access_token 就是你以后每次请求 QQ 用户资源时,需要在请求中带上的许可证!说了这么多,就是为了拿到这个东西!

        你还可以使用 refresh_token去主动刷新 access_token,请求如下:

        https://AUTHORIZATION_SERVER_URL/v1/oauth/token?
          grant_type=refresh_token&            ---->  注意这里的grant_type哦!
          client_id=CLIENT_ID&
          client_secret=CLIENT_SECRET&
          refresh_token=REFRESH_TOKEN
        

    第一个模式花了较多的口水讲解,后面的你可别指望了了哈!!!

    • 隐式模式(Implicit)
    implicit_flow.png

    ​ 隐式模式是个什么鬼,它跟授权码模式的区别是什么?先看请求:

    https://AUTHORIZATION_SERVER_URL/v1/oauth/authorize?
        response_type=token&
        client_id=CLIENT_ID&
        redirect_uri=CALLBACK_URL&
        scope=read
    

    ​ 注意哦,这里的 response_typetoken,表示要请求一个 access_token。直接找认证服务器要 token,并且也没有带上授权码模式请求中的client_secret,你牛!

    ​ 第三步的认证服务器重定向到https://CLIENT_URL/callback#token=ACCESS_TOKEN, 注意这里面有个 #号哦!你回头去看看上一种模式里面跳转的URL长什么样??

    ​ 第四步浏览器跟着重定向URL回到客户端(Client),但却并没有把 Token 直接带过去,token 被留在了浏览器里!

    ​ 第五步,客户端(Client) 使用 JavaScript 从浏览器里提取出 token

    ​ 第六步,浏览器将 token 正式交给客户端(Client)

    关于隐式模式你可能会问,为啥要这么干?我的回答是“这是一个好问题!” 接着就交给你自己去找到答案吧,反正我没去研究过!如果你找到了答案,还麻烦给我留言~ 我是个懒人!!!

    ​ 对了,补上一张这个模式的时序图:

    oauth_user_agent_flow.png
    • 密码模式(Resource Owner Password Credentials)

      当你看到这张图,我知道你在想什么,不好意思,我参考的文章的作者就是没有提供下面两个模式的流程图,我又懒的画,就成了这个画风!

      oauth_username_password_flow.png

      请求如下:

      https://AUTHORIZATION_SERVER_URL/v1/oauth/token?
        grant_type=password&
        username=USERNAME&
        password=PASSWORD&
        client_id=CLIENT_ID
      

      这个模式就不多讲了,简单直接而且危险! QQ用户直接把自己的账号密码交给客户端(Client),由 Client向认证服务器发起认证,换作是你,你敢给吗?反正我是不敢!

      也正因为在客户端代码中可以直接使用 QQ 账户找到认证服务器获取access_token,所以这里不需要跳转到QQ 登录页,也就不需要再从 QQ 登录页重定向到客户端,因此这个请求中没有redirect_url

    • 客户端模式(Client Credentials)

    oauth_client_credentials_flow.png

    ​ 请求如下:

    https://AUTHORIZATION_SERVER_URL/v1/oauth/token?
        grant_type=client_credentials&
        client_id=CLIENT_ID&
        client_secret=CLIENT_SECRET
    

    ​ 客户端模式倒是简单,不需要用户提供账号密码,只需要提供客户端的账号密码(就是 CSDN 在 QQ 那边注册时得到的作为唯一表示的client_idclient_secret但如果是这样的话客户端咋知道使用的第三方(QQ) 的那个用户呢?有待继续研究吧~ 如果我还记得起来这个问题!

    讲了这么多,也许你已经看累了,但我想强调一下这篇文章的标题是 《OAuth & OpenID & SAML 工作流程梳理对比》,是对比,对比,对比!请休息一下,整理好心情,接着往下看~

    OpenID

    OpenID 是基于 OAuth 协议的,在工作流程上跟OAuth很相近。OpenID的版本有OpenID 1.0,OpenID 2.0,OpenID connection

    概念:

    • OP (OpenID Provider)
    • RP (Relying Party,就理解成谁给用户提供服务吧)

    OpenID 有三种模式 (我们只讲第一种,后面的请自行查阅资料):

    • 授权码模式 (Authorization flow)

      open_id_code.png

      授权码模式的获取授权码的请求如下:

      https://AUTHORIZATION_SERVER_URL/authorize?
                response_type=code
                &scope=openid
                &client_id=CLIENT_ID
                &state=STATE
                &redirect_uri=REDIRECT_URL
      

      OpenID 的授权码模式跟 OAuth的授权码模式几乎一样,区别:

      • 这个请求中的 scope=openid以及多了一个state=xxx,倒回去看看OAuth 获取授权码的请求吧!

        这里的scope字段你可以带上更多的值,比如openid%20email%20name,这样在后面的id_token中才会有这些你想要的信息哦!

      • 授权成功后端的redirect_url是 :

        https://AUTHORIZATION_SERVER_URL/callback?code=AUTHORIZATION_CODE&state=STATE

    在讲下一步之前,先告诉你两点:

    • OpenID 不再使用 access_token去请求资源,而是使用id_token

    • 获取 id_token的操作不在客户端进行,而是由资源服务器发起,这样避免了id_token残留在浏览器中引起安全漏洞

      值得一提的是,既然 id_token 是一个 JWT,那么资源服务器就需要拥有 OP 给予的公钥进行 token 验证!

    获取id_token请求如下:

    POST /token HTTP/1.1
    Host: AUTHORIZATION_SERVER_URL
    Content-Type: application/x-www-form-urlencoded
    Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
    
    https://AUTHORIZATION_SERVER_URL/token?
      grant_type=authorization_code&
      code=AUTHORIZATION_CODE&
      redirect_uri=REDIRECT_URL
    

    这里你会发现请求中没有client_idclient_secret,他们是通过 Header 中的 Authorization字段传递的。请求返回:

     {
      "id_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6IjFlOWdkazcifQ.ewogImlzc
        yI6ICJodHRwOi8vc2VydmVyLmV4YW1wbGUuY29tIiwKICJzdWIiOiAiMjQ4Mjg5
        NzYxMDAxIiwKICJhdWQiOiAiczZCaGRSa3F0MyIsCiAibm9uY2UiOiAibi0wUzZ
        fV3pBMk1qIiwKICJleHAiOiAxMzExMjgxOTcwLAogImlhdCI6IDEzMTEyODA5Nz
        AKfQ.ggW8hZ1EuVLuxNuuIJKX_V8a_OMXzR0EHR9R6jgdqrOOF4daGU96Sr_P6q
        Jp6IcmD3HP99Obi1PRs-cwh3LO-p146waJ8IhehcwL7F09JdijmBqkvPeB2T9CJ
        NqeGpe-gccMg4vfKjkM8FcGvnzZUN4_KSP0aAp1tOJ1zZwgjxqGByKHiOtX7Tpd
        QyHE5lcMiKPXfEIQILVq0pc_E2DzL7emopWoaoZTF_m0_N0YzFC6g6EJbOEoRoS
        K5hoDalrcvRYLSrQAZZKflyuVCyixEoV9GfNQC3_osjzw2PAithfubEEBLuVVk4
        XUVrWOLrLl0nx7RkKU8NXNHq-rvKMzqg"
      "access_token": "SlAV32hkKG",
      "token_type": "Bearer",
      "expires_in": 3600,
    }
    
    • id_token 是一个 JWT,如果你还不了解JWT,请查阅资料哦! 这个 id_token的 body 部分就是通过对一系列的 claims 进行编码而来,claims 是 JSON 格式:

      {
         "sub"                     : "alice",
         "email"                   : "alice@wonderland.net",
         "email_verified"          : true,
         "name"                    : "Alice Adams",
         "given_name"              : "Alice",
         "family_name"             : "Adams",
         "phone_number"            : "+359 (99) 100200305",
         "profile"                 : "https://c2id.com/users/alice",
         "https://c2id.com/groups" : [ "audit", "admin" ]
      }
      
    • 上面的返回依然包含了一个access_token,它只是为了保证获取 token 返回体符合OAuth2.0 的标准,同时一些实现中也可以用它去调用获取用户信息的 endpoint。

    • 隐式模式 (Implicit flow)

    • 混合模式 (Hybrid flow)

    SAML

    SAML 是比较早的协议,有1.0,2.0版本,采用XML格式传递请求和返回数据。

    基本概念:

    • SP (Service Provider)
    • IdP (Identity Prodiver)

    以下是SAML的基础工作流程图:

    这张图取自: https://www.oasis-open.org/committees/download.php/11511/sstc-saml-tech-overview-2.0-draft-03.pdf

    以下的步奏讲解参考了: https://www.cnblogs.com/shuidao/p/3463947.html

    samel_workflow.png
    • 第1步访问SP提供的资源

    • SP发现你没有认证信息,它就会生成一个 SAML 的认证请求数据包,把这个请求放在一个 HTML 的 form 的一个隐藏域中,把这个HTML form返回给你。 这个form后面有一句 javascript 会自动提交这个 form。 而 form 的 action 地址就是提前配置好的 IdP上的一个地址。

      认证请求数据包如下:

      <samlp:AuthnRequest
          xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
          xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
          ID="aaf23196-1773-2113-474a-fe114412ab72"
          Version="2.0"
          IssueInstant="2004-12-05T09:21:59"
          AssertionConsumerServiceIndex="0"
          AttributeConsumingServiceIndex="0">
          <saml:Issuer>https://sp.example.com/SAML2</saml:Issuer>
          <samlp:NameIDPolicy
            AllowCreate="true"
            Format="urn:oasis:names:tc:SAML:2.0:nameid-format:transient"/>
        </samlp:AuthnRequest>
      

      HTML form 大概是这样:

      <form method="post" action="https://idp.example.org/SAML2/SSO/POST" ...>
          <input type="hidden" name="SAMLRequest" value="<samlp:AuthnRequest>.......... </samlp:authnreques>" />
          ... other input parameter....
          <input type="submit" value="Submit" />
      
      </form>
      
      <javascript>
      document.form[0].submit();      -------> 后面紧跟一句类似这样的提交代码
      </javascript>
      
    • 第4步 IdP 需要用户提供账号登陆

    • IdP 在认证你的身份之后,为你生成一些断言, 证明你是谁,你有什么权限等等,并用自己的私钥签名,然后包装成一个 response 格式,放在 form 里返回给你。

    断言的格式大概如下:

    <saml:Assertion
       xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
       xmlns:xs="http://www.w3.org/2001/XMLSchema"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       ID="b07b804c-7c29-ea16-7300-4f3d6f7928ac"
       Version="2.0"
       IssueInstant="2004-12-05T09:22:05">
       <saml:Issuer>https://idp.example.org/SAML2</saml:Issuer>
       <ds:Signature
         xmlns:ds="http://www.w3.org/2000/09/xmldsig#">...</ds:Signature>
       <saml:Subject>
    ..........
       </saml:Subject>
       <saml:Conditions
    .........
       </saml:Conditions>
       <saml:AuthnStatement
         AuthnInstant="2004-12-05T09:22:00"
         SessionIndex="b07b804c-7c29-ea16-7300-4f3d6f7928ac">
         <saml:AuthnContext>
           <saml:AuthnContextClassRef>
             urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport
          </saml:AuthnContextClassRef>
         </saml:AuthnContext>
       </saml:AuthnStatement>
       <saml:AttributeStatement>
         <saml:Attribute
           xmlns:x500="urn:oasis:names:tc:SAML:2.0:profiles:attribute:X500"
           x500:Encoding="LDAP"
           NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri"
           Name="urn:oid:1.3.6.1.4.1.5923.1.1.1.1"
           FriendlyName="eduPersonAffiliation">
           <saml:AttributeValue
             xsi:type="xs:string">member</saml:AttributeValue>
           <saml:AttributeValue
             xsi:type="xs:string">staff</saml:AttributeValue>
         </saml:Attribute>
       </saml:AttributeStatement>
     </saml:Assertion>
    

    Response 语句大概如下:

     <samlp:Response
        xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
        xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
        ID="identifier_2"
        InResponseTo="identifier_1"
        Version="2.0"
        IssueInstant="2004-12-05T09:22:05"
        Destination="https://sp.example.com/SAML2/SSO/POST">
        <saml:Issuer>https://idp.example.org/SAML2</saml:Issuer>
        <samlp:Status>
          <samlp:StatusCode
            Value="urn:oasis:names:tc:SAML:2.0:status:Success"/>
        </samlp:Status>
        <saml:Assertion
          xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
          ID="identifier_3"
          Version="2.0"
          IssueInstant="2004-12-05T09:22:05">
          <saml:Issuer>https://idp.example.org/SAML2</saml:Issuer>
          <!-- a POSTed assertion MUST be signed -->
         ....................
        </saml:Assertion>
      </samlp:Response>
    
    • 正如上面第2步一样,它也会把 response 包装在一个 HTML form 里面返回给你,并自动提交给 SP 的某个地址,也就是第7步。

       <form method="post" action="https://sp.example.com/SAML2/SSO/POST" ...>
          <input type="hidden" name="SAMLResponse" value="<samlp:Response>.........</samlp:respons>" />
          <input type="hidden" name="RelayState" value="''token''" />
          ...
          <input type="submit" value="Submit" />
        </form>
      <javascript>
      document.form[0].submit();      -------> 后面紧跟一句类似这样的提交代码.
      </javascript>
      
    • SP 读到 form 提交上来的 断言。 并通过 IdP 的公钥验证了断言的签名,于是信任了断言。 知道你是IdP的合法用户。 所以就最终给你返回了你最初请求的页面了。

      请注意,SP 拥有 IdP 给予的公钥!

    SAML本身含有很多不同变种的流程,这里演示的是最基本的一个,方便理解!

    小结

    • 简单对比
    OpenID OAuth SAML
    Dates from 2005 2006 2001
    Current version OpenID Connect OAuth 2.0 SAML 2.0
    Main purpose Single sign-on for consumers API authorization between applications Single sign-on for enterprise users
    Protocols used XRDS, HTTP JSON, HTTP SAM, XML, HTTP, SOAP
    • 从流程图赏可以看出,SAML跟OAuth本身做的事儿并没有多大区别,都是先请求资源,资源服务器发现没有认证,去找事先约定好的认证服务器认证。

      但是!! 上面的流程中有一个很重要的区别并没有表现出来,那就是在OAuth 和 OpenID 中,资源服务器跟认证服务器(或者OpenID Provider) 之间始终是存在交互的,因为资源服务器需要去验证自己拿到的用户身份是否合法。而SAML的流程图中可以看出,Service Provider 和 Identitiy Provider 并没有直接的交互,所有中间步奏都由浏览器代劳了!但 Service Provider 需要事先拥有一个用语验证断言的公钥, 切记哦~

      当然,如果你去看了完整的SAML文档,其实它也有很多变种是需要Service Provider 和 Identity Provider 直接通信的。

    • OAuth 属于认证 + 授权,而OpenID 和 SAML 则是做认证,那什么是认证,什么是授权?

      • 直白的讲验证用户账号密码即为认证,表示你是合法的用户
      • 授权是指用于对某些资源的访问权限,比如头像,邮箱等等

    这三个概念本身包含的内容还远不止于此,篇(因)幅(为)有(懒)限(惰),就写这么一些基本的东西。如果你有兴趣近一步加深理解,可以参考以下这些链接:

    也欢迎你留言参与讨论,有很多问题我也还没了解透彻,希望能有人传道授业解惑!

    相关文章

      网友评论

          本文标题:OAuth & OpenID & SAML 工作流程梳理对比

          本文链接:https://www.haomeiwen.com/subject/nvjibqtx.html