前言

由于之前的博客都是基于其他的博客进行开发,现在重新整理一下方便以后后期使用与学习

新建IdentityServer4服务端

服务端也就是提供服务,如QQ Weibo等。

新建项目解决方案AuthSample.

新建一个ASP.NET Core Web Application 项目MvcCookieAuthSample,选择模板Web 应用程序 不进行身份验证。

给网站设置默认地址     http://localhost:5000

第一步:添加Nuget包:IdentityServer4

添加IdentityServer4 引用:

  1. Install-Package IdentityServer4

第二步:添加Config.cs配置类

然后添加配置类Config.cs:

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Threading.Tasks;
  5. using IdentityServer4;
  6. using IdentityServer4.Models;
  7. using IdentityServer4.Test;
  8.  
  9. namespace MvcCookieAuthSample
  10. {
  11. public class Config
  12. {
  13. //所有可以访问的Resource
  14. public static IEnumerable<ApiResource> GetApiResources()
  15. {
  16. return new List<ApiResource>()
  17. {
  18. new ApiResource("api1","API Application")
  19. };
  20. }
  21.  
  22. //客户端
  23. public static IEnumerable<Client> GetClients()
  24. {
  25. return new List<Client>
  26. {
  27. new Client{
  28. ClientId="mvc",
  29. AllowedGrantTypes=GrantTypes.Implicit,//模式:隐式模式
  30. ClientSecrets={//私钥
  31. new Secret("secret".Sha256())
  32. },
  33. AllowedScopes={//运行访问的资源
  34. IdentityServerConstants.StandardScopes.Profile,
  35. IdentityServerConstants.StandardScopes.OpenId,
  36. },
  37. RedirectUris={"http://localhost:5001/signin-oidc"},//跳转登录到的客户端的地址
  38. PostLogoutRedirectUris={"http://localhost:5001/signout-callback-oidc"},//跳转登出到的客户端的地址
  39. RequireConsent=false//是否需要用户点击确认进行跳转
  40. }
  41. };
  42. }
  43.  
  44. //测试用户
  45. public static List<TestUser> GetTestUsers()
  46. {
  47. return new List<TestUser>
  48. {
  49. new TestUser{
  50. SubjectId="",
  51. Username="wyt",
  52. Password="password"
  53. }
  54. };
  55. }
  56.  
  57. //定义系统中的资源
  58. public static IEnumerable<IdentityResource> GetIdentityResources()
  59. {
  60. return new List<IdentityResource>
  61. {
  62. //这里实际是claims的返回资源
  63. new IdentityResources.OpenId(),
  64. new IdentityResources.Profile(),
  65. new IdentityResources.Email()
  66. };
  67. }
  68.  
  69. }
  70. }

第三步:添加Startup配置

引用命名空间:

  1. using IdentityServer4;

然后打开Startup.cs 加入如下:

  1. services.AddIdentityServer()
  2. .AddDeveloperSigningCredential()//添加开发人员签名凭据
  3. .AddInMemoryApiResources(Config.GetApiResources())//添加内存apiresource
  4. .AddInMemoryClients(Config.GetClients())//添加内存client
  5. .AddInMemoryIdentityResources(Config.GetIdentityResources())//添加系统中的资源
  6. .AddTestUsers(Config.GetTestUsers());//添加测试用户
  1. public void Configure(IApplicationBuilder app, IHostingEnvironment env)
  2. {
  3. ...
  4. app.UseIdentityServer();
  5. ...
  6. }

注册登录实现

我们还需要新建一个ViewModels,在ViewModels中新建RegisterViewModel.cs和LoginViewModel.cs来接收表单提交的值以及来进行强类型视图

  1. using System.ComponentModel.DataAnnotations;
  2.  
  3. namespace MvcCookieAuthSample.ViewModels
  4. {
  5. public class RegisterViewModel
  6. {
  7. [Required]//必须的
  8. [DataType(DataType.EmailAddress)]//内容检查是否为邮箱
  9. public string Email { get; set; }
  10.  
  11. [Required]//必须的
  12. [DataType(DataType.Password)]//内容检查是否为密码
  13. public string Password { get; set; }
  14.  
  15. [Required]//必须的
  16. [DataType(DataType.Password)]//内容检查是否为密码
  17. public string ConfirmedPassword { get; set; }
  18. }
  19. }
  1. using System;
  2. using System.Collections.Generic;
  3. using System.ComponentModel.DataAnnotations;
  4. using System.Linq;
  5. using System.Threading.Tasks;
  6.  
  7. namespace MvcCookieAuthSample.ViewModels
  8. {
  9. public class LoginViewModel
  10. {
  11.  
  12. [Required]
  13. public string UserName { get; set; }
  14.  
  15. [Required]//必须的
  16. [DataType(DataType.Password)]//内容检查是否为密码
  17. public string Password { get; set; }
  18. }
  19. }

在Controllers文件夹下新建AdminController.cs

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Threading.Tasks;
  5. using Microsoft.AspNetCore.Mvc;
  6.  
  7. namespace MvcCookieAuthSample.Controllers
  8. {
  9. public class AdminController : Controller
  10. {
  11. public IActionResult Index()
  12. {
  13. return View();
  14. }
  15. }
  16. }

在Views文件夹下新建Admin文件夹,并在Admin文件夹下新建Index.cshtml

  1. @{
  2. ViewData["Title"] = "Admin";
  3. }
  4. <h2>@ViewData["Title"]</h2>
  5.  
  6. <p>Admin Page</p>

在Controllers文件夹下新建AccountController.cs

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Threading.Tasks;
  5. using Microsoft.AspNetCore.Mvc;
  6. using IdentityServer4.Test;
  7. using Microsoft.AspNetCore.Identity;
  8. using MvcCookieAuthSample.ViewModels;
  9. using Microsoft.AspNetCore.Authentication;
  10.  
  11. namespace MvcCookieAuthSample.Controllers
  12. {
  13. public class AccountController : Controller
  14. {
  15. private readonly TestUserStore _users;
  16. public AccountController(TestUserStore users)
  17. {
  18. _users = users;
  19. }
  20.  
  21. //内部跳转
  22. private IActionResult RedirectToLocal(string returnUrl)
  23. {
  24. if (Url.IsLocalUrl(returnUrl))
  25. {//如果是本地
  26. return Redirect(returnUrl);
  27. }
  28.  
  29. return RedirectToAction(nameof(HomeController.Index), "Home");
  30. }
  31.  
  32. //添加验证错误
  33. private void AddError(IdentityResult result)
  34. {
  35. //遍历所有的验证错误
  36. foreach (var error in result.Errors)
  37. {
  38. //返回error到model
  39. ModelState.AddModelError(string.Empty, error.Description);
  40. }
  41. }
  42.  
  43. public IActionResult Register(string returnUrl = null)
  44. {
  45. ViewData["returnUrl"] = returnUrl;
  46. return View();
  47. }
  48.  
  49. [HttpPost]
  50. public async Task<IActionResult> Register(RegisterViewModel registerViewModel, string returnUrl = null)
  51. {
  52. return View();
  53. }
  54.  
  55. public IActionResult Login(string returnUrl = null)
  56. {
  57. ViewData["returnUrl"] = returnUrl;
  58. return View();
  59. }
  60.  
  61. [HttpPost]
  62. public async Task<IActionResult> Login(LoginViewModel loginViewModel, string returnUrl = null)
  63. {
  64. if (ModelState.IsValid)
  65. {
  66. ViewData["returnUrl"] = returnUrl;
  67. var user = _users.FindByUsername(loginViewModel.UserName);
  68.  
  69. if (user==null)
  70. {
  71. ModelState.AddModelError(nameof(loginViewModel.UserName), "UserName not exists");
  72. }
  73. else
  74. {
  75. if (_users.ValidateCredentials(loginViewModel.UserName,loginViewModel.Password))
  76. {
  77. //是否记住
  78. var prop = new AuthenticationProperties
  79. {
  80. IsPersistent = true,
  81. ExpiresUtc = DateTimeOffset.UtcNow.Add(TimeSpan.FromMinutes())
  82. };
  83.  
  84. await Microsoft.AspNetCore.Http.AuthenticationManagerExtensions.SignInAsync(HttpContext, user.SubjectId, user.Username, prop);
  85. }
  86. }
  87.  
  88. return RedirectToLocal(returnUrl);
  89. }
  90.  
  91. return View();
  92. }
  93.  
  94. public async Task<IActionResult> Logout()
  95. {
  96. await HttpContext.SignOutAsync();
  97. return RedirectToAction("Index", "Home");
  98. }
  99. }
  100. }

