【ASP.NET Core分布式項目實戰】(三)整理IdentityServer4 MVC授權、Consent功能實現


前言

由於之前的博客都是基於其他的博客進行開發,現在重新整理一下方便以后后期使用與學習

新建IdentityServer4服務端

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

新建項目解決方案AuthSample.

新建一個ASP.NET Core Web Application 項目MvcCookieAuthSample,選擇模板Web 應用程序 不進行身份驗證。

給網站設置默認地址     http://localhost:5000

第一步:添加Nuget包:IdentityServer4

添加IdentityServer4 引用:

Install-Package IdentityServer4

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

然后添加配置類Config.cs:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using IdentityServer4;
using IdentityServer4.Models;
using IdentityServer4.Test;

namespace MvcCookieAuthSample
{
    public class Config
    {
        //所有可以訪問的Resource
        public static IEnumerable<ApiResource> GetApiResources()
        {
            return new List<ApiResource>()
            {
                new ApiResource("api1","API Application")
            };
        }

        //客戶端
        public static IEnumerable<Client> GetClients()
        {
            return new List<Client>
            {
                new Client{
                    ClientId="mvc",
                    AllowedGrantTypes=GrantTypes.Implicit,//模式:隱式模式
                    ClientSecrets={//私鑰
                        new Secret("secret".Sha256())
                    },
                    AllowedScopes={//運行訪問的資源
                        IdentityServerConstants.StandardScopes.Profile,
                        IdentityServerConstants.StandardScopes.OpenId,
                    },
                    RedirectUris={"http://localhost:5001/signin-oidc"},//跳轉登錄到的客戶端的地址
                    PostLogoutRedirectUris={"http://localhost:5001/signout-callback-oidc"},//跳轉登出到的客戶端的地址
                    RequireConsent=false//是否需要用戶點擊確認進行跳轉
                }
            };
        }

        //測試用戶
        public static List<TestUser> GetTestUsers()
        {
            return new List<TestUser>
            {
                new TestUser{
                    SubjectId="10000",
                    Username="wyt",
                    Password="password"
                }
            };
        }

        //定義系統中的資源
        public static IEnumerable<IdentityResource> GetIdentityResources()
        {
            return new List<IdentityResource>
            {
                //這里實際是claims的返回資源
                new IdentityResources.OpenId(),
                new IdentityResources.Profile(),
                new IdentityResources.Email()
            };
        }

    }
}
View Code

 

第三步:添加Startup配置

引用命名空間:

using IdentityServer4;

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

services.AddIdentityServer()
                .AddDeveloperSigningCredential()//添加開發人員簽名憑據
                .AddInMemoryApiResources(Config.GetApiResources())//添加內存apiresource
                .AddInMemoryClients(Config.GetClients())//添加內存client
                .AddInMemoryIdentityResources(Config.GetIdentityResources())//添加系統中的資源
                .AddTestUsers(Config.GetTestUsers());//添加測試用戶
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    ...
    app.UseIdentityServer();
    ...
}

注冊登錄實現

我們還需要新建一個ViewModels,在ViewModels中新建RegisterViewModel.cs和LoginViewModel.cs來接收表單提交的值以及來進行強類型視圖

using System.ComponentModel.DataAnnotations;

namespace MvcCookieAuthSample.ViewModels
{
    public class RegisterViewModel
    {
        [Required]//必須的
        [DataType(DataType.EmailAddress)]//內容檢查是否為郵箱
        public string Email { get; set; }

        [Required]//必須的
        [DataType(DataType.Password)]//內容檢查是否為密碼
        public string Password { get; set; }

        [Required]//必須的
        [DataType(DataType.Password)]//內容檢查是否為密碼
        public string ConfirmedPassword { get; set; }
    }
}
View Code
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading.Tasks;

namespace MvcCookieAuthSample.ViewModels
{
    public class LoginViewModel
    {

        [Required]
        public string UserName { get; set; }

        [Required]//必須的
        [DataType(DataType.Password)]//內容檢查是否為密碼
        public string Password { get; set; }
    }
}
View Code

在Controllers文件夾下新建AdminController.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;

namespace MvcCookieAuthSample.Controllers
{
    public class AdminController : Controller
    {
        public IActionResult Index()
        {
            return View();
        }
    }
}
View Code

在Views文件夾下新建Admin文件夾,並在Admin文件夾下新建Index.cshtml

@{
    ViewData["Title"] = "Admin";
}
<h2>@ViewData["Title"]</h2>

<p>Admin Page</p>
View Code

在Controllers文件夾下新建AccountController.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using IdentityServer4.Test;
using Microsoft.AspNetCore.Identity;
using MvcCookieAuthSample.ViewModels;
using Microsoft.AspNetCore.Authentication;

namespace MvcCookieAuthSample.Controllers
{
    public class AccountController : Controller
    {
        private readonly TestUserStore _users;
        public AccountController(TestUserStore users)
        {
            _users = users;
        }

        //內部跳轉
        private IActionResult RedirectToLocal(string returnUrl)
        {
            if (Url.IsLocalUrl(returnUrl))
            {//如果是本地
                return Redirect(returnUrl);
            }

            return RedirectToAction(nameof(HomeController.Index), "Home");
        }

        //添加驗證錯誤
        private void AddError(IdentityResult result)
        {
            //遍歷所有的驗證錯誤
            foreach (var error in result.Errors)
            {
                //返回error到model
                ModelState.AddModelError(string.Empty, error.Description);
            }
        }

        public IActionResult Register(string returnUrl = null)
        {
            ViewData["returnUrl"] = returnUrl;
            return View();
        }

        [HttpPost]
        public async Task<IActionResult> Register(RegisterViewModel registerViewModel, string returnUrl = null)
        {
            return View();
        }

        public IActionResult Login(string returnUrl = null)
        {
            ViewData["returnUrl"] = returnUrl;
            return View();
        }

        [HttpPost]
        public async Task<IActionResult> Login(LoginViewModel loginViewModel, string returnUrl = null)
        {
            if (ModelState.IsValid)
            {
                ViewData["returnUrl"] = returnUrl;
                var user = _users.FindByUsername(loginViewModel.UserName);

                if (user==null)
                {
                    ModelState.AddModelError(nameof(loginViewModel.UserName), "UserName not exists");
                }
                else
                {
                    if (_users.ValidateCredentials(loginViewModel.UserName,loginViewModel.Password))
                    {
                        //是否記住
                        var prop = new AuthenticationProperties
                        {
                            IsPersistent = true,
                            ExpiresUtc = DateTimeOffset.UtcNow.Add(TimeSpan.FromMinutes(30))
                        };

                        await Microsoft.AspNetCore.Http.AuthenticationManagerExtensions.SignInAsync(HttpContext, user.SubjectId, user.Username, prop);
                    }
                }

                return RedirectToLocal(returnUrl);
            }

            return View();
        }

