identity4 系列————启航篇[二]
前言
开始identity的介绍了。
正文
前文介绍了一些概念,如果概念不清的话,可以去前文查看。
https://www.cnblogs.com/aoximin/p/13475444.html 对一些理论概念的初步介绍一下。
那么什么是identityserver4 呢?
IdentityServer is a free, open source OpenID Connect and OAuth 2.0 framework for ASP.NET Core.
Founded and maintained by Dominick Baier and Brock Allen, IdentityServer4 incorporates all the protocol implementations and extensibility points needed to integrate token-based authentication, single-sign-on and API access control in your applications. IdentityServer4 is officially certified by the OpenID Foundation and thus spec-compliant and interoperable. It is part of the .NET Foundation, and operates under their code of conduct. It is licensed under Apache 2 (an OSI approved license).
上面说identityserver 是免费开源的的OpenId Connect 和 oauth 2.0 框架。
这里有人可能会有疑问openid connect 不是基于oauth 2.0吗,这个是的呢。 只是openId connect 基于oauth 2.0 是为了实现身份相关的,而oauth 2.0 是授权其实和身份没有关系。有些不需要身份的,依然可以使用identityservice 提供的oauth 2.0的实现。
简单点说就是identityservice 实现了oauth 2.0,在oauth 2.0 的基础上又实现了openid connect,你还可以利用oauth 2.0 实现其他的。
下面也介绍了他们两个的关系:
OpenID Connect and OAuth 2.0 are very similar – in fact OpenID Connect is an extension on top of OAuth 2.0. The two fundamental security concerns, authentication and API access, are combined into a single protocol - often with a single round trip to the security token service.
We believe that the combination of OpenID Connect and OAuth 2.0 is the best approach to secure modern applications for the foreseeable future. IdentityServer4 is an implementation of these two protocols and is highly optimized to solve the typical security problems of today’s mobile, native and web applications.
现在就来实践一下吧。
学习一个框架,先看下他的quick start 吧。
https://identityserver4.readthedocs.io/en/latest/quickstarts/0_overview.html
这里有个preparation 让我们先安装模板。
dotnet new -i IdentityServer4.Templates
可以看到这样模板就安装完成了。
然后随便找一个目录,进行通过模板创建。
先创建一个空的吧。
dotnet new is4empty -n IdentityServer
官方也介绍了这几个文件是啥了。
IdentityServer.csproj - the project file and a Properties\launchSettings.json file
Program.cs and Startup.cs - the main application entry point
Config.cs - IdentityServer resources and clients configuration file
使用identity 也很简单了。
第一步就是注入服务:
var builder = services.AddIdentityServer(options =>
{
// see https://identityserver4.readthedocs.io/en/latest/topics/resources.html
options.EmitStaticAudienceClaim = true;
})
.AddInMemoryIdentityResources(Config.IdentityResources)
.AddInMemoryApiScopes(Config.ApiScopes)
.AddInMemoryClients(Config.Clients);
public static class Config
{
public static IEnumerable<IdentityResource> IdentityResources =>
new IdentityResource[]
{
new IdentityResources.OpenId()
};
public static IEnumerable<ApiScope> ApiScopes =>
new ApiScope[]
{ };
public static IEnumerable<Client> Clients =>
new Client[]
{ };
}
第二步就是注入中间件:
app.UseIdentityServer();
这里就不直接看源码了,identity4的源码篇在后面,后面会有identity4的源码篇, 后面的更新速度也会加快。
那么来看看这几个配置是什么吧。
第一个是identityResource, 这个是什么呢? 这个是用户的身份资源。
比如邮箱地址,OpenId、 地址啊、电话号码头像之类的。
这些是用户的身份资源,表示用户的一些信息。
然后apiscopes 是什么东西呢?
An API is a resource in your system that you want to protect. Resource definitions can be loaded in many ways, the template you used to create the project above shows how to use a “code as configuration” approach.
The Config.cs is already created for you. Open it, update the code to look like this:
public static class Config
{
public static IEnumerable<ApiScope> ApiScopes =>
new List<ApiScope>
{
new ApiScope("api1", "My API")
};
}
官方也介绍了这个是干什么的, 这个就是受保护的api 资源。
为什么要创建这个呢?
If you will be using this in production it is important to give your API a logical name.
Developers will be using this to connect to your api though your Identity server.
It should describe your api in simple terms to both developers and users.
上面是这个api的作用。
最后一个介绍的是客户端, 这里的客户端并不是我们提到的前端。
我们知道identityServer 表示的是身份服务,那么和其对接需要身份认证授权的就是客户端了。
这一点需要理解,而不是说客户端就是用户展示层,客户端和服务端都是相对而言的。
客户端的注册方式如下:
public static IEnumerable<Client> Clients =>
new List<Client>
{
new Client
{
ClientId = "client",
// no interactive user, use the clientid/secret for authentication
AllowedGrantTypes = GrantTypes.ClientCredentials,
// secret for authentication
ClientSecrets =
{
new Secret("secret".Sha256())
},
// scopes that client has access to
AllowedScopes = { "api1" }
}
};
这样我们就注册了一个客户端, 这个客户端获取到access_token 能获得的授权是api1。
当我们配置好这些,启动程序后,我们将可以访问一个url:
https://localhost:5001/.well-known/openid-configuration
里面是什么东西呢?我们指定well-known 的意思是众所周知的意思,那么这个应该是ids 公开的信息,看一下。
里面是ids 的一些配置,比如说grant_types_supported 支持哪些授权方式。
scopes_supported 表示支持哪些资源的授权。
claims_supported 表示的是支持哪些用户身份信息。
我修改配置后:
public static IEnumerable<IdentityResource> IdentityResources =>
new IdentityResource[]
{
new IdentityResources.OpenId(),
new IdentityResources.Address()
};
public static IEnumerable<ApiScope> ApiScopes =>
new List<ApiScope>
{
new ApiScope("api1", "My API")
};
那么我打开配置后是这样的。
好了,这里ids 就配置完成了,也就是identityServer 就配置完了。
那么上面我们注册了受保护的api。
public static IEnumerable<ApiScope> ApiScopes =>
new List<ApiScope>
{
new ApiScope("api1", "My API")
};
那么我们如果建立一个api1,那么我们该怎么写呢?
官网给了我们例子,那么可以看一下吧。
https://github.com/IdentityServer/IdentityServer4/tree/main/samples/Quickstarts/1_ClientCredentials
其实就是我们的api1, 这个怎么写的问题。
打开项目后,看到了这个Api,这个就是官方给了我们例子。
其实也是很简单的, 在Startup中, 看下怎么配置的。
// accepts any access token issued by identity server
services.AddAuthentication("Bearer")
.AddJwtBearer("Bearer", options =>
{
options.Authority = "https://localhost:5001";
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateAudience = false
};
});
// adds an authorization policy to make sure the token is for scope 'api1'
services.AddAuthorization(options =>
{
options.AddPolicy("ApiScope", policy =>
{
policy.RequireAuthenticatedUser();
policy.RequireClaim("scope", "api1");
});
});
上面配置了access_token的验证方式。
然后增加了授权策略,scope 里面必须有api1。
最后再管道中注入策略, 这样就不用每个api 都加上策略了。
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers()
.RequireAuthorization("ApiScope");
});
授权策略其实就是验证scope 中带有api1。
那么现在就知道了,apiResouces 是什么了, 且知道了api 怎么进行验证了。
那么看下客户端吧。
前文我们在ids注册了客户端为:
public static IEnumerable<Client> Clients =>
new List<Client>
{
new Client
{
ClientId = "client",
// no interactive user, use the clientid/secret for authentication
AllowedGrantTypes = GrantTypes.ClientCredentials,
// secret for authentication
ClientSecrets =
{
new Secret("secret".Sha256())
},
// scopes that client has access to
AllowedScopes = { "api1" }
}
};
那么我们怎么请求token呢?
// discover endpoints from metadata
var client = new HttpClient();
var disco = await client.GetDiscoveryDocumentAsync("https://localhost:5001");
if (disco.IsError)
{
Console.WriteLine(disco.Error);
return;
}
// request token
var tokenResponse = await client.RequestClientCredentialsTokenAsync(new ClientCredentialsTokenRequest
{
Address = disco.TokenEndpoint,
ClientId = "client",
ClientSecret = "secret",
Scope = "api1"
});
if (tokenResponse.IsError)
{
Console.WriteLine(tokenResponse.Error);
return;
}
Console.WriteLine(tokenResponse.Json);
Console.WriteLine("\n\n");
这里面使用了IdentityModel, 里面为我们封装了对identityService的请求。
上面client.GetDiscoveryDocumentAsync("https://localhost:5001"); 其实就是请求:
https://localhost:5001/.well-known/openid-configuration
通过这个获取到TokenEndpoint。
然后通过tokenEndpoint 获取到token。
里面传的也就是我们在identityServer 里面注册的客户端信息了。
来看一下获取的access_token 是怎么样的。
"eyJhbGciOiJSUzI1NiIsImtpZCI6IkM3OTEyQjMyMTREMTc1MTg3NzgyNDc1ODk4OEY1MjUxIiwidHlwIjoiYXQrand0In0.eyJuYmYiOjE2NjA0NjIyMjgsImV4cCI6MTY2MDQ2NTgyOCwiaXNzIjoiaHR0cHM6Ly9sb2NhbGhvc3Q6NTAwMSIsImNsaWVudF9pZCI6ImNsaWVudCIsImp0aSI6IkMzRjEyMTQwMTdGQTRCRjMyNTQ1QzJEQTA1MTlFREVDIiwiaWF0IjoxNjYwNDYyMjI4LCJzY29wZSI6WyJhcGkxIl19.u_KqoiIsdu33Tl79CbMLeVSM3Avr2tRqeyrFe62_oPlCjD34AzIa0JI1afhneRxkZ4iftENlYapSzoa2QPfvc8G4AF4HyZN-MS1CjU2nNT_ASxg84nXWUR9k6e8D7PIc-gBlPNqje-zObJjiEb_2qDTkEgmBbx8gd8zjGrClwGXVo85yhYE2efF64ymP0FV9XE34Zi9OELtVL41xGYghw-BKEL-NAXFJGBqlkmqZ-EPccsNlaBq-EG0OwqTIVTg7JNGd5LRjLLIFmY79bsns5v02qJGCVluOF8dEKCG_sRqbw7VvyBC5vP5xUElx10TsPDCn9TxyAeMGObLYninnvQ"
然后通过这个token,我们用jwt解析一下。
可以看到这个token的访问区域就是api1了。
然后通过这个token 去访问api1吧。
api1 中有一个action为:
[Route("identity")]
[Authorize]
public class IdentityController : ControllerBase
{
public IActionResult Get()
{
return new JsonResult(from c in User.Claims select new { c.Type, c.Value });
}
}
访问方式为:
// call api
var apiClient = new HttpClient();
apiClient.SetBearerToken(tokenResponse.AccessToken);
var response = await apiClient.GetAsync("https://localhost:6001/identity");
if (!response.IsSuccessStatusCode)
{
Console.WriteLine(response.StatusCode);
}
else
{
var content = await response.Content.ReadAsStringAsync();
Console.WriteLine(JArray.Parse(content));
}
查看结果:
上面介绍了一个quick start。 但是仅仅这个是不满足我们的业务需求的,我们还需要实现单点登录。下一章介绍。
结
简单整理一下identityServer4的使用,后面把那几个例子解释一下,然后就介绍实际开发中的问题,包括数据迁移、无法访问等、跳转慢等等等问题。