tnblog
首页
视频
资源
登录

.net core 3.1 Identity Server4 (Code模式)

5755人阅读 2020/12/15 14:33 总访问:3658147 评论:0 收藏:0 手机
分类: Ids4

.netcore

.net core 3.1 Identity Server4 (Code模式)

Code 模式的理解

大致说一下,这种授权模式的意义。
A. 用户通过浏览器在页面上请求客户端需要授权的页面时,会自动跳转到授权服务器上去登录(这里授权服务器会去验证客户端信息)
B. 然后用户在授权服务器上的登录页面进行登录,登录成功后会返回一个授权验证码(授权验证码!=授权码)
C. 然后跳转到用户需要授权的页面
D. 客户端就会拿这这授权验证码去授权服务器那边验证,然后获取授权码[access_token](注意这里授权服务器会去验证你的客户端,比如验证访问链接来源,secret,clientid)
E. 获取到Access Token

创建MVC客户端(AiDaSi.OcDemo.MVC)

安装依赖包 Microsoft.AspNetCore.Authentication.OpenIdConnect

  1. Install-Package Microsoft.AspNetCore.Authentication.OpenIdConnect -Version 3.1.9

修改Startup.cs

  1. public void ConfigureServices(IServiceCollection services)
  2. {
  3. // 我们关闭了JWT的Claim 类型映射, 以便允许well-known claims
  4. JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
  5. JwtSecurityTokenHandler.DefaultMapInboundClaims = false;
  6. services.AddAuthentication(options =>
  7. {
  8. options.DefaultScheme = "Cookies";
  9. options.DefaultChallengeScheme = "oidc";
  10. })
  11. .AddCookie("Cookies") // 我们用作Cookies作为首选方式
  12. .AddOpenIdConnect("oidc", options =>
  13. {
  14. options.SignInScheme = "Cookies";
  15. options.Authority = "https://localhost:7200"; // 授权地址
  16. options.ClientId = "client_id_mvc";
  17. options.ClientSecret = "mvc_secret";
  18. // 退出设置
  19. options.SignedOutCallbackPath = "/Home/Index";
  20. // options.RequireHttpsMetadata = true; // 为http方式请求尝试了一下行不通必须https两边都是
  21. options.ResponseType = "code";// 类型
  22. options.SaveTokens = true; // 保存token
  23. // options.GetClaimsFromUserInfoEndpoint = true; // 获取所有信息
  24. options.Scope.Clear(); // 清理范围
  25. options.Scope.Add("ApiOne");
  26. options.Scope.Add("openid");
  27. options.Scope.Add("profile");
  28. options.Scope.Add("rc.bc");
  29. options.Scope.Add("offline_access");// 脱机访问令牌
  30. });
  31. services.AddHttpClient();
  32. services.AddControllersWithViews();
  33. }

在下面的Configure方法中添加好下列两句代码

  1. app.UseAuthentication();
  2. app.UseAuthorization();

修改客户端HomeController中的Privacy方法与页面

  1. [Authorize]
  2. public async Task<IActionResult> Privacy()
  3. {
  4. var accessToken = await HttpContext.GetTokenAsync(OpenIdConnectParameterNames.AccessToken);
  5. var idToken = await HttpContext.GetTokenAsync(OpenIdConnectParameterNames.IdToken);
  6. var refreshToken = await HttpContext.GetTokenAsync(OpenIdConnectParameterNames.RefreshToken);
  7. var code = await HttpContext.GetTokenAsync(OpenIdConnectParameterNames.Code);
  8. ViewData["accessToken"] = accessToken;
  9. ViewData["idToken"] = idToken;
  10. ViewData["refreshToken"] = refreshToken;
  11. ViewData["code"] = code;
  12. // 获取接口数据
  13. var httpClient = _httpClientFactory.CreateClient();
  14. httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
  15. var Result = await httpClient.GetAsync("http://localhost:5280/WeatherForecast");
  16. if (Result.IsSuccessStatusCode)
  17. {
  18. ViewData["Apione"] = await Result.Content.ReadAsStringAsync();
  19. }
  20. return View();
  21. }
  1. @{
  2. ViewData["Title"] = "Privacy Policy";
  3. }
  4. <h1>@ViewData["Title"]</h1>
  5. <h2>Access Token:</h2>
  6. <p>@ViewData["accessToken"]</p>
  7. <h2>Id Token:</h2>
  8. <p>@ViewData["idToken"]</p>
  9. <h2>Refresh Token:</h2>
  10. <p>@ViewData["refreshToken"]</p>
  11. <h2>Code:</h2>
  12. <p>@ViewData["code"]</p>
  13. <h2>Apione:</h2>
  14. <p>@ViewData["Apione"]</p>
  15. <dl>
  16. @foreach (var claim in User.Claims)
  17. {
  18. <dt>@claim.Type</dt>
  19. <dd>@claim.Value</dd>
  20. }
  21. </dl>