        public async Task<IActionResult> Logout()
        {
            await HttpContext.SignOutAsync();
            return RedirectToAction("Index", "Home");
        }
    }
}
View Code

然后在Views文件夾下新增Account文件夾並新增Register.cshtml與Login.cshtml視圖

@{
    ViewData["Title"] = "Register";
}

@using MvcCookieAuthSample.ViewModels;
@model RegisterViewModel;

<h2>@ViewData["Title"]</h2>
<h3>@ViewData["Message"]</h3>

<div class="row">
    <div class="col-md-4">
        @* 這里將asp-route-returnUrl="@ViewData["returnUrl"],就可以在進行register的post請求的時候接收到returnUrl *@
        <form method="post" asp-route-returnUrl="@ViewData["returnUrl"]">
            <h4>Create a new account.</h4>
            <hr />

           @*統一顯示錯誤信息*@
            <div class="text-danger" asp-validation-summary="All"></div>

            <div class="form-group">
                <label asp-for="Email"></label>
                <input asp-for="Email" class="form-control" />
                <span asp-validation-for="Email" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Password"></label>
                <input asp-for="Password" class="form-control" />
                <span asp-validation-for="Password" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="ConfirmedPassword"></label>
                <input asp-for="ConfirmedPassword" class="form-control" />
                <span asp-validation-for="ConfirmedPassword" class="text-danger"></span>
            </div>
            <button type="submit" class="btn btn-default">Register</button>
        </form>
    </div>
</div>
View Code
@{
    ViewData["Title"] = "Login";
}

@using MvcCookieAuthSample.ViewModels;
@model LoginViewModel;

<div class="row">
    <div class="col-md-4">
        <section>
            <form method="post" asp-controller="Account" asp-action="Login"  asp-route-returnUrl="@ViewData["returnUrl"]">
                <h4>Use a local account to log in.</h4>
                <hr />

                 @*統一顯示錯誤信息*@
                <div class="text-danger" asp-validation-summary="All"></div>

                <div class="form-group">
                    <label asp-for="UserName"></label>
                    <input asp-for="UserName" class="form-control" />
                    <span asp-validation-for="UserName" class="text-danger"></span>
                </div>

                <div class="form-group">
                    <label asp-for="Password"></label>
                    <input asp-for="Password" type="password" class="form-control" />
                    <span asp-validation-for="Password" class="text-danger"></span>
                </div>

                <div class="form-group">
                    <button type="submit" class="btn btn-default">Log in</button>
                </div>

            </form>
        </section>
    </div>
</div>

@section Scripts
    {
    @await Html.PartialAsync("_ValidationScriptsPartial")
}
View Code

我們接下來要修改_Layout.cshtml視圖頁面判斷注冊/登陸按鈕是否應該隱藏

完整的_Layout.cshtml代碼:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>@ViewData["Title"] - MvcCookieAuthSample</title>

    <environment include="Development">
        <link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.css" />
        <link rel="stylesheet" href="~/css/site.css" />
    </environment>
    <environment exclude="Development">
        <link rel="stylesheet" href="https://ajax.aspnetcdn.com/ajax/bootstrap/3.3.7/css/bootstrap.min.css"
              asp-fallback-href="~/lib/bootstrap/dist/css/bootstrap.min.css"
              asp-fallback-test-class="sr-only" asp-fallback-test-property="position" asp-fallback-test-value="absolute" />
        <link rel="stylesheet" href="~/css/site.min.css" asp-append-version="true" />
    </environment>
</head>
<body>
    <nav class="navbar navbar-inverse navbar-fixed-top">
        <div class="container">
            <div class="navbar-header">
                <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
                    <span class="sr-only">Toggle navigation</span>
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                </button>
                <a asp-area="" asp-controller="Home" asp-action="Index" class="navbar-brand">MvcCookieAuthSample</a>
            </div>
            <div class="navbar-collapse collapse">
                <ul class="nav navbar-nav">
                    <li><a asp-area="" asp-controller="Home" asp-action="Index">Home</a></li>
                    <li><a asp-area="" asp-controller="Home" asp-action="About">About</a></li>
                    <li><a asp-area="" asp-controller="Home" asp-action="Contact">Contact</a></li>
                </ul>

                @if (User.Identity.IsAuthenticated)
                {
                    <form asp-action="Logout" asp-controller="Account" method="post">
                        <ul class="nav navbar-nav navbar-right">
                            <li>
                                <a title="Welcome" asp-controller="Admin" asp-action="Index">@User.Identity.Name</a>
                            </li>
                            <li>
                                <button type="submit" class="btn btn-link navbar-btn navbar-link">Log out</button>
                            </li>
                        </ul>
                    </form>

                }
                else
                {
                    <ul class="nav navbar-nav navbar-right">
                        <li><a asp-area="" asp-controller="Account" asp-action="Register">Register</a></li>
                        <li><a asp-area="" asp-controller="Account" asp-action="Login">Log in</a></li>
                    </ul>
                }


            </div>
        </div>
    </nav>
    <div class="container body-content">
        @RenderBody()
        <hr />
        <footer>
            <p>&copy; 2018 - MvcCookieAuthSample</p>
        </footer>
    </div>

    <environment include="Development">
        <script src="~/lib/jquery/dist/jquery.js"></script>
        <script src="~/lib/bootstrap/dist/js/bootstrap.js"></script>
        <script src="~/js/site.js" asp-append-version="true"></script>
    </environment>
    <environment exclude="Development">
        <script src="https://ajax.aspnetcdn.com/ajax/jquery/jquery-2.2.0.min.js"
                asp-fallback-src="~/lib/jquery/dist/jquery.min.js"
                asp-fallback-test="window.jQuery"
                crossorigin="anonymous"
                integrity="sha384-K+ctZQ+LL8q6tP7I94W+qzQsfRV2a+AfHIi9k8z8l9ggpc8X+Ytst4yBo/hH+8Fk">
        </script>
        <script src="https://ajax.aspnetcdn.com/ajax/bootstrap/3.3.7/bootstrap.min.js"
                asp-fallback-src="~/lib/bootstrap/dist/js/bootstrap.min.js"
                asp-fallback-test="window.jQuery && window.jQuery.fn && window.jQuery.fn.modal"
                crossorigin="anonymous"
                integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa">
        </script>
        <script src="~/js/site.min.js" asp-append-version="true"></script>
    </environment>

    @RenderSection("Scripts", required: false)
</body>
</html>
View Code

最后給AdminController加上  [Authorize]  特性標簽即可  

然后我們就可以運行網站,輸入用戶名和密碼進行登錄了

新建客戶端 

新建一個MVC網站MvcClient 

dotnet new mvc --name MvcClient

給網站設置默認地址     http://localhost:5001

MVC的網站已經內置幫我們實現了Identity,所以我們不需要再額外添加Identity引用

添加認證

services.AddAuthentication(options =>
{
    options.DefaultScheme = "Cookies";//使用Cookies認證
    options.DefaultChallengeScheme = "oidc";//使用oidc
})
.AddCookie("Cookies")//配置Cookies認證
.AddOpenIdConnect("oidc",options=> {//配置oidc
    options.SignInScheme = "Cookies";
    options.Authority = "http://localhost:5000";
    options.RequireHttpsMetadata = false;

    options.ClientId = "mvc";
    options.ClientSecret = "secret";
    options.SaveTokens = true;
});

在管道中使用Authentication

app.UseAuthentication();

接下來我們在HomeController上打上  [Authorize]  標簽,然后啟動運行

我們這個時候訪問首頁http://localhost:5001會自動跳轉到ocalhost:5000/account/login登錄

登錄之后會自動跳轉回來

我們可以在Home/About頁面將claim的信息顯示出來

@{
    ViewData["Title"] = "About";
}
<h2>@ViewData["Title"]</h2>
<h3>@ViewData["Message"]</h3>

<dl>
    @foreach (var claim in User.Claims)
    {
        <dt>@claim.Type</dt>
        <dt>@claim.Value</dt>
    }
</dl>
View Code

 

 

 這邊的內容是根據我們在IdentityServer服務中定義的返回資源決定的

Consent功能實現 

首先在ViewModels文件夾下創建兩個視圖模型

ScopeViewModel.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace MvcCookieAuthSample.ViewModels
{
    //領域
    public class ScopeViewModel
    {
        public string Name { get; set; }
        public string DisplayName { get; set; }
        public string Description { get; set; }
        public bool Emphasize { get; set; }
        public bool Required { get; set; }
        public bool Checked { get; set; }
    }
}
View Code

ConsentViewModel.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace MvcCookieAuthSample.ViewModels
{
    public class ConsentViewModel
    {
        public string ClientId { get; set; }
        public string ClientName { get; set; }
        public string ClientUrl { get; set; }
        public string ClientLogoUrl { get; set; }
        public bool AllowRememberConsent { get; set; }

        public IEnumerable<ScopeViewModel> IdentityScopes { get; set; }
        public IEnumerable<ScopeViewModel> ResourceScopes { get; set; }
    }
}
View Code

 

我們在MvcCookieAuthSample項目中添加新控制器ConsentController

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using MvcCookieAuthSample.ViewModels;
using IdentityServer4.Models;
using IdentityServer4.Services;
using IdentityServer4.Stores;

namespace MvcCookieAuthSample.Controllers
{
    public class ConsentController : Controller
    {
        private readonly IClientStore _clientStore;
        private readonly IResourceStore _resourceStore;
        private readonly IIdentityServerInteractionService _identityServerInteractionService;

        public ConsentController(IClientStore clientStore, IResourceStore resourceStore, IIdentityServerInteractionService identityServerInteractionService)
        {
            _clientStore = clientStore;
            _resourceStore = resourceStore;
            _identityServerInteractionService = identityServerInteractionService;
        }

        private async Task<ConsentViewModel> BuildConsentViewModel(string returnUrl)
        {
            var request =await _identityServerInteractionService.GetAuthorizationContextAsync(returnUrl);
            if (request == null)
                return null;

            var client =await  _clientStore.FindEnabledClientByIdAsync(request.ClientId);
            var resources =await _resourceStore.FindEnabledResourcesByScopeAsync(request.ScopesRequested);

            return CreateConsentViewModel(request, client, resources);
        }

        private ConsentViewModel CreateConsentViewModel(AuthorizationRequest request,Client client,Resources resources)
        {
            var vm = new ConsentViewModel();
            vm.ClientName = client.ClientName;
            vm.ClientLogoUrl = client.LogoUri;
            vm.ClientUrl = client.ClientUri;
            vm.AllowRememberConsent = client.AllowRememberConsent;

            vm.IdentityScopes = resources.IdentityResources.Select(i => CreateScopeViewModel(i));
            vm.ResourceScopes = resources.ApiResources.SelectMany(i =>i.Scopes).Select(i=>CreateScopeViewModel(i));

            return vm;
        }

        private ScopeViewModel CreateScopeViewModel(IdentityResource identityResource)
        {
            return new ScopeViewModel
            {
                Name = identityResource.Name,
                DisplayName = identityResource.DisplayName,
                Description = identityResource.Description,
                Required = identityResource.Required,
                Checked = identityResource.Required,
                Emphasize = identityResource.Emphasize
            };
        }

        private ScopeViewModel CreateScopeViewModel(Scope scope)
        {
            return new ScopeViewModel
            {
                Name = scope.Name,
                DisplayName = scope.DisplayName,
                Description = scope.Description,
                Required = scope.Required,
                Checked = scope.Required,
                Emphasize = scope.Emphasize
            };
        }

       [HttpGet]
        public async Task<IActionResult> Index(string returnUrl)
        {
            var model =await BuildConsentViewModel(returnUrl);
            if (model==null)
            {

            }
            return View(model);
        }
    }
}
View Code

 

然后新建Idenx.cshtml視圖和_ScopeListitem.cshtml分部視圖

_ScopeListitem.cshtml

@using MvcCookieAuthSample.ViewModels;
@model ScopeViewModel

<li>
    <label>
        <input type="checkbox" name="ScopesConsented" id="scopes_@Model.Name" value="@Model.Name" checked="@Model.Checked" disabled="@Model.Required"/>

        <strong>@Model.Name</strong>
        @if (Model.Emphasize)
        {
            <span class="glyphicon glyphicon-exclamation-sign"></span>
        }
    </label>
    @if (string.IsNullOrWhiteSpace(Model.Description))
    {
        <div>
            <label for="scopes_@Model.Name">@Model.Description</label>
        </div>
    }