然后在Views文件夹下新增Account文件夹并新增Register.cshtml与Login.cshtml视图

  1. @{
  2. ViewData["Title"] = "Register";
  3. }
  4.  
  5. @using MvcCookieAuthSample.ViewModels;
  6. @model RegisterViewModel;
  7.  
  8. <h2>@ViewData["Title"]</h2>
  9. <h3>@ViewData["Message"]</h3>
  10.  
  11. <div class="row">
  12. <div class="col-md-4">
  13. @* 这里将asp-route-returnUrl="@ViewData["returnUrl"],就可以在进行register的post请求的时候接收到returnUrl *@
  14. <form method="post" asp-route-returnUrl="@ViewData["returnUrl"]">
  15. <h4>Create a new account.</h4>
  16. <hr />
  17.  
  18. @*统一显示错误信息*@
  19. <div class="text-danger" asp-validation-summary="All"></div>
  20.  
  21. <div class="form-group">
  22. <label asp-for="Email"></label>
  23. <input asp-for="Email" class="form-control" />
  24. <span asp-validation-for="Email" class="text-danger"></span>
  25. </div>
  26. <div class="form-group">
  27. <label asp-for="Password"></label>
  28. <input asp-for="Password" class="form-control" />
  29. <span asp-validation-for="Password" class="text-danger"></span>
  30. </div>
  31. <div class="form-group">
  32. <label asp-for="ConfirmedPassword"></label>
  33. <input asp-for="ConfirmedPassword" class="form-control" />
  34. <span asp-validation-for="ConfirmedPassword" class="text-danger"></span>
  35. </div>
  36. <button type="submit" class="btn btn-default">Register</button>
  37. </form>
  38. </div>
  39. </div>
  1. @{
  2. ViewData["Title"] = "Login";
  3. }
  4.  
  5. @using MvcCookieAuthSample.ViewModels;
  6. @model LoginViewModel;
  7.  
  8. <div class="row">
  9. <div class="col-md-4">
  10. <section>
  11. <form method="post" asp-controller="Account" asp-action="Login" asp-route-returnUrl="@ViewData["returnUrl"]">
  12. <h4>Use a local account to log in.</h4>
  13. <hr />
  14.  
  15. @*统一显示错误信息*@
  16. <div class="text-danger" asp-validation-summary="All"></div>
  17.  
  18. <div class="form-group">
  19. <label asp-for="UserName"></label>
  20. <input asp-for="UserName" class="form-control" />
  21. <span asp-validation-for="UserName" class="text-danger"></span>
  22. </div>
  23.  
  24. <div class="form-group">
  25. <label asp-for="Password"></label>
  26. <input asp-for="Password" type="password" class="form-control" />
  27. <span asp-validation-for="Password" class="text-danger"></span>
  28. </div>
  29.  
  30. <div class="form-group">
  31. <button type="submit" class="btn btn-default">Log in</button>
  32. </div>
  33.  
  34. </form>
  35. </section>
  36. </div>
  37. </div>
  38.  
  39. @section Scripts
  40. {
  41. @await Html.PartialAsync("_ValidationScriptsPartial")
  42. }

我们接下来要修改_Layout.cshtml视图页面判断注册/登陆按钮是否应该隐藏

完整的_Layout.cshtml代码:

  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <meta charset="utf-8" />
  5. <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  6. <title>@ViewData["Title"] - MvcCookieAuthSample</title>
  7.  
  8. <environment include="Development">
  9. <link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.css" />
  10. <link rel="stylesheet" href="~/css/site.css" />
  11. </environment>
  12. <environment exclude="Development">
  13. <link rel="stylesheet" href="https://ajax.aspnetcdn.com/ajax/bootstrap/3.3.7/css/bootstrap.min.css"
  14. asp-fallback-href="~/lib/bootstrap/dist/css/bootstrap.min.css"
  15. asp-fallback-test-class="sr-only" asp-fallback-test-property="position" asp-fallback-test-value="absolute" />
  16. <link rel="stylesheet" href="~/css/site.min.css" asp-append-version="true" />
  17. </environment>
  18. </head>
  19. <body>
  20. <nav class="navbar navbar-inverse navbar-fixed-top">
  21. <div class="container">
  22. <div class="navbar-header">
  23. <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
  24. <span class="sr-only">Toggle navigation</span>
  25. <span class="icon-bar"></span>
  26. <span class="icon-bar"></span>
  27. <span class="icon-bar"></span>
  28. </button>
  29. <a asp-area="" asp-controller="Home" asp-action="Index" class="navbar-brand">MvcCookieAuthSample</a>
  30. </div>
  31. <div class="navbar-collapse collapse">
  32. <ul class="nav navbar-nav">
  33. <li><a asp-area="" asp-controller="Home" asp-action="Index">Home</a></li>
  34. <li><a asp-area="" asp-controller="Home" asp-action="About">About</a></li>
  35. <li><a asp-area="" asp-controller="Home" asp-action="Contact">Contact</a></li>
  36. </ul>
  37.  
  38. @if (User.Identity.IsAuthenticated)
  39. {
  40. <form asp-action="Logout" asp-controller="Account" method="post">
  41. <ul class="nav navbar-nav navbar-right">
  42. <li>
  43. <a title="Welcome" asp-controller="Admin" asp-action="Index">@User.Identity.Name</a>
  44. </li>
  45. <li>
  46. <button type="submit" class="btn btn-link navbar-btn navbar-link">Log out</button>
  47. </li>
  48. </ul>
  49. </form>
  50.  
  51. }
  52. else
  53. {
  54. <ul class="nav navbar-nav navbar-right">
  55. <li><a asp-area="" asp-controller="Account" asp-action="Register">Register</a></li>
  56. <li><a asp-area="" asp-controller="Account" asp-action="Login">Log in</a></li>
  57. </ul>
  58. }
  59.  
  60. </div>
  61. </div>
  62. </nav>
  63. <div class="container body-content">
  64. @RenderBody()
  65. <hr />
  66. <footer>
  67. <p>&copy; - MvcCookieAuthSample</p>
  68. </footer>
  69. </div>
  70.  
  71. <environment include="Development">
  72. <script src="~/lib/jquery/dist/jquery.js"></script>
  73. <script src="~/lib/bootstrap/dist/js/bootstrap.js"></script>
  74. <script src="~/js/site.js" asp-append-version="true"></script>
  75. </environment>
  76. <environment exclude="Development">
  77. <script src="https://ajax.aspnetcdn.com/ajax/jquery/jquery-2.2.0.min.js"
  78. asp-fallback-src="~/lib/jquery/dist/jquery.min.js"
  79. asp-fallback-test="window.jQuery"
  80. crossorigin="anonymous"
  81. integrity="sha384-K+ctZQ+LL8q6tP7I94W+qzQsfRV2a+AfHIi9k8z8l9ggpc8X+Ytst4yBo/hH+8Fk">
  82. </script>
  83. <script src="https://ajax.aspnetcdn.com/ajax/bootstrap/3.3.7/bootstrap.min.js"
  84. asp-fallback-src="~/lib/bootstrap/dist/js/bootstrap.min.js"
  85. asp-fallback-test="window.jQuery && window.jQuery.fn && window.jQuery.fn.modal"
  86. crossorigin="anonymous"
  87. integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa">
  88. </script>
  89. <script src="~/js/site.min.js" asp-append-version="true"></script>
  90. </environment>
  91.  
  92. @RenderSection("Scripts", required: false)
  93. </body>
  94. </html>

最后给AdminController加上  [Authorize]  特性标签即可  

然后我们就可以运行网站,输入用户名和密码进行登录了

新建客户端

新建一个MVC网站MvcClient

  1. dotnet new mvc --name MvcClient

给网站设置默认地址     http://localhost:5001

MVC的网站已经内置帮我们实现了Identity,所以我们不需要再额外添加Identity引用

