IdentityServer4 是为ASP.NET Core 2.系列量身打造的一款基于 OpenID Connect 和 OAuth 2.0 认证框架。
官网地址:http://www.identityserver.com.cn/
什么是 OAuth 2.0?OpenID Connect又是什么?如果有兴趣可以深入研究下。这里就不展开了,大致有个了解就行,本文主要是介绍基于IdentityServer4中间件来快速实现一个OAuth2.0的授权,保护API资源。如果你选择通过直接发送和处理 HTTP 请求或使用第三方开源库来编写代码,而不是使用中间件来实现,就需要去研究下标准协议了。
IdentityServer是将规范兼容的OpenID Connect和OAuth 2.0端点添加到任意ASP.NET Core应用程序的中间件。通常,您构建(或重新使用)包含登录和注销页面的应用程序,IdentityServer中间件会向其添加必要的协议头,以便客户端应用程序可以与其对话 使用这些标准协议。
专业术语
用户(User)
用户是使用已注册的客户端(指在id4中已经注册)访问资源的人。
客户端(Client)
客户端就是从identityserver请求令牌的软件(你可以理解为一个app即可),既可以通过身份认证令牌来验证识别用户身份,又可以通过授权令牌来访问服务端的资源。但是客户端首先必须在申请令牌前已经在identityserver服务中注册过。
实际客户端不仅可以是Web应用程序,app或桌面应用程序(你就理解为pc端的软件即可),SPA,服务器进程等。
资源(Resources)
资源就是你想用identityserver保护的东东,可以是用户的身份数据或者api资源。
身份令牌(顾名思义用于做身份认证)
一个身份令牌指的就是对认证过程的描述。它至少要标识某个用户(Called the sub aka subject claim)的主身份信息,和该用户的认证时间和认证方式。但是身份令牌可以包含额外的身份数据,具体开发者可以自行设定,但是一般情况为了确保数据传输的效率,开发者一般不做过多额外的设置,大家也可以根据使用场景自行决定。
访问令牌(用于做客户端访问授权)
访问令牌允许客户端访问某个 API 资源。客户端请求到访问令牌,然后使用这个令牌来访问 API资源。访问令牌包含了客户端和用户(如果有的话,这取决于业务是否需要,但通常不必要)的相关信息,API通过这些令牌信息来授予客户端的数据访问权限。
认证服务,可以为你的应用(如网站、本地应用、移动端、服务)做集中式的登录逻辑和工作流控制。IdentityServer是完全实现了OpenID Connect协议标准。
单点登录登出(SSO),在各种类型的应用上实现单点登录登出。
API访问控制,为各种各样的客户端颁发access token令牌,如服务与服务之间的通讯、网站应用、SPAS和本地应用或者移动应用。
联合网关,支持来自Azure Active Directory, Google, Facebook这些知名应用的身份认证,可以不必关心连接到这些应用的细节就可以保护你的应用。
专注于定制,最重要的是identityserver可以根据需求自行开发来适应应用程序的变化。identityserver不是一个框架、也不是一个盒装产品或一个saas系统,您可以编写代码来适应各种场景。
成熟的开源系统,IdentityServer拥有apache 2 授权许可,允许构建商业化的应用,也是.net基金会组织的成员之一,并为其提供法律支持。
免费和商业支持
OAuth2.0 定义了四种授权模式:
简化模式
客户端凭证模式
密码模式
授权码模式
一般分为IdentityServer、API资源服务、第三方客户端(app),并且都是独立的。所以我们要分别建立三个项目:
颁发授权的服务,也就是说要访问特定的API资源必须经过该服务进行授权验证。
API资源的项目,这是客户端实际要访问的API资源,比如:平台的openapi。
如下图:
这里我就偷懒了只新建了两个项目,WebOAuthDemo.Server是AuthorizationServer,WebOAuthDemo.WebApi是webAPI资源服务,第三方客户端可以用postman来模拟请求。
取名为WebOAuthDemo.Server
包管理器中安装IdentityServer4,我用的版本是3.1.3,现在4.+预览版已经有了。
PM>Install-Package IdentityServer4 -Version 3.1.3
新建一个Config文件,添加一个获取API资源的静态方法,如下:
public static IEnumerable
{
return new[]
{
new ApiResource("clientservice", "CAS Client Service"),
new ApiResource("productservice", "CAS Product Service"),
new ApiResource("agentservice", "CAS Agent Service")
};
}
正常环境下,API资源是通过服务注册到指定地方,比如数据库中,然后通过接口获取所有API的资源列表,这里代码中写死得了。
Config类中再添加一个获取客户端列表的静态方法
public static IEnumerable
{
return new[]
{
new Client
{
ClientId = "client.api.service",
ClientSecrets = new [] { new Secret("clientsecret".Sha256()) },
//客户端授权类型,用户密码模式
AllowedGrantTypes=GrantTypes.ResourceOwnerPasswordAndClientCredentials,
AllowedScopes = new [] { "clientservice" }
}
};
}
同理,第三方客户端是通过某种方式添加到数据库中进行持久化管理,这里也只是测试,简化了第三方授权和生成appid,appSecret等业务逻辑。
修改Startup.cs 为了让 IdentityServer 使用你的Scopes和客户端定义,你需要向ConfigureServices 方法中添加一些代码
public void ConfigureServices(IServiceCollection services)
{
//使用内存存储,密钥,客户端和资源来配置身份服务器。
services.AddIdentityServer()
.AddDeveloperSigningCredential()
.AddInMemoryApiResources(Config.GetApiResources())
.AddInMemoryClients(Config.GetClients());
services.AddControllers().SetCompatibilityVersion(CompatibilityVersion.Version_3_0);
}
修改Configure方法,启用IdentityServer
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
//启用IdentityServer
app.UseIdentityServer();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
运行项目
浏览器输入[http://localhost:59234/.well-known/openid-configuration],可以看到下图所示:
获取token
我们用Postman发送请求测试是否能获取到token,输入Url[http://localhost:59234/connect/token],请求body当中输入参数:
client_id:client.api.service //appid
client_secret:clientsecret //appsecret
grant_type:client_credentials //客户端模式
结果是成功的,如下图所示:
那接下来我们新建一个API资源客户端。
取名WebOAuthDemo.WebApi,Nuget包管理控制台,安装IdentityServer4.AccessTokenValidation
PM>Install-Package IdentityServer4.AccessTokenValidation -Version 3.0.1
修改Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication(Configuration["Identity:Scheme"])
.AddIdentityServerAuthentication(options =>
{
options.RequireHttpsMetadata = false;
options.Authority = $"http://{Configuration["Identity:IP"]}:{Configuration["Identity:Port"]}";
options.ApiName = Configuration["Service:Name"];
});
services.AddControllers().SetCompatibilityVersion(CompatibilityVersion.Version_3_0);
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
//启用Authentication中间件
app.UseAuthentication();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
appsetings.json加上如下配置,可根据自己的需求修改:
"Service": {
"Name": "clientservice", //本服务的名称
"Port": "60806", //本服务的端口号,根据自己服务启动时的端口号进行更改
"DocName": "clientservice",
"Version": "v1",
"Title": "CAS Client Service API",
"Description": "CAS Client Service API provide some API to help you get client information from CAS",
"Contact": {
"Name": "zhanwei",
"Email": "zhanwei103@126.com"
}
},
"Identity": { //去请求授权的Identity服务,这里即IdentityServerDemo的服务启动时的地址
"IP": "localhost",
"Port": "59234", //IdentityServerDemo项目启动时的端口号,根据实际情况修改
"Scheme": "Bearer"
}
控制器 添加一个新的控制器到你的 API 项目中:
[Authorize]
[Route("api/[controller]")]
[ApiController]
public class DefaultController : ControllerBase
{
[HttpGet]
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
}
运行API项目
我们试着Postman中发送API请求,返回的是401 Unauthorized
加上token再进行请求,结果如下图:
Config类中再添加一个获取客户端列表的静态方法
public static IEnumerable
{
return new[]
{
new TestUser
{
SubjectId = "10001",
Username = "test1@163.com",
Password = "test1password"
},
new TestUser
{
SubjectId = "10002",
Username = "test2@163.com",
Password = "test2password"
}
};
}
增加一些测试用户,然后修改ConfigureServices
public void ConfigureServices(IServiceCollection services)
{
services.AddIdentityServer()
.AddDeveloperSigningCredential()
.AddTestUsers(Config.GetUsers().ToList())//注入测试用户
.AddInMemoryApiResources(Config.GetApiResources())
.AddInMemoryClients(Config.GetClients());
services.AddControllers().SetCompatibilityVersion(CompatibilityVersion.Version_3_0);
}
重新运行IdentityServer项目 Postman发送请求,此时已经能够通过密码获取到token了,请求参数如下
client_id:client.api.service //appid
client_secret:clientsecret //appsecret
grant_type:password //密码模式
username:test1@163.com
password:test1password
结果如下图所示:
API项目不需要修改,只不过是第三方客户端请求token的参数变了,其他基本和客户端模式一样,这里就不重复了。
首先,在授权服务中,设置需要请求的ApiResource,client,user
在postman(相当于client)中,输入client的相关信息(client_id,client_serect)去请求token
然后就可以根据授权服务中相应client的AllowedScopes设置的范围来请求服务了。
另外,IdentityServer提供了个QuickStartUI的项目,这套UI能快速的开发具有基本功能的认证/授权界面,可以下载下来研究下。
下载地址:https://github.com/IdentityServer/IdentityServer4.Quickstart.UI/
下一篇准备写一下OAuth2.0的授权码模式,这是一种混合模式,是目前功能最完整、流程最严密的授权模式,也是最常用的一种模式,安全性也高,适用于那些有后端的Web 应用。