</li>
View Code

Idenx.cshtml

@using MvcCookieAuthSample.ViewModels;
@model ConsentViewModel
<p>Consent Page</p>
<!--Client Info-->
<div class="row page-header">
    <div class="col-sm-10">
        @if (!string.IsNullOrWhiteSpace(Model.ClientLogoUrl))
        {
            <div><img src="@Model.ClientLogoUrl" /></div>
        }

        <h1>
            @Model.ClientName
            <small>希望使用你的賬戶</small>
        </h1>
    </div>
</div>

<!--Scope Info-->
<div class="row">
    <div class="col-sm-8">
        <form asp-action="Index">
            @if (Model.IdentityScopes.Any())
            {
                <div>
                    <div class="panel-heading">
                        <span class="glyphicon glyphicon-user"></span>
                        用戶信息
                    </div>
                    <ul class="list-group">
                        @foreach (var scope in Model.IdentityScopes)
                        {
                            @Html.Partial("_ScopeListitem",scope)
                        }
                    </ul>
                </div>
            }
            @if (Model.ResourceScopes.Any())
            {
                <div>
                    <div class="panel-heading">
                        <span class="glyphicon glyphicon-tasks"></span>
                        應用權限
                    </div>
                    <ul class="list-group">
                        @foreach (var scope in Model.ResourceScopes)
                        {
                            @Html.Partial("_ScopeListitem",scope)
                        }
                    </ul>
                </div>
            }
        </form>
    </div>
</div>
View Code

 

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

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using IdentityServer4;
using IdentityServer4.Models;
using IdentityServer4.Test;

namespace MvcCookieAuthSample
{
    public class Config
    {
        //所有可以訪問的Resource
        public static IEnumerable<ApiResource> GetApiResources()
        {
            return new List<ApiResource>()
            {
                new ApiResource("api1","API Application")
            };
        }

        //客戶端
        public static IEnumerable<Client> GetClients()
        {
            return new List<Client>
            {
                new Client{
                    ClientId="mvc",
                    AllowedGrantTypes=GrantTypes.Implicit,//模式:隱式模式
                    ClientSecrets={//私鑰
                        new Secret("secret".Sha256())
                    },
                    AllowedScopes={//運行訪問的資源
                        IdentityServerConstants.StandardScopes.Profile,
                        IdentityServerConstants.StandardScopes.OpenId,
 IdentityServerConstants.StandardScopes.Email,
                    },
                    RedirectUris={"http://localhost:5001/signin-oidc"},//跳轉登錄到的客戶端的地址
                    PostLogoutRedirectUris={"http://localhost:5001/signout-callback-oidc"},//跳轉登出到的客戶端的地址
                    RequireConsent=true,//是否需要用戶點擊確認進行跳轉,改為點擊確認后進行跳轉

                    ClientName="MVC Clent",
                    ClientUri="http://localhost:5001",
                    LogoUri="https://chocolatey.org/content/packageimages/aspnetcore-runtimepackagestore.2.0.0.png",
                    AllowRememberConsent=true,
                }
            };
        }

        //測試用戶
        public static List<TestUser> GetTestUsers()
        {
            return new List<TestUser>
            {
                new TestUser{
                    SubjectId="10000",
                    Username="wyt",
                    Password="password",
                    
                }
            };
        }

        //定義系統中的資源
        public static IEnumerable<IdentityResource> GetIdentityResources()
        {
            return new List<IdentityResource>
            {
                //這里實際是claims的返回資源
                new IdentityResources.OpenId(),
                new IdentityResources.Profile(),
                new IdentityResources.Email()
            };
        }

    }
}

 

我們這個時候訪問首頁http://localhost:5001會自動跳轉到ocalhost:5000/account/login登錄

登錄之后會自動跳轉到登錄確認頁面

Consent 確認邏輯實現

首先我們在 ViewModels 文件夾中增加一個類 InputConsentViewModel.cs 用於接收 Consent/Index.cshtml 提交的表單信息

public class InputConsentViewModel
{
    /// <summary>
    /// 按鈕
    /// </summary>
    public string Button { get; set; }
    /// <summary>
    /// 接收到的勾選的Scope
    /// </summary>
    public IEnumerable<string> ScopesConsented { get; set; }
    /// <summary>
    /// 是否選擇記住
    /// </summary>
    public bool RememberConsent { get; set; }
    /// <summary>
    /// 跳轉地址
    /// </summary>
    public string ReturnUrl { get; set; }
}
View Code

然后修改 ConsentViewModel.cs ,加入ReturnUrl

public class ConsentViewModel
{
    public string ClientId { get; set; }
    public string ClientName { get; set; }
    public string ClientUrl { get; set; }
    public string ClientLogoUrl { get; set; }
    public bool AllowRememberConsent { get; set; }

    public IEnumerable<ScopeViewModel> IdentityScopes { get; set; }
    public IEnumerable<ScopeViewModel> ResourceScopes { get; set; }

    public string ReturnUrl { get; set; }
}

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

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

private async Task<ConsentViewModel> BuildConsentViewModel(string returnUrl)
{
    AuthorizationRequest request = await _identityServerInteractionService.GetAuthorizationContextAsync(returnUrl);
    if (request == null)
    {
        return null;
    }

    Client client = await _clientStore.FindEnabledClientByIdAsync(request.ClientId);
    Resources resources = await _resourceStore.FindEnabledResourcesByScopeAsync(request.ScopesRequested);

    var vm= CreateConsentViewModel(request, client, resources);
    vm.ReturnUrl = returnUrl;
    return vm;
}

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

[HttpPost]
public async Task<IActionResult> Index(InputConsentViewModel viewModel)
{
    ConsentResponse consentResponse=null;
    if (viewModel.Button == "no")
    {
        consentResponse= ConsentResponse.Denied;
    }
    else if (viewModel.Button == "yes")
    {
        if (viewModel.ScopesConsented!=null&&viewModel.ScopesConsented.Any())
        {
            consentResponse = new ConsentResponse()
            {
                RememberConsent = viewModel.RememberConsent,
                ScopesConsented = viewModel.ScopesConsented
            };
        }
    }

    if ( consentResponse!=null)
    {
        var request =await _identityServerInteractionService.GetAuthorizationContextAsync(viewModel.ReturnUrl);
        await _identityServerInteractionService.GrantConsentAsync(request, consentResponse);
        return Redirect(viewModel.ReturnUrl);
    }

    var model = await BuildConsentViewModel(viewModel.ReturnUrl);
    if (model == null)
    {

    }

    return View(model);
}
View Code