添加认证

  1. services.AddAuthentication(options =>
  2. {
  3. options.DefaultScheme = "Cookies";//使用Cookies认证
  4. options.DefaultChallengeScheme = "oidc";//使用oidc
  5. })
  6. .AddCookie("Cookies")//配置Cookies认证
  7. .AddOpenIdConnect("oidc",options=> {//配置oidc
  8. options.SignInScheme = "Cookies";
  9. options.Authority = "http://localhost:5000";
  10. options.RequireHttpsMetadata = false;
  11.  
  12. options.ClientId = "mvc";
  13. options.ClientSecret = "secret";
  14. options.SaveTokens = true;
  15. });

在管道中使用Authentication

  1. app.UseAuthentication();

接下来我们在HomeController上打上  [Authorize]  标签,然后启动运行

我们这个时候访问首页http://localhost:5001会自动跳转到ocalhost:5000/account/login登录

登录之后会自动跳转回来

我们可以在Home/About页面将claim的信息显示出来

  1. @{
  2. ViewData["Title"] = "About";
  3. }
  4. <h2>@ViewData["Title"]</h2>
  5. <h3>@ViewData["Message"]</h3>
  6.  
  7. <dl>
  8. @foreach (var claim in User.Claims)
  9. {
  10. <dt>@claim.Type</dt>
  11. <dt>@claim.Value</dt>
  12. }
  13. </dl>

这边的内容是根据我们在IdentityServer服务中定义的返回资源决定的

Consent功能实现

首先在ViewModels文件夹下创建两个视图模型

ScopeViewModel.cs

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Threading.Tasks;
  5.  
  6. namespace MvcCookieAuthSample.ViewModels
  7. {
  8. //领域
  9. public class ScopeViewModel
  10. {
  11. public string Name { get; set; }
  12. public string DisplayName { get; set; }
  13. public string Description { get; set; }
  14. public bool Emphasize { get; set; }
  15. public bool Required { get; set; }
  16. public bool Checked { get; set; }
  17. }
  18. }

ConsentViewModel.cs

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Threading.Tasks;
  5.  
  6. namespace MvcCookieAuthSample.ViewModels
  7. {
  8. public class ConsentViewModel
  9. {
  10. public string ClientId { get; set; }
  11. public string ClientName { get; set; }
  12. public string ClientUrl { get; set; }
  13. public string ClientLogoUrl { get; set; }
  14. public bool AllowRememberConsent { get; set; }
  15.  
  16. public IEnumerable<ScopeViewModel> IdentityScopes { get; set; }
  17. public IEnumerable<ScopeViewModel> ResourceScopes { get; set; }
  18. }
  19. }

我们在MvcCookieAuthSample项目中添加新控制器ConsentController

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Threading.Tasks;
  5. using Microsoft.AspNetCore.Mvc;
  6. using MvcCookieAuthSample.ViewModels;
  7. using IdentityServer4.Models;
  8. using IdentityServer4.Services;
  9. using IdentityServer4.Stores;
  10.  
  11. namespace MvcCookieAuthSample.Controllers
  12. {
  13. public class ConsentController : Controller
  14. {
  15. private readonly IClientStore _clientStore;
  16. private readonly IResourceStore _resourceStore;
  17. private readonly IIdentityServerInteractionService _identityServerInteractionService;
  18.  
  19. public ConsentController(IClientStore clientStore, IResourceStore resourceStore, IIdentityServerInteractionService identityServerInteractionService)
  20. {
  21. _clientStore = clientStore;
  22. _resourceStore = resourceStore;
  23. _identityServerInteractionService = identityServerInteractionService;
  24. }
  25.  
  26. private async Task<ConsentViewModel> BuildConsentViewModel(string returnUrl)
  27. {
  28. var request =await _identityServerInteractionService.GetAuthorizationContextAsync(returnUrl);
  29. if (request == null)
  30. return null;
  31.  
  32. var client =await _clientStore.FindEnabledClientByIdAsync(request.ClientId);
  33. var resources =await _resourceStore.FindEnabledResourcesByScopeAsync(request.ScopesRequested);
  34.  
  35. return CreateConsentViewModel(request, client, resources);
  36. }
  37.  
  38. private ConsentViewModel CreateConsentViewModel(AuthorizationRequest request,Client client,Resources resources)
  39. {
  40. var vm = new ConsentViewModel();
  41. vm.ClientName = client.ClientName;
  42. vm.ClientLogoUrl = client.LogoUri;
  43. vm.ClientUrl = client.ClientUri;
  44. vm.AllowRememberConsent = client.AllowRememberConsent;
  45.  
  46. vm.IdentityScopes = resources.IdentityResources.Select(i => CreateScopeViewModel(i));
  47. vm.ResourceScopes = resources.ApiResources.SelectMany(i =>i.Scopes).Select(i=>CreateScopeViewModel(i));
  48.  
  49. return vm;
  50. }
  51.  
  52. private ScopeViewModel CreateScopeViewModel(IdentityResource identityResource)
  53. {
  54. return new ScopeViewModel
  55. {
  56. Name = identityResource.Name,
  57. DisplayName = identityResource.DisplayName,
  58. Description = identityResource.Description,
  59. Required = identityResource.Required,
  60. Checked = identityResource.Required,
  61. Emphasize = identityResource.Emphasize
  62. };
  63. }
  64.  
  65. private ScopeViewModel CreateScopeViewModel(Scope scope)
  66. {
  67. return new ScopeViewModel
  68. {
  69. Name = scope.Name,
  70. DisplayName = scope.DisplayName,
  71. Description = scope.Description,
  72. Required = scope.Required,
  73. Checked = scope.Required,
  74. Emphasize = scope.Emphasize
  75. };
  76. }
  77.  
  78. [HttpGet]
  79. public async Task<IActionResult> Index(string returnUrl)
  80. {
  81. var model =await BuildConsentViewModel(returnUrl);
  82. if (model==null)
  83. {
  84.  
  85. }
  86. return View(model);
  87. }
  88. }
  89. }

然后新建Idenx.cshtml视图和_ScopeListitem.cshtml分部视图

_ScopeListitem.cshtml

  1. @using MvcCookieAuthSample.ViewModels;
  2. @model ScopeViewModel
  3.  
  4. <li>
  5. <label>
  6. <input type="checkbox" name="ScopesConsented" id="scopes_@Model.Name" value="@Model.Name" checked="@Model.Checked" disabled="@Model.Required"/>
  7.  
  8. <strong>@Model.Name</strong>
  9. @if (Model.Emphasize)
  10. {
  11. <span class="glyphicon glyphicon-exclamation-sign"></span>
  12. }
  13. </label>
  14. @if (string.IsNullOrWhiteSpace(Model.Description))
  15. {
  16. <div>
  17. <label for="scopes_@Model.Name">@Model.Description</label>
  18. </div>
  19. }
  20. </li>

Idenx.cshtml

  1. @using MvcCookieAuthSample.ViewModels;
  2. @model ConsentViewModel
  3. <p>Consent Page</p>
  4. <!--Client Info-->
  5. <div class="row page-header">
  6. <div class="col-sm-10">
  7. @if (!string.IsNullOrWhiteSpace(Model.ClientLogoUrl))
  8. {
  9. <div><img src="@Model.ClientLogoUrl" /></div>
  10. }
  11.  
  12. <h1>
  13. @Model.ClientName
  14. <small>希望使用你的账户</small>
  15. </h1>
  16. </div>
  17. </div>
  18.  
  19. <!--Scope Info-->
  20. <div class="row">
  21. <div class="col-sm-8">
  22. <form asp-action="Index">
  23. @if (Model.IdentityScopes.Any())
  24. {
  25. <div>
  26. <div class="panel-heading">
  27. <span class="glyphicon glyphicon-user"></span>
  28. 用户信息
  29. </div>
  30. <ul class="list-group">
  31. @foreach (var scope in Model.IdentityScopes)
  32. {
  33. @Html.Partial("_ScopeListitem",scope)
  34. }
  35. </ul>
  36. </div>
  37. }
  38. @if (Model.ResourceScopes.Any())
  39. {
  40. <div>
  41. <div class="panel-heading">
  42. <span class="glyphicon glyphicon-tasks"></span>
  43. 应用权限
  44. </div>
  45. <ul class="list-group">
  46. @foreach (var scope in Model.ResourceScopes)
  47. {
  48. @Html.Partial("_ScopeListitem",scope)
  49. }
  50. </ul>
  51. </div>
  52. }
  53. </form>
  54. </div>
  55. </div>

最后我们修改Config.cs,增加一些信息

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Threading.Tasks;
  5. using IdentityServer4;
  6. using IdentityServer4.Models;
  7. using IdentityServer4.Test;
  8.  
  9. namespace MvcCookieAuthSample
  10. {
  11. public class Config
  12. {
  13. //所有可以访问的Resource
  14. public static IEnumerable<ApiResource> GetApiResources()
  15. {
  16. return new List<ApiResource>()
  17. {
  18. new ApiResource("api1","API Application")
  19. };
  20. }
  21.  
  22. //客户端
  23. public static IEnumerable<Client> GetClients()
  24. {
  25. return new List<Client>
  26. {
  27. new Client{
  28. ClientId="mvc",
  29. AllowedGrantTypes=GrantTypes.Implicit,//模式:隐式模式
  30. ClientSecrets={//私钥
  31. new Secret("secret".Sha256())
  32. },
  33. AllowedScopes={//运行访问的资源
  34. IdentityServerConstants.StandardScopes.Profile,
  35. IdentityServerConstants.StandardScopes.OpenId,
  36. IdentityServerConstants.StandardScopes.Email,
  37. },
  38. RedirectUris={"http://localhost:5001/signin-oidc"},//跳转登录到的客户端的地址
  39. PostLogoutRedirectUris={"http://localhost:5001/signout-callback-oidc"},//跳转登出到的客户端的地址
  40. RequireConsent=true,//是否需要用户点击确认进行跳转,改为点击确认后进行跳转
  41.  
  42. ClientName="MVC Clent",
  43. ClientUri="http://localhost:5001",
  44. LogoUri="https://chocolatey.org/content/packageimages/aspnetcore-runtimepackagestore.2.0.0.png",
  45. AllowRememberConsent=true,
  46. }
  47. };
  48. }
  49.  
  50. //测试用户
  51. public static List<TestUser> GetTestUsers()
  52. {
  53. return new List<TestUser>
  54. {
  55. new TestUser{
  56. SubjectId="",
  57. Username="wyt",
  58. Password="password",
  59.  
  60. }
  61. };
  62. }
  63.  
  64. //定义系统中的资源
  65. public static IEnumerable<IdentityResource> GetIdentityResources()
  66. {
  67. return new List<IdentityResource>
  68. {
  69. //这里实际是claims的返回资源
  70. new IdentityResources.OpenId(),
  71. new IdentityResources.Profile(),
  72. new IdentityResources.Email()
  73. };
  74. }
  75.  
  76. }
  77. }

我们这个时候访问首页http://localhost:5001会自动跳转到ocalhost:5000/account/login登录

登录之后会自动跳转到登录确认页面

Consent 确认逻辑实现

首先我们在 ViewModels 文件夹中增加一个类 InputConsentViewModel.cs 用于接收 Consent/Index.cshtml 提交的表单信息

  1. public class InputConsentViewModel
  2. {
  3. /// <summary>
  4. /// 按钮
  5. /// </summary>
  6. public string Button { get; set; }
  7. /// <summary>
  8. /// 接收到的勾选的Scope
  9. /// </summary>
  10. public IEnumerable<string> ScopesConsented { get; set; }
  11. /// <summary>
  12. /// 是否选择记住
  13. /// </summary>
  14. public bool RememberConsent { get; set; }
  15. /// <summary>
  16. /// 跳转地址
  17. /// </summary>
  18. public string ReturnUrl { get; set; }
  19. }

然后修改 ConsentViewModel.cs ,加入ReturnUrl

  1. public class ConsentViewModel
  2. {
  3. public string ClientId { get; set; }
  4. public string ClientName { get; set; }
  5. public string ClientUrl { get; set; }
  6. public string ClientLogoUrl { get; set; }
  7. public bool AllowRememberConsent { get; set; }
  8.  
  9. public IEnumerable<ScopeViewModel> IdentityScopes { get; set; }
  10. public IEnumerable<ScopeViewModel> ResourceScopes { get; set; }
  11.  
  12. public string ReturnUrl { get; set; }
  13. }

然后修改 Consent\Index.cshtml ,加入ReturnUrl

然后修改 Controllers\ConsentController.cs 中的 BuildConsentViewModel 方法

  1. private async Task<ConsentViewModel> BuildConsentViewModel(string returnUrl)
  2. {
  3. AuthorizationRequest request = await _identityServerInteractionService.GetAuthorizationContextAsync(returnUrl);
  4. if (request == null)
  5. {
  6. return null;
  7. }
  8.  
  9. Client client = await _clientStore.FindEnabledClientByIdAsync(request.ClientId);
  10. Resources resources = await _resourceStore.FindEnabledResourcesByScopeAsync(request.ScopesRequested);
  11.  
  12. var vm= CreateConsentViewModel(request, client, resources);
  13. vm.ReturnUrl = returnUrl;
  14. return vm;
  15. }

然后在 Controllers\ConsentController.cs 中添加action

  1. [HttpPost]
  2. public async Task<IActionResult> Index(InputConsentViewModel viewModel)
  3. {
  4. ConsentResponse consentResponse=null;
  5. if (viewModel.Button == "no")
  6. {
  7. consentResponse= ConsentResponse.Denied;
  8. }
  9. else if (viewModel.Button == "yes")
  10. {
  11. if (viewModel.ScopesConsented!=null&&viewModel.ScopesConsented.Any())
  12. {
  13. consentResponse = new ConsentResponse()
  14. {
  15. RememberConsent = viewModel.RememberConsent,
  16. ScopesConsented = viewModel.ScopesConsented
  17. };
  18. }
  19. }
  20.  
  21. if ( consentResponse!=null)
  22. {
  23. var request =await _identityServerInteractionService.GetAuthorizationContextAsync(viewModel.ReturnUrl);
  24. await _identityServerInteractionService.GrantConsentAsync(request, consentResponse);
  25. return Redirect(viewModel.ReturnUrl);
  26. }
  27.  
  28. var model = await BuildConsentViewModel(viewModel.ReturnUrl);
  29. if (model == null)
  30. {
  31.  
  32. }
  33.  
  34. return View(model);
  35. }

然后将 ViewModels\ConsentViewModel.cs 中 ConsentViewModel 的 AllowRememberConsent 改为 RememberConsent ,这样才能与 ViewModels\InputConsentViewModel.cs 保持一致

  1. public class ConsentViewModel:InputConsentViewModel
  2. {
  3. public string ClientId { get; set; }
  4. public string ClientName { get; set; }
  5. public string ClientUrl { get; set; }
  6. public string ClientLogoUrl { get; set; }
  7. //public bool RememberConsent { get; set; }
  8. public IEnumerable<ScopeViewModel> IdentityScopes { get; set; }
  9. public IEnumerable<ScopeViewModel> ResourceScopes { get; set; }
  10. //public string ReturnUrl { get; set; }
  11. }

最后修改视图 Consent\Index.cshtml ,加入记住选项和确认按钮

  1. @using MvcCookieAuthSample.ViewModels;
  2. @model ConsentViewModel
  3. <p>Consent Page</p>
  4. <!--Client Info-->
  5. <div class="row page-header">
  6. <div class="col-sm-10">
  7. @if (!string.IsNullOrWhiteSpace(Model.ClientLogoUrl))
  8. {
  9. <div><img src="@Model.ClientLogoUrl" /></div>
  10. }
  11.  
  12. <h1>
  13. @Model.ClientId
  14. <small>希望使用您的账户</small>
  15. </h1>
  16. </div>
  17. </div>
  18.  
  19. <!--Scope Info-->
  20. <div class="row">
  21. <div class="col-sm-8">
  22. <form asp-action="Index" method="post">
  23. <input type="hidden" asp-for="ReturnUrl"/>
  24. @if (Model.IdentityScopes.Any())
  25. {
  26. <div>
  27. <div class="panel-heading">
  28. <span class="glyphicon glyphicon-user"></span>
  29. 用户信息
  30. </div>
  31. <ul class="list-group">
  32. @foreach (var scope in Model.IdentityScopes)
  33. {
  34. @Html.Partial("_ScopeListitem", scope)
  35. }
  36. </ul>
  37. </div>
  38. }
  39. @if (Model.ResourceScopes.Any())
  40. {
  41. <div>
  42. <div class="panel-heading">
  43. <span class="glyphicon glyphicon-tasks"></span>
  44. 应用权限
  45. </div>
  46. <ul class="list-group">
  47. @foreach (var scope in Model.ResourceScopes)
  48. {
  49. @Html.Partial("_ScopeListitem", scope)
  50. }
  51. </ul>
  52. </div>
  53. }
  54.  
  55. <div>
  56. <label>
  57. <input type="checkbox" asp-for="RememberConsent"/>
  58. <strong>记住我的选择</strong>
  59. </label>
  60. </div>
  61.  
  62. <div>
  63. <button name="button" value="yes" class="btn btn-primary" autofocus>同意</button>
  64. <button name="button" value="no" >取消</button>
  65. @if (!string.IsNullOrEmpty(Model.ClientUrl))
  66. {
  67. <a href="@Model.ClientUrl" class="pull-right btn btn-default">
  68. <span class="glyphicon glyphicon-info-sign"></span>
  69. <strong>@Model.ClientUrl</strong>
  70. </a>
  71. }
  72. </div>
  73. </form>
  74. </div>
  75. </div>

