自己有时捣鼓一些小型演示项目,服务端主要是提供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 == ""); 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

@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,增加一个分部视图

<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分部视图内容

@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类,定义认证的一些属性。

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 =>
{
//认证失败,会自动跳转到这个地址
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()
};
}); 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 == ""));
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(),
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混合使用cookie和JwtBearer认证方案的更多相关文章

  1. 理解ASP.NET Core - 基于Cookie的身份认证(Authentication)

    注:本文隶属于<理解ASP.NET Core>系列文章,请查看置顶博客或点击此处查看全文目录 概述 通常,身份认证(Authentication)和授权(Authorization)都会放 ...

  2. 关于ASP.Net Core Web及API身份认证的解决方案

    6月15日,在端午节前的最后一个工作日,想起有段日子没有写过文章了,倒有些荒疏了.今借夏日蒸蒸之气,偷得浮生半日悠闲.闲话就说到这里吧,提前祝大家端午愉快(屈原听了该不高兴了:))!.NetCore自 ...

  3. 【ASP.NET Core】运行原理[3]:认证

    本节将分析Authentication 源代码参考.NET Core 2.0.0 HttpAbstractions Security 目录 认证 AddAuthentication IAuthenti ...

  4. ASP.NET Core如何使用WSFederation身份认证集成ADFS

    如果要在ASP.NET Core项目中使用WSFederation身份认证,首先需要在项目中引入NuGet包: Microsoft.AspNetCore.Authentication.WsFedera ...

  5. ASP.NET Core编程实现基本身份认证

    概览 在HTTP中,基本认证(Basic access authentication,简称BA认证)是一种用来允许网页浏览器或其他客户端程序在请求资源时提供用户名和口令形式的身份凭证的一种登录验证方式 ...

  6. ASP.NET Core系列:JWT身份认证

    1. JWT概述 JSON Web Token(JWT)是目前流行的跨域身份验证解决方案. JWT的官网地址:https://jwt.io JWT的实现方式是将用户信息存储在客户端,服务端不进行保存. ...

  7. Asp.Net Core存储Cookie不成功

    Asp.Net Core存储Cookie不成功 Asp.Net Core2.1生成的项目模板默认实现了<>,所以设置存储Cookie需要做一些处理. 1.第一种是在Startup的Conf ...

  8. asp.net core 3.1多种身份验证方案,cookie和jwt混合认证授权

    开发了一个公司内部系统,使用asp.net core 3.1.在开发用户认证授权使用的是简单的cookie认证方式,然后开发好了要写几个接口给其它系统调用数据.并且只是几个简单的接口不准备再重新部署一 ...

  9. ASP.NET Core 3.1使用JWT认证Token授权 以及刷新Token

    传统Session所暴露的问题 Session: 用户每次在计算机身份认证之后,在服务器内存中会存放一个session,在客户端会保存一个cookie,以便在下次用户请求时进行身份核验.但是这样就暴露 ...

随机推荐

  1. 基于sql service会话共享,实现SSO

    1:session的存储基于sql service数据库来存储 2:修改sql service中会话管理的系统存储过程 3:实现几个站点的会话共享 4:应用共享会话,实现单点登录

  2. 微信 JS-SDK 签名验证

    doc: http://mp.weixin.qq.com/wiki/7/aaa137b55fb2e0456bf8dd9148dd613f.html demo:http://demo.open.weix ...

  3. SQL 日期相减(间隔)datediff函数

    select datediff(year, 开始日期,结束日期); --两日期间隔年 select datediff(quarter, 开始日期,结束日期); --两日期间隔季 select date ...

  4. macOS Java安装与配置

    运行环境: macOS Hight Sierra(Version 10.13.6) Terminal(oh my zsh) 下载安装JRE Download URL 下载安装JDK Download ...

  5. j2ee高级开发技术课程第十四周

    RPC(Remote Procedure Call Protocol) RPC使用C/S方式,采用http协议,发送请求到服务器,等待服务器返回结果.这个请求包括一个参数集和一个文本集,通常形成“cl ...

  6. 【转载】表单中 Readonly 和 Disabled 的区别

    今天写代码,遇到表单提交的问题,某个字段在不同的情况下,要传递不同的值进行赋值,试过一些方法都有些问题,后来请教前端同学,使用 disabled 这个属性终于搞定了问题,查到一篇讲解 readonly ...

  7. 面试:用快排实现数组中的第K大的数

    #include <iostream> #include <cassert> using namespace std; int selectKth(int a[],int st ...

  8. AndroidStudio+ideasmali动态调试smali汇编

    0x00    前言 之前对于app反编译的smali汇编语言都是静态分析为主,加上一点ida6.6的动态调试,但是ida的调试smali真的像鸡肋一样,各种不爽,遇到混淆过的java代码就欲哭无泪了 ...

  9. 【详解】JNI (Java Native Interface) (四)

    案例四:回调实例方法与静态方法 描述:此案例将通过Java调用的C语言代码回调Java方法. 要想调用实例对象的方法,需要进行以下步骤: 1. 通过对象实例,获取到对象类的引用  => GetO ...

  10. java web 手动部署项目步骤

    java Web 手动部署项目步骤 1 在tomcat下面的webapps下面建立需要部署的文件夹(eg:demo);2 在demo下建立 WEB-INF WETA-INF src 文件夹;3 在sr ...