然后將 ViewModels\ConsentViewModel.cs 中 ConsentViewModel 的 AllowRememberConsent 改為 RememberConsent ,這樣才能與 ViewModels\InputConsentViewModel.cs 保持一致

public class ConsentViewModel:InputConsentViewModel
{
    public string ClientId { get; set; }
    public string ClientName { get; set; }
    public string ClientUrl { get; set; }
    public string ClientLogoUrl { get; set; }
    //public bool RememberConsent { get; set; }
    public IEnumerable<ScopeViewModel> IdentityScopes { get; set; }
    public IEnumerable<ScopeViewModel> ResourceScopes { get; set; }
    //public string ReturnUrl { get; set; }
}

最后修改視圖 Consent\Index.cshtml ,加入記住選項和確認按鈕

@using MvcCookieAuthSample.ViewModels;
@model ConsentViewModel
<p>Consent Page</p>
<!--Client Info-->
<div class="row page-header">
    <div class="col-sm-10">
        @if (!string.IsNullOrWhiteSpace(Model.ClientLogoUrl))
        {
            <div><img src="@Model.ClientLogoUrl" /></div>
        }

        <h1>
            @Model.ClientId
            <small>希望使用您的賬戶</small>
        </h1>
    </div>
</div>

<!--Scope Info-->
<div class="row">
    <div class="col-sm-8">
        <form asp-action="Index" method="post">
            <input type="hidden" asp-for="ReturnUrl"/>
            @if (Model.IdentityScopes.Any())
            {
                <div>
                    <div class="panel-heading">
                        <span class="glyphicon glyphicon-user"></span>
                        用戶信息
                    </div>
                    <ul class="list-group">
                        @foreach (var scope in Model.IdentityScopes)
                        {
                            @Html.Partial("_ScopeListitem", scope)
                        }
                    </ul>
                </div>
            }
            @if (Model.ResourceScopes.Any())
            {
                <div>
                    <div class="panel-heading">
                        <span class="glyphicon glyphicon-tasks"></span>
                        應用權限
                    </div>
                    <ul class="list-group">
                        @foreach (var scope in Model.ResourceScopes)
                        {
                            @Html.Partial("_ScopeListitem", scope)
                        }
                    </ul>
                </div>
            }
            
            <div>
                <label>
                    <input type="checkbox" asp-for="RememberConsent"/>
                    <strong>記住我的選擇</strong>
                </label>
            </div>

            <div>
                <button name="button" value="yes" class="btn btn-primary" autofocus>同意</button>
                <button name="button" value="no" >取消</button>
                @if (!string.IsNullOrEmpty(Model.ClientUrl))
                {
                    <a href="@Model.ClientUrl" class="pull-right btn btn-default">
                        <span class="glyphicon glyphicon-info-sign"></span>
                        <strong>@Model.ClientUrl</strong>
                    </a>
                }
            </div>
        </form>
    </div>
</div>
View Code

修改視圖 Views\Consent\_ScopeListitem.cshtml 

@using MvcCookieAuthSample.ViewModels;
@model ScopeViewModel

<li>
    <label>
        <input type="checkbox" name="ScopesConsented" id="scopes_@Model.Name" value="@Model.Name" checked="@Model.Checked" disabled="@Model.Required"/>
        @if (Model.Required)
        {
            <input type="hidden" name="ScopesConsented" value="@Model.Name"/>
        }

        <strong>@Model.Name</strong>
        @if (Model.Emphasize)
        {
            <span class="glyphicon glyphicon-exclamation-sign"></span>
        }
    </label>
    
    @if (!string.IsNullOrWhiteSpace(Model.Description))
    {
        <div>
            <label for="scopes_@Model.Name">@Model.Description</label>
        </div>
    }

</li>
View Code

運行效果

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

Consent 代碼重構

新建 Services 文件夾,添加 ConsentService.cs 用於業務封裝

public class ConsentService
    {
        private readonly IClientStore _clientStore;
        private readonly IResourceStore _resourceStore;
        private readonly IIdentityServerInteractionService _identityServerInteractionService;

        public ConsentService(IClientStore clientStore
            , IResourceStore resourceStore
            , IIdentityServerInteractionService identityServerInteractionService)
        {
            _clientStore = clientStore;
            _resourceStore = resourceStore;
            _identityServerInteractionService = identityServerInteractionService;
        }

        public async Task<ConsentViewModel> BuildConsentViewModel(string returnUrl,InputConsentViewModel model=null)
        {
            AuthorizationRequest request = await _identityServerInteractionService.GetAuthorizationContextAsync(returnUrl);
            if (request == null)
            {
                return null;
            }

            Client client = await _clientStore.FindEnabledClientByIdAsync(request.ClientId);
            Resources resources = await _resourceStore.FindEnabledResourcesByScopeAsync(request.ScopesRequested);

            var vm = CreateConsentViewModel(request, client, resources,model);
            vm.ReturnUrl = returnUrl;
            return vm;
        }

        public async Task<ProcessConsentResult> ProcessConsent(InputConsentViewModel model)
        {
            ConsentResponse consentResponse = null;
            var result=new ProcessConsentResult();
            if (model.Button == "no")
            {
                consentResponse = ConsentResponse.Denied;
            }
            else if (model.Button == "yes")
            {
                if (model.ScopesConsented != null && model.ScopesConsented.Any())
                {
                    consentResponse = new ConsentResponse()
                    {
                        RememberConsent = model.RememberConsent,
                        ScopesConsented = model.ScopesConsented
                    };
                }
                else
                {
                    result.ValidationError = "請至少選擇一個權限";
                }
            }

            if (consentResponse != null)
            {
                var request = await _identityServerInteractionService.GetAuthorizationContextAsync(model.ReturnUrl);
                await _identityServerInteractionService.GrantConsentAsync(request, consentResponse);
                result.RedirectUrl = model.ReturnUrl;
            }
            else
            {
                ConsentViewModel consentViewModel = await BuildConsentViewModel(model.ReturnUrl,model);
                result.ViewModel = consentViewModel;
            }

            return result;
        }

        #region Private Methods

        private ConsentViewModel CreateConsentViewModel(AuthorizationRequest request, Client client,
            Resources resources,InputConsentViewModel model)
        {
            var rememberConsent = model?.RememberConsent ?? true;
            var selectedScopes = model?.ScopesConsented ?? Enumerable.Empty<string>();

            var vm = new ConsentViewModel();
            vm.ClientName = client.ClientName;
            vm.ClientLogoUrl = client.LogoUri;
            vm.ClientUrl = client.ClientUri;
            vm.RememberConsent = rememberConsent;

            vm.IdentityScopes = resources.IdentityResources.Select(i => CreateScopeViewModel(i,selectedScopes.Contains(i.Name)||model==null));
            vm.ResourceScopes = resources.ApiResources.SelectMany(i => i.Scopes).Select(i => CreateScopeViewModel(i, selectedScopes.Contains(i.Name)||model==null));

            return vm;
        }

        private ScopeViewModel CreateScopeViewModel(IdentityResource identityResource,bool check)
        {
            return new ScopeViewModel()
            {
                Name = identityResource.Name,
                DisplayName = identityResource.DisplayName,
                Description = identityResource.Description,
                Required = identityResource.Required,
                Checked = check|| identityResource.Required,
                Emphasize = identityResource.Emphasize
            };
        }

        private ScopeViewModel CreateScopeViewModel(Scope scope, bool check)
        {
            return new ScopeViewModel()
            {
                Name = scope.Name,
                DisplayName = scope.DisplayName,
                Description = scope.Description,
                Required = scope.Required,
                Checked = check||scope.Required,
                Emphasize = scope.Emphasize
            };
        }
        #endregion
    }
View Code

Asp.Net Core2.2源碼(重構):鏈接: https://pan.baidu.com/s/1mVdPDfDiDVToLSV9quC5KQ 提取碼: 3dsq

集成ASP.NETCore Identity

EF實現

首先我們添加一個Data文件夾

我們首先在Models文件夾下面新建ApplicationUser.cs與ApplicationUserRole.cs

ApplicationUser.cs代碼:

using Microsoft.AspNetCore.Identity;

namespace MvcCookieAuthSample.Models
{
    public class ApplicationUser:IdentityUser<int>//不加int的話是默認主鍵為guid
    {
    }
}

ApplicationUserRole.cs代碼:

using Microsoft.AspNetCore.Identity;

namespace MvcCookieAuthSample.Models
{
    public class ApplicationUserRole: IdentityRole<int>//不加int的話是默認主鍵為guid
    {
    }
}

然后在Data文件夾下新建一個ApplicationDbContext.cs類,使它繼承IdentityDbContext

using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
using MvcCookieAuthSample.Models;

namespace MvcCookieAuthSample.Data
{
    public class ApplicationDbContext:IdentityDbContext<ApplicationUser, ApplicationUserRole,int>
    {
        public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options):base(options)
        {

        }
    }
}

然后我們需要在Startup.cs添加EF的注冊進來

//使用配置ApplicationDbContext使用sqlserver數據庫,並配置數據庫連接字符串
services.AddDbContext<ApplicationDbContext>(options=> {
    options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"));
});

然后我們需要在appsettings.json中配置數據庫連接字符串

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

EF實現結束

Identity實現

我們需要在Startup.cs添加Identity的注冊進來

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

由於默認的Identity在密碼上限制比較嚴格,我們把它改的寬松簡單一點(不設置也行)

//修改Identity密碼強度設置配置
services.Configure<IdentityOptions>(options =>
{
    options.Password.RequireLowercase = false; //需要小寫
    options.Password.RequireNonAlphanumeric = false; //需要字母
    options.Password.RequireUppercase = false; //需要大寫
});

 

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

IdentityServer4.AspNetIdentity
services.AddIdentityServer()
    .AddDeveloperSigningCredential()//添加開發人員簽名憑據
    .AddInMemoryApiResources(Config.GetApiResources())//添加內存apiresource
    .AddInMemoryClients(Config.GetClients())//添加內存client
    .AddInMemoryIdentityResources(Config.GetIdentityResources())//添加系統中的資源
    //.AddTestUsers(Config.GetTestUsers())//添加測試用戶(這里不需要測試用戶了)
    .AddAspNetIdentity<ApplicationUser>();

然后我們修改AccountController,修改代碼,替換掉TestUsers的功能

private readonly UserManager<ApplicationUser> _userManager;//創建用戶的
private readonly SignInManager<ApplicationUser> _signInManager;//用來登錄的
private readonly IIdentityServerInteractionService _interaction;
//依賴注入
public AccountController(UserManager<ApplicationUser> userManager
    , SignInManager<ApplicationUser> signInManager
    , IIdentityServerInteractionService interaction)
{
    _userManager = userManager;
    _signInManager = signInManager;
    _interaction = interaction;
}

完整的AccountController

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using IdentityServer4.Services;
using IdentityServer4.Test;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using MvcCookieAuthSample.Models;
using MvcCookieAuthSample.ViewModels;

namespace MvcCookieAuthSample.Controllers
{
    public class AccountController : Controller
    {
        //private TestUserStore _users;

        //public AccountController(TestUserStore users)
        //{
        //    _users = users;
        //}

        private readonly UserManager<ApplicationUser> _userManager;//創建用戶的
        private readonly SignInManager<ApplicationUser> _signInManager;//用來登錄的
        private readonly IIdentityServerInteractionService _interaction;
        //依賴注入
        public AccountController(UserManager<ApplicationUser> userManager
            , SignInManager<ApplicationUser> signInManager
            , IIdentityServerInteractionService interaction)
        {
            _userManager = userManager;
            _signInManager = signInManager;
            _interaction = interaction;
        }

        public IActionResult Register(string returnUrl = null)
        {
            ViewData["returnUrl"] = returnUrl;
            return View();
        }

        [HttpPost]
        public async Task<IActionResult> Register(RegisterViewModel registerViewModel, string returnUrl = null)
        {
            var identityUser = new ApplicationUser
            {
                Email = registerViewModel.Email,
                UserName = registerViewModel.Email,
                NormalizedUserName = registerViewModel.Email
            };
            var identityResult = await _userManager.CreateAsync(identityUser, registerViewModel.Password);
            if (identityResult.Succeeded)
            {
                return RedirectToAction("Index", "Home");
            }
            return View();
        }

        public IActionResult Login(string returnUrl = null)
        {
            ViewData["returnUrl"] = returnUrl;
            return View();
        }