将AiDaSi.OcDemo.Authenzation(授权服务器),ApIDemo1(接口)修改为https。因为本人亲自尝试了很多遍,如果为http,在授权服务器登录成功后将会一直循环到login登录页面那儿…(这是幻术伊邪那美^_^)。

修改授权服务器

Config.cs添加Mvc客户端

  1. new Client {
  2. ClientId = "client_id_mvc",
  3. ClientName = "ASP.NET Core MVC Client",
  4. AllowedGrantTypes = GrantTypes.Code,
  5. ClientSecrets = { new Secret("mvc_secret".Sha256()) },
  6. // Species允许 URI返回令牌或授权代码到
  7. RedirectUris = { "https://localhost:5002/signin-oidc" },
  8. // 为基于HTTP前端通道的注销指定客户端的注销 URI。
  9. FrontChannelLogoutUri = "https://localhost:5002/signout-oidc",
  10. // 允许URI在注销后重定向到的退出
  11. PostLogoutRedirectUris = { "https://localhost:5002/Home/Index" },
  12. AllowedScopes = {
  13. "ApiOne",
  14. IdentityServerConstants.StandardScopes.OpenId,
  15. IdentityServerConstants.StandardScopes.Profile,
  16. "rc.bc"
  17. },
  18. // 将所有声明放在 id标记中,允许有很多Claims
  19. AlwaysIncludeUserClaimsInIdToken = true,
  20. // 获取或设置一个值,该值指示是否允许脱机访问. 默认值为 false。
  21. AllowOfflineAccess = true,
  22. // 指定是否需要同意屏幕(默认值为 false)
  23. RequireConsent = false,
  24. },

StartupConfigureServices中添加授权登录地址。

  1. services.ConfigureApplicationCookie(config => {
  2. config.Cookie.Name = "IdentityServer.Cookie"; // 设置Cookie名称
  3. config.LoginPath = "/IdentityCodeAuth/Login"; // 设置登录地址
  4. });

创建用户实例

  1. public class LoginViewModel
  2. {
  3. /// <summary>
  4. /// 用户名
  5. /// </summary>
  6. public string Username { get; set; }
  7. /// <summary>
  8. /// 密码
  9. /// </summary>
  10. public string Password { get; set; }
  11. /// <summary>
  12. /// 返回连接
  13. /// </summary>
  14. public string ReturnUrl { get; set; }
  15. }

在添加授权地址后我们也应该添加相应的控制器,并给对应的用户模型附上用户名与返回地址。returnUrl参数表示服务器跳转到客户端时的链接。

  1. public class IdentityCodeAuthController : Controller
  2. {
  3. // 用户界面使用提供的服务与 IdentityServer进行通信。
  4. private readonly IIdentityServerInteractionService _interaction;
  5. public IdentityCodeAuthController(
  6. IIdentityServerInteractionService interaction,
  7. )
  8. {
  9. _interaction = interaction;
  10. }
  11. [HttpGet]
  12. public async Task<IActionResult> Login(string returnUrl)
  13. {
  14. var vm = new LoginViewModel() { ReturnUrl = returnUrl };
  15. // 获取上下文的内容
  16. var context = await _interaction.GetAuthorizationContextAsync(returnUrl);
  17. // 判断用户名是否存在
  18. vm.Username = context?.LoginHint;
  19. return View(vm);
  20. }
  21. }

