Fork me on GitHub

IdentityServer4笔记整理(更新中)

博客与笔记无法实时同步, 笔记最新链接

OAuth 2.0

定义:OAuth 2.0是一个开放授权标准:允许资源所有者(用户)授权第三方应用访问该用户在某服务上的特定私有资源,但不提供账号密码给第三方应用。
安全提示:授权码和所有令牌必须通过Tsl加密传输。进入OAuth 2.0官网

OAuth 2.0协议流程图

     +--------+                               +---------------+
     |        |--(A)- Authorization Request ->|   Resource    |
     |        |                               |     Owner     |
     |        |<-(B)-- Authorization Grant ---|               |
     |        |                               +---------------+
     |        |
     |        |                               +---------------+
     |        |--(C)-- Authorization Grant -->| Authorization |
     | Client |                               |     Server    |
     |        |<-(D)----- Access Token -------|               |
     |        |                               +---------------+
     |        |
     |        |                               +---------------+
     |        |--(E)----- Access Token ------>|    Resource   |
     |        |                               |     Server    |
     |        |<-(F)--- Protected Resource ---|               |
     +--------+                               +---------------+
                         (OAuth 2.0协议流程)
                         
    (A)客户端发起授权请求,客户端可以向资源所有者直接发起授权请求,建议的做法是将授权服务器作为中间人,客户端向授权服务器发起授权请求
    (B)客户端收到授权凭证(和具体的授权类型有关)
    (C)客户端通过授权凭证向授权服务器请求访问令牌
    (D)授权服务器验证授权凭证和客户端,如果通过验证,返回给客户端访问令牌
    (E)客户端通过访问令牌向资源服务器发出访问请求
    (F)资源服务器验证访问令牌有效后,返回请求的资源

授权码模式

授权码模式:code的生命周期必须短暂、或者只能单次使用,如果code被多次使用,授权服务器必须使该code生成的所有令牌失效。访问令牌由client后端保存。

     +----------+
     | Resource |
     |   Owner  |
     |          |
     +----------+
          ^
          |
         (B)
     +----|-----+          Client Identifier      +---------------+
     |         -+----(A)-- & Redirection URI ---->|               |
     |  User-   |                                 | Authorization |
     |  Agent  -+----(B)-- User authenticates --->|     Server    |
     |          |                                 |               |
     |         -+----(C)-- Authorization Code ---<|               |
     +-|----|---+                                 +---------------+
       |    |                                         ^      v
      (A)  (C)                                        |      |
       |    |                                         |      |
       ^    v                                         |      |
     +---------+                                      |      |
     |         |>---(D)-- Authorization Code ---------'      |
     |  Client |          & Redirection URI                  |
     |         |                                             |
     |         |<---(E)----- Access Token -------------------'
     +---------+       (w/ Optional Refresh Token)
     
    (A)客户端引导用户代理(浏览器)到达授权终结点,并携带参数:response_type(code)、client_id、redirect_uri、scope、state
    (B)授权服务器通过用户代理(浏览器)验证资源所有者,并确定资源所有者是否给予客户端授权
    (C)验证资源所有者成功,并且允许授权,授权服务器将用户代理(浏览器)重定向到redirect_uri,并返回code和授权请求的state
    (D)客户端向授权服务器令牌终结点请求令牌,携带参数:grant_type、code、redirect_uri、client_id、secret(可选)
    (E)授权服务器验证code、client_id、secret是否有效,并验证redirect_uri是否与步骤C中一致,如果验证成功,携带Access Token将用户代理(浏览器)重定向到redirect_uri

简化模式

