轉:https://www.cnblogs.com/sunnytrudeau/p/9693512.html
一些小型演示項目,服務端主要是提供Web Api功能。爲了便於管理,需要在服務端加一些簡單的MVC網頁,用管理員身份登錄,做一些簡單的操作。
因此需要實現一個功能,在一個Asp.Net Core網站裏,MVC網頁用cookie認證,Web Api用JwtBearer認證。雖然Identity Server 4可以實現多種認證方案,但是我覺得它太重了,想直接在網站內集成2種認證方案。在網上沒有找到現成的DEMO,自己折騰了一段時間搞定了,所以記錄一下。
創建cookie認證方案的MVC網站
新建Asp.Net Core MVC項目。無身份驗證。無https方便調試。
添加登錄網頁視圖模型類LoginViewModel
public class LoginViewModel { public string UserName { get; set; } = ""; [DataType(DataType.Password)] public string Password { get; set; } = ""; }
給Home控制器增加登錄和註銷函數,登錄的時候要創建用戶身份標識。
[HttpGet] public IActionResult Login(string returnUrl = "") { ViewData["ReturnUrl"] = returnUrl; return View(); } [HttpPost, ActionName("Login")] public async Task<IActionResult> LoginPost(LoginViewModel model, string returnUrl = "") { ViewData["ReturnUrl"] = returnUrl; if (ModelState.IsValid) { bool succee = (model.UserName == "admin") && (model.Password == "123"); if (succee) { //創建用戶身份標識 var claimsIdentity = new ClaimsIdentity(CookieAuthenticationDefaults.AuthenticationScheme); claimsIdentity.AddClaims(new List<Claim>() { new Claim(ClaimTypes.Sid, model.UserName), new Claim(ClaimTypes.Name, model.UserName), new Claim(ClaimTypes.Role, "admin"), }); //調用HttpContext.SignInAsync進行登錄,注意此方法的第一個參數,必需與StartUp.cs中services.AddAuthentication的參數相同,
//AddAuthentication是設置登錄,SigninAsync是按設置參數進行登錄 await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, new ClaimsPrincipal(claimsIdentity)); return Redirect(returnUrl); } else { ModelState.AddModelError(string.Empty, "帳號或者密碼錯誤。"); return View(model); } } return View(model); } public async Task<IActionResult> Logout() { await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme); return Redirect("/Home/Index"); }
新建一個登錄網頁Login.cshtml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
@model MixAuth.Models.LoginViewModel @{ ViewData["Title"] = "登錄"; } < div class="row"> < div class="col-xs-10 col-sm-8 col-md-6"> < form asp-action="Login" asp-route-returnurl="@ViewData["ReturnUrl"]" method="post"> < div asp-validation-summary="All" class="text-danger"></ div > < div class="form-group"> < label asp-for="UserName"></ label > < input asp-for="UserName" class="form-control" placeholder="請輸入用戶名" /> < span asp-validation-for="UserName" class="text-danger"></ span > </ div > < div class="form-group"> < label asp-for="Password"></ label > < input asp-for="Password" class="form-control" placeholder="請輸入密碼" /> < span asp-validation-for="Password" class="text-danger"></ span > </ div > < button type="submit" class="btn btn-primary">登錄</ button > </ form > </ div > </ div > @section Scripts { @await Html.PartialAsync("_ValidationScriptsPartial") } |
然後在Startup.cs增加cookie認證方案,並開啓認證中間件。
public void ConfigureServices(IServiceCollection services) { services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme) .AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, options => { //認證失敗,會自動跳轉到這個地址 options.LoginPath = "/Home/Login"; }); services.Configure<CookiePolicyOptions>(options => { // This lambda determines whether user consent for non-essential cookies is needed for a given request. //options.CheckConsentNeeded = context => true; options.MinimumSameSitePolicy = SameSiteMode.None; }); services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IHostingEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } else { app.UseExceptionHandler("/Home/Error"); } app.UseStaticFiles(); //app.UseCookiePolicy(); //開啓認證中間件 app.UseAuthentication(); app.UseMvc(routes => { routes.MapRoute( name: "default", template: "{controller=Home}/{action=Index}/{id?}"); }); }
給Home控制器的About函數增加認證要求。
[Authorize] public IActionResult About()
把網站跑起來,點擊關於,就會跳轉到登錄頁面,登錄通過後,會調回關於頁面。
給網頁再增加顯示用戶登錄狀態的功能。修改\Views\Shared\_Layout.cshtml,增加一個分部視圖
1
2
3
4
5
6
7
8
|
< 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 > @await Html.PartialAsync("_LoginPartial") </ div > |
_LoginPartial.cshtml分部視圖內容
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
@if (User.Identity.IsAuthenticated) { < form asp-controller="Home" asp-action="Logout" method="post" class="navbar-right"> < ul class="nav navbar-nav navbar-right"> < li > < a href="#">@User.Identity.Name</ a > </ li > < li > < button type="submit" class="btn btn-link navbar-btn navbar-link"> 退出登錄 </ button > </ li > </ ul > </ form > } else { < ul class="nav navbar-nav navbar-right"> < li > < a asp-controller="Home" asp-action="Login" asp-route-returnUrl="/Home"> 登錄 </ a > </ li > </ ul > } |
現在可以點擊頁面導航欄的按鈕的登錄和註銷了。
至此,網頁用cookie認證方案搞定。下面要在這個基礎上,增加Web Api和JwtBearer認證。
創建JwtBearer認證方案的Web Api控制器
添加一個Web Api控制器,就用默認的value好了。
增加一個JWTTokenOptions類,定義認證的一些屬性。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
|
public class JWTTokenOptions { //誰頒發的 public string Issuer { get ; set ; } = "server" ; //頒發給誰 public string Audience { get ; set ; } = "client" ; //令牌密碼 public string SecurityKey { get ; private set ; } = "a secret that needs to be at least 16 characters long" ; //修改密碼,重新創建數字簽名 public void SetSecurityKey( string value) { SecurityKey = value; CreateKey(); } //對稱祕鑰 public SymmetricSecurityKey Key { get ; set ; } //數字簽名 public SigningCredentials Credentials { get ; set ; } public JWTTokenOptions() { CreateKey(); } private void CreateKey() { Key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(SecurityKey)); Credentials = new SigningCredentials(Key, SecurityAlgorithms.HmacSha256); } } |
在startup.cs增加JwtBearer認證方案。
public void ConfigureServices(IServiceCollection services)
{
JWTTokenOptions jwtTokenOptions = new JWTTokenOptions();
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, options =>
{
//認證失敗,會自動跳轉到這個地址(認證使用[Authorize]屬性)
options.LoginPath = "/Home/Login";
})
.AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, jwtBearerOptions =>
{
jwtBearerOptions.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = jwtTokenOptions.Key,
ValidateIssuer = true,
ValidIssuer = jwtTokenOptions.Issuer,
ValidateAudience = true,
ValidAudience = jwtTokenOptions.Audience,
ValidateLifetime = true,
ClockSkew = TimeSpan.FromMinutes(5)
};
});
services.Configure<CookiePolicyOptions>(options =>
{
// This lambda determines whether user consent for non-essential cookies is needed for a given request.
//options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
}
給value控制器增加認證方案,注意指定方案名稱爲JwtBearerDefaults.AuthenticationScheme。MVC控制器無需指定方案名稱,因爲默認就是CookieAuthenticationDefaults.AuthenticationScheme。
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] [Route("api/[controller]")] [ApiController] public class ValueController : ControllerBase
此時通過瀏覽器訪問Web Api控制器,http://localhost:5000/api/Value,會得到401錯誤,這是對的,我們也不打算通過瀏覽器的方式訪問Web Api,而是通過PC或者手機客戶端。爲了讓認證客戶端,需要增加一個獲取Token的函數,暫時放在Home控制器,它的屬性設置爲[AllowAnonymous],允許未認證者訪問。
[AllowAnonymous] [HttpGet] public string GetToken(string userName, string password) { bool success = ((userName == "user") && (password == "111")); if (!success) return ""; JWTTokenOptions jwtTokenOptions = new JWTTokenOptions(); //創建用戶身份標識 var claims = new Claim[] { new Claim(ClaimTypes.Sid, userName), new Claim(ClaimTypes.Name, userName), new Claim(ClaimTypes.Role, "user"), }; //創建令牌 var token = new JwtSecurityToken( issuer: jwtTokenOptions.Issuer, audience: jwtTokenOptions.Audience, claims: claims, notBefore: DateTime.Now, expires: DateTime.Now.AddDays(1), signingCredentials: jwtTokenOptions.Credentials ); string jwtToken = new JwtSecurityTokenHandler().WriteToken(token); return jwtToken; }
編寫客戶端使用JwtBearer認證
編寫一個WPF客戶端軟件去獲取Token,訪問Web Api。
private async Task GetTokenAsync() { try { using (WebClient client = new WebClient()) { //地址 string path = $"{webUrl}/Home/GetToken?userName=user&password=111"; token = await client.DownloadStringTaskAsync(path); txbMsg.Text = $"獲取到令牌={token}"; } } catch (Exception ex) { txbMsg.Text = $"獲取令牌出錯={ex.Message}"; } } private async Task GetValueAsync() { try { using (WebClient client = new WebClient()) { //地址 string path = $"{webUrl}/api/Value"; client.Headers.Add(HttpRequestHeader.Authorization, $"Bearer {token}"); string value = await client.DownloadStringTaskAsync(path); txbMsg.Text = $"獲取到數據={value}"; } } catch (Exception ex) { txbMsg.Text = $"獲取數據出錯={ex.Message}"; } }
如果直接獲取數據,能夠捕捉到401錯誤。
先獲取令牌。
再獲取數據,就沒問題了。
DEMO代碼參見:
https://github.com/woodsun2018/MixAuth