创建出所对应的view,里面放了一个表单

  1. @model LoginViewModel
  2. <form>
  3. <input type="hidden" asp-for="ReturnUrl" />
  4. <div>
  5. <label>Username</label>
  6. <input asp-for="Username" />
  7. </div>
  8. <div>
  9. <label>Password</label>
  10. <input asp-for="Password" />
  11. </div>
  12. <div>
  13. <button type="submit">Sign In</button>
  14. </div>
  15. </form>

接着我们来运行一下,通过点击客户端首页上的Privacy,然后成功跳转到了授权服务器的登录页面。完成了A项。

接着我们添加登录时需要处理的代码,我们准备放到IdentityCodeAuth控制器中的Login页面进行处理。处理时登录成功,拿到授权验证码到跳转到客户端的页面,就完成了B与C两项。

  1. <form asp-controller="IdentityCodeAuth" asp-action="Login" method="post">
  1. /// <summary>
  2. /// ValidateAntiForgeryToken 防伪
  3. /// </summary>
  4. /// <param name="vm"></param>
  5. /// <returns></returns>
  6. [HttpPost]
  7. [ValidateAntiForgeryToken]
  8. public async Task<IActionResult> Login(LoginViewModel vm)
  9. {
  10. // 获取上下文的内容
  11. var context = await _interaction.GetAuthorizationContextAsync(vm.ReturnUrl);
  12. if (ModelState.IsValid)
  13. {
  14. var result = await _signInManager.PasswordSignInAsync(vm.Username, vm.Password, false, false);
  15. // 判断登录是否成功
  16. if (result.Succeeded)
  17. {
  18. // 登录成功
  19. return Redirect(vm.ReturnUrl);
  20. }
  21. else if (result.IsLockedOut)
  22. {
  23. // 如果登录失败就执行如下。。。
  24. // 不做处理就会直接回到Login页面中。。。
  25. }
  26. }
  27. return View(vm);
  28. }

接着我们尝试登录试一下

最后我们看到访问需要授权接口时拿去到了Access Token,并访问到了接口。

退出授权

_Layout.cshtml页面上Privacy后面添加一行判断是否登录的代码;如果登录了,则显示登录按钮。(同样的我们这里Logout方法并不存在,所以需要添加Home控制器下的Logout方法)

  1. @if (User.Identity.IsAuthenticated)
  2. {
  3. <li class="nav-item">
  4. <a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Logout">Logout</a>
  5. </li>
  6. }
  1. public async Task<IActionResult> Logout()
  2. {
  3. return SignOut("Cookies", "oidc");
  4. }

接着在授权服务器上Startup.cs添加登出的地址。(因为你再客户端上登出了,在没有授权服务器上退出)

  1. services.ConfigureApplicationCookie(config => {
  2. config.Cookie.Name = "IdentityServer.Cookie"; // 设置Cookie名称
  3. config.LoginPath = "/IdentityCodeAuth/Login"; // 设置登录地址
  4. config.LogoutPath = "/IdentityCodeAuth/Logout"; // 设置退出地址
  5. });

IdentityCodeAuth控制器下添加退出的Logout方法

  1. [HttpGet]
  2. public async Task<IActionResult> Logout(string logoutId)
  3. {
  4. await _signInManager.SignOutAsync();
  5. var logoutRequest = await _interaction.GetLogoutContextAsync(logoutId);
  6. if (string.IsNullOrEmpty(logoutRequest.PostLogoutRedirectUri))
  7. {
  8. return RedirectToAction("Index", "WeatherForecast");
  9. }
  10. return Redirect(logoutRequest.PostLogoutRedirectUri);
  11. }

运行测试一下。

注册模块

在授权服务器上创建RegisterViewModel.cs注册实例,添加注册方法(Register)与对应的视图

RegisterViewModel.cs

  1. public class RegisterViewModel
  2. {
  3. [Required]
  4. public string Username { get; set; }
  5. [Required]
  6. [DataType(DataType.Password)]
  7. public string Password { get; set; }
  8. [Required]
  9. [DataType(DataType.Password)]
  10. [Compare("Password")]
  11. public string ConfirmPassword { get; set; }
  12. public string ReturnUrl { get; set; }
  13. }

IdentityCodeAuthController.cs

  1. /// <summary>
  2. /// 注册页面
  3. /// </summary>
  4. /// <param name="returnUrl"></param>
  5. /// <returns></returns>
  6. [HttpGet]
  7. public IActionResult Register(string returnUrl)
  8. {
  9. return View(new RegisterViewModel { ReturnUrl = returnUrl });
  10. }