        [HttpPost]
        public async Task<IActionResult> Login(LoginViewModel loginViewModel, string returnUrl = null)
        {
            if (ModelState.IsValid)
            {
                ViewData["returnUrl"] = returnUrl;
                var user =await _userManager.FindByEmailAsync(loginViewModel.Email);
                if (user==null)
                {
                    ModelState.AddModelError(nameof(loginViewModel.Email),"UserName not exist");
                }
                else
                {
                    if (await _userManager.CheckPasswordAsync(user,loginViewModel.Password))
                    {
                        AuthenticationProperties prop = null;
                        if (loginViewModel.RememberMe)
                        {
                            prop = new AuthenticationProperties()
                            {
                                IsPersistent = true,
                                ExpiresUtc = DateTimeOffset.UtcNow.Add(TimeSpan.FromMinutes(30))
                            };
                        }

                        //await Microsoft.AspNetCore.Http.AuthenticationManagerExtensions.SignInAsync(HttpContext,
                        //    user.SubjectId, user.Username,prop);
                        //return RedirectToLocal(returnUrl);

                        await _signInManager.SignInAsync(user, prop);
                        if (_interaction.IsValidReturnUrl(returnUrl))
                        {
                            return Redirect(returnUrl);
                        }

                        return Redirect("~/");
                    }
                    ModelState.AddModelError(nameof(loginViewModel.Password),"Wrong Password");
                }

            }
            return View(loginViewModel);
        }

        public async Task<IActionResult> LogOut()
        {
            await _signInManager.SignOutAsync();
            //await HttpContext.SignOutAsync();
            return RedirectToAction("Index", "Home");
        }

        //內部跳轉
        private IActionResult RedirectToLocal(string returnUrl)
        {
            if (Url.IsLocalUrl(returnUrl))
            {
                return Redirect(returnUrl);
            }

            return RedirectToAction("Index", "Home");
        }

        //添加驗證錯誤
        private void AddError(IdentityResult result)
        {
            //遍歷所有的驗證錯誤
            foreach (var error in result.Errors)
            {
                //返回error到model
                ModelState.AddModelError(string.Empty, error.Description);
            }
        }
    }
}
View Code

接下來我們重新生成一下,我們需要執行shell命令生成一下數據庫

dotnet ef migrations add VSInit

這時候Migrations文件夾下已經有新增的數據庫更新配置文件了

DbContextSeed初始化

由於我們現在每次EF實體模型變化的時候每次都是手動更改,我們想通過代碼的方式讓他自動更新,或者程序啟動的時候添加一些數據進去

首先,在Data文件夾下添加一個ApplicationDbContextSeed.cs初始化類

public class ApplicationDbContextSeed
{
    private UserManager<ApplicationUser> _userManager;

    public async Task SeedAsync(ApplicationDbContext context, IServiceProvider services)
    {
        if (!context.Users.Any())
        {
            _userManager = services.GetRequiredService<UserManager<ApplicationUser>>();

            var defaultUser = new ApplicationUser
            {
                UserName = "Administrator",
                Email = "786744873@qq.com",
                NormalizedUserName = "admin"
            };

            var result = await _userManager.CreateAsync(defaultUser, "Password$123");
            if (!result.Succeeded)
            {
                throw new Exception("初始默認用戶失敗");
            }
        }
    }
}
View Code

那么如何調用呢?接下來我們寫一個WebHost的擴展方法類WebHostMigrationExtensions.cs來調用ApplicationDbContextSeed方法

public static class WebHostMigrationExtensions
{
    public static IWebHost MigrateDbContext<TContext>(this IWebHost host, Action<TContext, IServiceProvider> sedder) where TContext : DbContext
    {
        using (var scope = host.Services.CreateScope())
        {//只在本區間內有效
            var services = scope.ServiceProvider;
            var logger = services.GetRequiredService<ILogger<TContext>>();
            var context = services.GetService<TContext>();

            try
            {
                context.Database.Migrate();
                sedder(context, services);

                logger.LogInformation($"執行DBContext {typeof(TContext).Name} seed執行成功");
            }
            catch (Exception ex)
            {
                logger.LogError(ex, $"執行DBContext {typeof(TContext).Name} seed方法失敗");
            }
        }

        return host;
    }
}
View Code

那么我們程序啟動的時候要怎調用呢?

要在Program.cs中執行

public static void Main(string[] args)
{
    CreateWebHostBuilder(args).Build()
        //自動初始化數據庫開始
        .MigrateDbContext<ApplicationDbContext>((context, services) =>
            {
                new ApplicationDbContextSeed().SeedAsync(context, services).Wait();
            })
        //自動初始化數據庫結束
        .Run();
}

然后運行即可自動化創建數據庫和數據

ProfileService實現(調試)

在 Services 文件夾下添加 ProfileService.cs 

public class ProfileService : IProfileService
    {
        private readonly UserManager<ApplicationUser> _userManager;//創建用戶的

        public ProfileService(UserManager<ApplicationUser> userManager)
        {
            _userManager = userManager;
        }

        private async Task<List<Claim>> GetClaimsFromUserAsync(ApplicationUser user)
        {
            var claims=new List<Claim>()
            {
                new Claim(JwtClaimTypes.Subject,user.Id.ToString()),
                new Claim(JwtClaimTypes.PreferredUserName,user.UserName)
            };

            var roles =await _userManager.GetRolesAsync(user);
            foreach (var role in roles)
            {
                claims.Add(new Claim(JwtClaimTypes.Role,role));
            }

            if (!string.IsNullOrWhiteSpace(user.Avatar))
            {
                claims.Add(new Claim("avatar", user.Avatar));
            }

            return claims;
        }

        public async Task GetProfileDataAsync(ProfileDataRequestContext context)
        {
            var subjectId = context.Subject.Claims.FirstOrDefault(c => c.Type == "sub").Value;
            var user = await _userManager.FindByIdAsync(subjectId);

            var claims =await GetClaimsFromUserAsync(user);
            context.IssuedClaims = claims;
        }

        public async Task IsActiveAsync(IsActiveContext context)
        {
            context.IsActive = false;

            var subjectId = context.Subject.Claims.FirstOrDefault(c => c.Type == "sub").Value;
            var user = await _userManager.FindByIdAsync(subjectId);

            context.IsActive = user != null;
        }
    }
View Code

修改 Config.cs 中的GetClients方法

