Turn: https: //www.cnblogs.com/sunnytrudeau/p/9693512.html
Some small-scale demonstration projects, the server mainly offer Web Api function. For ease of management, you need to add in some simple server-side MVC page, log in as an administrator, to do some simple operations.
Therefore need to implement a function, at a site in Asp.Net Core, MVC pages use cookie authentication, Web Api with JwtBearer certification. Although the Identity Server 4 can achieve a variety of authentication schemes, but I think it is too heavy, I want to integrate two kinds of certification schemes directly on our website. Not found on the Internet ready DEMO, toss some time to get, so the record about.
Create a cookie authentication scheme MVC website
New Asp.Net Core MVC project. No authentication. No https convenient debugging.
Add login page view model class LoginViewModel
public class LoginViewModel { public string UserName { get; set; } = ""; [DataType(DataType.Password)] public string Password { get; set; } = ""; }
Home controller to increase logon and logoff function, when you want to create a user login identity.
[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) { //创建用户身份标识 claimsIdentity new new ClaimsIdentity = var (CookieAuthenticationDefaults.AuthenticationScheme); claimsIdentity.AddClaims (new new List <the Claim> () { new new the Claim (ClaimTypes.Sid, model.UserName), new new the Claim (ClaimTypes.Name, model.UserName), new new the Claim ( ClaimTypes.Role, "ADMIN"), }); // call log HttpContext.SignInAsync, note that this method is the first parameter, the parameter required in services.AddAuthentication StartUp.cs same, // AddAuthentication login is provided, SigninAsync parameter is set by log 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"); }
Create a login page 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 { The using (new new = the WebClient the WebClient Client ()) { // Address String $ path = "{} WebUrl / API / the Value"; client.Headers.Add (HttpRequestHeader.Authorization, $ "Bearer token {}"); String value the await client.DownloadStringTaskAsync = (path); txbMsg.Text $ = "Get value} {data ="; } } the catch (Exception EX) { txbMsg.Text $ = "Get data error ex.Message {} ="; } }
If direct access to data, able to capture 401 error.
First get a token.
And then get the data, no problem.
DEMO Code See also:
https://github.com/woodsun2018/MixAuth