修改视图 Views\Consent\_ScopeListitem.cshtml

  1. @using MvcCookieAuthSample.ViewModels;
  2. @model ScopeViewModel
  3.  
  4. <li>
  5. <label>
  6. <input type="checkbox" name="ScopesConsented" id="scopes_@Model.Name" value="@Model.Name" checked="@Model.Checked" disabled="@Model.Required"/>
  7. @if (Model.Required)
  8. {
  9. <input type="hidden" name="ScopesConsented" value="@Model.Name"/>
  10. }
  11.  
  12. <strong>@Model.Name</strong>
  13. @if (Model.Emphasize)
  14. {
  15. <span class="glyphicon glyphicon-exclamation-sign"></span>
  16. }
  17. </label>
  18.  
  19. @if (!string.IsNullOrWhiteSpace(Model.Description))
  20. {
  21. <div>
  22. <label for="scopes_@Model.Name">@Model.Description</label>
  23. </div>
  24. }
  25.  
  26. </li>

运行效果

Asp.Net Core2.2源码:链接: https://pan.baidu.com/s/1pndxJwqpTsHmNmfQsQ0_2w 提取码: jxwd

Consent 代码重构

新建 Services 文件夹,添加 ConsentService.cs 用于业务封装

  1. public class ConsentService
  2. {
  3. private readonly IClientStore _clientStore;
  4. private readonly IResourceStore _resourceStore;
  5. private readonly IIdentityServerInteractionService _identityServerInteractionService;
  6.  
  7. public ConsentService(IClientStore clientStore
  8. , IResourceStore resourceStore
  9. , IIdentityServerInteractionService identityServerInteractionService)
  10. {
  11. _clientStore = clientStore;
  12. _resourceStore = resourceStore;
  13. _identityServerInteractionService = identityServerInteractionService;
  14. }
  15.  
  16. public async Task<ConsentViewModel> BuildConsentViewModel(string returnUrl,InputConsentViewModel model=null)
  17. {
  18. AuthorizationRequest request = await _identityServerInteractionService.GetAuthorizationContextAsync(returnUrl);
  19. if (request == null)
  20. {
  21. return null;
  22. }
  23.  
  24. Client client = await _clientStore.FindEnabledClientByIdAsync(request.ClientId);
  25. Resources resources = await _resourceStore.FindEnabledResourcesByScopeAsync(request.ScopesRequested);
  26.  
  27. var vm = CreateConsentViewModel(request, client, resources,model);
  28. vm.ReturnUrl = returnUrl;
  29. return vm;
  30. }
  31.  
  32. public async Task<ProcessConsentResult> ProcessConsent(InputConsentViewModel model)
  33. {
  34. ConsentResponse consentResponse = null;
  35. var result=new ProcessConsentResult();
  36. if (model.Button == "no")
  37. {
  38. consentResponse = ConsentResponse.Denied;
  39. }
  40. else if (model.Button == "yes")
  41. {
  42. if (model.ScopesConsented != null && model.ScopesConsented.Any())
  43. {
  44. consentResponse = new ConsentResponse()
  45. {
  46. RememberConsent = model.RememberConsent,
  47. ScopesConsented = model.ScopesConsented
  48. };
  49. }
  50. else
  51. {
  52. result.ValidationError = "请至少选择一个权限";
  53. }
  54. }
  55.  
  56. if (consentResponse != null)
  57. {
  58. var request = await _identityServerInteractionService.GetAuthorizationContextAsync(model.ReturnUrl);
  59. await _identityServerInteractionService.GrantConsentAsync(request, consentResponse);
  60. result.RedirectUrl = model.ReturnUrl;
  61. }
  62. else
  63. {
  64. ConsentViewModel consentViewModel = await BuildConsentViewModel(model.ReturnUrl,model);
  65. result.ViewModel = consentViewModel;
  66. }
  67.  
  68. return result;
  69. }
  70.  
  71. #region Private Methods
  72.  
  73. private ConsentViewModel CreateConsentViewModel(AuthorizationRequest request, Client client,
  74. Resources resources,InputConsentViewModel model)
  75. {
  76. var rememberConsent = model?.RememberConsent ?? true;
  77. var selectedScopes = model?.ScopesConsented ?? Enumerable.Empty<string>();
  78.  
  79. var vm = new ConsentViewModel();
  80. vm.ClientName = client.ClientName;
  81. vm.ClientLogoUrl = client.LogoUri;
  82. vm.ClientUrl = client.ClientUri;
  83. vm.RememberConsent = rememberConsent;
  84.  
  85. vm.IdentityScopes = resources.IdentityResources.Select(i => CreateScopeViewModel(i,selectedScopes.Contains(i.Name)||model==null));
  86. vm.ResourceScopes = resources.ApiResources.SelectMany(i => i.Scopes).Select(i => CreateScopeViewModel(i, selectedScopes.Contains(i.Name)||model==null));
  87.  
  88. return vm;
  89. }
  90.  
  91. private ScopeViewModel CreateScopeViewModel(IdentityResource identityResource,bool check)
  92. {
  93. return new ScopeViewModel()
  94. {
  95. Name = identityResource.Name,
  96. DisplayName = identityResource.DisplayName,
  97. Description = identityResource.Description,
  98. Required = identityResource.Required,
  99. Checked = check|| identityResource.Required,
  100. Emphasize = identityResource.Emphasize
  101. };
  102. }
  103.  
  104. private ScopeViewModel CreateScopeViewModel(Scope scope, bool check)
  105. {
  106. return new ScopeViewModel()
  107. {
  108. Name = scope.Name,
  109. DisplayName = scope.DisplayName,
  110. Description = scope.Description,
  111. Required = scope.Required,
  112. Checked = check||scope.Required,
  113. Emphasize = scope.Emphasize
  114. };
  115. }
  116. #endregion
  117. }

Asp.Net Core2.2源码(重构):链接: https://pan.baidu.com/s/1mVdPDfDiDVToLSV9quC5KQ 提取码: 3dsq

集成ASP.NETCore Identity

EF实现

首先我们添加一个Data文件夹

我们首先在Models文件夹下面新建ApplicationUser.cs与ApplicationUserRole.cs

ApplicationUser.cs代码:

  1. using Microsoft.AspNetCore.Identity;
  2.  
  3. namespace MvcCookieAuthSample.Models
  4. {
  5. public class ApplicationUser:IdentityUser<int>//不加int的话是默认主键为guid
  6. {
  7. }
  8. }

ApplicationUserRole.cs代码:

  1. using Microsoft.AspNetCore.Identity;
  2.  
  3. namespace MvcCookieAuthSample.Models
  4. {
  5. public class ApplicationUserRole: IdentityRole<int>//不加int的话是默认主键为guid
  6. {
  7. }
  8. }

然后在Data文件夹下新建一个ApplicationDbContext.cs类,使它继承IdentityDbContext

  1. using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
  2. using Microsoft.EntityFrameworkCore;
  3. using MvcCookieAuthSample.Models;
  4.  
  5. namespace MvcCookieAuthSample.Data
  6. {
  7. public class ApplicationDbContext:IdentityDbContext<ApplicationUser, ApplicationUserRole,int>
  8. {
  9. public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options):base(options)
  10. {
  11.  
  12. }
  13. }
  14. }

