认证授权:IdentityServer4

前言

  上一篇文章<学习OIDC>介绍了OIDC协议,本篇开始我们就来具体来学习OIDC的具体实现IdentityServer4 学习。

一、IdentityServer4 是什么?

   IdentityServer4是用于ASP.NET Core的OpenID Connect和OAuth 2.0框架。

  可以构建(或重新使用)包含登录和注销页面的应用程序,IdentityServer中间件会向其添加必要的协议头,以便客户端应用程序可以使用这些标准协议与其对话。

   

   可以在应用程序中使用以下功能:

    • 身份验证即服务

      所有应用程序(Web,本机,移动,服务)的集中式登录逻辑和工作流。IdentityServer是OpenID Connect 的官方认证实现

    • 单点登录/退出

      多种应用程序类型的单点登录(注销)

    • API的访问控制

      为各种类型的客户端(例如,服务器到服务器,Web应用程序,SPA和本机/移动应用程序)的API发出访问令牌

    • 联合网关

      支持外部身份提供程序,例如Azure Active Directory,Google,Facebook等。这使您的应用程序免受如何连接到这些外部提供程序的详细信息的影响。

二、简单使用示例

先创建项目目录结构(如下图)

 1、IdentityServer 认证服务实现

  a) 创建一个空的WebApi项目-cz.IdentityServer,并添加IdentityServer4项目引用:如下图:

Install-Package IdentityServer4

  b) 要启用IdentityServer服务,不仅要把 IdentityServer 注册到容器中, 还需要配置一下内容:

    •  Authorization Server 保护了哪些 API (资源);
    • 哪些客户端 Client(应用) 可以使用这个 Authorization Server;

    • 指定可以使用 Authorization Server 授权的 Users(用户)

    创建文件 InMemoryConfig.cs,用于设置以上相关内容:    

using IdentityServer4;
using IdentityServer4.Models;
using IdentityServer4.Test;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace cz.IdentityServer
{
    public class InMemoryConfig
    {
        public static IEnumerable<IdentityResource> GetIdentityResourceResources()
        {
            return new List<IdentityResource>
            {
                //必须要添加,否则报无效的scope错误
                new IdentityResources.OpenId(),
                new IdentityResources.Profile()
            };
        }

        /// <summary>
        /// api资源列表
        /// </summary>
        /// <returns></returns>
        public static IEnumerable<ApiResource> GetApiResources()
        {
            //可访问的API资源(资源名,资源描述)
            return new List<ApiResource>
            {
                new ApiResource("goods", "Goods Service"),
                new ApiResource("order", "Order Service")
            };
        }

        /// <summary>
        /// 客户端列表
        /// </summary>
        /// <returns></returns>
        public static IEnumerable<Client> GetClients()
        {
            return new List<Client>
            {
                new Client
                {
                    ClientId = "clientGoods", //访问客户端Id,必须唯一
                    //使用客户端授权模式,客户端只需要clientid和secrets就可以访问对应的api资源。
                    AllowedGrantTypes = GrantTypes.ClientCredentials,
                    ClientSecrets =
                    {
                        new Secret("secret".Sha256())
                    },
                    AllowedScopes = {
                        "goods",
                        IdentityServerConstants.StandardScopes.OpenId,
                        IdentityServerConstants.StandardScopes.Profile
                    },
                },
                new  Client
                {
                    ClientId = "clientOrder",
                    ClientSecrets = new [] { new Secret("secret".Sha256()) },
                    //这里使用的是通过用户名密码和ClientCredentials来换取token的方式. ClientCredentials允许Client只使用ClientSecrets来获取token. 这比较适合那种没有用户参与的api动作
                    AllowedGrantTypes = GrantTypes.ResourceOwnerPasswordAndClientCredentials,
                    AllowedScopes = {
                        "order","goods",
                        IdentityServerConstants.StandardScopes.OpenId,
                        IdentityServerConstants.StandardScopes.Profile
                    }
                }
            };
        }

        /// <summary>
        /// 指定可以使用 Authorization Server 授权的 Users(用户)
        /// </summary>
        /// <returns></returns>
        public static IEnumerable<TestUser> Users()
        {
            return new[]
            {
                    new TestUser
                    {
                        SubjectId = "1",
                        Username = "cba",
                        Password = "cba"
                    },
                    new TestUser
                    {
                        SubjectId = "2",
                        Username = "chaney",
                        Password = "123"
                    }
            };
        }
    }
}

