IdentityServer4学习(一)

573 阅读5分钟

IdentityServer4

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

它为您的所有应用程序提供单点登录的功能。单个中央服务器,可以验证您的客户端应用程序,为各种类型的客户端颁发 API 的访问令牌,您可以根据自己的要求配置选项。

身份服务器将为您提供安全登录和 API 访问保护——您将通过安全令牌访问 api 资源,身份服务器建立在 openID connect 和 oauth2.0 之上,它将为您管理令牌。

不要忘记,所有登录代码和逻辑(当今几乎所有网络应用程序都需要)已经过数百次测试并随着时间的推移而改进。这使得它几乎没有错误,并且比自己编写相同的东西更可靠!

来源

一、Terminology

IdentityServer:

IdentityServer 是一个 OpenID Connect 提供者——它实现了 OpenID Connect 和 OAuth 2.0 协议。

IdentityServer功能包括:

  • 保护您的资源
  • 使用本地帐户存储或通过外部身份提供者对用户进行身份验证
  • 提供会话管理和单点登录
  • 管理和验证客户端
  • 向客户颁发身份和访问令牌
  • 验证令牌

User:

使用注册客户端访问资源的人

Client:

从 IdentityServer 请求令牌的软件——用于验证用户(请求身份令牌)或访问资源(请求访问令牌)。客户端必须先向 IdentityServer 注册,然后才能请求令牌。一般是Web应用程序、移动程序或桌面应用、SPA、服务器进程等

Resources:

被IdentityServer保护的用户身份数据或者API,每个Resources都有唯一的名称,指定能访问那些资源

IdentityToken:

身份验证过程的结果,至少有用户的标识符、用户如何何时尽心身份验证的信息,还可以携带身份数据\

Account Token:

允许访问API资源,客户端请求访问令牌并转发到API。访问令牌包含有关客户端和用户的信息。API使用该信息授权访问其数据。

二、

模拟一种常见的IdentityServer方案:

使用IdentityServer保护API,定义一个IdentityServer、API、Client,Client从身份服务器请求访问令牌,然后使用访问令牌获取对API的访问权限。

这里创建一个空白解决方案,创建三个项目:IdentityServer、API、Client(模拟用户访问情况),使用的是 .NET6

IdentityServer项目

创建

新建项目——选择WebApi,命名:Sample.IdentityServer,删除默认生成的Model和Controller文件夹,成为以下模样

安装Nuget包:IdentityServer4

配置

创建静态配置文件Config

定义API Scope:

具体有哪些构造函数可以进入ApiScope类进行查看

public static IEnumerable ApiScopes =>
        new List        {
            //构造函数方式(name,displayName)
            new ApiScope("api1""My API"),
            new ApiScope
            {
                //标志api范围的名称
                Name="sample_api",
                //显示名称,对api范围进行描述
                DisplayName = "Sample API"
            }
        };

定义Client:

定义可以访问API的客户端应用程序

//客户端应用
        public static IEnumerable Clients => new[]
        {
            new Client
            {
                //客户端id
                ClientId = "sample_client",
                //客户端密钥
                ClientSecrets =
                {
                    //可以有多个  需要sha256加密
                    new Secret("sample_client_secret".Sha256())
                },
                //客户端凭据 指定授权类型
                AllowedGrantTypes =  GrantTypes.ClientCredentials,
                //允许的api范围
                AllowedScopes = { "sample_api" }
            },
             new Client
            {
                ClientId = "sample_pass_client",
                ClientSecrets =
                {
                    new Secret("sample_client_secret".Sha256())
                },
                //组合多个授权类型
                AllowedGrantTypes =  { GrantType.ClientCredentials, GrantType.ResourceOwnerPassword },
                //多个允许的范围
                AllowedScopes = { "sample_api" , "api1" }
            }
        };

定义Users:

//资源拥有者凭据授权
        public static List Users => new List        {
            new TestUser
            {
                SubjectId ="1",
                Username ="admin",
                Password ="123"
            }
        };

配置IdentityServer:

在Program中添加代码就这样,首次启动时,IdentityServer会创建一个开发者签名密钥,tempkey.jwk,启动以后查看发现文档https://localhost:7199/.well-known/openid-configuration ,换成你自己的端口,Client和API要使用发现文档里的配置数据

using Sample.IdentityServer;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddIdentityServer()//依赖注入IdentityServer
    .AddDeveloperSigningCredential()//添加签名凭据
    .AddInMemoryApiScopes(Config.ApiScopes)//使用内存中的配置
    .AddInMemoryClients(Config.Clients)//使用内存中的配置
    .AddTestUsers(Config.Users);

var app = builder.Build();

app.UseIdentityServer();
app.Run();

\

Api项目

创建

在同一个解决方案中创建新的WebApi,删除默认创建的Model文件,创建Controller文件IdentityController