Register.cshtml

  1. @model RegisterViewModel
  2. @* 注册失败的错误消息 *@
  3. @if (ViewData["Message"] != null )
  4. {
  5. <div style="color:red">
  6. @ViewData["Message"].ToString()
  7. </div>
  8. }
  9. <form asp-controller="IdentityCodeAuth" asp-action="Register" method="post">
  10. <input type="hidden" asp-for="ReturnUrl" />
  11. <div>
  12. <label>Username</label>
  13. <input asp-for="Username" />
  14. <span asp-validation-for="Username"></span>
  15. </div>
  16. <div>
  17. <label>Password</label>
  18. <input asp-for="Password" />
  19. <span asp-validation-for="Password"></span>
  20. </div>
  21. <div>
  22. <label>Confirm Password</label>
  23. <input asp-for="ConfirmPassword" />
  24. <span asp-validation-for="ConfirmPassword"></span>
  25. </div>
  26. <div>
  27. <button type="submit">Register In</button>
  28. </div>
  29. </form>
  30. <a asp-controller="IdentityCodeAuth" asp-action="Login" asp-route-returnUrl="@Model.ReturnUrl">Back to Login</a>

为登录页面添加注册标签 Login.cshtml

  1. <a asp-controller="IdentityCodeAuth" asp-action="Register" asp-route-returnUrl="@Model.ReturnUrl">Register</a>

接着我们在IdentityCodeAuthController添加处理注册方法

  1. /// <summary>
  2. /// 验证注册
  3. /// </summary>
  4. /// <param name="vm"></param>
  5. /// <returns></returns>
  6. [HttpPost]
  7. public async Task<IActionResult> Register(RegisterViewModel vm)
  8. {
  9. string Message = "";
  10. try
  11. {
  12. //验证模型是否有效
  13. //判断两次密码是否一致
  14. if (!ModelState.IsValid)
  15. {
  16. throw new Exception("验证模型失败");
  17. }
  18. //创建用户
  19. var user = new IdentityUser(vm.Username);
  20. var result = await _userManager.CreateAsync(user, vm.Password);
  21. if (result.Succeeded)
  22. {
  23. //登录用户
  24. await _signInManager.SignInAsync(user, false);
  25. return Redirect(vm.ReturnUrl);
  26. }
  27. }
  28. catch (Exception ex)
  29. {
  30. Message = ex.Message;
  31. }
  32. ViewData["Message"] = Message;
  33. return View(vm);
  34. }

当我们验证不通过时,会显示错误消息,通过则会直接登录返回到客户端。


我们发现有些Cookie声明映射是多余的不需要的,如:amr、s_hash,我们可以在MVC客户端中的Startup.cs中AddOpenIdConnect委托里将其删除。

  1. //删除cookie声明映射
  2. options.ClaimActions.DeleteClaim("amr");
  3. options.ClaimActions.DeleteClaim("s_hash");

刷新Token

在这之前呢我们先把授权服务器的Access Token设置为1分钟,并且把Api的验证Token的时间也为1分钟。二者缺一不可。在此之前请在mvc客户端引用好IdentityModel

AiDaSi.OcDemo.Authenzation —> Config.cs

  1. new Client {
  2. ClientId = "client_id_mvc",
  3. ClientName = "ASP.NET Core MVC Client",
  4. AllowedGrantTypes = GrantTypes.Code,
  5. ClientSecrets = { new Secret("mvc_secret".Sha256()) },
  6. // Species允许 URI返回令牌或授权代码到
  7. RedirectUris = { "https://localhost:5002/signin-oidc" },
  8. // 为基于HTTP前端通道的注销指定客户端的注销 URI。
  9. FrontChannelLogoutUri = "https://localhost:5002/signout-oidc",
  10. // 允许URI在注销后重定向到的退出
  11. PostLogoutRedirectUris = { "https://localhost:5002/Home/Index" },
  12. AllowedScopes = {
  13. "ApiOne",
  14. IdentityServerConstants.StandardScopes.OpenId,
  15. IdentityServerConstants.StandardScopes.Profile,
  16. "rc.bc"
  17. },
  18. // 允许浏览器通过 (如果js等SPA需要通过验证)
  19. // AllowAccessTokensViaBrowser = true,
  20. // 将所有声明放在 id标记中,允许有很多Claims
  21. AlwaysIncludeUserClaimsInIdToken = true,
  22. // 获取或设置一个值,该值指示是否允许脱机访问. 默认值为 false。
  23. AllowOfflineAccess = true,
  24. // 设置Token时间为60秒,除了这里要设置之外也需要在api资源中设置验证过期时间
  25. AccessTokenLifetime = 60,
  26. // 指定是否需要同意屏幕(默认值为 false)
  27. RequireConsent = false,
  28. },

