zoukankan      html  css  js  c++  java
  • 深入了解身份认证和授权机制,看看API请求到底发生了什么?

    前段时间写了一篇基于.NetCore环境使用IdentityServer4为API接口鉴权的文章,更多的是从快速上手的角度描述了IdentityServer4的使用。后续使用过程中,自己有了一些其他想法和困惑,于是便进行一番探索,在这里记录分享一下。

    本文主要和大家认识下Client获取到Token之后,请求API资源时具体是如何实现身份认证资源授权的。

    解决以下困惑:

     1.Client请求时携带的Token具体是如何实现认证的?

     2.Token中自包含的信息(IdentityServer4使用的Token实际是JWT类型)是如何传递到API资源服务器中的?

     3.为什么被标识了【Authorize】特性或者被全局设置过滤器AuthorizeFilter的资源就能实现鉴权,系统是如何实现的?

    准备环境:

      1.IdentityServer4授权服

      2.API资源服务

      3.Postman模拟发起http请求的客户端

    由于之前文章讲解过如何实现IdentityServer鉴权,本文就不再赘述如何搭建项目环境,感兴趣的同学可以看下之前的文章IdentityServer4实现.Net Core API接口权限认证

    关注下代码核心部分:

    API资源服务中注册认证服务

            public void ConfigureServices(IServiceCollection services)
            {
                services.AddControllers();//注册身份认证服务
                services.AddAuthentication("Bearer")
                .AddIdentityServerAuthentication(options =>
                {
                    options.Authority = Configuration.GetValue<string>("Authority:Url");//你要请求验证的identity服务端的地址
                    options.RequireHttpsMetadata = false;
                    options.ApiName = "api";//你选择的验证方式。 对应的GetClients中定义的作用域
                });
    
            }

           1)AddAuthentication内部注册了我们最主要的三个对象AuthenticationService, AuthenticationHandlerProvider, AuthenticationSchemeProvider

           2)AddIdentityServerAuthentication(或者AddCookie、AddJwtBearer)指定Scheme类型和需要验证的参数

    API资源服务中添加身份认证、授权管道中间件

            public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
            {
                if (env.IsDevelopment())
                {
                    app.UseDeveloperExceptionPage();
                }
                else
                {
                    app.UseHsts();
                }
                app.UseRouting();
                //添加权限认证中间件
                app.UseAuthentication();
                app.UseAuthorization();
                app.UseEndpoints(endpoints =>
                {
                    endpoints.MapControllers();
                });
            }

          1) app.UseAuthentication() 添加了中间件AuthenticationMiddleware

          2) app.UseAuthorization() 添加了中间件AuthorizationMiddleware

    .NetCore中Http请求过程:

       Http请求 > 其它中间件 > 路由中间件(这里就拿到终点路由了) > 身份验证中间件 > 授权中间件 > MVC中间件 > Controller > Action

    深入认识下身份认证和授权两个过程:

     发起API接口请求(header中参数Authorization携带token)

      Http请求先经过身份认证AuthenticationMiddleware中间件请求管道,然后经过授权AuthorizationMiddleware中间件请求管道。

    1.身份认证 即token认证

    借用一张大佬的图,给小伙伴们解解渴。

    结合上面这张图,我们来详细看看这其中到底是如何实现的?

    API服务一共向IdentityServer4授权服务请求了两次

    1)第一次请求IdentityServer4授权服务,指向终点路由DiscoveryEndPoint(/.well-known/openid-configuration),获取IdentityServer授权服务配置信息

    {
        "issuer": "http://localhost:7000",
        "jwks_uri": "http://localhost:7000/.well-known/openid-configuration/jwks",
        "authorization_endpoint": "http://localhost:7000/connect/authorize",
        "token_endpoint": "http://localhost:7000/connect/token",
        "userinfo_endpoint": "http://localhost:7000/connect/userinfo",
        "end_session_endpoint": "http://localhost:7000/connect/endsession",
        "check_session_iframe": "http://localhost:7000/connect/checksession",
        "revocation_endpoint": "http://localhost:7000/connect/revocation",
        "introspection_endpoint": "http://localhost:7000/connect/introspect",
        "device_authorization_endpoint": "http://localhost:7000/connect/deviceauthorization",
        "frontchannel_logout_supported": true,
        "frontchannel_logout_session_supported": true,
        "backchannel_logout_supported": true,
        "backchannel_logout_session_supported": true,
        "scopes_supported": ["openid", "profile", "api1", "api2", "offline_access"],
        "claims_supported": ["sub", "name", "family_name", "given_name", "middle_name", "nickname", "preferred_username", "profile", "picture", "website", "gender", "birthdate", "zoneinfo", "locale", "updated_at"],
        "grant_types_supported": ["authorization_code", "client_credentials", "refresh_token", "implicit", "password", "urn:ietf:params:oauth:grant-type:device_code"],
        "response_types_supported": ["code", "token", "id_token", "id_token token", "code id_token", "code token", "code id_token token"],
        "response_modes_supported": ["form_post", "query", "fragment"],
        "token_endpoint_auth_methods_supported": ["client_secret_basic", "client_secret_post"],
        "id_token_signing_alg_values_supported": ["RS256"],
        "subject_types_supported": ["public"],
        "code_challenge_methods_supported": ["plain", "S256"],
        "request_parameter_supported": true
    }

    2)第二次请求IdentityServer授权服务,指向终点路由DiscoveryKeyEndPoint(/.well-known/openid-configuration/jwks),获取IdentityServer授权服务中jwks信息(kid和公钥)。kid唯一标识一个token

    {
        "keys": [{
            "kty": "RSA",
            "use": "sig",
            "kid": "B22FBEC9ACABEF0F9DFFD4DFD370D445",
            "e": "AQAB",
            "n": "45H1Uw6EFjClHK-LQpUptFWX0PrpQEqy-YVNljj8cYmrPK3Zjqgk6XyW8tSCEYpClJJnuvSrae9yyrYdCghChDkNFVU1H8PT3Y_aSYDrULfqKO39HdqF7pZoxayZpyseAHE9tYQjtAw7E5IBpnXd_02Wz4K2mpt8Z2s5hPwos2_ze1Msvdl7iPmPfNdncl2tvERkr9pM5vRvbzxA9N1aTF58W03oq6bZoIA2w28FhTedcCxpPb3euT926ribOKCtsxCKCqFiIZYc0ovAiSV-kuLhaywd9e5KA18sOObg0McOBGmCJsQ6PTkkpn_yqvb3Eu9jhgxCO-_8K3Ml_dFIXQ",
            "alg": "RS256"
        }]
    }

    3)API服务根据kid本地缓存token资源,当同一个kid再次发起请求时,不再向授权服务identityserver请求。

    4)通过授权服务拿到token的kid和公钥之后,由API资源服务验证token,我们看下AuthenticationMiddleware中间件做了什么

    
    
    public IAuthenticationSchemeProvider Schemes { get; set; }
    public async Task Invoke(HttpContext context)
    {
          context.Features.Set<IAuthenticationFeature>((IAuthenticationFeature) new AuthenticationFeature()
          {
            OriginalPath = context.Request.Path,
            OriginalPathBase = context.Request.PathBase
          });
          IAuthenticationHandlerProvider handlers = context.RequestServices.GetRequiredService<IAuthenticationHandlerProvider>();
          foreach (AuthenticationScheme authenticationScheme in await this.Schemes.GetRequestHandlerSchemesAsync())
          {
            IAuthenticationRequestHandler handlerAsync = await handlers.GetHandlerAsync(context, authenticationScheme.Name) as IAuthenticationRequestHandler;
            bool flag = handlerAsync != null;
            if (flag)
              flag = await handlerAsync.HandleRequestAsync();
            if (flag)
              return;
          }
          AuthenticationScheme authenticateSchemeAsync = await this.Schemes.GetDefaultAuthenticateSchemeAsync();
          if (authenticateSchemeAsync != null)
          {
            //实际核心认证处理的地方
            AuthenticateResult authenticateResult = await context.AuthenticateAsync(authenticateSchemeAsync.Name);
            if (authenticateResult?.Principal != null)
              context.User = authenticateResult.Principal;
          }
          await this._next(context);
    }

    在认证过程中, Schemes.GetRequestHandlerSchemesAsync() 通过AuthenticationSchemeProvider获取Scheme列表,

     handlers.GetHandlerAsync 通过IAuthenticationHandlerProvider 获取scheme对应的handler并存在handlerMap缓存字典里,

     context.AuthenticateAsync 通过Scheme和AuthenticationHandlerProvider获取实际处理请求的AuthenticationHandler(如JwtBearerHandler),最后通过实际的AuthenticationHandler的AuthenticateAsync方法进行认证流程。

    解析认证后会将token(JWT格式:header+payload+签名)中的Payload主体部分赋值到当前请求上下文context.User中。

    然后我们便可以在API资源服务器中通过ClaimsPrincipal—User获取解析后的相关信息。

            public ActionResult<IEnumerable<string>> Get()
            {
                var claims = User.Claims.Select(x => new { x.Type, x.Value }).ToList();
                var claimSub = User.FindFirstValue("sub");
                var claimId = User.FindFirstValue("id");
                return new string[] { "Hello,WebAPI" };
            }

    2.资源鉴权
    通过【Authorize】特性指定Controller或Action实现鉴权

            [HttpGet]
            [Authorize(Roles = "admin")]
            public ActionResult<IEnumerable<string>> Get()
            {
                return new string[] { "Hello,WebAPI" };
            }

    或者

    通过AuthorizeFilter给Controller添加全局鉴权

           public void ConfigureServices(IServiceCollection services)
           {
                services.AddControllers(x => x.Filters.Add(new AuthorizeFilter()));//全局添加权限认证
                //将身份验证服务添加到管道中
                services.AddAuthentication("Bearer")
                .AddIdentityServerAuthentication(options =>
                {
                    options.Authority = Configuration.GetValue<string>("Authority:Url");   //你要请求验证的identity服务端的地址
                    options.RequireHttpsMetadata = false;
                    options.ApiName = "api";          //你选择的验证方式。 对应的GetClients中定义的作用域
                });
           }

    那么.Net框架内部又是如何识别上面两种方式来实现鉴权的呢?

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
         app.UseAuthorization();
    }

    通过 app.UseAuthorization(); 在Http请求管道中添加了授权中间件AuthorizationMiddleware。

    public static IApplicationBuilder UseAuthorization(this IApplicationBuilder app)
        {
            if (app == null)
            {
              throw new ArgumentNullException(nameof(app));
            }
            VerifyServicesRegistered(app);
            return app.UseMiddleware<AuthorizationMiddleware>();
        }

     AuthorizationMiddleware 中间件中,我们发现了下面这行代码:

    var authorizeData = endpoint?.Metadata.GetOrderedMetadata<IAuthorizeData>() ?? Array.Empty<IAuthorizeData>();

    再看IAuthorizeData,定义了授权规则信息

    public interface IAuthorizeData
    {
        string AuthenticationSchemes { get; set; }
        string Policy { get; set; }
        string Roles { get; set; }
    }

    不难看出,正是通过 endpoint?.Metadata.GetOrderedMetadata<IAuthorizeData>() 获取了终点路由元素对应的授权规则信息。

    那么又如何与AuthorizeAttribute特性和全局过滤器实现了AuthorizeFilter关联呢?继续往下看

    先看AuthorizeAttribute特性

    public class AuthorizeAttribute : Attribute, IAuthorizeData
    {}

    实际上 AuthorizeAttribute  实现了 IAuthorizeData 授权规则接口,这也就不难理解被标识了特性【Authorize】的Controller和Action是如何被发现的。

    再看AuthorizeFilter过滤器,这里仅列出方法中部分代码,便于展示和理解

    public class AuthorizeFilter : IAsyncAuthorizationFilter, IFilterFactory,IFilterMetadata
    {
            public IEnumerable<IAuthorizeData> AuthorizeData { get; }
     
            //默认构造函数中默认创建了AuthorizeAttribute 对象
            public AuthorizeFilter()
                : this(authorizeData: new[] { new AuthorizeAttribute() })
            {
            }
     
            //赋值AuthorizeData
            public AuthorizeFilter(IEnumerable<IAuthorizeData> authorizeData)
            {
                if (authorizeData == null)
                {
                    throw new ArgumentNullException(nameof(authorizeData));
                }
     
                AuthorizeData = authorizeData;
            }
    }

    实际上 AuthorizeFilter  在默认构造函数中实例化了 AuthorizeAttribute 对象,并且完成了 IAuthorizeData 集合的赋值。

    这也就能理解为什么通过 IAuthorizeData 可以获取到过滤器AuthorizeFilter 对应的终点路由元素的授权规则信息。

    从本质上来说以上两种方式都是关联实现了接口 IAuthorizeData ,因此直接通过 endpoint?.Metadata.GetOrderedMetadata<IAuthorizeData>() 便可以获取到终点路由元素的授权规则信息。

    我相信看了上述文章之后,最开始的3个疑惑大家应该内心都有自己的答案了。

     1.client请求的时携带的Token具体是如何实现认证的?

    首先API资源服务向IdentityServer认证服务请求获取token的公钥,然后API服务根据公钥解密token的加签部分,认证token是否有效。

     2.Token中自包含的信息是如何传递到API资源服务器中的?

    IdentityServer认证服务使用的是JWT类型的token,自包含了用户信息。当API服务认证token有效后,会在AuthenticationMiddleware中间件中对token进行解析获取token中payload部分的信息,并赋值到当前请求的context.User上下文当中。

     3.为什么被标识了【Authorize】特性或者被全局设置过滤器AuthorizeFilter的资源就能实现鉴权,系统是如何实现的?

    AuthorizationMiddleware授权中间件中通过 endpoint?.Metadata.GetOrderedMetadata<IAuthorizeData>() 获取终点路由元素的授权规则,实现API资源鉴权。

  • 相关阅读:
    认识EXTJS
    Ext面向对象开发实践(turn)
    20多个在线操作系统(webOS)一览
    一个成功的博客必须知道的80个博客工具
    163.com免费邮箱背后的传奇故事
    Android ListView的滚动条始终显示并且滚动条样式自定义
    eclipse不格式化注释
    Android横屏竖屏切换
    喝茶的好处
    Android设置Gridview中的内容不滚动,然后控件中的内容随便添加的效果。
  • 原文地址:https://www.cnblogs.com/chenxf1117/p/14558510.html
Copyright © 2011-2022 走看看