然后我们需要在Startup.cs添加EF的注册进来

  1. //使用配置ApplicationDbContext使用sqlserver数据库,并配置数据库连接字符串
  2. services.AddDbContext<ApplicationDbContext>(options=> {
  3. options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"));
  4. });

然后我们需要在appsettings.json中配置数据库连接字符串

  1. "ConnectionStrings": {
  2. "DefaultConnection": "Server=127.0.0.1;Database=aspnet-IdentitySample;Trusted_Connection=True;MultipleActiveResultSets=true;uid=sa;pwd=123456"
  3. }

EF实现结束

Identity实现

我们需要在Startup.cs添加Identity的注册进来

  1. //配置Identity
  2. services.AddIdentity<ApplicationUser, ApplicationUserRole>()
  3. .AddEntityFrameworkStores<ApplicationDbContext>()
  4. .AddDefaultTokenProviders();

由于默认的Identity在密码上限制比较严格,我们把它改的宽松简单一点(不设置也行)

  1. //修改Identity密码强度设置配置
  2. services.Configure<IdentityOptions>(options =>
  3. {
  4. options.Password.RequireLowercase = false; //需要小写
  5. options.Password.RequireNonAlphanumeric = false; //需要字母
  6. options.Password.RequireUppercase = false; //需要大写
  7. });

然后我们要修改 IdentityServer 的配置,首先要添加Nuget包

  1. IdentityServer4.AspNetIdentity
  1. services.AddIdentityServer()
  2. .AddDeveloperSigningCredential()//添加开发人员签名凭据
  3. .AddInMemoryApiResources(Config.GetApiResources())//添加内存apiresource
  4. .AddInMemoryClients(Config.GetClients())//添加内存client
  5. .AddInMemoryIdentityResources(Config.GetIdentityResources())//添加系统中的资源
  6. //.AddTestUsers(Config.GetTestUsers())//添加测试用户(这里不需要测试用户了)
  7. .AddAspNetIdentity<ApplicationUser>();

然后我们修改AccountController,修改代码,替换掉TestUsers的功能

  1. private readonly UserManager<ApplicationUser> _userManager;//创建用户的
  2. private readonly SignInManager<ApplicationUser> _signInManager;//用来登录的
  3. private readonly IIdentityServerInteractionService _interaction;
  4. //依赖注入
  5. public AccountController(UserManager<ApplicationUser> userManager
  6. , SignInManager<ApplicationUser> signInManager
  7. , IIdentityServerInteractionService interaction)
  8. {
  9. _userManager = userManager;
  10. _signInManager = signInManager;
  11. _interaction = interaction;
  12. }

完整的AccountController

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Threading.Tasks;
  5. using IdentityServer4.Services;
  6. using IdentityServer4.Test;
  7. using Microsoft.AspNetCore.Authentication;
  8. using Microsoft.AspNetCore.Identity;
  9. using Microsoft.AspNetCore.Mvc;
  10. using MvcCookieAuthSample.Models;
  11. using MvcCookieAuthSample.ViewModels;
  12.  
  13. namespace MvcCookieAuthSample.Controllers
  14. {
  15. public class AccountController : Controller
  16. {
  17. //private TestUserStore _users;
  18.  
  19. //public AccountController(TestUserStore users)
  20. //{
  21. // _users = users;
  22. //}
  23.  
  24. private readonly UserManager<ApplicationUser> _userManager;//创建用户的
  25. private readonly SignInManager<ApplicationUser> _signInManager;//用来登录的
  26. private readonly IIdentityServerInteractionService _interaction;
  27. //依赖注入
  28. public AccountController(UserManager<ApplicationUser> userManager
  29. , SignInManager<ApplicationUser> signInManager
  30. , IIdentityServerInteractionService interaction)
  31. {
  32. _userManager = userManager;
  33. _signInManager = signInManager;
  34. _interaction = interaction;
  35. }
  36.  
  37. public IActionResult Register(string returnUrl = null)
  38. {
  39. ViewData["returnUrl"] = returnUrl;
  40. return View();
  41. }
  42.  
  43. [HttpPost]
  44. public async Task<IActionResult> Register(RegisterViewModel registerViewModel, string returnUrl = null)
  45. {
  46. var identityUser = new ApplicationUser
  47. {
  48. Email = registerViewModel.Email,
  49. UserName = registerViewModel.Email,
  50. NormalizedUserName = registerViewModel.Email
  51. };
  52. var identityResult = await _userManager.CreateAsync(identityUser, registerViewModel.Password);
  53. if (identityResult.Succeeded)
  54. {
  55. return RedirectToAction("Index", "Home");
  56. }
  57. return View();
  58. }
  59.  
  60. public IActionResult Login(string returnUrl = null)
  61. {
  62. ViewData["returnUrl"] = returnUrl;
  63. return View();
  64. }
  65.  
  66. [HttpPost]
  67. public async Task<IActionResult> Login(LoginViewModel loginViewModel, string returnUrl = null)
  68. {
  69. if (ModelState.IsValid)
  70. {
  71. ViewData["returnUrl"] = returnUrl;
  72. var user =await _userManager.FindByEmailAsync(loginViewModel.Email);
  73. if (user==null)
  74. {
  75. ModelState.AddModelError(nameof(loginViewModel.Email),"UserName not exist");
  76. }
  77. else
  78. {
  79. if (await _userManager.CheckPasswordAsync(user,loginViewModel.Password))
  80. {
  81. AuthenticationProperties prop = null;
  82. if (loginViewModel.RememberMe)
  83. {
  84. prop = new AuthenticationProperties()
  85. {
  86. IsPersistent = true,
  87. ExpiresUtc = DateTimeOffset.UtcNow.Add(TimeSpan.FromMinutes())
  88. };
  89. }
  90.  
  91. //await Microsoft.AspNetCore.Http.AuthenticationManagerExtensions.SignInAsync(HttpContext,
  92. // user.SubjectId, user.Username,prop);
  93. //return RedirectToLocal(returnUrl);
  94.  
  95. await _signInManager.SignInAsync(user, prop);
  96. if (_interaction.IsValidReturnUrl(returnUrl))
  97. {
  98. return Redirect(returnUrl);
  99. }
  100.  
  101. return Redirect("~/");
  102. }
  103. ModelState.AddModelError(nameof(loginViewModel.Password),"Wrong Password");
  104. }
  105.  
  106. }
  107. return View(loginViewModel);
  108. }
  109.  
  110. public async Task<IActionResult> LogOut()
  111. {
  112. await _signInManager.SignOutAsync();
  113. //await HttpContext.SignOutAsync();
  114. return RedirectToAction("Index", "Home");
  115. }
  116.  
  117. //内部跳转
  118. private IActionResult RedirectToLocal(string returnUrl)
  119. {
  120. if (Url.IsLocalUrl(returnUrl))
  121. {
  122. return Redirect(returnUrl);
  123. }
  124.  
  125. return RedirectToAction("Index", "Home");
  126. }
  127.  
  128. //添加验证错误
  129. private void AddError(IdentityResult result)
  130. {
  131. //遍历所有的验证错误
  132. foreach (var error in result.Errors)
  133. {
  134. //返回error到model
  135. ModelState.AddModelError(string.Empty, error.Description);
  136. }
  137. }
  138. }
  139. }

接下来我们重新生成一下,我们需要执行shell命令生成一下数据库

  1. dotnet ef migrations add VSInit

这时候Migrations文件夹下已经有新增的数据库更新配置文件了

DbContextSeed初始化

由于我们现在每次EF实体模型变化的时候每次都是手动更改,我们想通过代码的方式让他自动更新,或者程序启动的时候添加一些数据进去

首先,在Data文件夹下添加一个ApplicationDbContextSeed.cs初始化类

  1. public class ApplicationDbContextSeed
  2. {
  3. private UserManager<ApplicationUser> _userManager;
  4.  
  5. public async Task SeedAsync(ApplicationDbContext context, IServiceProvider services)
  6. {
  7. if (!context.Users.Any())
  8. {
  9. _userManager = services.GetRequiredService<UserManager<ApplicationUser>>();
  10.  
  11. var defaultUser = new ApplicationUser
  12. {
  13. UserName = "Administrator",
  14. Email = "786744873@qq.com",
  15. NormalizedUserName = "admin"
  16. };
  17.  
  18. var result = await _userManager.CreateAsync(defaultUser, "Password$123");
  19. if (!result.Succeeded)
  20. {
  21. throw new Exception("初始默认用户失败");
  22. }
  23. }
  24. }
  25. }