View Code

    GetApiResources:这里指定了name和display name, 以后api使用authorization server的时候, 这个name一定要一致

    GetClients: 认证客户端列表

    Users: 这里的内存用户的类型是TestUser, 只适合学习和测试使用, 实际生产环境中还是需要使用数据库来存储用户信息的, 例如接下来会使用asp.net core identity. TestUser的SubjectId是唯一标识.

   在Startup.cs中启用IdentityServer服务

       修改StartUp.cs中的ConfigureServices方法

public void ConfigureServices(IServiceCollection services)
{
            services.AddControllersWithViews();
            services.AddIdentityServer()
              .AddDeveloperSigningCredential()
              .AddInMemoryApiResources(InMemoryConfig.GetApiResources())
              .AddTestUsers(InMemoryConfig.Users().ToList())
              .AddInMemoryIdentityResources(InMemoryConfig.GetIdentityResourceResources())
              .AddInMemoryClients(InMemoryConfig.GetClients());
}

    修改StartUp.cs中的Configure方法    

        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            app.UseRouting();
       //启用IdentityServer
            app.UseIdentityServer();

            app.UseAuthorization();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllerRoute(name: "default", pattern: "{controller=Home}/{action=Index}/{id?}");
            });
        }

   运行此项目,打开浏览器访问//localhost:5600/.well-known/openid-configuration你将会看到IdentityServer的各种元数据信息。

    

  c) 引入QuickStartUI界面

   IdentityServer提供了一套UI以使我们能快速的开发具有基本功能的认证/授权界面,下载地址:QuickStartUI

   下载后,把QuickStartUI中:wwwroot、Quickstart、Views拷贝到项目中,如下结构:、

   

   修改Statup.cs内容如下:

public class Startup
    {
        // This method gets called by the runtime. Use this method to add services to the container.
        // For more information on how to configure your application, visit //go.microsoft.com/fwlink/?LinkID=398940
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllersWithViews();

            services.AddIdentityServer()
              .AddDeveloperSigningCredential()
              .AddTestUsers(InMemoryConfig.Users().ToList())
              .AddInMemoryApiResources(InMemoryConfig.GetApiResources())
              .AddInMemoryClients(InMemoryConfig.GetClients());
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            app.UseRouting();

            app.UseStaticFiles();

            app.UseIdentityServer();

            app.UseAuthorization();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllerRoute(name: "default", pattern: "{controller=Home}/{action=Index}/{id?}");
            });
        }
    }

View Code

   运行如下效果:

   

 2、IdentityServer 集成Api服务

   a)添加web api项目cz.Api.Order,并添加nuget中安装IdentityServer4.AccessTokenValidation ,如下图:  

命令:Install-Package IdentityServer4.AccessTokenValidation 

    

 

   b) 修改StartUp.cs文件    

public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllers();

            //IdentityServer
            services.AddMvcCore()
                    .AddAuthorization();

            //配置IdentityServer
            services.AddAuthentication("Bearer")
                        .AddIdentityServerAuthentication(options =>
                        {
                            options.RequireHttpsMetadata = false; //是否需要https
                            options.Authority = $"//localhost:5600";  //IdentityServer授权路径
                            options.ApiName = "order";  //需要授权的服务名称
                        });
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            app.UseHttpsRedirection();

            app.UseRouting();
       //启用Authentication中间件
            app.UseAuthentication();

            app.UseAuthorization();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
            });
        }
    }

    c) 对cz.Api.Order项目中WebApi添加[Authorize]特性    

[Route("identity")]
[ApiController]
[Authorize]
 public class IdentityController : ControllerBase
{
}

   d) 此时调用该服务时提示401,如下图:

    

 

   e) 可以通过client信息获取token,然后通过Header传递token 调用weapi

  

 

    

三、总结

  通过上面的例子,很简单就实现了WepApi的认证授权效果;主要步骤如下:

  • 前往IdentityServer服务中,设置需要请求的ApiResource,Client,User内容
  • WebApi服务设置IdentityServerAuthentication地址(对接认证服务)
  • 调用WebApi时,先在IdentityServer中根据设置的方式获取access token,再带着access token请求接口才能正常访问

四、后续

  IdentityServer包含的内容有很多,准备从多个内容来学习记录使用IdentityServer的功能:SSO(单点登录)、各种授权模式使用、三方账号登录……

  最后来实现一个自己的统一身份认证服务