要点:不支持刷新令牌,访问令牌编码在Url中,所以会有暴露的风险(在Oauth2.0官网中已经不推荐使用)

     +----------+
     | Resource |
     |  Owner   |
     |          |
     +----------+
          ^
          |
         (B)
     +----|-----+          Client Identifier     +---------------+
     |         -+----(A)-- & Redirection URI --->|               |
     |  User-   |                                | Authorization |
     |  Agent  -|----(B)-- User authenticates -->|     Server    |
     |          |                                |               |
     |          |<---(C)--- Redirection URI ----<|               |
     |          |          with Access Token     +---------------+
     |          |            in Fragment
     |          |                                +---------------+
     |          |----(D)--- Redirection URI ---->|   Web-Hosted  |
     |          |          without Fragment      |     Client    |
     |          |                                |    Resource   |
     |     (F)  |<---(E)------- Script ---------<|               |
     |          |                                +---------------+
     +-|--------+
       |    |
      (A)  (G) Access Token
       |    |
       ^    v
     +---------+
     |         |
     |  Client |
     |         |
     +---------+
    (A)客户端引导用户代理(浏览器)到达授权终结点,并携带参数:response_type(token)、client_id、redirect_uri、scope、state
    (B)授权服务器通过用户代理(浏览器)验证资源所有者,并确定资源所有者是否给予客户端授权
    (C)验证资源所有者成功,并且允许授权,授权服务器将用户代理(浏览器)重定向到redirect_uri,redirect_uri的URL 锚点(fragment)部分包括了响应参数(以#hash值的方式追加):access_token、token_type、expires_in、scope、state
    (D)用户代理(浏览器)向redirect_uri发出请求,但不包括URL 锚点,用户代理在本地保存了fragment。
    (E)redirect_uri的服务器返回页面给浏览器,该页面需要包含解码fragment的脚本。
    (F)浏览器接收到页面后,执行脚本,将C中的响应参数解码出来
    (G)用户代理(浏览器)将响应参数传递给客户端

资源所有者密码模式

用户将用户名及密码提供给可信任的第三方应用,第三方应用向令牌终结点请求令牌。常用于第一方应用进行登陆. 优点:应用不需要存储用户账号、密码,通过刷新令牌保持持久登陆,降低密码泄露的风险。


     +----------+
     | Resource |
     |  Owner   |
     |          |
     +----------+
          v
          |    Resource Owner
         (A) Password Credentials
          |
          v
     +---------+                                  +---------------+
     |         |>--(B)---- Resource Owner ------->|               |
     |         |         Password Credentials     | Authorization |
     | Client  |                                  |     Server    |
     |         |<--(C)---- Access Token ---------<|               |
     |         |    (w/ Optional Refresh Token)   |               |
     +---------+                                  +---------------+

            Figure 5: Resource Owner Password Credentials Flow

   (A)  资源所有者向客户端提供其用户名及密码
   (B)  客户端携带用户名、密码向授权服务器的令牌终结点发起请求
   (C)  授权服务器授权客户端,并验证用户名、密码是否有效。如果有效,返回访问令牌
#请求token
Request:
    POST /connect/token HTTP/1.1
    Host: idsrv-server.com
    Content-type: application/x-www-form-urlencoded
    body:
    {
        grant_type:password
        username:dd
        password:dd
        client_id:eshopOnVue
        scope:orders(可选参数)
    }

#请求刷新令牌:原刷新令牌失效、之前颁发的access_token不受影响(需要实现手动失效)
Request:
    POST /connect/token HTTP/1.1
    Host: idsrv-server.com
    Content-type: application/x-www-form-urlencoded
    body:
    {
        grant_type:refresh_token
        refresh_token:e4364377ec69c8d5c06a49d7b74efbd2a29015ac37e9ede8e17597d348931d32
        client_id:eshopOnVue
    }
Respose:
{
    "id_token": "eyJhbGciO.iJSUzI1NiI.sImtpZCw",
    "access_token": "eyJhb.GciOiJSUz.I1NiIsIm",
    "expires_in": 3600,
    "token_type": "Bearer",
    "refresh_token": "60e7dda6e30473ce6dc0a1656b38c174a74ef73310d"
}
#通过access_token请求用户终结点(需要scope:profile):/connect/userinfo

客户端凭证模式

客户端直接使用自身的凭证向授权服务器终结点请求访问令牌。只能用于可信的客户端。不支持刷新令牌、无法访问用户资源scope(openid、profile、email等)


     +---------+                                  +---------------+
     |         |                                  |               |
     |         |>--(A)- Client Authentication --->| Authorization |
     | Client  |                                  |     Server    |
     |         |<--(B)---- Access Token ---------<|               |
     |         |                                  |               |
     +---------+                                  +---------------+

   (A)  客户端向授权服务器令牌终结点请求访问令牌
   (B)  授权服务器对客户端进行认证,如果成功,返回访问令牌

Request:
    POST /connect/token HTTP/1.1  #请求方式只能为post
    Host: idsrv-server.com
    Content-type: application/x-www-form-urlencoded #参数只能放在body里面
    body:
    {
        grant_type:client_credentials
        client_id:ClientCredentials
        client_secret:iwiaXNzIjoibnVsbCIsImF1ZCI6WyJudWxsL3Jlc291cmNlcyIsIm9yZGVycyJdLCJjbGllbnRfaWQiOiJDb
        scope:orders openid(可选,默认请求所有scope)
    }
Response:
    HTTP/1.1 200 OK
    Content-Type: application/json
    Cache-Control: no-store
    Pragma: no-cache
     
    {
      "access_token":"MTQ0NjJkZmQ5OTM2NDE1ZTZjNGZmZjI3",
      "token_type":"bearer",
      "expires_in":3600
    }

OpenID Connect(OIDC)

定义:它在OAuth2上构建了一个身份层,是一个基于OAuth2协议的身份认证标准协议

image

OIDC协议流程图

+--------+                                   +--------+
|        |                                   |        |
|        |---------(1) AuthN Request-------->|        |
|        |                                   |        |
|        |  +--------+                       |        |
|        |  |        |                       |        |
|        |  |  End-  |<--(2) AuthN & AuthZ-->|        |
|        |  |  User  |                       |        |
|   RP   |  |        |                       |   OP   |
|        |  +--------+                       |        |
|        |                                   |        |
|        |<--------(3) AuthN Response--------|        |
|        |                                   |        |
|        |---------(4) UserInfo Request----->|        |
|        |                                   |        |
|        |<--------(5) UserInfo Response-----|        |
|        |                                   |        |
+--------+                                   +--------+
EU:End User:一个人类用户。
RP:Relying Party ,用来代指OAuth2中的受信任的客户端,身份认证和授权信息的消费方;
OP:OpenID Provider,有能力提供EU认证的服务(比如OAuth2中的授权服务),用来为RP提供EU的身份认证信息;
ID Token:JWT格式的数据,包含EU身份认证的信息。
UserInfo Endpoint:用户信息接口(受OAuth2保护),当RP使用Access Token访问时,返回授权用户的信息,此接口必须使用HTTPS。
    (1).RP发送一个认证请求给OP;
    (2).OP对EU进行身份认证,然后提供授权;
    (3).OP把ID Token和Access Token(需要的话)返回给RP;
    (4).RP使用Access Token发送一个请求到UserInfo EndPoint;
    (5).UserInfo EndPoint返回EU的Claims。

OIDC在OAuth之上的扩展

"response_type"参数值 OIDC授权类型
code Authorization Code Flow
id_token token Implicit Flow
code id_token Hybrid Flow
  • Authorization Code Flow:从授权终结点返回code,从令牌终结点返回token
  • Implicit Flow:从授权终结点返回所有token
  • Hybrid Flow:id_token返回给前端,access_token在后端保存(access_token的安全性要求比id_token)高

JSON Web Token

定义:JWT是一个定义一种紧凑的,自包含的并且提供防篡改机制的传递数据的方式的标准协议

JWT格式组成

JWT由3部分构成:header.payload.signature
在IdSrv中,payload中的键值对根据token类型和授权流程的不同有所区别
header:
{
  "alg": "RS256",//签名算法
  "kid": "9dcf733a1192a6da053e64c6ee22ff87",
  "typ": "JWT"//token类型
}
payload://需要传递的数据
{
  "nbf": 1556591630,//该jwt在此之前无效
  "exp": 1556595230,//该jwt在此之后无效
  "iss": "http://localhost:7102",//jwt颁发者
  "iat": 1516239022,//jwt颁发时间
  "aud": "http://localhost:7102/resources",//jwt接收者
  "client_id": "jsImplicit",
  "sub": "SubjectId",//用户唯一id
  "auth_time": 1556591629,//授权时间
  "idp": "local",//identityProvider
  "name": "Username",
  "scope": [
    "openid",
    "profile"
  ],
  "amr": [
    "pwd"//authenticationMethod
  ]
}
signature://token生成方使用私匙生成token,token消费方使用用公匙验证token是否被修改过
RSASHA256(
  base64UrlEncode(header) + "." +base64UrlEncode(payload),Public Key,Private Key
  )
#RSA签名过程
#1.将消息内容进行base64编码:base64UrlEncode(header) + "." +base64UrlEncode(payload)
#2.对编码后的内容进行SHA256哈希计算(不可逆)
#3.对hash值使用privateKey加密,加密后内容作为签名
#4.token接收方通过publicKey对签名进行解密得到hash值,并将内容进行hash计算,比较两个hash值,确保消息未被篡改
expires_in="exp"-"nbf"

RSA非对称加密算法

1 RSA算法特点:
1.1 公钥私钥对等,可以互换
1.2 私钥需要保存在可靠方
1.3 无法从公钥计算出私钥(理论上从私钥也无法计算出公钥,例外:openssl的公钥e固定为65537,且私钥文件中含有额外的参数,可以从私钥计算出公钥)
2 RSA应用
2.1 加密:消息发送方:公钥加密=>消息接收方:私钥解密(用于加密传递消息) 
##缺点:无法防止伪造消息
2.2 签名:消息发送方:私钥加密=>消息接收方:公钥解密(用于消息签名,防止消息被篡改)
##缺点:消息明文传输

HTTPS简单流程

1.服务器通过非对称算法生成私钥公钥
2.服务方将公钥提供给相关机构(CA)
3.CA生成证书并颁发给服务器:
{
    证书发布机构CA
    证书有效期
    公钥
    证书所有者
    签名(签名方法与JWT类似,私钥属于CA)
    ...
}
4.服务器将证书发送给客户端:
{
    1.TCP三次握手
    2.建立tunnel
    3.client hello(包括SessionId,可以避免重新握手,并重新使用已有对话密钥)
    4.server hello
    5.发送Certificate给客户端
}
5.客户端通过系统中内置的CA公钥验证证书的合法性
6.客户端通过服务方公钥与服务器协商对称加密算法与密钥
7.进行对称加密通信

参考:https://zhuanlan.zhihu.com/p/22142170 https://zhuanlan.zhihu.com/p/27395037

IdentityServer4

授权码模式

PKCE(Proof Key for Code Exchange)

利用不可逆算法,确保在被窃取了授权码或其他密匙的情况下,也无法向授权服务器换取访问令牌

PKCE流程:
1.客户端随机生成一串字符:
{
    code_verifier=base64url(RandomString),
    code_challenge=base64url(sha256(code_verifier)) 
}
2.客户端携带code_challenge向授权服务器发起授权请求
3.授权服务器对客户端进行认证成功后返回授权码,并保存code_challenge
4.客户端获取到授权码之后,携带code_verifier向令牌终结点发起请求,换取Access Token
5.授权服务器验证授权码,将code_verifier进行sha256计算并url编码后与code_challenge对比,如果一致,颁发访问令牌

参考:https://tonyxu.io/zh/posts/2018/oauth2-pkce-flow/

授权码模式流程

#Step 1 客户端向授权服务器授权终结点发起请求:
    GET /connect/authorize HTTP/1.1
    Host: Idsrv.com,
    Query String Parameters:
    {
        client_id: Swagger_UI
        redirect_uri: http://localhost:9528/Callback
        response_type: code
        scope: openid profile orders
        state: 668ae852a74f4923ad140d79d2f10fee
        code_challenge: i2CnOeIHTBZZrAsgzEZV3-KpMTb_OCvl05ydETjrqIc
        code_challenge_method: S256
    }
state:客户端随机生成的字符串,授权服务器在重定向到redirect_uri时会原样返回,客户端检查state是否相同,来防止CSRFF攻击
#step 2 授权服务器对客户端进行认证,认证成功,授权服务器返回302重定向并携带ReturnUrl参数:
Response:
{
    Status Code: 302 Found,
    Location: http://localhost:6102/Account/Login?ReturnUrl=%2Fconnect%2Fauthorize%2Fcallback%3Fclient_id%3DSwagger_UI%26redirect_uri%3Dhttp%253A%252F%252Flocalhost%253A9528%252FCallback%26response_type%3Dcode%26scope%3Dopenid%2520profile%2520orders%26state%3D668ae852a74f4923ad140d79d2f10fee%26code_challenge%3Di2CnOeIHTBZZrAsgzEZV3-KpMTb_OCvl05ydETjrqIc%26code_challenge_method%3DS256
}
#step 3 /Account/Login请求返回登陆页面,并将ReturnUrl写入登陆页面
#step 4 用户在登陆页面发起登陆请求:
参数:
{
    1.用户名
    2.密码
    3.ReturnUrl
}
#step 4 授权服务器验证用户名、密码成功,且ReturnUrl有效:
{
    1.通过HttpContext.SignInAsync为当前请求上下文颁发登陆凭证:
    Set-Cookie:
    {
        idsrv.session:'8c9e9e80f92da2551c77dc6ab03c69ca,path=/'
        idsrv:'CfDJ8IOXoUULE4dDgZ02v48m533Xg,expires=Mon,08 Jul 2019 08:38:26 GMT, path=/,httponly'
    }
    2.发起重定向到ReturnUrl
}
#step 5 在 /connect/authorize/callback 终结点:
{
    授权服务器更新Cookie:idsrv,并重定向到redirect_uri,携带以下参数:
    {
        code: 21a9deaac4457e29f669a91bb36795c048aae5680b49ae3a1ffafa50aff0d169
        scope: openid profile orders
        state: 668ae852a74f4923ad140d79d2f10fee
        session_state: ZcDaWfAmzNGLsXS3-1ofTvNGryU-KxeurTpPLxP6oF0.89cd4e2083165f0701dbd2181ede5b7b(会话状态)
    }
}
#step 6 在redirect_uri向令牌终结点请求访问令牌:
POST /connect/token HTTP/1.1
Host: Idsrv.com,
Content-type: application/x-www-form-urlencoded
body:
{
    client_id: Swagger_UI
    code: 21a9deaac4457e29f669a91bb36795c048aae5680b49ae3a1ffafa50aff0d169
    redirect_uri: http://localhost:9528/Callback
    code_verifier: dde8c25afc8d42728225154fea0aa098556671d3344c423f9007f1895b47dbf2be2116c7c5f34720842383ec9a7e0a66
    grant_type: authorization_code
}
#step 7 授权服务器验证code和code_verifier,并颁发访问令牌

刷新访问令牌

配置new Oidc.UserManager()时 设置scope=offline_access,直接通过刷新令牌请求访问令牌。offline_access优先于silent_redirect_uri

通过Iframe页面进行静默刷新:
#step 1 通过Iframe页面向授权终结点发起请求:
    GET /connect/authorize HTTP/1.1
    Host: Idsrv.com,
    Cookie: 
    {
        idsrv.session=8c9e9e80f92da2551c77dc6ab03c69ca;
        idsrv=CfDJ8IOXoUULE4dDgZ02v48m53JzoKhJcuEwwXcdvHRYodIZ2nTuD
    }
    Query String Parameters:
    {
        client_id: Swagger_UI
        redirect_uri: http://localhost:9528/SilentCallback
        response_type: code
        scope: openid profile orders
        state: 54207b37bb644f90800d0993d5a5c210
        code_challenge: 8bOHunvRggEM9m3Hwb-8m24KIiRV9rPSbz0OOvTP7D0
        code_challenge_method: S256
        prompt: none
        id_token_hint: eyJhbGciOiJSUzI1NiIsImtp2UFyLw
    }
id_token_hint://为之前获取的id_token
prompt:none//用来指示授权服务器是否引导用户重新认证和同意授权 
#step 2 重定向到redirect_uri,携带以下参数: 
{
    code: a956650cd653debe11989d225c2caa2619521b9176b444fbcc83d3a1663bf1ed
    scope: openid profile orders
    state: 54207b37bb644f90800d0993d5a5c210
    session_state: SGmLH8gIy6VAPtdlT6_zQtix_VM229bPkpY0OpwQ6fc.229a9d1e0be856145c035db0aa6033cc
}
#step 3 在redirect_uri页面执行new Oidc.UserManager().signinSilentCallback()
#step 4 向令牌终结点请求访问令牌
登出操作:
Js客户端mgr.signoutRedirect()
1.向IdSrv请求/connect/endsession 并携带以下两个参数
id_token_hint:
post_logout_redirect_uri: 登出回调地址
2.重定向到/Account/Logout 并携带生成的logoutId参数
3.在Logout里清除await HttpContext.SignOutAsync() 并跳转到post_logout_redirect_uri

资源所有者密码模式

posted @ 2019-06-27 12:27  Kane_Blake  阅读(1253)  评论(1编辑  收藏  举报