ApIDemo1 —> Startup.cs

  1. services
  2. .AddAuthentication("Bearer")
  3. .AddJwtBearer("Bearer", config =>
  4. {
  5. config.Authority = "https://localhost:7200"; // 授权服务器地址
  6. //确定自己是哪个资源(资源名称)
  7. config.Audience = "ApiOne";
  8. config.RequireHttpsMetadata = false; // 是否使用https进行通信
  9. //取消验证用户以及验证角色
  10. config.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters()
  11. {
  12. ValidateIssuer = true,
  13. ValidateAudience = false,
  14. //每间隔1分钟去检查Token是否有效
  15. ClockSkew = TimeSpan.FromMinutes(1),
  16. //要求运行有超时时间
  17. RequireExpirationTime = true
  18. };
  19. });

启动项目,我们通过 jwt.io 解析到过期时间(exp),相隔1分钟后我们再次刷新页面。

我们发现Api请求不了了,下面我们将添加刷新Token的代码;由于exp是Unix时间戳,所以这里我们创建一个TimeHelper.cs工具类

  1. public static class TimeHelper
  2. {
  3. //将unix时间戳转换成系统时间
  4. public static DateTime unixtime(this string time)
  5. {
  6. DateTime dtStart = TimeZone.CurrentTimeZone.ToLocalTime(new DateTime(1970, 1, 1));
  7. long lTime = long.Parse(time + "0000000");
  8. TimeSpan toNow = new TimeSpan(lTime);
  9. DateTime dtResult = dtStart.Add(toNow);
  10. return dtResult;
  11. }
  12. //将系统时间转换成unix时间戳
  13. public static long timeunix2(this DateTime dt)
  14. {
  15. DateTimeOffset dto = new DateTimeOffset(dt);
  16. return dto.ToUnixTimeSeconds();
  17. }
  18. //将系统时间转换成unix时间戳
  19. public static DateTime unixtime2(this double d)
  20. {
  21. System.DateTime time = System.DateTime.MinValue;
  22. System.DateTime startTime = TimeZone.CurrentTimeZone.ToLocalTime(new System.DateTime(1970, 1, 1));
  23. time = startTime.AddMilliseconds(d);
  24. return time;
  25. }
  26. }

