Asp.Net Core混合使用cookie和JwtBearer认证方案
自己有时捣鼓一些小型演示项目,服务端主要是提供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"),
});
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 |
|
然后在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 |
|
_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 |
|
现在可以点击页面导航栏的按钮的登录和注销了。

至此,网页用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 |
|
在startup.cs增加JwtBearer认证方案。

public void ConfigureServices(IServiceCollection services)
{
JWTTokenOptions jwtTokenOptions = new JWTTokenOptions();
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, options =>
{
//认证失败,会自动跳转到这个地址
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
本文详细介绍如何在ASP.NET Core应用中同时实现MVC网页的Cookie认证和WebAPI的JwtBearer认证,涵盖从项目搭建、代码实现到客户端交互的全过程。


被折叠的 条评论
为什么被折叠?



