1:创建授权服务器项目:AuthorizationServer,添加包:IdentityServer4
2:创建资源服务器项目:ResourcesServer,添加包:IdentityServer4.AccesstokenValidation
3:创建请求客户端项目:ClientCredentials,添加包:IdentityModel
AuthorizationServer代码示例:
public class Config { /// <summary> /// 定义用户可以访问的资源 /// </summary> /// <returns></returns> public static List<ApiResource> GetApiResources() { return new List<ApiResource> { /* 具有单个作用域的简单API,这样定义的话,作用域(scope)和Api名称(ApiName)相同 */ new ApiResource("api","描述"), //如果需要更多控制,则扩展版本 new ApiResource{ Name="userinfo", //资源名称,对应客户端的:ApiName,必须是唯一的 Description="描述", DisplayName="", //显示的名称 //ApiSecrets = //{ // new Secret("secret11".Sha256()) //}, //作用域,对应下面的Cliet的 AllowedScopes Scopes={ new Scope { Name = "apiInfo.read_full", DisplayName = "完全的访问权限", UserClaims={ "super" } }, new Scope { Name = "apiinfo.read_only", DisplayName = "只读权限" } }, }, }; } /// <summary> /// 客户端合法性验证 /// </summary> /// <returns></returns> public static List<Client> GetClients() { #region 客户端模式 ClientCredentials var ClientCredentials = new Client { /******************客户端 请求对应的字段******************* client_id:客户端的ID,必选 grant_type:授权类型,必选,此处固定值“code” client_secret:客户端的密码,必选 scope:申请的权限范围,可选,如果传了必须是正确的,否则也不通过 ************************************/ //这个Client集合里面,ClientId必须是唯一的 ClientId = "780987652", // 客户端ID,客户端传过来的必须是这个,验证才能通过, AllowedGrantTypes = GrantTypes.ClientCredentials,// 授权类型,指客户端可以使用的模式 ClientSecrets = { new Secret("secret".Sha256()) }, //客户端密钥 //ClientSecrets={new Secret("secret".Sha512()) }, //RequireClientSecret = false, //不验证secret ,一般是信得过的第三方 ClientName = "客户端名称", Description = "描述", //Claims = new List<Claim> { // new Claim("super","super") //}, /* 权限范围,对应的ApiResouce,这里是客户端模式,对应的是用户资源,所以是ApiResouce 如果是oidc 这对应的是identityResouece,身份资源 所以是取决于AllowedGrantTypes的类型 允许客户端访问的API作用域 */ AllowedScopes = { "apiInfo.read_full" } // }; var ClientCredentials1 = new Client { ClientId = "userinfo", AllowedGrantTypes = GrantTypes.ClientCredentials, //客户端输入:client_credentials ClientSecrets = { new Secret("secret".Sha256()) }, ClientName = "客户端名称", AllowedScopes = { "apiInfo.read_full" } // }; #endregion #region 密码模式 ResourceOwnerPassword var pwd = new Client { ClientId = "userinfo_pwd", AllowedGrantTypes = GrantTypes.ResourceOwnerPassword,//客户端输入:password ClientSecrets = { new Secret("secret".Sha256()) }, ClientName = "客户端名称", RefreshTokenUsage = TokenUsage.ReUse, AlwaysIncludeUserClaimsInIdToken = true, AllowOfflineAccess = true, AllowedScopes = { "apiInfo.read_full" } // }; #endregion return new List<Client> { ClientCredentials, //ClientCredentials1, //pwd, }; } /// <summary> /// 密码模式,需要用的到用户名和密码,正式操作是在数据库中找 /// </summary> /// <returns></returns> public static List<TestUser> GetTestUsers() { return new List<TestUser> { new TestUser { SubjectId="100000", //用户ID Username="cnblogs", //用户名 Password="123", //密码 Claims=new List<Claim>{ new Claim("name","name") } } }; } }
Startup.cs配置:
public void ConfigureServices(IServiceCollection services) { services.Configure<CookiePolicyOptions>(options => { // This lambda determines whether user consent for non-essential cookies is needed for a given request. options.CheckConsentNeeded = context => true; options.MinimumSameSitePolicy = SameSiteMode.None; }); services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2); //注册ids中间件 services.AddIdentityServer() //设置开发者临时签名凭据 .AddDeveloperSigningCredential() //in-men 方式把信息添加到内存中 .AddInMemoryApiResources(Config.GetApiResources()) .AddInMemoryClients(Config.GetClients()) .AddTestUsers(Config.GetTestUsers()); }
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
//使用ids中间件
app.UseIdentityServer();
}
ResourcesServer 资源服务器认证示例:
public void ConfigureServices(IServiceCollection services) { services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2); //默认的认证方式是Bearer认证 services.AddAuthentication("Bearer") //配置要验证的信息 .AddIdentityServerAuthentication(options => { //令牌或者说AccessToken颁发的地址,Token中会包含该地址 //第一次会去认证服务器获取配置信息 options.Authority = "http://localhost:5003"; //必填 options.ApiName = "userinfo"; options.ApiSecret = "secret"; //options.SaveToken = true; options.RequireHttpsMetadata = false;//暂时取消Https验证, }); //services.AddAuthorization(options => { // options.AddPolicy("client", policy => policy.RequireClaim("client_id")); //}); } public void Configure(IApplicationBuilder app, IHostingEnvironment env) { app.UseAuthentication(); }
资源接口:
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using System.Linq; namespace ResourcesServer.Controllers { [Route("identity")] [Authorize] public class IdentityController : ControllerBase { /// <summary> /// 获取当前的信息 /// </summary> /// <returns></returns> [HttpGet] public ActionResult Get() { return new JsonResult(User.Claims.Select( c => new { c.Type, c.Value })); } [HttpGet] [Route("userInfo")] public ActionResult GetUserInfo() { return new JsonResult(User.Claims.Select( c => new { c.Type, c.Value })); } } }
通过Postman请求授权服务器获取access_token
参数:
client_id:780987652
client_secret:secret
grant_type:client_credentials
然后通过该access_token 请求资源服务器获取资源
nbf:非必须。not before。如果当前时间在nbf里的时间之前,则Token不被接受;一般都会留一些余地,比如几分钟。
exp:#非必须。expire 指定token的生命周期。unix时间戳格式
iss:#非必须。issuer 请求实体,可以是发起请求的用户的信息,也可是jwt的签发者。
aud:#非必须。接收该JWT的一方。
详细信息参考:https://www.cnblogs.com/zjutzz/p/5790180.html
可以把access_token放到jwt.io 看下:
可以通过 http://localhost:5003/.well-known/openid-configuration 查看配置信息
ClientCredentials第三方代码请求方式:
using IdentityModel; using IdentityModel.Client; using Newtonsoft.Json.Linq; using System; using System.Net.Http; using System.Text; namespace ClientCredentials { /// <summary> /// 客户端模式,请求授权服务器获取token,请求资源服务器获取资源 /// 依赖包:IdentityModel /// </summary> class Program { static void Main(string[] args) { string Authority = "http://localhost:5003"; string ApiResurce = "http://localhost:5002/"; var tokenCliet = new HttpClient() { BaseAddress = new Uri(ApiResurce) }; /* 这样做的目的是: 资源服务器会去授权服务器认证,所以在客户端可以先判断下授权服务器是否挂了 */ DiscoveryCache _cache = new DiscoveryCache(Authority); var disco1 = _cache.GetAsync().Result; if (disco1.IsError) throw new Exception(disco1.Error); //或者 var disco = tokenCliet.GetDiscoveryDocumentAsync(Authority).Result; if (disco.IsError) throw new Exception(disco.Error); var response = tokenCliet.RequestClientCredentialsTokenAsync(new ClientCredentialsTokenRequest { Address = disco.TokenEndpoint, ClientId = "780987652", ClientSecret= "secret", //GrantType= "client_credentials" }).Result; if (response.IsError) throw new Exception(response.Error); var token = response.AccessToken; //把token,Decode if (response.AccessToken.Contains(".")) { //Console.WriteLine("\nAccess Token (decoded):"); Console.ForegroundColor = ConsoleColor.Green; Console.WriteLine("\nAccess Token (decoded):"); Console.ResetColor(); var parts = response.AccessToken.Split('.'); var header = parts[0]; var claims = parts[1]; Console.WriteLine(JObject.Parse(Encoding.UTF8.GetString(Base64Url.Decode(header)))); Console.WriteLine(JObject.Parse(Encoding.UTF8.GetString(Base64Url.Decode(claims)))); } //设置请求的Token tokenCliet.SetBearerToken(token); //请求并返回字符串 var apiResource1 = tokenCliet.GetStringAsync("identity").Result; var userinfo = tokenCliet.GetStringAsync("identity/userinfo").Result; var j = JObject.Parse(userinfo); //或者 var getVal = tokenCliet.GetAsync("api/values").Result; if (getVal.IsSuccessStatusCode) { Console.WriteLine(getVal.Content.ReadAsStringAsync().Result); } Console.ReadLine(); } } }
Access Token (decoded)的结果