HomeController.cs控制器中引用AiDaSi.OcDemo.MVC.Helper的命名空间,并添加RenewTokensAsync方法,对Token进行刷新。获取到新Token后我们有两种不同的方法对Token进行更新。

  1. private async Task<string> RenewTokensAsync()
  2. {
  3. var client = _httpClientFactory.CreateClient();
  4. var disco = await client.GetDiscoveryDocumentAsync("https://localhost:7200");
  5. if (disco.IsError)
  6. {
  7. // 我们这里将Cookie清空掉
  8. foreach (var item in Request.Cookies)
  9. {
  10. Response.Cookies.Delete(item.Key);
  11. }
  12. // 报错
  13. return await Task.FromResult(disco.Error);
  14. // throw new Exception(disco.Error);
  15. }
  16. var refreshToken = await HttpContext.GetTokenAsync(OpenIdConnectParameterNames.RefreshToken)
  17. // 刷新token的操作
  18. var tokenResponse = await client.RequestRefreshTokenAsync(new RefreshTokenRequest
  19. {
  20. Address = disco.TokenEndpoint,
  21. ClientId = "client_id_mvc",
  22. ClientSecret = "mvc_secret",
  23. RefreshToken = refreshToken
  24. });
  25. #region 第一种写法
  26. if (tokenResponse.IsError)
  27. {
  28. // 我们这里将Cookie清空掉
  29. foreach (var item in Request.Cookies)
  30. {
  31. Response.Cookies.Delete(item.Key);
  32. }
  33. return await Task.FromResult(tokenResponse.Error);
  34. // 报错
  35. // throw new Exception(tokenResponse.Error);
  36. }
  37. var expiresAt = DateTime.UtcNow + TimeSpan.FromSeconds(tokenResponse.ExpiresIn);
  38. var tokens = new[]
  39. {
  40. new AuthenticationToken
  41. {
  42. Name = OpenIdConnectParameterNames.IdToken,
  43. Value = tokenResponse.IdentityToken
  44. },
  45. new AuthenticationToken
  46. {
  47. Name = OpenIdConnectParameterNames.AccessToken,
  48. Value = tokenResponse.AccessToken
  49. },
  50. new AuthenticationToken
  51. {
  52. Name = OpenIdConnectParameterNames.RefreshToken,
  53. Value = tokenResponse.RefreshToken
  54. },
  55. new AuthenticationToken
  56. {
  57. Name = "expires_at",
  58. Value = expiresAt.ToString("o", CultureInfo.InvariantCulture)
  59. }
  60. };
  61. // 获取身份认证的结果,包含当前的pricipal和properties
  62. var currentAuthenticateResult =
  63. await HttpContext.AuthenticateAsync(CookieAuthenticationDefaults.AuthenticationScheme);
  64. // 把新的tokens存起来
  65. currentAuthenticateResult.Properties.StoreTokens(tokens);
  66. // 登录
  67. await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme,
  68. currentAuthenticateResult.Principal, currentAuthenticateResult.Properties);
  69. return tokenResponse.AccessToken;
  70. #endregion
  71. #region 第二种写法
  72. //下面将修改上下文
  73. var authInfo = await HttpContext.AuthenticateAsync("Cookies");
  74. authInfo.Properties.UpdateTokenValue("access_token", tokenResponse.AccessToken);
  75. authInfo.Properties.UpdateTokenValue("id_token", tokenResponse.IdentityToken);
  76. authInfo.Properties.UpdateTokenValue("refresh_token", tokenResponse.RefreshToken);
  77. //二次认证(更新token)
  78. await HttpContext.SignInAsync("Cookies", authInfo.Principal, authInfo.Properties);
  79. #endregion
  80. }

