IdentityServer4在.Net Core 3.1中应用(客户端授权模式)
前言
这里只是记录自己学习IdentityServer4的过程,不讲述原理。 如要学习原理,请查询其他相关资料。
一、配置IdentityServer4
1.1 安装程序包
我们这里将IdentityServer4服务器作为一个项目,所有的认证和授权都是从这个项目开始的,Access Token也是从这里发出
的,我们要将IdentityServer4安装到授权和认证的服务器项目中。
使用Visual Studio 2019创建一个名称为 “Samlpe.IdentityServer” 的基于.Net Core 3.1的 ASP.NET Core Web API 项目,用于IdentityServer4的服务器。
项目创建完成后,在此项目中通过NuGet安装IdentityServer4。安装的IdentityServer4的版本为4.1.2
1.2 配置IdentityServer4
在“Samlpe.IdentityServer”此项目中,需要对认证和授权进行配置。添加一个名称为Config的类文件,用于配置IdentityServer4。
Config.cs 代码如下:
using IdentityServer4.Models;
using IdentityServer4.Test;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Sample.IdentityServer
{
public class Config
{
/// <summary>
/// 配置ApiResource
/// </summary>
/// <returns></returns>
public static IEnumerable<ApiResource> ApiResources()
{
return new List<ApiResource>() {
//将多个具体的ApiScope归为一个ApiResource。可将多个Api范围进行分组,
//这样分的好处是对Api资源分的更细,在授权时,可更精细的控制。
new ApiResource("Sample","IdentityServer4学习用例"){
Scopes={ "sample_api","demo_api" }
}
};
}
/// <summary>
/// 设置具体的Api的范围
/// </summary>
public static IEnumerable<ApiScope> ApiScopes()
{
return new List<ApiScope>(){
new ApiScope("sample_api","Sample Api"),
new ApiScope("demo_api","Demo Api")
};
}
/// <summary>
/// 配置客户端应用
/// </summary>
public static IEnumerable<Client> Clients()
{
return new List<Client> {
//客户端模式
new Client{
//客户端ID
ClientId = "sample_api",
//认证秘钥
ClientSecrets = { new Secret("sample_client_secret".Sha256()) },
//认证模式:设置为客户端模式
AllowedGrantTypes = GrantTypes.ClientCredentials,
//设置客户端有权访问的范围
AllowedScopes = { "sample_api" }
},
};
}
}
}
在最新的IdentityServer4版本中,多了一个ApiScope的配置,用于配置多个API范围。
而在ApiResource中,将多个Api范围进行了分组,这样分组的好处是API资源分的更细,在授权时,可更精细的控制。
- ApiResources()方法用于将多个ApiScope归为一组,成为API资源,其中包括多个ApiScope。
- ApiScopes()方法定义多个具体的ApiScope,在ApiResources中必须引用的这里定义的ApiScope。
- Clients()方法定了多个客户端相关的认证和授权信息,用于在IDS4服务器上接受这些客户端,从而完成授权和认证。
1.3 注册DI容器
接下来,在Startup.ConfigureServices()方法中,将IdentityServer4组件注册到DI容器中。
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
//将IdentityServer注册到DI容器当中
services.AddIdentityServer()
.AddDeveloperSigningCredential() //在开发模式下自动生成JWT的秘钥,实际运行过程中需要自己生成秘钥。
//.AddSigningCredential()运用自己生成的秘钥。
.AddInMemoryApiResources(Config.ApiResources())//注册ApiResource
.AddInMemoryApiScopes(Config.ApiScopes())// 注册API的范围
.AddInMemoryClients(Config.Clients());//注册Api的客户端
}
1.4 添加中间件
接下来,还需要将IdentityServe4的中间件添加到管道中:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseRouting();
//这个必须在UseRouting和UseEndpoints中间。如果IdentityServer服务端和API端要写在一起,
//那么这个必须在UseAuthorization和UseAuthentication的上面。
app.UseIdentityServer();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
1.5 修改项目配置信息
默认情况下,ASP.NET Core项目都会监听 5000 端口。这个项目不做修改,需要在launchSettings.json文件中修改启动类型。
将launchBrowser都修改为False,并删除launchUrl设置的默认启动页。
1.6 测试发现文档
现在,Smaple.IdentityServer修改完成后,编译一下项目,直接按Control+F5启动项目。
认证服务器的项目已经跑起来了。在浏览器中输入如下地址访问IdentityServer4的发现文档:
http://localhost:5000/.well-known/openid-configuration
此时,已经访问到了IdentityServer4的发现文档,使用这些文档可以提供认证和授权服务。
二、配置受保护的API项目
2.1 创建API项目
在Sample解决方案中,添加一个名称为Sample.WebApi的ASP.NET Core WebAPI项目,用于创建API操作资源,充当API资源服务器。
创建一个基于ASP.NET Core 3.1的WebApi项目。
现在,WebApi项目已经创建完成了,在此项目中添加API操作,用于操作Api资源。
2.2 配置认证授权服务器
接下来,要在API资源服务器上配置IdentityServer4服务器,也就是要与IdentityServer4服务器关联起来,让客户端传过来的AccessToken能够在API资源服务器上识别并向客户端返回响应的API资源。
配置IdentityServer4服务器,需要在API资源服务器上安装Microsoft.AspNetCore.Authentication.JwtBearer包。
这里是基于ASP.NET Core 3.1的框架,JwtBearer包要选择3.1.14版本的。如果创建的是ASP.NET Core 5.0的框架,则选择5.0.5版本即可。
接下来,在Startup.cs类ConfigureServices()方法中,将身份验证服务添加到DI并配置Bearer为默认方案,需要添加
Microsoft.IdentityModel.Tokens 命名空间。。
代码如下:
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
//注册身份验证服务
services.AddAuthentication("Bearer").AddJwtBearer("Bearer", options =>
{
options.Authority = "http://localhost:5000"; //设置颁发Token的服务器地址
options.RequireHttpsMetadata = false; //设置不为Https请求
options.TokenValidationParameters = new TokenValidationParameters //不验证jwt的aud信息
{
ValidateAudience = false
};
});
//认证授权服务
services.AddAuthorization(options =>
{
//添加一个授权范围,这个名字可以随便起
options.AddPolicy("ApiScope", polict =>
{
//鉴定用户
polict.RequireAuthenticatedUser();
polict.RequireClaim("scope", "sample_api");
});
});
}
通过上面的代码,就建立了与IdentityServer服务器的通信。
2.3 配置认证中间件
在API资源服务器上的Startup.Configure()方法中,将身份验证中间件添加到管道中,以便对主机的每次调用都将自动执行身份验证。
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseRouting();
//身份验证中间件 (身份验证必须在授权的前面)
app.UseAuthentication();
//授权验证中间件
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
//.RequireAuthorization("ApiScope") 将授权应用到当前Api所有的端点上。
});
}
此代码中的身份验证中间件app.UseAuthentication()必须放在app.UseAuthorization()的前面,app.UseRouting()的后面。
2.4 保护API资源
在ASP.NET Core框架中,要保护API资源,需要在控制类中,添加 [Authorize] 特性。
[Authorize] 特性存在于 Microsoft.AspNetCore.Authorization 命名空间中。
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
namespace Sample.WebApi.Controllers
{
[ApiController]
[Route("[controller]")]
[Authorize("ApiScope")]
public class WeatherForecastController : ControllerBase
{
private static readonly string[] Summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};
private readonly ILogger<WeatherForecastController> _logger;
public WeatherForecastController(ILogger<WeatherForecastController> logger)
{
_logger = logger;
}
[HttpGet]
public IEnumerable<WeatherForecast> Get()
{
var rng = new Random();
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = DateTime.Now.AddDays(index),
TemperatureC = rng.Next(-20, 55),
Summary = Summaries[rng.Next(Summaries.Length)]
})
.ToArray();
}
}
}
这里引用的是WebAPI项目中自带的天气预报控制器。此时,我们直接再直接访问这个控制器则会出现401错误。
如上图所属,发现我的源代码中写的是 [Authorize(“ApiScope”)] 而不是 [Authorize] 。这与我们在API资源访问服务器的Startup.Configure()方法中定义的认证授权策略相关,我们可以使用定义的授权范围去限制哪些API操作可以被访问。如果指定的策略名称与API资源服务器上备案的策略名称不一致,则是无法访问的。
[Authorize(“ApiScope”)] 中括号中的名称必须与Startup.Configure()中定义的认证授权服务中起的别名一致。
2.5 修改端口号
这里将API资源服务器的端口改为 6000,以免发生端口冲突的问题。并将launchBrowser改为 False。
上面的工作完成后,编译一下项目,保证没有错误。
三、Postman测试
3.1 启动项目
首先将Sample.IdentityServer和Sample.WebApi两个项目都运行起来。然后在Postman里面进行模拟测试。
3.2 获取Token
项目启动起来后,我们在Postman里面请求Sample.IdentityServer服务器。获取到Token数据。
获取Token的地址为IdentityServer4发现文档中的token_endpoint所携带的地址。
我们创建一个Post请求,在请求体Body中加入client_id、client_secret 、grant_type三个参数,对应的值为配置客户端时设置的值。
接下来,我们开始发送请求,获取Token。
如上图所示,我们已经顺利的拿到了Token。
3.3 携带Token请求资源服务器
下来我们将携带Token去请求Sample.WebApi API资源服务器。
除了上图的方式外,我们还可以在Header头部信息中加入Authorization,在参数中写入Bearer+空格+Token。
我们携带Token开始发送请求,获取API资源服务器中的数据。
两种方式,我们均拿到了API资源服务器返回的数据。此时表明我们整体流程是没有问题的。
四、配置客户端
4.1 简介
经过Postman测试之后,已经表明我们整体上没有问题。我们接下来使用.Net Core客户端来测试开发的IdentityServer4服务器和API资源服务器。
4.2 创建控制台应用程序
在Sample解决方案中,添加一个Sample.Client的.Net Core控制台应用程序。
使用这个控制台应用程序通过IdentityServer4服务器获取AccessToken,然后再拿着这个AccessToken去访问API资源服务器获取数据。
4.3 安装 IdentityModel 包
客户端要能访问IdentityServer认证服务器,需要在客户端应用程序安装IdentityModel包。
4.4 请求方法
在客户端的Program.cs中编写请求代码。具体的代码如下所示:
using System;
using System.Net.Http;
using System.Threading.Tasks;
using IdentityModel.Client;
using Newtonsoft.Json.Linq;
namespace Sample.Client
{
class Program
{
static async Task Main(string[] args)
{
var client = new HttpClient();
var disco = await client.GetDiscoveryDocumentAsync("http://localhost:5000");
if (disco.IsError)
{
Console.WriteLine(disco.Error);
return;
}
var tokenRequest = await client.RequestClientCredentialsTokenAsync(new ClientCredentialsTokenRequest
{
Address = disco.TokenEndpoint,
ClientId = "sample_api",
ClientSecret = "sample_client_secret"
});
Console.WriteLine("IdentityServer4服务器返回的结果 : {0}", tokenRequest.Json);
var apiClient = new HttpClient();
apiClient.SetBearerToken(tokenRequest.AccessToken);
var response = await apiClient.GetAsync("http://localhost:6000/WeatherForecast");
if (!response.IsSuccessStatusCode)
{
Console.WriteLine(response.StatusCode);
return;
}
var content = await response.Content.ReadAsStringAsync();
Console.WriteLine(JArray.Parse(content));
Console.ReadKey();
}
}
}
4.5 整体测试
我们先启动IdentityServer认证服务器和API资源服务器。
两个项目启动后,我们接下来启动客户端程序,看是否能拿到和Postman中返回一样的数据。
如图所示,和Postman中的结果一样。至此,客户端认证模式就已经全部完成了。
如需要源码,请在Gitee上下载:IdentityServer4Demo