.NET8入门:8.身份验证(JWT)

ASP.NET Core中提供了多种身份证认证模式,几种常见的身份认证模式有以下几种。本文将详细介绍一下JWT身份认证在ASP.NET Core中如何使用。

认证模式

介绍

Cookie

最常见的身份认证方式之一。用户登录成功后,服务器会生成一个加密的 Cookie 并发送给客户端,客户端在后续请求中携带该 Cookie 来验证用户身份。

JWT

JSON Web Token)是一种基于 JSON 的开放标准(RFC 7519),用于在网络上安全地传输声明。JWT 认证通过在客户端和服务器之间传递加密的 Token 来验证用户身份。

OAuth

一种开放标准,用于授权第三方应用程序访问用户数据。ASP.NET Core 提供了 OAuth 认证机制,允许应用程序通过 OAuth 协议与第三方身份提供者进行集成。

OpenID Connect

建立在 OAuth 2.0 协议之上的身份认证协议,用于验证用户身份。ASP.NET Core 提供了对 OpenID Connect 的支持,可以与支持 OpenID Connect 的身份提供者集成。

Windows

ASP.NET Core 支持 Windows 身份认证,允许用户使用他们的 Windows 凭据登录到应用程序。

JWT

JWT身份验证的请求过程如下图所述:

.NET8入门:8.身份验证(JWT)_第1张图片

ASP.NET Core中如需使用JWT身份证验证需要以下几个步骤:

  • 使用AddAuthentication和AddJwtBearer方法添加身份验证中间件服务。
  • 调用 UseAuthentication 和 UseAuthorization 以设置 HttpContext.User 属性,并为请求运行授权中间件。 必须在调用 Map 方法(例如 MapRazorPages 和 MapDefaultControllerRoute)之前调用 UseAuthentication 和 UseAuthorization。
  • 使用JWT时常会遇到跨域问题,使用UseCors进行处理。
  • 客户端获取Token保存到本地用于请求服务器。

服务器

项目(新建的WebApi)结构如图所示:

.NET8入门:8.身份验证(JWT)_第2张图片

实际代码示例如下:

Program.cs

using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
                .AddJwtBearer(options => 
                {
                    options.TokenValidationParameters = new()
                    {
                        //NameClaimType = "",
                        //RoleClaimType = "",

                        //是否验证Issuer
                        ValidateIssuer = true,
                        //是否验证Audience
                        ValidateAudience = true,
                        //是否验证失效时间
                        ValidateLifetime = true,
                        //是否验证SecurityKey
                        ValidateIssuerSigningKey = true,
                        //Audience
                        ValidAudience = "MDCClient",
                        //Issuer,这两项和签发jwt的设置一致
                        ValidIssuer = "MDCServer",
                        //SecurityKey
                        IssuerSigningKey = new SymmetricSecurityKey(System.Text.Encoding.UTF8.GetBytes("KKqUZx15DF2y01rPNaPOYr4doES59Mbu"))
                    };
                });

builder.Services.AddAuthorization(options =>
{
    //Claim中携带Admin即可通过认证
    //options.AddPolicy("Admin", policy =>policy.RequireClaim("Admin"));

    //Role角色为Admin通过认证
    options.AddPolicy("Admin", policy => policy.RequireRole("Admin"));
});


// Add services to the container.
builder.Services.AddControllersWithViews();

var app = builder.Build();

// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Home/Error");
    // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
    app.UseHsts();
}

//app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

//启用跨域
app.UseCors(builder =>
{
    builder.AllowAnyOrigin()
           .AllowAnyMethod()
           .AllowAnyHeader();
});

//启用授权
app.UseAuthentication();
app.UseAuthorization();

app.MapControllerRoute(
    name: "default",
    pattern: "{controller=Home}/{action=Index}/{id?}");

app.Run();
  • 添加授权控制器,实现获取Token以及授权测试接口

        AuthController.cs

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;

namespace JWT.Controllers
{
    public class AuthController : Controller
    {
        [HttpPost]
        public string GetToken(string userName, string password)
        {
            // 验证用户名和密码逻辑
            if (userName == "admin" && password == "password")
            {
                return  GenerateToken(userName);
            }

            return "Login failed";
        }

        [Authorize]
        [HttpGet]
        public string TestAuth() 
        {
            return "Access JWT Auth Success!";
        }

        [Authorize(Policy = "Admin")]
        [HttpGet]
        public string TestAuthAdmin()
        {
            return "Access JWT Admin Auth Success!";
        }

        /// 
        /// 生成Token
        /// 
        /// 
        /// 
        private string GenerateToken(string userName)
        {
            List claims = new()
            {
                //new Claim(ClaimTypes.Name,userName),
                //放开这段代码    TestAuthAdmin请求会正常返回数据
                new Claim(ClaimTypes.Role,"Admin")
            };

            var key = new SymmetricSecurityKey(System.Text.Encoding.UTF8.GetBytes("KKqUZx15DF2y01rPNaPOYr4doES59Mbu"));
            var expires = DateTime.Now.AddDays(30);
            var token = new JwtSecurityToken(
                issuer: "MDCServer",
                audience: "MDCClient",
                claims: claims,
                notBefore: DateTime.Now,
                expires: expires,
                signingCredentials: new SigningCredentials(key, SecurityAlgorithms.HmacSha256));

            return new JwtSecurityTokenHandler().WriteToken(token);
        }
    }
}

客户端

Program.cs

// See https://aka.ms/new-console-template for more information
using System.Net.Http.Headers;

Console.OutputEncoding = System.Text.Encoding.Unicode;
Console.WriteLine("Hello, World!");

using (var client =new HttpClient())
{
    var token = "";

    var parameters = new Dictionary
        {
            { "userName", "admin" },
            { "password", "password" }
        };

    var content = new FormUrlEncodedContent(parameters);
    Console.WriteLine("请求授权接口获取Token......");
    HttpResponseMessage response = await client.PostAsync("http://localhost:5000/Auth/GetToken", content);
    if (response.IsSuccessStatusCode)
    {
        token =await response.Content.ReadAsStringAsync();
        Console.WriteLine("获取Token成功:");
        Console.WriteLine(token);
    }
    else
    {
        Console.WriteLine("获取Token失败!");
        return;
    }

    Console.WriteLine("未设置Token时请求Authorize接口...");


    response = await client.GetAsync("http://localhost:5000/Auth/TestAuth");
    Console.WriteLine($"请求Authorize接口状态码:{response.StatusCode}");

    Console.WriteLine("设置Token...");
    client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);

    response = await client.GetAsync("http://localhost:5000/Auth/TestAuth");
    Console.WriteLine($"请求Auth 接口状态码:{response.StatusCode}");
    Console.WriteLine($"请求Auth 接口结果:{await response.Content.ReadAsStringAsync()}");

    response = await client.GetAsync("http://localhost:5000/Auth/TestAuthAdmin");
    Console.WriteLine($"请求Admin Auth接口状态码:{response.StatusCode}");
    Console.WriteLine($"请求Admin Auth接口结果:{await response.Content.ReadAsStringAsync()}");
}

Console.ReadKey();

上述代码为一个简单的JWT身份验证的Demo,代码分析如下:

服务器

  • Program.cs
    • builder.Services.AddAuthentication使用JwtBearerDefaults.AuthenticationScheme(JWT默认授权)方式添加了授权中间件。
    • AddJwtBearer中

属性

参数

作用

ValidateIssuer

true

是否验证Issuer

ValidateAudience

true

是否验证Audience

ValidateLifetime

true

是否验证失效时间

ValidateIssuerSigningKey

true

是否验证SecurityKey

ValidAudience

MDCClient

Audience

ValidIssuer

MDCServer

Issuer,这两项和签发jwt的设置一致

IssuerSigningKey

new SymmetricSecurityKey

(System.Text.Encoding.UTF8.GetBytes

("KKqUZx15DF2y01rPNaPOYr4doES59Mbu"))

SecurityKey

  • builder.Services.AddAuthorization中被注释掉的为身份授权策略——RequireClaim。指Claim中包含Admin则认为授权。

RequireRole表示Cookie的Claim的ClaimTypes.Role必须包含"Admin"才视为授权。

builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
                .AddJwtBearer(options => 
                {
                    options.TokenValidationParameters = new()
                    {
                        //NameClaimType = "",
                        //RoleClaimType = "",

                        //是否验证Issuer
                        ValidateIssuer = true,
                        //是否验证Audience
                        ValidateAudience = true,
                        //是否验证失效时间
                        ValidateLifetime = true,
                        //是否验证SecurityKey
                        ValidateIssuerSigningKey = true,
                        //Audience
                        ValidAudience = "MDCClient",
                        //Issuer,这两项和签发jwt的设置一致
                        ValidIssuer = "MDCServer",
                        //SecurityKey
                        IssuerSigningKey = new SymmetricSecurityKey(System.Text.Encoding.UTF8.GetBytes("KKqUZx15DF2y01rPNaPOYr4doES59Mbu"))
                    };
                });

builder.Services.AddAuthorization(options =>
{
    //Claim中携带Admin即可通过认证
    //options.AddPolicy("Admin", policy =>policy.RequireClaim("Admin"));

    //Role角色为Admin通过认证
    options.AddPolicy("Admin", policy => policy.RequireRole("Admin"));
});
  • 使用app.UseCors()来启用跨域中间件。
//启用跨域
app.UseCors(builder =>
{
    builder.AllowAnyOrigin()
           .AllowAnyMethod()
           .AllowAnyHeader();
});
  • 使用app.UseAuthentication();和app.UseAuthorization();来启用授权中间件。
app.UseAuthentication();
app.UseAuthorization();
  • AuthController.cs
    • GetToken的方法上用来获取服务器生成的Token并返回给客户端,这边的参数需要与AddJwtBearer里的option参数一致。

        [HttpPost]
        public string GetToken(string userName, string password)
        {
            // 验证用户名和密码逻辑
            if (userName == "admin" && password == "password")
            {
                return  GenerateToken(userName);
            }

            return "Login failed";
        }
        
        /// 
        /// 生成Token
        /// 
        /// 
        /// 
        private string GenerateToken(string userName)
        {
            List claims = new()
            {
                //new Claim(ClaimTypes.Name,userName),
                //放开这段代码    TestAuthAdmin请求会正常返回数据
                new Claim(ClaimTypes.Role,"Admin")
            };

            var key = new SymmetricSecurityKey(System.Text.Encoding.UTF8.GetBytes("KKqUZx15DF2y01rPNaPOYr4doES59Mbu"));
            var expires = DateTime.Now.AddDays(30);
            var token = new JwtSecurityToken(
                issuer: "MDCServer",
                audience: "MDCClient",
                claims: claims,
                notBefore: DateTime.Now,
                expires: expires,
                signingCredentials: new SigningCredentials(key, SecurityAlgorithms.HmacSha256));

            return new JwtSecurityTokenHandler().WriteToken(token);
        }
  • TestAuth与TestAuthAdmin方法分别验证普通策略以及在AddAuthorization中自定义的Admin策略( [Authorize]标识需要授权访问)。
        [Authorize]
        [HttpGet]
        public string TestAuth() 
        {
            return "Access JWT Auth Success!";
        }

        [Authorize(Policy = "Admin")]
        [HttpGet]
        public string TestAuthAdmin()
        {
            return "Access JWT Admin Auth Success!";
        }

客户端

客户端分别演示了未携带Token,以及在请求头携带Token访问的情况。

// See https://aka.ms/new-console-template for more information
using System.Net.Http.Headers;

Console.OutputEncoding = System.Text.Encoding.Unicode;
Console.WriteLine("Hello, World!");

using (var client =new HttpClient())
{
    var token = "";

    var parameters = new Dictionary
        {
            { "userName", "admin" },
            { "password", "password" }
        };

    var content = new FormUrlEncodedContent(parameters);
    Console.WriteLine("请求授权接口获取Token......");
    HttpResponseMessage response = await client.PostAsync("http://localhost:5000/Auth/GetToken", content);
    if (response.IsSuccessStatusCode)
    {
        token =await response.Content.ReadAsStringAsync();
        Console.WriteLine("获取Token成功:");
        Console.WriteLine(token);
    }
    else
    {
        Console.WriteLine("获取Token失败!");
        return;
    }

    Console.WriteLine("未设置Token时请求Authorize接口...");


    response = await client.GetAsync("http://localhost:5000/Auth/TestAuth");
    Console.WriteLine($"请求Authorize接口状态码:{response.StatusCode}");

    Console.WriteLine("设置Token...");
    client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);

    response = await client.GetAsync("http://localhost:5000/Auth/TestAuth");
    Console.WriteLine($"请求Auth 接口状态码:{response.StatusCode}");
    Console.WriteLine($"请求Auth 接口结果:{await response.Content.ReadAsStringAsync()}");

    response = await client.GetAsync("http://localhost:5000/Auth/TestAuthAdmin");
    Console.WriteLine($"请求Admin Auth接口状态码:{response.StatusCode}");
    Console.WriteLine($"请求Admin Auth接口结果:{await response.Content.ReadAsStringAsync()}");
}

Console.ReadKey();

你可能感兴趣的:(.NET8从入门到放弃,.net)