Privacy方法中对Token失效进行验证

  1. [Authorize]
  2. public async Task<IActionResult> Privacy()
  3. {
  4. var accessToken = await HttpContext.GetTokenAsync(OpenIdConnectParameterNames.AccessToken);
  5. var idToken = await HttpContext.GetTokenAsync(OpenIdConnectParameterNames.IdToken);
  6. var refreshToken = await HttpContext.GetTokenAsync(OpenIdConnectParameterNames.RefreshToken);
  7. var code = await HttpContext.GetTokenAsync(OpenIdConnectParameterNames.Code);
  8. ViewData["accessToken"] = accessToken;
  9. ViewData["idToken"] = idToken;
  10. ViewData["refreshToken"] = refreshToken;
  11. ViewData["code"] = code;
  12. // 获取接口数据
  13. var httpClient = _httpClientFactory.CreateClient();
  14. //httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
  15. httpClient.SetBearerToken(accessToken);
  16. // 验证Token是否失效
  17. string tokenStr = accessToken;
  18. var handler = new JwtSecurityTokenHandler();
  19. var payload = handler.ReadJwtToken(tokenStr).Payload;
  20. var expclaim = payload.Claims.FirstOrDefault(x=>x.Type == "exp");
  21. DateTime dateTime = expclaim.Value.unixtime();
  22. int compNum = DateTime.Compare(DateTime.Now, dateTime);
  23. //判断当前时间是否大于token的过期时间,如果有就刷新token,这样就能达到无缝衔接
  24. if (compNum > 0)
  25. {
  26. await RenewTokensAsync();
  27. return RedirectToAction();
  28. }
  29. var Result = await httpClient.GetAsync("http://localhost:5280/WeatherForecast");
  30. if (Result.IsSuccessStatusCode)
  31. {
  32. ViewData["Apione"] = await Result.Content.ReadAsStringAsync();
  33. }
  34. return View();

由于场景很难模拟,大家可以打断点自行测试。

接下来将更新javascript的客户端…


欢迎加群讨论技术,1群:677373950(满了,可以加,但通过不了),2群:656732739

评价

ServiceStack.Redis操作Redis配置单例模式

我携漫天星辰以赠你,仍觉漫天星辰不如你。单利的应该是连接池而不应该是redis对象。如果每次操作都是一个redis对象是会有...

GIT中的PR模式

GIT中的Pull Request模式(简称PR)PR是开发者使用Github进行协作的利器。PR是协作者修改代码后或在原基础上增加新代码后向...

EF三种模式解析

万般皆下品,惟有编程高我希望你是为我而来如果我爱你,而你也正巧爱我。你头发乱了时候,我会笑笑地替你拨一拨,然后,手...

identity server4 的授权模式

授权模式OAuth2.0 定义了四种授权模式:Implicit:简化模式;直接通过浏览器的链接跳转申请令牌。Client Credentials:客户...

identity server4 四种授权模式

爱情哪有那么复杂,能让你开开心心笑得最甜的那个人就是对的人下面介绍4种模式安全性从低到高客户端模式客户端模式只对客户...

IdentityServer4实现OAuth2.0四种模式之授权码模式

授权码模式隐藏码模式最大不同是授权码模式不直接返回token,而是先返回一个授权码,然后再根据这个授权码去请求token。这...

也谈TDD,以及三层架构、设计模式、ORM……

想在园子里写点东西已经很久了,但一直没有落笔,还有些软文做推广,还要做奶爸带孩子,还要……好吧,我承认,真正的原因...

.netcore 3.1 MediatR:轻松实现命令查询职责分离模式(CQRS)

.netcore 3.1 MediatR:轻松实现命令查询职责分离模式(CQRS)[TOC] 中介者模式 用一个中介对象封装一系列的对象交...

.net core 3.1 Identity Server4 (ClientCredentials模式)

.net core 3.1 Identity Server4 (ClientCredentials模式)[TOC] ClientCredentials 模式的理解 在这之前我先问大家...

.net core 3.1 Identity Server4 (Password模式)

.net core 3.1 Identity Server4 (Password模式)[TOC] Password 模式的理解 当应用程序将用户的用户名和密码交换为...

Go Map与工厂模式,在Go语言中实现Set

Go Map与工厂模式,在Go语言中实现Set[TOC] Map与工厂模式 Map 的 value 可以是一个方法与 Go 的 Dock type 接口方式一起...

.net core 3.1 Identity Server4 (Implicit模式)

.net core 3.1 Identity Server4 (Implicit模式)[TOC] Implicit 模式的理解 A.用户通过浏览器访问客户端,然后客...

.net core 3.1 Identity Server4 (Hybrid模式)

.net core 3.1 Identity Server4 (Hybrid模式)[TOC] Hybrid 模式的理解 Hybrid 模式相当于(Code模式+Impact模式),所...

.net core 3.1 Identity Server4 (自定义模式)

.net core 3.1 Identity Server4 (自定义模式)[TOC] IdentityServer4除了提供常规的几种授权模式外(AuthorizationCod...

策略设计模式需要的4大步骤

1:策略接口类: 是对策略, 算法的抽象. 定义了每个策略和算法必须有的算法和属性.2:策略实现类: 策略,算法的具体实现. 策...
这一世以无限游戏为使命!
排名
2
文章
657
粉丝
44
评论
93
docker中Sware集群与service
尘叶心繁 : 想学呀!我教你呀
一个bug让程序员走上法庭 索赔金额达400亿日元
叼着奶瓶逛酒吧 : 所以说做程序员也要懂点法律知识
.net core 塑形资源
剑轩 : 收藏收藏
映射AutoMapper
剑轩 : 好是好,这个对效率影响大不大哇,效率高不高
ASP.NET Core 服务注册生命周期
剑轩 : http://www.tnblog.net/aojiancc2/article/details/167
ICP备案 :渝ICP备18016597号-1
网站信息:2018-2025TNBLOG.NET
技术交流:群号656732739
联系我们:contact@tnblog.net
公网安备:50010702506256
欢迎加群交流技术