关于Jwt的介绍网上很多,此处不在赘述,我们主要看看jwt的结构。

JWT主要由三部分组成,如下:

HEADER.PAYLOAD.SIGNATURE

​HEADER​​包含token的元数据,主要是加密算法,和签名的类型,如下面的信息,说明了

加密的对象类型是JWT,加密算法是HMAC SHA-256

{"alg":"HS256","typ":"JWT"}

然后需要通过BASE64编码后存入token中

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9

​Payload​​主要包含一些声明信息(claim),这些声明是key-value对的数据结构。

通常如用户名,角色等信息,过期日期等,因为是未加密的,所以不建议存放敏感信息。

{"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name":"admin","exp":1578645536,"iss":"webapi.cn","aud":"WebApi"}

最后生成的token如下

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1lIjoiYWRtaW4iLCJleHAiOjE1Nzg2NDU1MzYsImlzcyI6IndlYmFwaS5jbiIsImF1ZCI6IldlYkFwaSJ9.2_akEH40LR2QWekgjm8Tt3lesSbKtDethmJMo_3jpF4

开发环境

框架:asp.net 3.1

IDE:VS2019

ASP.NET 3.1 Webapi中使用JWT认证【Microsoft.AspNetCore.Authentication.JwtBearer v3.1.6】

执行:

Install-Package Microsoft.AspNetCore.Authentication.JwtBearer -Version 3.1.6

1、引用Jwt 的 Microsoft.AspNetCore.Authentication.JwtBearer库

NetCore 3.1  JWT认证简单介绍_sql


 2、在Dto层新建一个Jwt签发类

/// <summary>
/// POCO类,用来存储签发或者验证jwt时用到的信息
/// </summary>
public class TokenManagement
{
public static string Secret = "123456123456123456";//私钥

public static string Issuer = "webapi.cn";

public static string Audience = "WebApi";

public static int AccessExpiration = 180;//过期时间

public static int RefreshExpiration = 180;//刷新时间
}

3、在启动类中注册JWT服务,如下:

NetCore 3.1  JWT认证简单介绍_sql_02NetCore 3.1  JWT认证简单介绍_sql_03

public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
#region 注册JWT
services.AddAuthentication(x =>
{
x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(x =>
{
x.RequireHttpsMetadata = false;
x.SaveToken = true;
x.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(TokenManagement.Secret)),
ValidIssuer = TokenManagement.Issuer,
ValidAudience = TokenManagement.Audience,
ValidateIssuer = false,
ValidateAudience = false
};
});
#endregion

View Code

4、在​​Configure​​方法中启用验证

app.UseAuthentication();

注意要添加在 app.UseAuthorization(); 上边

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}

//允许跨域
app.UseCors(builder => builder.AllowAnyOrigin().AllowAnyHeader().AllowAnyMethod());
// 启用Swagger中间件
app.UseSwagger(c => c.RouteTemplate = "swagger/{documentName}/swagger.json");
// 配置SwaggerUI
app.UseSwaggerUI(c =>
{
//c.SwaggerEndpoint($"/swagger/demo/swagger.json", "demo");
c.SwaggerEndpoint($"/swagger/v1/swagger.json", "V1");
});

app.UseHttpsRedirection();

app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();

app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}

 5、在接口层新建一个JwtToken的生成服务,如下:

bool IsAuthenticated(LoginRequestDTO request, out string token);

6、在服务层实现这个接口,如下:

public class TokenAuthenticationService : IAuthenticateService
{
private readonly IUserService _userService;
public TokenAuthenticationService(IUserService userService)
{
_userService = userService;
}
public bool IsAuthenticated(LoginRequestDTO request, out string token)
{
token = string.Empty;
if (!_userService.IsValid(request))
return false;
var claims = new[]
{
new Claim(ClaimTypes.Name,request.Username),
new Claim(ClaimTypes.Role,"admin"),
new Claim(ClaimTypes.Role,"admin2"),
new Claim(ClaimTypes.Actor,"chenwolong"),
new Claim(ClaimTypes.Country,"中国"),
new Claim(ClaimTypes.UserData,"其他用户数据")
};
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(TokenManagement.Secret));
var credentials = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
var jwtToken = new JwtSecurityToken(TokenManagement.Issuer, TokenManagement.Audience, claims, expires: DateTime.Now.AddMinutes(TokenManagement.AccessExpiration), signingCredentials: credentials);

token = new JwtSecurityTokenHandler().WriteToken(jwtToken);

return true;

}
}

上述代码中:

_userService.IsValid(request)

是登录验证, 就是验证密码,账户,在此不做累述。

我的登录代码为:

NetCore 3.1  JWT认证简单介绍_sql_02NetCore 3.1  JWT认证简单介绍_sql_03

#region 加盟商登录
/// <summary>
/// 加盟商登录 验证通过后产生Token返回
/// </summary>
/// <param name="data"></param>
/// <returns></returns>
public BaseResponse<LoginReturnModel> Login(LoginModel data)
{
var pwd = (data.Account + data.Password).ToMD5String();

var IsExtisAccount = _context.TeacherMain.Any(A => A.Account == data.Account);
if (!IsExtisAccount)
{
return CommonBaseResponse.SetResponse<LoginReturnModel>(null, false, "账号不存在");
}
var isJiaMs = _context.TeacherMain.Any(A => string.IsNullOrEmpty(A.FranchiseeId)&& A.Account == data.Account);
if (isJiaMs)
{
return CommonBaseResponse.SetResponse<LoginReturnModel>(null, false, "非加盟商无权登录此系统");
}
var IsUser= _context.TeacherMain.Any(A => A.Account == data.Account&&A.Password==pwd);
LoginReturnModel loginResult = new LoginReturnModel();
if (IsUser)
{
List<RoleModel> UserRoleLst = new List<RoleModel>();
userDataModel UserDatas = new userDataModel();
var Token = GetToken(data.Account,pwd,ref UserRoleLst,ref UserDatas);

loginResult.Roles = UserRoleLst;
loginResult.JwtToken = Token;
loginResult.userData = UserDatas;
return CommonBaseResponse.SetResponse<LoginReturnModel>(loginResult, true);
}
return CommonBaseResponse.SetResponse<LoginReturnModel>(loginResult, false, "密码错误");
}

private string GetToken(string Account,string pwd,ref List<RoleModel> UserRoleLst,ref userDataModel userData)
{
var ef = _context.TeacherMain.FirstOrDefault(A => A.Account == Account && A.Password == pwd);
string DeptSql = @"select B.TeacherID,C.DeptId,C.DeptName from Teacher_Main A
inner JOIN franchisee_UserAccount_Dept B on A.ID=B.TeacherID
inner JOIN franchisee_dept C on C.DeptId=B.DeptId
where IFNULL(A.DeleteFlag,0)=0 and IFNULL(C.DeleteFlag,0)=0 and A.ID='" + ef.Id + "' ";


//构造加盟商数据
var FranchiseeData = new TokenFranchiseeModel()
{
UserId = ef.Id,
FranchiseeID = ef.FranchiseeId
};
//构造组织架构数据
List<TokenDeptModel> deptList = new List<TokenDeptModel>();
DatabaseFacade fd_dept = new DatabaseFacade(_context);
var dept_ds = DbContextExtensions.SqlQuery(fd_dept, DeptSql);
for (int i = 0; i < dept_ds.Rows.Count; i++)
{
TokenDeptModel M = new TokenDeptModel()
{
DeptId = dept_ds.Rows[i]["DeptId"].ToString(),
DeptName = dept_ds.Rows[i]["DeptName"].ToString(),
UserId = dept_ds.Rows[i]["TeacherID"].ToString()

};
deptList.Add(M);
}
//获取当前用户角色

string RoleSql = @"select B.TeacherID,C.Name,A.Account,RoleCode,RoleId from Teacher_Main A
inner JOIN franchisee_useraccount_role B on A.ID=B.TeacherID
inner JOIN franchisee_role C on C.ID=B.RoleID
where IFNULL(A.DeleteFlag,0)=0 and IFNULL(C.DeleteFlag,0)=0 and A.ID='" + ef.Id + "' ";

DatabaseFacade fd = new DatabaseFacade(_context);
var ds = DbContextExtensions.SqlQuery(fd,RoleSql);
List<Claim> lst = new List<Claim>();
lst.Add(new Claim(ClaimTypes.Name, Account));
lst.Add(new Claim(ClaimTypes.Actor, Account));
lst.Add(new Claim(ClaimTypes.Country, "China"));
lst.Add(new Claim(ClaimTypes.UserData, FranchiseeData.ToJson()));
for (int i = 0; i < ds.Rows.Count; i++)
{
lst.Add(new Claim(ClaimTypes.Role,ds.Rows[i]["RoleCode"].ToString()));
RoleModel M = new RoleModel()
{
RoleName= ds.Rows[i]["Name"].ToString(),
RoleId = ds.Rows[i]["RoleId"].ToString()
};
UserRoleLst.Add(M);
}
//获取功能权限数据 注意Token的大小
List<MenuTreeModel> MenuButtonList = new List<MenuTreeModel>();
var RoleLst = UserRoleLst.Select(A => A.RoleId).ToList();
MenuButtonList = GetMenuButtonTreeList(RoleLst);
userData = new userDataModel()
{
deptList = deptList,
FranchiseeData = FranchiseeData,
MenuButtonList = MenuButtonList
};

var claims = lst.ToArray();
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(TokenManagement.Secret));
var credentials = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
var jwtToken = new JwtSecurityToken(TokenManagement.Issuer, TokenManagement.Audience, claims, expires: DateTime.Now.AddMinutes(TokenManagement.AccessExpiration), signingCredentials: credentials);

