二.客户端凭据的认证方式
public static class Config{//定义APIpublic static IEnumerable<ApiScope> ApiScopes =>new List<ApiScope>{new ApiScope("api1", "My API")};//定义客户端// 定义访问 API 的客户端应用// 这种情况下,客户端没有交互式的用户,只能通过客户端密钥进行身份验证。// ClientId 和 Client Secret 可以视为登录名和密码。让身份认证服务器知道是哪个用户。public static IEnumerable<Client> Clients => new List<Client>{new Client{// 定义一个客户端ClientId = "client",// 认证类型。//使用客户端密钥的方式进行验证。AllowedGrantTypes = GrantTypes.ClientCredentials,// 认证的密码ClientSecrets ={new Secret("secret".Sha256())},// 这个客户端端可以访问的 APIAllowedScopes = { "api1" }}};
2.在 里面做调用设置
public Startup(IConfiguration configuration){Configuration = configuration;}public IConfiguration Configuration { get; }public void ConfigureServices(IServiceCollection services){services.AddControllers();var builder = services.AddIdentityServer().AddInMemoryApiScopes(Config.ApiScopes) // 那个api可以使用.AddInMemoryClients(Config.Clients)// 哪个client可以使用.AddDeveloperSigningCredential() ;//解决连接不上的问题,实际中,签名需要一对公钥和私钥,他会帮我们将公钥和私钥存储到硬盘上}public void Configure(IApplicationBuilder app, IWebHostEnvironment env){if (env.IsDevelopment()){app.UseDeveloperExceptionPage();}app.UseRouting();app.UseIdentityServer();//使用 Identity 4app.UseEndpoints(endpoints =>{endpoints.MapControllers();});}
重生成,而使用命令行运行一下
… --urls *:5001
2.建立受保护的API
1.建立要访问受保护的
[ApiController]public class IdentityController : ControllerBase{[HttpGet("identity")][Authorize]public IActionResult Get(){return new JsonResult(from c in User.Claims select new { c.Type, c.Value });}[Authorize][HttpGet("test")]public string Getst(){return "受保护的 API 访问成功";}}
2.在 里面进行配置
public void ConfigureServices(IServiceCollection services){services.AddControllers();//接受授服务器发送的任何访问令牌。services.AddAuthentication("Bearer").AddJwtBearer("Bearer", options =>{options.Authority = "https://localhost:5001";options.TokenValidationParameters = new TokenValidationParameters{ValidateAudience = false};});// 允许检测客户端请求的令牌中是否存在作用域services.AddAuthorization(options =>{options.AddPolicy("ApiScope", policy =>{policy.RequireAuthenticatedUser();policy.RequireClaim("scope", "api1");});});}public void Configure(IApplicationBuilder app, IWebHostEnvironment env){if (env.IsDevelopment()){app.UseDeveloperExceptionPage();}app.UseRouting();app.UseAuthentication();//每次都要执行身份验证.app.UseAuthorization(); //授权,确保匿名客户端无法访问我们的 api 端点app.UseEndpoints(endpoints =>{endpoints.MapControllers();});}
3.建立用来访问的客户端
public class Program{// IdentityModel包括一个与发现端点一起使用的客户端库。这样,您只需要知道IdentityServer的基地址-可以从元数据中读取实际的端点地址://找到授权的服务器public static async System.Threading.Tasks.Task Main(string[] args){//找到授权服务器var client = new HttpClient();var disco = await client.GetDiscoveryDocumentAsync("http://localhost:5001"); //异步的方法名前边必须声明 asyncif (disco.IsError){Console.WriteLine($"连接失败****{disco.Error}");return;}//向授权服务器请求 tokenvar tokenResponse = await client.RequestClientCredentialsTokenAsync(new ClientCredentialsTokenRequest{Address = disco.TokenEndpoint,ClientId = "client",ClientSecret = "secret",Scope = "api1"});if (tokenResponse.IsError){Console.WriteLine("token返回错误", tokenResponse.Error);return;}Console.WriteLine(tokenResponse.Json);//将访问令牌发送给 APIvar apiClient = new HttpClient();apiClient.SetBearerToken(tokenResponse.AccessToken);var response = await apiClient.GetAsync("http://localhost:6001/test");if (!response.IsSuccessStatusCode){Console.WriteLine(response.StatusCode);Console.WriteLine("访问失败");}else{var content = await response.Content.ReadAsStringAsync();Console.WriteLine("访问成功");// Console.WriteLine(JArray.Parse(content));Console.WriteLine(content);}Console.ReadLine();}
具体查看gitee代码:
4.保护WPF客户端
这种是有用户参与的,使用的是 Grant (用名名密码)这种授权方式。
1.流程
运行的顺序和字母的顺序是一致的:
所以在这个过程之中,即对用户进行身份认证,也对浏览器进行身份认证。
具体操作看代码吧。我也没用过MVC
2.为MVC刷新 token
使用 Token 刷新 Token。
1.在MVC 客户端进行设置
new Client{ClientId = "mvc client",ClientName = "ASP.NET Core MVC Client", // 随便写AllowedGrantTypes = GrantTypes.CodeAndClientCredentials,//允许两种授权方式.ClientSecrets = { new Secret("mvc secret".Sha256()) },//密码.//下边都是固定的地址。RedirectUris = { "http://localhost:5002/signin-oidc" },FrontChannelLogoutUri = "http://localhost:5002/signout-oidc",PostLogoutRedirectUris = { "http://localhost:5002/signout-callback-oidc" },AlwaysIncludeUserClaimsInIdToken = true,AllowOfflineAccess = true, // offline_access//默认是一个小时。现在改成 60s 。AccessTokenLifetime = 60, // 60 secondsAllowedScopes ={"api1",IdentityServerConstants.StandardScopes.OpenId,IdentityServerConstants.StandardScopes.Email,IdentityServerConstants.StandardScopes.Address,IdentityServerConstants.StandardScopes.Phone,IdentityServerConstants.StandardScopes.Profile}},
但是仅仅这样设置,过了一分钟以后,进入 MVC ,依然还能得到 API 的相关信息 。这是因为我们的 API 没有设置对于 对于token检验。
2.对受保护的 API进行设置
services.AddAuthentication("Bearer")//bearer 的授权方式。.AddJwtBearer("Bearer", options =>{options.Authority = "https://localhost:5000";options.RequireHttpsMetadata = false;//不需要httpsoptions.TokenValidationParameters = new TokenValidationParameters{ValidateAudience = false};//每隔一分钟,验证一次这个tokenoptions.TokenValidationParameters.RequireExpirationTime = true;//必须要有超时时间这个参数。options.TokenValidationParameters.ClockSkew = TimeSpan.FromMinutes(1);});
3.刷新 Token
在 MVC 的控制器里面,再添加一个 ,用来刷新 Token。
//刷新 tokenprivate async Task<string> RenewTokensAsync(){//发现文档var client = new HttpClient();var disco = await client.GetDiscoveryDocumentAsync("http://localhost:5000");if (disco.IsError){throw new Exception(disco.Error);}//获取 refreshTokenvar refreshToken = await HttpContext.GetTokenAsync(OpenIdConnectParameterNames.RefreshToken);//请求tokenvar tokenResponse = await client.RequestRefreshTokenAsync(new RefreshTokenRequest{Address = disco.TokenEndpoint,ClientId = "mvc client",ClientSecret = "mvc secret",Scope = "api1 openid profile email phone address",GrantType = OpenIdConnectGrantTypes.RefreshToken,RefreshToken = refreshToken});if (tokenResponse.IsError){throw new Exception(tokenResponse.Error);}//超时时间,使用的是 UTC 时间。var expiresAt = DateTime.UtcNow + TimeSpan.FromSeconds(tokenResponse.ExpiresIn);// 把 token 数组全部集中起来,做成一个数组。var tokens = new[]{new AuthenticationToken{Name = OpenIdConnectParameterNames.IdToken,Value = tokenResponse.IdentityToken},new AuthenticationToken{Name = OpenIdConnectParameterNames.AccessToken,Value = tokenResponse.AccessToken},new AuthenticationToken{Name = OpenIdConnectParameterNames.RefreshToken,Value = tokenResponse.RefreshToken},new AuthenticationToken{Name = "expires_at",Value = expiresAt.ToString("o", CultureInfo.InvariantCulture)}};// 获取身份认证的结果,包含当前的pricipal和propertiesvar currentAuthenticateResult =await HttpContext.AuthenticateAsync(CookieAuthenticationDefaults.AuthenticationScheme);// 把获得的token再更新一遍。currentAuthenticateResult.Properties.StoreTokens(tokens);// 再进行以下登录动作.await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme,currentAuthenticateResult.Principal, currentAuthenticateResult.Properties);// 再把 access token 返回过去.return tokenResponse.AccessToken;}
在前边用到token的地方,再更新一下这个方法:
var response = await client.GetAsync("http://localhost:5001/identity");if (!response.IsSuccessStatusCode){if (response.StatusCode == HttpStatusCode.Unauthorized){await RenewTokensAsync();return RedirectToAction();}throw new Exception(response.ReasonPhrase);}
5. Flow 6. Flow
使用 保护 api 资源。
之前使用的 隐式流,所有的令牌都是通过浏览器来传输,这对于 ID token 来说当然没有问题,但是我们还想请求一个 token, 更加敏感,所以在没有必要的时候,我们不会想把他暴露给外部世界, 包含了一个叫做 “混合流( flowe)”,它为我们提供了两方面的优点,身份令牌通过浏览器频道来传输,这样客户端就能能够在做任何工作前验证他,如果验证成功,客户端就会打开一个后端通道,来链接令牌,以检索访问令牌。
1.客户端类型 机密客户端:这种客户端有能力维护其凭据的机密性。位于服务端,例如服务器端的 Web 应用,例如 MVC。公开客户端:这种客户端无法维持其凭据的机密性,位于客户端设备,例如 JS 应用,移动应用,原生应用和软件等。
.和相比 就是相当于多出来一个 Flow
2.返回类型
根据 的不同,分为三种情况:
3.角色和策略 3.1基于角色
在注册的用户里面直接声明 管理员角色