用来测试授权要求,需要添加Nuget包:Microsoft.AspNetCore.Authentication.JwtBearer

[Route("identity")]
    [Authorize]
    public class IdentityController : ControllerBase
    {
        [HttpGet]
        public IActionResult Get()
        {
            return new JsonResult(from c in User.Claims select new { c.Type, c.Value });
        }
    }

配置

在Program中添加配置

using Microsoft.IdentityModel.Tokens;
var builder = WebApplication.CreateBuilder(args);

// Add services to the container.

builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

//注册认证服务
//Bearer认证方案
builder.Services.AddAuthentication("Bearer")
    .AddJwtBearer("Bearer", options =>
    {
        //配置委托
        //服务地址
        options.Authority = "https://localhost:7199";
        //验证参数  默认不需要吧
        options.TokenValidationParameters = new TokenValidationParameters
        {
            ValidateAudience = false
        };
    });
builder.Services.AddAuthorization(options =>
{
    options.AddPolicy("ApiScope", builder =>
    {
        //是否通过前面的认证
        builder.RequireAuthenticatedUser();
        //鉴定api范围
        builder.RequireClaim("scope", "sample_api");
    });
});

var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseHttpsRedirection();

//将身份验证中间件添加到管道中,以便在每次调用主机时自动执行身份验证。
app.UseAuthentication();
//添加授权中间件以确保匿名客户端无法访问我们的 API 端点。
app.UseAuthorization();
//所有控制器都要验证
app.MapControllers().RequireAuthorization("Apiscope");

app.Run();

Client项目

请求访问令牌的客户端,使用令牌访问API\

创建

创建个控制台程序,添加NuGet包:IdentityModel\

创建静态类ClientTest

添加ClientGet方法测试ClientCredentials请求

         // 客户端模式请求测试
         public static async Task ClientGet()
        {

            //通过IdentityServer获取token
            var client = new HttpClient();
            //获取发现文档
            var disco = await client.GetDiscoveryDocumentAsync("https://localhost:7199");
            if (disco.IsError)
            {
                Console.WriteLine(disco.Error);
                return;
            }
            var tokenResponse = await client.RequestClientCredentialsTokenAsync(
                new ClientCredentialsTokenRequest
                {
                    Address = disco.TokenEndpoint,
                    ClientId = "sample_client",
                    ClientSecret = "sample_client_secret"
                });

            if (tokenResponse.IsError)
            {
                Console.WriteLine(tokenResponse.Error);
                return;
            }
            Console.WriteLine(tokenResponse.Json);


            //根据token调用api
            var apiClient = new HttpClient();
            //设置请求头
            apiClient.SetBearerToken(tokenResponse.AccessToken);
            //请求方法
            var response = await apiClient.GetAsync("https://localhost:7078/identity");
            if (!response.IsSuccessStatusCode)
            {
                Console.WriteLine(response.StatusCode);
                return;
            }
            var content = await response.Content.ReadAsStringAsync();
            Console.WriteLine(JArray.Parse(content));

        }

添加PassGet方法测试ResourceOwnerPassword请求

// 资源拥有者凭据授权请求测试
    public static async Task PassGet()
        {

            //通过IdentityServer获取token
            var client = new HttpClient();
            //获取发现文档
            var disco = await client.GetDiscoveryDocumentAsync("https://localhost:7199");
            if (disco.IsError)
            {
                Console.WriteLine(disco.Error);
                return;
            }
            var tokenResponse = await client.RequestPasswordTokenAsync(
                new PasswordTokenRequest
                {
                    Address = disco.TokenEndpoint,
                    ClientId = "sample_pass_client",
                    ClientSecret = "sample_client_secret",
                    UserName = "admin",
                    Password = "123"
                });

            if (tokenResponse.IsError)
            {
                Console.WriteLine(tokenResponse.Error);
                return;
            }
            Console.WriteLine(tokenResponse.Json);


            //根据token调用api
            var apiClient = new HttpClient();
            //设置请求头
            apiClient.SetBearerToken(tokenResponse.AccessToken);
            //请求方法
            var response = await apiClient.GetAsync("https://localhost:7078/identity");
            if (!response.IsSuccessStatusCode)
            {
                Console.WriteLine(response.StatusCode);
                return;
            }
            var content = await response.Content.ReadAsStringAsync();
            Console.WriteLine(JArray.Parse(content));

        }

Program代码

// See https://aka.ms/new-console-template for more information
using Sample.Client;

Console.WriteLine("Hello, World!");
await ClientTest.ClientGet();
await ClientTest.PassGet();
Console.ReadLine();

启动

设置多个启动项目,邮件解决方案点击属性,启动完三个

设置运行API不默认启动浏览器,打开API项目的Properties的launchSettings.json将launchBrowser属性设置为false

参考:B站文档gitee