string Token = new JwtSecurityTokenHandler().WriteToken(jwtToken);
return Token;
}

/// <summary>
/// 查询菜单按钮树状结构 功能权限
/// </summary>
/// <returns></returns>
private List<MenuTreeModel> GetMenuButtonTreeList(List<string> Roles)
{
if (Roles != null && Roles.Count > 0)
{
List<MenuTreeModel> TreeList = new List<MenuTreeModel>();
var Searchlist = _context.FranchiseeRoleMenubutton.Where(A=>Roles.Contains(A.RoleId)).Include(A => A.MenuButton).Select(A => new MenuTreeDataModel
{
Component = A.MenuButton.Component,
Hidden = A.MenuButton.Hidden,
MenuButtonId = A.MenuButton.MenuButtonId,
MenuPath = A.MenuButton.MenuPath,
MenuName = A.MenuButton.MenuName,
MenuIcon = A.MenuButton.MenuIcon,
MenuSort = A.MenuButton.MenuSort,
ParentId = A.MenuButton.ParentId,
MenuSystem = A.MenuButton.MenuSystem,
MenuType = A.MenuButton.MenuType,
ParentName = A.MenuButton.ParentName,
Redirect = A.MenuButton.Redirect,
Title = A.MenuButton.Title
});

var list = Searchlist.ToList();
List<MenuTreeDataModel> TreeModels = MenuTreeModel.BuildTreeModel(list, string.Empty);
foreach (var item in TreeModels)
{
var Tree = MenuTreeModel.MakeTree(item, list);
TreeList.Add(Tree);

}
var result = TreeList;

return result;
}
else
{
return null;
}
}
#endregion

View Code

如果登录验证通过,我们就会签发一个Token,然后我们需要把Token返回给前端开发,前端开发拿到这个Token后,放在Http请求头中,请求我们的接口。

那么,我们该如何解析Token呢?

7、新建一个BaseController,在请求接口之前,我们验证Token,如下:

NetCore 3.1  JWT认证简单介绍_sql_02NetCore 3.1  JWT认证简单介绍_sql_03

public class BaseController : Controller
{
public CurrentUserData CurrentUser = new CurrentUserData();
/// <summary>
/// 第1步 执行父类控制器构造函数
/// </summary>
public BaseController()
{

}
/// <summary>
/// 第2步执行基类构造函数
/// 第3步 执行父类的异步方法 异步方法 似乎可以和OnActionExecuting同时执行
/// </summary>
/// <param name="context"></param>
/// <param name="next"></param>
/// <returns></returns>
public override Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
return base.OnActionExecutionAsync(context, next);
}

/// <summary>
/// 第4步 执行OnActionExecuting方法
/// </summary>
/// <param name="context"></param>
public override void OnActionExecuting(ActionExecutingContext context)
{
//验证Token是否正确
string displayUrl = HttpContext.Request.GetDisplayUrl();
if (displayUrl.Contains("Api/V1/Franchisee/Login"))
{
base.OnActionExecuting(context);
}
else
{
//解析Token 赋值给UserData
if (!Request.Headers.TryGetValue("Authorization", out var apiKeyHeaderValues))
{
CurrentUser = null;
}
else
{
//swagger 需要加Bearer 开头 真实的Token中也不存在空格 因此 Replace 是为了兼容swagger
var token = apiKeyHeaderValues.FirstOrDefault().Replace("Bearer ", "");
var jwtToken= new JwtSecurityTokenHandler().ReadJwtToken(token);
var roleLst = jwtToken.Claims.Where(A => A.Type.Contains("role")).ToList();
if (roleLst != null)
{
foreach(var item in roleLst)
{
if (!string.IsNullOrEmpty(item.Value))
{
CurrentUser.RoldeCodeList.Add(item.Value);
}
}
}
var NameModel = jwtToken.Claims.Where(A => A.Type.Contains("name")).ToList().FirstOrDefault();
CurrentUser.Account = NameModel == null ? "" : NameModel.Value;
var userdataModel = jwtToken.Claims.Where(A => A.Type.Contains("userdata")).ToList().FirstOrDefault();
string UserDataJson = userdataModel == null ? "" : userdataModel.Value;
var UserData = JsonConvert.DeserializeObject<TokenFranchiseeModel>(UserDataJson);
CurrentUser.userData = UserData;
var expModel = jwtToken.Claims.Where(A => A.Type.Contains("exp")).ToList().FirstOrDefault();
CurrentUser.exp = expModel == null ? "" : expModel.Value;
}
//获取请求的相对路径、
var relativeUri = UrlHelper.GetrelativeUri(HttpContext.Request);
//
//非登录接口均需要进行接口访问权限的验证,我们将接口访问权限存储到数据库中,这样即使我们的Token别盗取,他也只能访问盗取Token对应的接口权限。
//验证接口权限
//
//验证接口权限待完善
//
//验证不通过,抛出异常,由异常中间件捕获并返回异常消息。
//
}
}

/// <summary>
/// 第五步执行基类Action
/// 第6步 Aciton执行完毕后 执行 OnActionExecuted
/// </summary>
/// <param name="context"></param>
public override void OnActionExecuted(ActionExecutedContext context)
{
base.OnActionExecuted(context);
}


}

View Code

8、写一个接口验证下吧,想、如下:

NetCore 3.1  JWT认证简单介绍_sql_02NetCore 3.1  JWT认证简单介绍_sql_03

/// <summary>
/// 获取当前登录人信息
/// </summary>
/// <returns></returns>
[HttpPost]
[Authorize]
[Route("GetCurrentUser")]
public IActionResult GetCurrentUser()
{
return Ok(CurrentUser);
}

View Code

注意上述中的:Authorize标签既是启动Token验证。

关于上述中的当前用户:

public CurrentUserData CurrentUser = new CurrentUserData();

这个是解析Token成功后,赋值给当前对象的,这样,用户在系统中就能根据带的Token获取当前登录的用户信息。

我的当前用户类如下:【大家可根据不同项目,构造自己的当前用户类】

NetCore 3.1  JWT认证简单介绍_sql_02NetCore 3.1  JWT认证简单介绍_sql_03

public class CurrentUserData
{
public CurrentUserData()
{
RoldeCodeList = new List<string>();
}
/// <summary>
/// 当前请求人ID
/// </summary>
public string Id { get; set; }
/// <summary>
/// 当前请求人账户
/// </summary>
public string Account { get; set; }

/// <summary>
/// 当前请求人Token过期时间
/// </summary>
public string exp { get; set; }
/// <summary>
/// 当前请求人角色列表
/// </summary>
public List<string> RoldeCodeList { get; set; }

public TokenFranchiseeModel userData { get; set; } = new TokenFranchiseeModel();

}

public class userDataModel
{
/// <summary>
/// 当前请求人所属加盟商ID
/// </summary>
public TokenFranchiseeModel FranchiseeData { get; set; }
/// <summary>
/// 当前登录人的组织架构
/// </summary>
public List<TokenDeptModel> deptList { get; set; } = new List<TokenDeptModel>();
public List<MenuTreeModel> MenuButtonList { get; set; } = new List<MenuTreeModel>();
}
/// <summary>
/// 用户登录后 Token用的UserData数据 Json
/// </summary>
public class TokenFranchiseeModel
{
/// <summary>
/// 当前登录人Id
/// </summary>
public string UserId { get; set; }
/// <summary>
/// 当前登录人所属加盟商ID
/// </summary>
public string FranchiseeID { get; set; }
}

/// <summary>
/// 登录用户的组织架构信息
/// </summary>
public class TokenDeptModel
{
/// <summary>
/// 当前登录人Id
/// </summary>
public string UserId { get; set; }
/// <summary>
/// deptIdD
/// </summary>
public string DeptId { get; set; }

public string DeptName { get; set; }
}

public class TokenMenuButtonModel
{
/// <summary>
/// 当前登录人Id
/// </summary>
public string MenuButtonId { get; set; }
/// <summary>
/// deptIdD
/// </summary>
public string MenuButtonName { get; set; }

}

View Code

以上便是我的Token实现方式。

@天才卧龙的博客