id4 使用 授权码模式授权
1 添加客户端
设置好clientid,添加客户端密钥,设置允许的作用域(scope)加上 openid 和 profile以及其它允许访问的scope,允许离线访问(请求 token 的时候scope要加上offline_access 会返回 refresh token),设置好重定向url(接收code的地址),设置好允许的授权类型为 authorization_code
2 mvc 客户端相关代码
startup.cs 代码如下:
using IdentityModel; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using System.IdentityModel.Tokens.Jwt; namespace MvcClient { public class Startup { public Startup(IConfiguration configuration) { Configuration = configuration; } public IConfiguration Configuration { get; } // This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { services.AddControllersWithViews(); // 添加认证 services.AddAuthentication("IdentityCookieAuthenScheme").AddCookie("IdentityCookieAuthenScheme", options => { // sso 登录 options.LoginPath = "/Login/Index"; // 未登陆的回调地址 // 本地登录 //options.LoginPath = "/Login/LocalIndex"; options.Cookie.Name = "AuthCookie"; }); //JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear(); // 包 System.IdentityModel.Tokens.Jwt; //services.AddAuthentication(options => //{ // options.DefaultScheme = "Cookies"; // options.DefaultChallengeScheme = "oidc"; //}) //.AddCookie("Cookies", options => //{ // options.LoginPath = "/Login/Index"; // 未认证的回跳地址 // options.Cookie.Name = "AuthCookie"; //}) //.AddOpenIdConnect("oidc", options => // 安装包 Microsoft.AspNetCore.Authentication.OpenIdConnect //{ // options.Authority = "https://localhost:44310"; // identity server 服务器地址 // options.SignInScheme = "Cookies"; // options.ClientId = "mvc_client"; // options.ClientSecret = "mvc_secret"; // options.ResponseType = "code"; // //options.RequireHttpsMetadata = false; // options.SaveTokens = true; // 把获取到的token写入到cookie // options.Scope.Clear(); // options.Scope.Add("openid"); // options.Scope.Add("profile"); // options.Scope.Add(OidcConstants.StandardScopes.OfflineAccess); //}); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } else { app.UseExceptionHandler("/Home/Error"); // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. app.UseHsts(); } app.UseHttpsRedirection(); app.UseStaticFiles(); app.UseRouting(); app.UseAuthentication(); app.UseAuthorization(); app.UseEndpoints(endpoints => { endpoints.MapControllerRoute( name: "default", pattern: "{controller=Home}/{action=Index}/{id?}"); }); } } }
using IdentityModel; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using System.IdentityModel.Tokens.Jwt; namespace MvcClient { public class Startup { public Startup(IConfiguration configuration) { Configuration = configuration; } public IConfiguration Configuration { get; } // This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { services.AddControllersWithViews(); // 添加认证 services.AddAuthentication("IdentityCookieAuthenScheme").AddCookie("IdentityCookieAuthenScheme", options => { // sso 登录 options.LoginPath = "/Login/Index"; // Authorize 失败后的回调地址 // 本地登录 //options.LoginPath = "/Login/LocalIndex"; options.Cookie.Name = "AuthCookie"; }); //JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear(); // 包 System.IdentityModel.Tokens.Jwt; //services.AddAuthentication(options => //{ // options.DefaultScheme = "Cookies"; // options.DefaultChallengeScheme = "oidc"; //}) //.AddCookie("Cookies", options => //{ // options.LoginPath = "/Login/Index"; // 未认证的回跳地址 // options.Cookie.Name = "AuthCookie"; //}) //.AddOpenIdConnect("oidc", options => // 安装包 Microsoft.AspNetCore.Authentication.OpenIdConnect //{ // options.Authority = "https://localhost:44310"; // identity server 服务器地址 // options.SignInScheme = "Cookies"; // options.ClientId = "mvc_client"; // options.ClientSecret = "mvc_secret"; // options.ResponseType = "code"; // //options.RequireHttpsMetadata = false; // options.SaveTokens = true; // 把获取到的token写入到cookie // options.Scope.Clear(); // options.Scope.Add("openid"); // options.Scope.Add("profile"); // options.Scope.Add(OidcConstants.StandardScopes.OfflineAccess); //}); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } else { app.UseExceptionHandler("/Home/Error"); // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. app.UseHsts(); } app.UseHttpsRedirection(); app.UseStaticFiles(); app.UseRouting(); app.UseAuthentication(); app.UseAuthorization(); app.UseEndpoints(endpoints => { endpoints.MapControllerRoute( name: "default", pattern: "{controller=Home}/{action=Index}/{id?}"); }); } } }
未认证用户处理 LoginController 代码如下:
using Microsoft.AspNetCore.Mvc; using System.Text; using System.Net.Http; using System.IO; using Newtonsoft.Json.Linq; namespace MvcClient.Controllers { public class LoginController : Controller { private static string clientId = "mvc_client"; private static string clientSecret = "mvc_secret"; private static string id4Server = "https://localhost:44310";// 5001 44310 private static string scope = "openid profile offline_access client_credentials_apis.WeatherForecastController.scope"; // 获取指定scope的token才有权调用该scope对应的apiresource 加上 offline_access 会返回 refresh token private static string redirectUri = "https://localhost:7001/Login/Callback"; // 接口code的地址 public IActionResult Index() { //// 未登录 // 从授权服务器获取 code string url = $"{id4Server}/connect/authorize?client_id={clientId}&response_type=code&scope={scope}&redirect_uri={redirectUri}&state=custom_state"; return Redirect(url); } /// <summary> /// code 回调 /// </summary> /// <param name="code"></param> /// <param name="state">自定义值</param> /// <returns></returns> public IActionResult Callback(string code, string state) { if (string.IsNullOrWhiteSpace(code)) return Json(new { code = 0,msg="获取 code 失败"}); string accessToken = string.Empty; string refreshToken = string.Empty; /* * 根据code 换取 access token */ var postData = $"grant_type=authorization_code&code={code}&client_id={clientId}&client_secret={clientSecret}&redirect_uri={redirectUri}"; var requestBody = Encoding.UTF8.GetBytes(postData); // 设置请求体类型 string requestContentType = "application/x-www-form-urlencoded"; using (HttpClient http = new HttpClient()) { HttpResponseMessage message = null; using (Stream requestBodyStream = new MemoryStream(requestBody ?? new byte[0])) { using (HttpContent content = new StreamContent(requestBodyStream)) { content.Headers.Add("Content-Type", requestContentType); var task = http.PostAsync(id4Server + "/connect/token", content); message = task.Result; } } using (message) { string result = message.Content.ReadAsStringAsync().Result; var json = JObject.Parse(result); accessToken = json.GetValue("access_token").ToString(); refreshToken = json["refresh_token"].ToString(); } } /* * 使用 access token 访问受保护资源 */ using (HttpClient httpclient = new HttpClient()) { // 不使用token访问 // 401 (Unauthorized).) //string res1 = httpclient.GetStringAsync("https://localhost:6001/Identity/Get").Result; // 使用 token 访问 httpclient.DefaultRequestHeaders.Add("Authorization", "Bearer " + accessToken); var apiRes1 = httpclient.GetStringAsync($"https://localhost:6001/Test/Ping").Result; // 有token就能访问且 client allowedscope 包含 client_credentials_apis.WeatherForecastController.scope 才能访问 var res_res3 = httpclient.GetStringAsync($"https://localhost:6001/WeatherForecast/Ping").Result; // 有token就能访问且 client allowedscope 包含 client_credentials_apis.IdentityUserController.scope 才能访问 //var apiRes2 = httpclient.GetStringAsync($"https://localhost:6001/IdentityUser/Ping").Result; } // 通过 refresh token 刷新 access token var postData1 = $"grant_type=refresh_token&client_id={clientId}&client_secret={clientSecret}&refresh_token={refreshToken}"; var requestBody1 = Encoding.UTF8.GetBytes(postData1); using (HttpClient http = new HttpClient()) { HttpResponseMessage message = null; using (Stream requestBodyStream = new MemoryStream(requestBody1 ?? new byte[0])) { using (HttpContent content = new StreamContent(requestBodyStream)) { content.Headers.Add("Content-Type", requestContentType); var task = http.PostAsync(id4Server + "/connect/token", content); message = task.Result; } } using (message) { string result = message.Content.ReadAsStringAsync().Result; var json = JObject.Parse(result); accessToken = json.GetValue("access_token").ToString(); http.DefaultRequestHeaders.Add("Authorization", "Bearer " + accessToken); // 有token就能访问 var userInfoRes = http.GetStringAsync($"{id4Server}/connect/userinfo").Result; } } return Json(new { code = 0, msg = "OK", data = new { code, state } }); } } }