public static IEnumerable<Client> GetClients()
{
    return new Client[]
    {
        new Client()
        {
            ClientId = "mvc",
            AllowedGrantTypes = GrantTypes.HybridAndClientCredentials,//模式:混合模式
            ClientSecrets =//私鑰
            {
                new Secret("secret".Sha256())
            },
            AllowedScopes =//運行訪問的資源
            {
                IdentityServerConstants.StandardScopes.OpenId,
                IdentityServerConstants.StandardScopes.Profile,
                IdentityServerConstants.StandardScopes.Email,
                IdentityServerConstants.StandardScopes.OfflineAccess,
                "api1"

            },
            RedirectUris = { "http://localhost:5001/signin-oidc" },//跳轉登錄到的客戶端的地址
            PostLogoutRedirectUris = { "http://localhost:5001/signout-callback-oidc" },//跳轉登出到的客戶端的地址
            RequireConsent=true,//是否需要用戶點擊確認進行跳轉,改為點擊確認后進行跳轉
            AlwaysIncludeUserClaimsInIdToken = true,
            AllowOfflineAccess = true,//允許脫機訪問

            ClientName = "MVC Client",
            ClientUri = "http://localhost:5001",
            LogoUri = "https://img-prod-cms-rt-microsoft-com.akamaized.net/cms/api/am/imageFileData/RE1Mu3b?ver=5c31",
            AllowRememberConsent = true,
        }
    };
}

修改 Startup.cs 

services.AddIdentityServer()
    .AddDeveloperSigningCredential()//添加開發人員簽名憑據
    .AddInMemoryApiResources(Config.GetApiResources())//添加內存apiresource
    .AddInMemoryClients(Config.GetClients())//添加內存client
    .AddInMemoryIdentityResources(Config.GetIdentityResources())//添加系統中的資源
    //.AddTestUsers(Config.GetTestUsers())//添加測試用戶(這里不需要測試用戶了)
    .AddAspNetIdentity<ApplicationUser>()
    .Services.AddScoped<IProfileService,ProfileService>();

修改MvcClient項目中的 Startup.cs 

services.AddAuthentication(options =>
    {
        options.DefaultScheme = "Cookies";//使用Cookies認證
        options.DefaultChallengeScheme = "oidc";//使用oidc
    })
    .AddCookie("Cookies")//配置Cookies認證
    .AddOpenIdConnect("oidc", options =>//配置oidc
    {
        options.SignInScheme = "Cookies";
        options.Authority = "http://localhost:5000";
        options.RequireHttpsMetadata = false;
        options.ResponseType = OpenIdConnectResponseType.CodeIdToken;
        options.ClientId = "mvc";
        options.ClientSecret = "secret";
        options.SaveTokens = true;
        //options.GetClaimsFromUserInfoEndpoint = true;

        //options.ClaimActions.MapJsonKey("sub", "sub");
        //options.ClaimActions.MapJsonKey("preferred_username", "preferred_username");
        //options.ClaimActions.MapJsonKey("sub", "sub");
        //options.ClaimActions.MapJsonKey("avatar", "avatar");
        //options.ClaimActions.MapCustomJson("role", jobj => jobj["role"].ToString());

        options.Scope.Add("offline_access");
        options.Scope.Add("openid");
        options.Scope.Add("profile");
    });

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

集成EFCore配置Client和API

接下來的步驟是,以取代當前 AddInMemoryClients,AddInMemoryIdentityResources和AddInMemoryApiResources ConfigureServices在方法Startup.cs我們將使用以下代碼替換它們:

修改MvcCookieAuthSample項目中的ConfigureServices方法,copy鏈接字符串,這是一個官方的字符串,直接復制過來,放在上面。

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

添加包的引用

IdentityServer4.EntityFramework

引入IdentityServer4.EntityFramework的命名空間

初始化我們的數據庫,OperationStore的配置。這里實際上有兩套表, 一套存Client這些信息,Operation這套用來存token

加上ConfigrationStore和OperationStore以后就可以移除上面的三行代碼,那三行代碼之前都是從Config類里面獲取數據的,先在通過數據庫的方式去回去,所以這里不再需要了

services.AddIdentityServer()
    .AddDeveloperSigningCredential()//添加開發人員簽名憑據
    //.AddInMemoryApiResources(Config.GetApiResources())//添加內存apiresource
    //.AddInMemoryClients(Config.GetClients())//添加內存client
    //.AddInMemoryIdentityResources(Config.GetIdentityResources())//添加系統中的資源
    .AddConfigurationStore(options =>
        {
            options.ConfigureDbContext = builder => { builder.UseSqlServer(connectionString,sql=>sql.MigrationsAssembly(migrationsAssembly)); };
        })
    // this adds the operational data from DB (codes, tokens, consents)
    .AddOperationalStore(options =>
    {
        options.ConfigureDbContext = b =>
            b.UseSqlServer(connectionString,
                sql => sql.MigrationsAssembly(migrationsAssembly));

        // this enables automatic token cleanup. this is optional.
        options.EnableTokenCleanup = true;
    })
    //.AddTestUsers(Config.GetTestUsers())//添加測試用戶(這里不需要測試用戶了)
    .AddAspNetIdentity<ApplicationUser>()
    .Services.AddScoped<IProfileService,ProfileService>();

添加數據庫遷移

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

更新數據庫結構

Update-Database -c ConfigurationDbContext

這時數據庫會生成庫和表結構

初始化數據

Startup.cs中添加此方法以幫助初始化數據庫:

private void InitializeDatabase(IApplicationBuilder app)
{
    using (var serviceScope = app.ApplicationServices.GetService<IServiceScopeFactory>().CreateScope())
    {
        serviceScope.ServiceProvider.GetRequiredService<PersistedGrantDbContext>().Database.Migrate();

        var context = serviceScope.ServiceProvider.GetRequiredService<ConfigurationDbContext>();
        context.Database.Migrate();
        if (!context.Clients.Any())
        {
            foreach (var client in Config.GetClients())
            {
                context.Clients.Add(client.ToEntity());
            }
            context.SaveChanges();
        }

        if (!context.IdentityResources.Any())
        {
            foreach (var resource in Config.GetIdentityResources())
            {
                context.IdentityResources.Add(resource.ToEntity());
            }
            context.SaveChanges();
        }

        if (!context.ApiResources.Any())
        {
            foreach (var resource in Config.GetApiResources())
            {
                context.ApiResources.Add(resource.ToEntity());
            }
            context.SaveChanges();
        }
    }
}
View Code

然后我們可以從 Configure 方法中調用它

然后運行,我們可以看到在 Clients 表中已經有了數據

源碼:鏈接: https://pan.baidu.com/s/1BauxqrclWtlOJk9h6uxtAg 提取碼: dq4e 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM