创建一个基础的授权服务器,客户端和API
通过这个练习,你将创建一个最小可用的IdentityServer
做为OAuth2授权服务器。
我们从最基本的功能和配置项开始(完整代码可以从这里下载).
随着学习的深入,后面还有进一步的例程。当前这个练习包括:
创建一个自托管的
IdentityServer
设置好客户端,使用服务账号和代理用户账号与认证服务通信
注册一个API
获取访问令牌
调用 API
验证访问令牌
搭建 IdentityServer
首先我们创建一个控制台程序,然后配置IdentityServer
.
使用visual studio创建一个标准控制台程序,然后安装Nuget程序包
install-package identityserver3
登记API
所有需要访问令牌才能调用的APIs,可以归类到一系列作用域。这些作用域必须提前在IdentityServer
中注册。我们创建一个叫做Scope
的类来管理所有的作用域。
译者注:比如处理用户信息的可以成为用户作用域
.订单相关的可以放在订单作用域
下面.
using IdentityServer3.Core.Models;
static class Scopes
{
public static List<Scope> Get()
{
return new List<Scope>
{
new Scope
{
Name = "api1"
}
};
}
}
登记客户端
我们先注册一个单一客户端,这个客户端可以请求 api1
作用域的令牌。第一个迭代我们我们只使用服务账号获取令牌(可以认为这是机器到机器的通信),在后面的迭代中,我们会加入用户概念。
对于客户端,我们需要配置:
显示名和ID (ID不能重复)
客户端密钥 (用于令牌服务认证当前客户端)
处理方式(客户端凭据流动方向)
引用令牌,引用令牌不需要签名证书
访问
api1
作用域
using IdentityServer3.Core.Models;
static class Clients
{
public static List<Client> Get()
{
return new List<Client>
{
// no human involved
new Client
{
ClientName = "Silicon-only Client",
ClientId = "silicon",
Enabled = true,
AccessTokenType = AccessTokenType.Reference,
Flow = Flows.ClientCredentials,
ClientSecrets = new List<Secret>
{
new Secret("F621F470-9731-4A25-80EF-67A6F7C5F4B8".Sha256())
},
AllowedScopes = new List<string>
{
"api1"
}
}
};
}
}
配置IdentityServer
IdentityServer 其实是一个OWIN的中间件. 所以我们在Startup
类中用UseIdentityServer
扩展方法.
下面的方法使用前面的作用域和客户端配置好了一个基础的授权服务。我们同样配置了一个空的用户列表,后面我们会添加用户。
using Owin;
using System.Collections.Generic;
using IdentityServer3.Core.Configuration;
using IdentityServer3.Core.Services.InMemory;
namespace IdSrv
{
class Startup
{
public void Configuration(IAppBuilder app)
{
var options = new IdentityServerOptions
{
Factory = new IdentityServerServiceFactory()
.UseInMemoryClients(Clients.Get())
.UseInMemoryScopes(Scopes.Get())
.UseInMemoryUsers(new List<InMemoryUser>()),
RequireSsl = false
};
app.UseIdentityServer(options);
}
}
}
添加日志功能
因为我们的托管程序是一个控制台程序,所以我们希望日志可以直接在控制台上输出。
Serilog 是一个完美的选择:
install-package serilog
install-package serilog.sinks.literate
托管 IdentityServer
最后一步是托管IdentityServer
,所以我们在控制台程序中添加Katana自托管程序包。
install-package Microsoft.Owin.SelfHost
在 Program.cs
中添加如下代码:
// logging
Log.Logger = new LoggerConfiguration()
.WriteTo
.LiterateConsole(outputTemplate: "{Timestamp:HH:mm} [{Level}] ({Name:l}){NewLine} {Message}{NewLine}{Exception}")
.CreateLogger();
// hosting identityserver
using (WebApp.Start<Startup>("http://localhost:5000"))
{
Console.WriteLine("server running...");
Console.ReadLine();
}
这个控制台程序运行的时候,会在输出一系列信息,最后打印Server running
...
添加API
现在我们来添加一个简单的web API,这个API必须获得上面IdentityServer的访问令牌才可以调用。
创建一个web应用
在解决方案中增加一个 ASP.NET Web Application
并选择 Empty
选项 (不引用任何framework).
安装必要的程序包:
install-package Microsoft.Owin.Host.SystemWeb
install-package Microsoft.AspNet.WebApi.Owin
install-package IdentityServer3.AccessTokenValidation
增加一个控制器
添加一个最简单的控制器:
[Route("test")]
public class TestController : ApiController
{
public IHttpActionResult Get()
{
var caller = User as ClaimsPrincipal;
return Json(new
{
message = "OK computer",
client = caller.FindFirst("client_id").Value
});
}
}
控制的User
属性可以取到访问令牌中的声明信息。
添加Startup
Add the following Startup
class for both setting up web api and configuring trust with IdentityServer
在web API项目中,增加Startup
类,配置号webAPI,并信任上面建立的IdentityServer
。
using Microsoft.Owin;
using Owin;
using System.Web.Http;
using IdentityServer3.AccessTokenValidation;
[assembly: OwinStartup(typeof(Apis.Startup))]
namespace Apis
{
public class Startup
{
public void Configuration(IAppBuilder app)
{
// accept access tokens from identityserver and require a scope of 'api1'
app.UseIdentityServerBearerTokenAuthentication(new IdentityServerBearerTokenAuthenticationOptions
{
Authority = "http://localhost:5000",
ValidationMode = ValidationMode.ValidationEndpoint,
RequiredScopes = new[] { "api1" }
});
// configure web api
var config = new HttpConfiguration();
config.MapHttpAttributeRoutes();
// require authentication for all controllers
config.Filters.Add(new AuthorizeAttribute());
app.UseWebApi(config);
}
}
}
用浏览器访问上面的API--因为没有访问令牌,浏览器会显示401错误。
增加控制台客户端
接下来我们增加一个控制台客户端,它向IdentityServer
请求访问令牌,并用这个令牌访问API。
首先在解决方案里面增加一个控制台应用程序,然后装上OAuth2的nuget包:
install-package IdentityModel
首先用客户端凭据得到访问令牌:
using IdentityModel.Client;
static TokenResponse GetClientToken()
{
var client = new TokenClient(
"http://localhost:5000/connect/token",
"silicon",
"F621F470-9731-4A25-80EF-67A6F7C5F4B8");
return client.RequestClientCredentialsAsync("api1").Result;
}
然后用这个令牌调用API
static void CallApi(TokenResponse response)
{
var client = new HttpClient();
client.SetBearerToken(response.AccessToken);
Console.WriteLine(client.GetStringAsync("http://localhost:14869/test").Result);
}
在控制台客户端的Main函数里,依次调用上面两个方法,一切okay的话,控制台会显示{"message":"OK computer","client":"silicon"}
。
增加一个用户
目前为止,客户端使用服务账号得到访问令牌的,下面我们要引入用户--人!
添加一个用户服务
The user service manages users - for this sample we will use the simple in-memory user service.
First we need to define some users:
用户服务管理用户--为了减少不必要的复杂度, 这里我们用内存用户服务。
首先我们硬编码一些用户:
using IdentityServer3.Core.Services.InMemory;
static class Users
{
public static List<InMemoryUser> Get()
{
return new List<InMemoryUser>
{
new InMemoryUser
{
Username = "bob",
Password = "secret",
Subject = "1"
},
new InMemoryUser
{
Username = "alice",
Password = "secret",
Subject = "2"
}
};
}
}
Username
和Password
用来认证一个用户,
Subject
是一个标识符,在系统中唯一标识一个用, 它会被内嵌在访问令牌中。
在Startup
中,用上面的Get
方法替换空的用户列表。
添加一个新的客户端
接下来,我们添加一个客户端的定义,使用所谓的资源所有者密码凭据授权
,这个流允许客户端发送用户名密码给令牌服务,然后得到一个访问令牌。
客户端Clients
代码如下:
using IdentityServer3.Core.Models;
using System.Collections.Generic;
namespace IdSrv
{
static class Clients
{
public static List<Client> Get()
{
return new List<Client>
{
// no human involved
new Client
{
ClientName = "Silicon-only Client",
ClientId = "silicon",
Enabled = true,
AccessTokenType = AccessTokenType.Reference,
Flow = Flows.ClientCredentials,
ClientSecrets = new List<Secret>
{
new Secret("F621F470-9731-4A25-80EF-67A6F7C5F4B8".Sha256())
},
AllowedScopes = new List<string>
{
"api1"
}
},
// human is involved
new Client
{
ClientName = "Silicon on behalf of Carbon Client",
ClientId = "carbon",
Enabled = true,
AccessTokenType = AccessTokenType.Reference,
Flow = Flows.ResourceOwner,
ClientSecrets = new List<Secret>
{
new Secret("21B5F798-BE55-42BC-8AA8-0025B903DC3B".Sha256())
},
AllowedScopes = new List<string>
{
"api1"
}
}
};
}
}
}
更新API
当具体的人引入权限系统的时候,访问令牌包括一个sub
声明来唯一标识用户。
[Route("test")]
public class TestController : ApiController
{
public IHttpActionResult Get()
{
var caller = User as ClaimsPrincipal;
var subjectClaim = caller.FindFirst("sub");
if (subjectClaim != null)
{
return Json(new
{
message = "OK user",
client = caller.FindFirst("client_id").Value,
subject = subjectClaim.Value
});
}
else
{
return Json(new
{
message = "OK computer",
client = caller.FindFirst("client_id").Value
});
}
}
}
更新客户端
Next add a new method to the client to request an access token on behalf of a user:
接下来,我们在控制台客户端增加一个模拟用户名密码获取访问令牌的方法:
static TokenResponse GetUserToken()
{
var client = new TokenClient(
"http://localhost:5000/connect/token",
"carbon",
"21B5F798-BE55-42BC-8AA8-0025B903DC3B");
return client.RequestResourceOwnerPasswordAsync("bob", "secret", "api1").Result;
}
在客户端Main代码中,调用用户名密码来获取令牌,然后查看获取的令牌信息及API响应信息。
下一步:
经过这个代码演练,你已经会编写简单的OAuth2服务啦,接下来,你将尝试:
其它处理流程 - 如:隐式流,代码流和混合流。他们实现其它复杂场景(邦联认证,外部认证) 的基础。
使用用户数据库 - 或者编写自己的用户服务或者使用ASP.net标识服务和成员服务。
保存客户端和作用域配置到数据库中。我们有现成的EF支持。
使用OpenID Connect 和标识符作用域来认证用户。
上面提到的 大部分知识点,会在下一个代码演练MVC walkthrough 提及.