那么如何调用呢?接下来我们写一个WebHost的扩展方法类WebHostMigrationExtensions.cs来调用ApplicationDbContextSeed方法

  1. public static class WebHostMigrationExtensions
  2. {
  3. public static IWebHost MigrateDbContext<TContext>(this IWebHost host, Action<TContext, IServiceProvider> sedder) where TContext : DbContext
  4. {
  5. using (var scope = host.Services.CreateScope())
  6. {//只在本区间内有效
  7. var services = scope.ServiceProvider;
  8. var logger = services.GetRequiredService<ILogger<TContext>>();
  9. var context = services.GetService<TContext>();
  10.  
  11. try
  12. {
  13. context.Database.Migrate();
  14. sedder(context, services);
  15.  
  16. logger.LogInformation($"执行DBContext {typeof(TContext).Name} seed执行成功");
  17. }
  18. catch (Exception ex)
  19. {
  20. logger.LogError(ex, $"执行DBContext {typeof(TContext).Name} seed方法失败");
  21. }
  22. }
  23.  
  24. return host;
  25. }
  26. }

那么我们程序启动的时候要怎调用呢?

要在Program.cs中执行

  1. public static void Main(string[] args)
  2. {
  3. CreateWebHostBuilder(args).Build()
  4. //自动初始化数据库开始
  5. .MigrateDbContext<ApplicationDbContext>((context, services) =>
  6. {
  7. new ApplicationDbContextSeed().SeedAsync(context, services).Wait();
  8. })
  9. //自动初始化数据库结束
  10. .Run();
  11. }

然后运行即可自动化创建数据库和数据

ProfileService实现(调试)

在 Services 文件夹下添加 ProfileService.cs

  1. public class ProfileService : IProfileService
  2. {
  3. private readonly UserManager<ApplicationUser> _userManager;//创建用户的
  4.  
  5. public ProfileService(UserManager<ApplicationUser> userManager)
  6. {
  7. _userManager = userManager;
  8. }
  9.  
  10. private async Task<List<Claim>> GetClaimsFromUserAsync(ApplicationUser user)
  11. {
  12. var claims=new List<Claim>()
  13. {
  14. new Claim(JwtClaimTypes.Subject,user.Id.ToString()),
  15. new Claim(JwtClaimTypes.PreferredUserName,user.UserName)
  16. };
  17.  
  18. var roles =await _userManager.GetRolesAsync(user);
  19. foreach (var role in roles)
  20. {
  21. claims.Add(new Claim(JwtClaimTypes.Role,role));
  22. }
  23.  
  24. if (!string.IsNullOrWhiteSpace(user.Avatar))
  25. {
  26. claims.Add(new Claim("avatar", user.Avatar));
  27. }
  28.  
  29. return claims;
  30. }
  31.  
  32. public async Task GetProfileDataAsync(ProfileDataRequestContext context)
  33. {
  34. var subjectId = context.Subject.Claims.FirstOrDefault(c => c.Type == "sub").Value;
  35. var user = await _userManager.FindByIdAsync(subjectId);
  36.  
  37. var claims =await GetClaimsFromUserAsync(user);
  38. context.IssuedClaims = claims;
  39. }
  40.  
  41. public async Task IsActiveAsync(IsActiveContext context)
  42. {
  43. context.IsActive = false;
  44.  
  45. var subjectId = context.Subject.Claims.FirstOrDefault(c => c.Type == "sub").Value;
  46. var user = await _userManager.FindByIdAsync(subjectId);
  47.  
  48. context.IsActive = user != null;
  49. }
  50. }

修改 Config.cs 中的GetClients方法

  1. public static IEnumerable<Client> GetClients()
  2. {
  3. return new Client[]
  4. {
  5. new Client()
  6. {
  7. ClientId = "mvc",
  8. AllowedGrantTypes = GrantTypes.HybridAndClientCredentials,//模式:混合模式
  9. ClientSecrets =//私钥
  10. {
  11. new Secret("secret".Sha256())
  12. },
  13. AllowedScopes =//运行访问的资源
  14. {
  15. IdentityServerConstants.StandardScopes.OpenId,
  16. IdentityServerConstants.StandardScopes.Profile,
  17. IdentityServerConstants.StandardScopes.Email,
  18. IdentityServerConstants.StandardScopes.OfflineAccess,
  19. "api1"
  20.  
  21. },
  22. RedirectUris = { "http://localhost:5001/signin-oidc" },//跳转登录到的客户端的地址
  23. PostLogoutRedirectUris = { "http://localhost:5001/signout-callback-oidc" },//跳转登出到的客户端的地址
  24. RequireConsent=true,//是否需要用户点击确认进行跳转,改为点击确认后进行跳转
  25. AlwaysIncludeUserClaimsInIdToken = true,
  26. AllowOfflineAccess = true,//允许脱机访问
  27.  
  28. ClientName = "MVC Client",
  29. ClientUri = "http://localhost:5001",
  30. LogoUri = "https://img-prod-cms-rt-microsoft-com.akamaized.net/cms/api/am/imageFileData/RE1Mu3b?ver=5c31",
  31. AllowRememberConsent = true,
  32. }
  33. };
  34. }

修改 Startup.cs

  1. services.AddIdentityServer()
  2. .AddDeveloperSigningCredential()//添加开发人员签名凭据
  3. .AddInMemoryApiResources(Config.GetApiResources())//添加内存apiresource
  4. .AddInMemoryClients(Config.GetClients())//添加内存client
  5. .AddInMemoryIdentityResources(Config.GetIdentityResources())//添加系统中的资源
  6. //.AddTestUsers(Config.GetTestUsers())//添加测试用户(这里不需要测试用户了)
  7. .AddAspNetIdentity<ApplicationUser>()
  8. .Services.AddScoped<IProfileService,ProfileService>();

修改MvcClient项目中的 Startup.cs

  1. services.AddAuthentication(options =>
  2. {
  3. options.DefaultScheme = "Cookies";//使用Cookies认证
  4. options.DefaultChallengeScheme = "oidc";//使用oidc
  5. })
  6. .AddCookie("Cookies")//配置Cookies认证
  7. .AddOpenIdConnect("oidc", options =>//配置oidc
  8. {
  9. options.SignInScheme = "Cookies";
  10. options.Authority = "http://localhost:5000";
  11. options.RequireHttpsMetadata = false;
  12. options.ResponseType = OpenIdConnectResponseType.CodeIdToken;
  13. options.ClientId = "mvc";
  14. options.ClientSecret = "secret";
  15. options.SaveTokens = true;
  16. //options.GetClaimsFromUserInfoEndpoint = true;
  17.  
  18. //options.ClaimActions.MapJsonKey("sub", "sub");
  19. //options.ClaimActions.MapJsonKey("preferred_username", "preferred_username");
  20. //options.ClaimActions.MapJsonKey("sub", "sub");
  21. //options.ClaimActions.MapJsonKey("avatar", "avatar");
  22. //options.ClaimActions.MapCustomJson("role", jobj => jobj["role"].ToString());
  23.  
  24. options.Scope.Add("offline_access");
  25. options.Scope.Add("openid");
  26. options.Scope.Add("profile");
  27. });

源码:链接: https://pan.baidu.com/s/1EM-MC9N6RKb6MS2KjccIig 提取码: cq4c

集成EFCore配置Client和API

接下来的步骤是,以取代当前 AddInMemoryClients,AddInMemoryIdentityResources和AddInMemoryApiResources 在ConfigureServices在方法Startup.cs。我们将使用以下代码替换它们:

修改MvcCookieAuthSample项目中的ConfigureServices方法,copy链接字符串,这是一个官方的字符串,直接复制过来,放在上面。

  1. const string connectionString = @"Data Source=(LocalDb)\MSSQLLocalDB;database=IdentityServer4.Quickstart.EntityFramework-2.0.0;trusted_connection=yes;";

添加包的引用

  1. IdentityServer4.EntityFramework

引入IdentityServer4.EntityFramework的命名空间

初始化我们的数据库,OperationStore的配置。这里实际上有两套表, 一套存Client这些信息,Operation这套用来存token

加上ConfigrationStore和OperationStore以后就可以移除上面的三行代码,那三行代码之前都是从Config类里面获取数据的,先在通过数据库的方式去回去,所以这里不再需要了

  1. services.AddIdentityServer()
  2. .AddDeveloperSigningCredential()//添加开发人员签名凭据
  3. //.AddInMemoryApiResources(Config.GetApiResources())//添加内存apiresource
  4. //.AddInMemoryClients(Config.GetClients())//添加内存client
  5. //.AddInMemoryIdentityResources(Config.GetIdentityResources())//添加系统中的资源
  6. .AddConfigurationStore(options =>
  7. {
  8. options.ConfigureDbContext = builder => { builder.UseSqlServer(connectionString,sql=>sql.MigrationsAssembly(migrationsAssembly)); };
  9. })
  10. // this adds the operational data from DB (codes, tokens, consents)
  11. .AddOperationalStore(options =>
  12. {
  13. options.ConfigureDbContext = b =>
  14. b.UseSqlServer(connectionString,
  15. sql => sql.MigrationsAssembly(migrationsAssembly));
  16.  
  17. // this enables automatic token cleanup. this is optional.
  18. options.EnableTokenCleanup = true;
  19. })
  20. //.AddTestUsers(Config.GetTestUsers())//添加测试用户(这里不需要测试用户了)
  21. .AddAspNetIdentity<ApplicationUser>()
  22. .Services.AddScoped<IProfileService,ProfileService>();

添加数据库迁移

  1. Add-Migration init -Context PersistedGrantDbContext -OutputDir Data/Migrations/IdentityServer/PersistedGrantDb
  2. Add-Migration init -Context ConfigurationDbContext -OutputDir Data/Migrations/IdentityServer/ConfigurationDb

更新数据库结构

  1. Update-Database -c ConfigurationDbContext

这时数据库会生成库和表结构

初始化数据

Startup.cs中添加此方法以帮助初始化数据库:

  1. private void InitializeDatabase(IApplicationBuilder app)
  2. {
  3. using (var serviceScope = app.ApplicationServices.GetService<IServiceScopeFactory>().CreateScope())
  4. {
  5. serviceScope.ServiceProvider.GetRequiredService<PersistedGrantDbContext>().Database.Migrate();
  6.  
  7. var context = serviceScope.ServiceProvider.GetRequiredService<ConfigurationDbContext>();
  8. context.Database.Migrate();
  9. if (!context.Clients.Any())
  10. {
  11. foreach (var client in Config.GetClients())
  12. {
  13. context.Clients.Add(client.ToEntity());
  14. }
  15. context.SaveChanges();
  16. }
  17.  
  18. if (!context.IdentityResources.Any())
  19. {
  20. foreach (var resource in Config.GetIdentityResources())
  21. {
  22. context.IdentityResources.Add(resource.ToEntity());
  23. }
  24. context.SaveChanges();
  25. }
  26.  
  27. if (!context.ApiResources.Any())
  28. {
  29. foreach (var resource in Config.GetApiResources())
  30. {
  31. context.ApiResources.Add(resource.ToEntity());
  32. }
  33. context.SaveChanges();
  34. }
  35. }
  36. }

然后我们可以从 Configure 方法中调用它:

然后运行,我们可以看到在 Clients 表中已经有了数据

源码:链接: https://pan.baidu.com/s/1BauxqrclWtlOJk9h6uxtAg 提取码: dq4e

【ASP.NET Core分布式项目实战】(三)整理IdentityServer4 MVC授权、Consent功能实现的更多相关文章

  1. ASP.NET Core分布式项目实战

    ASP.NET Core开发者成长路线图 asp.net core 官方文档 https://docs.microsoft.com/zh-cn/aspnet/core/getting-started/ ...

  2. 【笔记目录2】ASP.NET Core分布式项目实战

    当前标签: ASP.NET Core分布式项目实战 共2页: 上一页 1 2  11.ClientCredential模式总结 GASA 2019-03-11 12:59 阅读:26 评论:0 10. ...

  3. 【笔记目录1】ASP.NET Core分布式项目实战

    当前标签: ASP.NET Core分布式项目实战 共2页: 1 2 下一页  35.Docker安装Mysql挂载Host Volume GASA 2019-06-20 22:02 阅读:51 评论 ...

  4. ASP.NET Core分布式项目实战-目录

    前言 今年是2018年,发现已经有4年没有写博客了,在这4年的时光里,接触了很多的.NET技术,自己的技术也得到很大的进步.在这段时光里面很感谢张队长以及其他开发者一直对.NET Core开源社区做出 ...

  5. 【ASP.NET Core分布式项目实战】(一)IdentityServer4登录中心、oauth密码模式identity server4实现

    本博客根据http://video.jessetalk.cn/my/course/5视频整理 资料 OAuth2 流程:http://www.ruanyifeng.com/blog/2014/05/o ...

  6. 【ASP.NET Core分布式项目实战】(二)oauth2 + oidc 实现 server部分

    本博客根据http://video.jessetalk.cn/my/course/5视频整理(内容可能会有部分,推荐看源视频学习) 资料 我们基于之前的MvcCookieAuthSample来做开发 ...

  7. 【ASP.NET Core分布式项目实战】(五)Docker制作dotnet core控制台程序镜像

    Docker制作dotnet core控制台程序镜像 基于dotnet SDK 新建控制台程序 mkdir /home/console cd /home/console dotnet new cons ...

  8. 【ASP.NET Core分布式项目实战】(六)Gitlab安装

    Gitlab GitLab是由GitLabInc.开发,使用MIT许可证的基于网络的Git仓库管理工具,且具有wiki和issue跟踪功能.使用Git作为代码管理工具,并在此基础上搭建起来的web服务 ...

  9. 【ASP.NET Core分布式项目实战】(四)使用mysql/mysql-server安装mysql

    Docker安装Mysql 拉取镜像 docker pull mysql/mysql-server 运行mysql docker run -d -p : --name mysql01 mysql/my ...

随机推荐

  1. java集合框架(Collections Framework)

    */ .hljs { display: block; overflow-x: auto; padding: 0.5em; color: #333; background: #f8f8f8; } .hl ...

  2. Struts配置详解

    一.Stuts的元素 1 web.xml 任何一个web应用程序都是基于请求响应模式进行构建的,所以无论采用哪种MVC框架,都离不开web.xml文件的配置.换句话说,web.xml并不是Struts ...

  3. iOS 蓝牙开发资料记录

    一.蓝牙基础认识:   1.iOS蓝牙开发:  iOS蓝牙开发:蓝牙连接和数据读写   iOS蓝牙后台运行  iOS关于app连接已配对设备的问题(ancs协议的锅)          iOS蓝牙空中 ...

  4. samba 搭建

    #useradd -M -s /sbin/nologin kvmshare #mkdir /home/etl #chown kvmshare:kvmshare /home/etl 将本地账号添加到 s ...

  5. Linux 学习记录 二 (文件的打包压缩).

     前言:本文参考<鸟哥的Linux 私房菜>,如有说的不对的地方,还请指正!谢谢!  环境:Centos 6.4    和window不同,在Linux压缩文件需要注意的是,压缩后的文件会 ...

  6. windows 下共享内存使用方法示例

    windows下共享内存使用方法较 linux 而言微微复杂 示例实现的功能 有一个视频文件,一块内存区域 : 程序 A,将该视频写入该内存区域 : 程序 B,从该内存区域读取该视频 : 代码模块实现 ...

  7. nginx搭建rtmp协议流媒体服务器总结

    最近在 ubuntu12.04+wdlinux(centos)上搭建了一个rtmp服务器,感觉还挺麻烦的,所以记录下. 大部分都是参考网络上的资料. 前提: 在linux下某个目录中新建一个nginx ...

  8. 原生JavaScript实现一个简单的todo-list

    直接上代码: <!DOCTYPE html> <html lang="en"> <head> <meta charset="UT ...

  9. Mobiscroll的介绍【一款兼容PC和移动设备的滑动插件】

    Mobiscroll是一个用于触摸设备的日期和时间选择器,它的使用不会改变HTML5.PhoneGap以及混合应用的原生用户体验.作为一款jQuery滑动选择插件,用户可以自定义主题样式,为自己的移动 ...

  10. JavaScript的DOM编程--01--js代码的写入位置

    DOM:Document Object Model(文本对象模型) D:文档 – html 文档 或 xml 文档 O:对象 – document 对象的属性和方法 M:模型 DOM 是针对xml(h ...