【实战 Ids4】║ 又一个项目迁移完成(MVC)
迎周一,臘月十九,小年倒計時
新年還有兩周時間就要到了,學習可不能停,這幾天一直在加班調休,周末也如此,不過也是趁著半夜凌晨的時間,繼續遷移我的項目到IdentityServer4統一認證授權中心Blog.IdentityServer上,也是基本統一了,目前進度如下:
01、前后端分離全家桶已經完成升級:Blog.Core為api,Blog.Admin為后臺管理,Blog.Vue為前臺信息展示已經全部搞定,具體的代碼查看指定Github的分支即可,分支名基本都是Is4,Ids4等字樣;
02、Nuxt.tBug項目目前正在升級中,其實和Vue的前后端分離是一樣的,都是使用的同一個組件框架oidc-client,這里就不多說了,如果真的差別大,我就單寫一篇文章,否則直接看我的代碼就行;
03、ChristDDD MVC項目已經完成遷移,就是今天本文講解的。
04、WPF項目在進度種,到時候簡單寫個小Demo就行,我會在我的視頻中,給大家講解,預計春節后出來。
上邊共涉及到了我開源的六個項目,三個后端,三個前端,想想這一年也是夠可以了,但是在遷移的IdentityServer4中,只用到了常用的兩種模式,Implicit和Code模式,其實一般我們web開發,掌握四種就行,除了這兩個,還有Hybrid和Client,其他的如果沒有精力,可以放一放,那下邊我們就快速的說一下如何將MVC項目遷移到Ids4上。這里就簡單的說一下操作過程,不會講解原理,原理我會在視頻教程中,詳細說到。
Idp項目如何配置
具體的原型圖,運行原理,等我視頻吧,直接看代碼,這里要說一下,如果你是第一次開發學習,我建議盡量使用內存模式,這樣會很好的調試,如果直接生成到數據庫的話,可能有時候修改了一個配置,還需要重新生成數據庫,這個有些浪費時間。
在我們的Config.cs中,新建一個Client,用來應對我們的MVC客戶端:
// interactive ASP.NET Core MVC client new Client {ClientId = "chrisdddmvc",ClientName="Chris DDD MVC項目",ClientSecrets = { new Secret("secret".Sha256()) },AllowedGrantTypes = GrantTypes.Code,RequireConsent = false,RequirePkce = true,AlwaysIncludeUserClaimsInIdToken=true,//將用戶所有的claims包含在IdToken內// 登錄回調RedirectUris = { "http://ddd.neters.club/signin-oidc" },// 登出回調地址PostLogoutRedirectUris = { "http://ddd.neters.club/signout-callback-oidc" },//?注意這些scope,一定是上邊已經定義好的資源AllowedScopes = new List<string>{IdentityServerConstants.StandardScopes.OpenId,IdentityServerConstants.StandardScopes.Profile,IdentityServerConstants.StandardScopes.Email,"roles","rolename",} }這里就強調兩點,就是配置一下回調地址,然后就是AlwaysIncludeUserClaimsInIdToken要設置為true,以方便我們后邊要從claims聲明中獲取返回的值。
當然,最后還有一個知識點,就是scope中,如果想要自定義的話,需要先在claims中注冊添加,然后在GetIdentityResources中配置:
// scopes define the resources in your systempublic static IEnumerable<IdentityResource> GetIdentityResources(){return new List<IdentityResource>{new IdentityResources.OpenId(),new IdentityResources.Profile(),new IdentityResources.Email(),new IdentityResource("roles", "角色", new List<string> { JwtClaimTypes.Role }),new?IdentityResource("rolename",?"角色名",?new?List<string>?{?"rolename"?}),};}這里配置就是很簡單的,咱們繼續看看如何在MVC中配置。
ChristDDD如何配置
如果你之前看過或者用到了我的DDD項目,會發現其實本來是用Identity寫的,這次我們遷移到Ids4后,需要做一些變化,具體的直接下載我的Ids4分支就行了,修改的內容比較多。
首先我們把響應的認證服務給抽出來,單獨封裝,上邊的是Ids4的,下邊的是普通的Identity的:
然后注入服務:
// IdentityServer4 注入services.AddId4OidcSetup();那我們直接看看服務是如何設置的:
private static readonly string config= "https://ids.neters.club";public static void AddId4OidcSetup(this IServiceCollection services){if (services == null) throw new ArgumentNullException(nameof(services));//關閉默認映射,否則它可能修改從授權服務返回的各種claim屬性JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();//添加認證服務,并設置其有關選項services.AddAuthentication(options =>{// 客戶端應用設置使用"Cookies"進行認證options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;// identityserver4設置使用"oidc"進行認證options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;}).AddCookie(CookieAuthenticationDefaults.AuthenticationScheme)// 對使用的OpenIdConnect進行設置,此設置與Identityserver的config.cs中相應client配置一致才可能登錄授權成功.AddOpenIdConnect(OpenIdConnectDefaults.AuthenticationScheme, options =>{options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;options.Authority = config;options.RequireHttpsMetadata = false;//必須https協議options.ClientId = "chrisdddmvc";//idp項目中配置的clientoptions.ClientSecret = "secret";options.SaveTokens = true;options.ResponseType = "code";//響應類型// 下邊是所有的scope,必須要和idp項目中一致,至少是一部分options.Scope.Clear();options.Scope.Add("roles");//"roles"options.Scope.Add("rolename");//"roles"options.Scope.Add(OidcConstants.StandardScopes.OpenId);//"openid"options.Scope.Add(OidcConstants.StandardScopes.Profile);//"profile"options.Scope.Add(OidcConstants.StandardScopes.Email);//"email"});}這里有幾個注意事項:ClientId一定要填對,Scope必須是Idp項目中配置的子集,Scope一定要寫對,不然的話,會報錯,比如我們隨便把roles改成roles3:
當然全部粘貼過去就行,其他的都有注釋,看看即可。
這里配置也是很簡單的,運行到了這里,我們就可以簡單的調試了,所有的地址,都可以換成localhost來調試。
沒有錯誤的話,我們就可以正式的跳轉登錄,登錄成功后,跳轉回來MVC項目,下面我們就說說如何在MVC客戶端項目中,進行策略授權。
MVC客戶端做策略授權
上邊我們已經登錄成功,并也跳回了,那現在就要根據情況,設計授權了,畢竟有些頁面是test用戶不能訪問的,只有超級管理員才能訪問的:
首先,在聲明策略,然后在控制器配置策略
services.AddAuthorization(options =>{options.AddPolicy("CanWriteStudentData", policy => policy.Requirements.Add(new ClaimRequirement("Students", "Write")));options.AddPolicy("CanRemoveStudentData", policy => policy.Requirements.Add(new ClaimRequirement("Students", "Remove")));options.AddPolicy("CanWriteOrRemoveStudentData", policy => policy.Requirements.Add(new ClaimRequirement("Students", "WriteOrRemove")));});//?這里的策略內容可以任意擴展[HttpGet][Authorize(Policy = "CanWriteStudentData")]public IActionResult Edit(Guid? id){}接著,我們就來定義授權策略處理器
public class ClaimsRequirementHandler : AuthorizationHandler<ClaimRequirement>{protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, ClaimRequirement requirement){var roleId = context.User.Claims.FirstOrDefault(c => c.Type == "role");var rolename = context.User.Claims.FirstOrDefault(c => c.Type == "rolename");var loginUserName = context.User.Claims.FirstOrDefault(c => c.Type == "preferred_username");if (roleId != null && roleId.Value == "4" && rolename != null && rolename.Value == "SuperAdmin"){context.Succeed(requirement);}return Task.CompletedTask;}}復雜策略授權如何寫,邏輯如何調,上下文中的claims聲明如何獲取,這里就不多說了,默認已經會了我的第一個項目的Blog.Core的相關內容,這里我們只是來看看是不是能獲取到相應的Claims就行:
可以看到我們已經獲取到了這個scope,這樣我們就可以任意的擴展了。
登錄與登出設計
這個其實就很簡單了,我們在客戶端里,直接登出就行,我寫的比較low,當然你可以自己找找例子,我就簡單的寫了寫:
[Authorize]public IActionResult Login(){return Redirect("index");}public async Task<IActionResult> Logout(){await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);await HttpContext.SignOutAsync(OpenIdConnectDefaults.AuthenticationScheme);return?View("Index");}登錄取了個巧,不寫內容,直接加了個Authorize,這樣肯定就跳轉到登錄頁了。
然后設計下UI展示 _LoginPartial.cshtml ,注入服務就行:
@inject?Christ3D.Domain.Interfaces.IUser?SignInManager@if (SignInManager.IsAuthenticated()) {<form asp-area="" asp-controller="Home" asp-action="Logout" method="post" id="logoutForm" class="navbar-right"><ul class="nav navbar-nav navbar-right"><li><a asp-area="" asp-controller="Manage" asp-action="Index" title="Manage">Hello @SignInManager.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="Home" asp-action="Login">Log in</a></li></ul> }最終的展示效果是醬紫的,登出:
登錄:
到了這里,我們就已經完成了整體流程了!下邊就是部署了。
生產環境部署聯調
現在還是兩個后端項目,一個是IdentityServer4的部署,很簡單的,我目前用的是Nginx部署的,Https安全協議。
客戶端是MVC項目,但是用的IIS部署的,因為如何也用Nginx部署的話,客戶端向授權中心認證的時候,一直報錯,錯誤是回調地址不匹配,因為nginx部署,顯示的地址還是本地的:
但是我在idp項目里,明明配置的是ddd域名:
錯誤信息是這樣的:
但是在IIS中配置,是一切正常的,真的是我學術不精啊,有小伙伴知道的,歡迎給我留言私信拍磚,這里我來個賞金(20大洋),給開源事業做貢獻了。
這個時候,PC端已經一切正常了,正當高興的時候,手機訪問,又不行了,這次我很機智,有了上次的JS客戶端經驗,我直接加了一個Cookie
手機移動端適配
在DDD項目中,新建一個擴展:
public static class SameSiteHandlingExtensions{public static IServiceCollection AddSameSiteCookiePolicy(this IServiceCollection services){services.Configure<CookiePolicyOptions>(options =>{options.MinimumSameSitePolicy = (SameSiteMode)(-1);options.OnAppendCookie = cookieContext => CheckSameSite(cookieContext.Context, cookieContext.CookieOptions);options.OnDeleteCookie = cookieContext => CheckSameSite(cookieContext.Context, cookieContext.CookieOptions);});return services;}private static void CheckSameSite(HttpContext httpContext, CookieOptions options){if (options.SameSite == SameSiteMode.None){var userAgent = httpContext.Request.Headers["User-Agent"].ToString();if (DisallowsSameSiteNone(userAgent)){// For .NET Core < 3.1 set SameSite = (SameSiteMode)(-1)options.SameSite = (SameSiteMode)(-1);}}}private static bool DisallowsSameSiteNone(string userAgent){// Cover all iOS based browsers here. This includes:// - Safari on iOS 12 for iPhone, iPod Touch, iPad// - WkWebview on iOS 12 for iPhone, iPod Touch, iPad// - Chrome on iOS 12 for iPhone, iPod Touch, iPad// All of which are broken by SameSite=None, because they use the iOS networking stackif (userAgent.Contains("CPU iPhone OS 12") || userAgent.Contains("iPad; CPU OS 12")){return true;}// Cover Mac OS X based browsers that use the Mac OS networking stack. This includes:// - Safari on Mac OS X.// This does not include:// - Chrome on Mac OS X// Because they do not use the Mac OS networking stack.if (userAgent.Contains("Macintosh; Intel Mac OS X 10_14") && userAgent.Contains("Version/") && userAgent.Contains("Safari")){return true;}// Cover Chrome 50-69, because some versions are broken by SameSite=None, // and none in this range require it.// Note: this covers some pre-Chromium Edge versions, // but pre-Chromium Edge does not require SameSite=None.if (userAgent.Contains("Chrome/5") || userAgent.Contains("Chrome/6")){return true;}return false;}}然后服務配置:
常見的錯誤
剛剛上邊我們已經遇到了兩個錯誤,其實總的來說,都是配置的問題,我會在博客園單寫一篇文章,來總結IdentityServer4的所有錯誤,目前還沒有,過一段時間查看就行,現在開發的還比較少。注意這兩個錯誤,然后會調試就行,調試主要在F12,去查看network,看看請求的數據是否異常即可。
到了這里,基本就結束了,還是建議大家多看看官網和官方Demo,真的很有用。
總結
以上是生活随笔為你收集整理的【实战 Ids4】║ 又一个项目迁移完成(MVC)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 从零开始开发 VS Code 插件之 T
- 下一篇: ASP.net Core MVC项目给j