集成JWT权限认证
为什么要使用JWT,我们先来看看传统的身份校验方式
用户向服务器发送用户名和密码->服务器验证通过后,在当前session对话里保存用户登录信息,比如用户角色,用户名,登录时间等->服务器向用户返回一个session_id,写入用户的cookie->随后的用户每次发送请求都会将session_id传回服务器->服务器搜到session_id,找到前期保存的数据,由此得知用户的身份
这种模式的问题在于,扩展性不好,单机情况下没有问题,如果是服务器集群,要求session数据共享,每台服务器都能够读取session,如果session存储的节点挂了,那么整个服务都会瘫痪,体验相当不好,风险也很高,相比之下,JWT的实现方式是将用户信息存储在客户端,服务端不进行保存,每次请求都把令牌带上来校验用户的登录状态,这样服务器就变成了无状态的,服务器集群也很好扩展。
当用户需要访问带权限验证的API时,应该使用承载模式发送JWT,通常在Authorization标头,格式如下:
Authorization:Bearer <boken>
网站的功能简介
登录成功后将用户信息写入Claim对象生成token返回web端,写入cookie,当用户修改密码时,通过ajax调用Api接口在头部添加Authorization:Bearer <token>发送请求,authorize会验证token是否有效,如果失效则返回401,成功则进行密码修改的操作。
登录API
/// <summary>
/// 登录
/// </summary>
/// <param name="user"></param>
/// <returns></returns>
[HttpPost]
[Route("Login")]
public async Task<IActionResult> Login(UserDto user)
{
var result = new ResultInclusion();
if (string.IsNullOrEmpty(user.UserName))
{
result.Code = -1;
result.Msg = "请输入账号";
return new JsonResult(result);
}
if (string.IsNullOrEmpty(user.Password))
{
result.Code = -1;
result.Msg = "请输入密码";
return new JsonResult(result);
}
//dto.LoginIP = ip.ToString();
////获取客户端浏览器信息
//dto.Explorer = _httpContextAccessor.HttpContext.Request.Headers["User-Agent"].ToString();
////获取客户端浏览器信息
//dto.ClientOS = HttpContextHelper.GetOsVersion(dto.Explorer);
var usermodel = await _service.GetAsync(p=>p.UserName==user.UserName);
if (usermodel != null)
{
if (usermodel.Password == GetMD5.Get_MD5(user.Password, "utf-8"))
{
var claims = new List<Claim>()
{
new Claim(JwtRegisteredClaimNames.Nbf,$"{new DateTimeOffset(DateTime.Now).ToUnixTimeSeconds()}"),
new Claim(JwtRegisteredClaimNames.Exp,$"{new DateTimeOffset(DateTime.Now.AddMinutes(30)).ToUnixTimeSeconds()}"),
new Claim(ClaimTypes.Name,user.UserName)
};
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["Jwt:SecretKey"]));
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
var token = new JwtSecurityToken(
issuer:Configuration["Jwt:Domain"],
audience:Configuration["Jwt:Domain"],
claims:claims,
expires:DateTime.Now.AddMinutes(30),
signingCredentials:creds
);
var accesstoken= new JwtSecurityTokenHandler().WriteToken(token);
result.Code = 0;
result.Msg = "登录成功";
result.Data = new
{
username = user.UserName,
token =accesstoken
};
return new JsonResult(result);
}
else {
result.Code = -1;
result.Msg = "密码错误";
return new JsonResult(result);
}
}
else {
result.Code = -1;
result.Msg = "用户名不存在";
return new JsonResult(result);
}
}
appsettings.json配置
"Jwt": {
"SecretKey": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDI2a2EJ7m872v0afyoSDJT2o1+SitIeJSWtLJU8/Wz2m7gStexajkeD+Lka6DSTy8gt9UwfgVQo6uKjVLG5Ex7PiGOODVqAEghBuS7JzIYU5RvI543nNDAPfnJsas96mSA7L/mD7RTE2drj6hf3oZjJpMPZUQI/B1Qjb5H3K3PNwIDAQAB",
"Domain": "http://localhost:5001"
}
startup.cs ConfigureServices方法中添加JWT相关配置
//添加权限认证+jwt认证
services.AddAuthentication(b =>
{
b.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
b.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options => {
options.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters
{
ValidateIssuer = true,//是否验证Issuer
ValidateAudience = true,//是否验证Audience
ValidateLifetime = true,//是否验证失效时间
ClockSkew = TimeSpan.FromMilliseconds(30),
ValidateIssuerSigningKey = true,//是否验证SecurityKey
ValidAudience = Configuration["Jwt:Domain"],//Audience
ValidIssuer = Configuration["Jwt:Domain"],//Issuer,这两项和前面签发jwt的设置一致
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["Jwt:SecretKey"]))//拿到SecurityKey
};
});
Configure方法中添加权限过滤器
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseAuthentication();
app.UseMvc();
}
Web端:用户登录成功后返回的token写进cookie
<form class="form-signin form-group" id="AppForm">
<h2 class="form-signin-heading">欢迎使用我的Blog</h2>
<label for="inputEmail" >账号</label>
<input type="text" id="username" name="username" class="form-control" placeholder="请输入账号" value="liulei" required autofocus>
<label for="inputPassword" >密码</label>
<input type="password" id="password" name="password" class="form-control" placeholder="请输入密码" value="123456" required>
<button class="btn btn-lg btn-primary btn-block" id="loginForm" type="submit">登录</button>
</form>
$.ajax({
type: 'post',
url: url + "/api/user/Login",
data:JSON.stringify( { UserName: $("#username").val(), Password: $("#password").val() }),
contentType:'application/json',
success: function (data) {
//写入cookie
$.cookie("access_token", data.data.token, {
expires: new Date().getTime() + (30 * 60 * 1000)
, path: '/'
});
console.log(data);
},
error: function (xhr) {
$('button[type="submit"]').text('登录').removeAttr('disabled');
},
beforeSend: function (XHR) {
$('button[type="submit"]').text('登录中...').attr('disabled', 'disabled');
},
complete: function (XHR, TS) {
$('button[type="submit"]').text('登录').removeAttr('disabled');
}
});
修改密码
<form class="form-signin form-group" id="AppForm">
<h2 class="form-signin-heading">修改密码</h2>
<label for="inputEmail" >旧密码</label>
<input type="password" id="oldpassword" name="oldpassword" class="form-control" placeholder="请输入旧密码" required>
<label for="inputPassword" >新密码</label>
<input type="password" id="newpassword" name="newpassword" class="form-control" placeholder="请输入新密码" required>
<button class="btn btn-lg btn-primary btn-block" id="loginForm" type="submit">登录</button>
</form>
$.ajax({
type: 'post',
url: url + "/api/user/UpdatePWD",
data: JSON.stringify({ OldPassWord: $("#oldpassword").val(), Password: $("#newpassword").val() }),
contentType: 'application/json',
beforeSend: function (XHR) {
XHR.setRequestHeader("Authorization", "bearer " + $.cookie("access_token"));
},
success: function (data) {
console.log(data);
},
error: function (xhr) {
$('button[type="submit"]').text('确定').removeAttr('disabled');
},
complete: function (XHR, TS) {
$('button[type="submit"]').text('确定').removeAttr('disabled');
}
});
修改密码API
/// <summary>
/// 修改密码
/// </summary>
/// <param name="user"></param>
/// <returns></returns>
[HttpPost]
[Route("UpdatePWD")]
[Authorize]
public async Task<IActionResult> UpdatePWD(UserDto user) {
var result = new ResultInclusion();
//从claim中获取用户信息
var schemeProvider = HttpContext.RequestServices.GetService(typeof(IAuthenticationSchemeProvider)) as IAuthenticationSchemeProvider;
var defaultAuthenticate = await schemeProvider.GetDefaultAuthenticateSchemeAsync();
if (defaultAuthenticate != null)
{
var JWTClaim = await HttpContext.AuthenticateAsync(defaultAuthenticate.Name);
var ClaimUser = JWTClaim?.Principal;
if (ClaimUser != null)
{
var userPassword = GetMD5.Get_MD5(user.OldPassWord, "utf-8");
var userinfo = await _service.GetAsync(p => p.UserName == ClaimUser.Identity.Name);
if (userinfo!=null&&userinfo.Password == userPassword)
{
userinfo.Password = GetMD5.Get_MD5(user.Password, "utf-8");
result.Data = await _service.UpdateAsync(userinfo);
result.Code = 0;
result.Msg = "修改成功";
}
else
{
result.Code = -1;
result.Msg = "旧密码不正确";
}
}
}
return new JsonResult(result);
}
若未登录直接修改密码则返回401权限未通过: