当今Web开发中,API的使用越来越广泛,而API的安全性也变得越来越重要。其中,JWT(JSON Web Token)鉴权和授权是一种常见的解决方案。
本篇文章将会介绍JWT鉴权和授权的原理、实现方式以及注意事项。
JWT是一种基于JSON格式的开放标准(RFC7519),用于在网络上传递声明信息的一种简洁、自包含的安全方式。JWT通常被用来在各个系统之间传递身份认证信息和用户授权信息。
在开始使用 JWT 进行授权鉴权之前,需要先安装 Microsoft.AspNetCore.Authentication.JwtBearer NuGet 包。可以使用 Visual Studio 的 NuGet 管理器或者命令行工具进行安装。
Install-Package Microsoft.AspNetCore.Authentication.JwtBearer
JWT由三个部分组成:Header(头部)、Payload(负载)和Signature(签名)。
头部通常由两部分组成:令牌类型(即JWT)和指定该令牌所使用的签名算法。例如:
{
"alg": "HS256",
"typ": "JWT"
}
负载通常包含了需要传递的声明信息,声明信息由键值对组成。例如:
{
"sub": "1234567890",
"name": "John Doe",
"iat": 1516239022
}
其中,“sub”表示主题(subject),可以是用户ID或其他标识符;“name”表示用户名;“iat”表示令牌发行时间。
签名是对Header和Payload的内容进行数字签名后得到的一串字符串。签名用于验证JWT是否被篡改或伪造。例如:
HMACSHA256(base64UrlEncode(header) + "." +base64UrlEncode(payload),secret)
其中,“secret”为使用该令牌的服务器端保存的密钥。
JWT鉴权是指通过JWT来验证用户身份和权限。在使用JWT鉴权时,客户端将用户凭证(例如用户名和密码)发送给服务器,在服务器验证用户凭证有效后,生成一个JWT并将其返回给客户端。客户端在以后的请求中携带这个JWT,服务器通过验证JWT的签名和有效期等信息来验证用户身份和授权信息。
我们需要在appsettings.json文件中配置JWT的相关信息。在您的ASP.NET Core项目中,找到appsettings.json文件,并添加以下配置:
"Authentication": {
"SecretKey": "yourIssuer",
"Issuer": "yourAudience",
"Audience": "yourSecretKey"
},
在ASP.NET Core中,可以使用JwtBearer认证方案来验证JWT。首先,在Startup.cs文件中添加以下代码:
public void ConfigureServices(IServiceCollection services)
{
// 添加JWT身份验证服务
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
var secretByte = Encoding.UTF8.GetBytes(Configuration["Authentication:SecretKey"]); // 从appsettings.json读取Jwt配置
options.TokenValidationParameters = new TokenValidationParameters()
{
ValidateIssuer = true,
ValidIssuer = Configuration["Authentication:Issuer"],
ValidateAudience = true,
ValidAudience = Configuration["Authentication:Audience"],
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(secretByte)
};
});
// 其他服务配置
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
// 其他中间件配置...
app.UseAuthentication();
app.UseAuthorization();
}
上面代码中,我们向依赖注入容器中注册了一个身份验证方案,名称为 JwtBearerDefaults.AuthenticationScheme,表示使用 JWT 进行身份验证。然后,我们使用 AddJwtBearer 扩展方法,将 JWT 鉴权服务添加到应用程序中。
在 AddJwtBearer 方法中,我们需要配置 TokenValidationParameters 来验证 JWT。其中,ValidateIssuer、ValidIssuer、ValidateAudience、ValidAudience 和 ValidateLifetime 属性用于验证 JWT 中的发行人、接收方、有效期等信息。IssuerSigningKey 属性表示密钥,用于对 JWT 进行数字签名。最后,ValidateIssuerSigningKey 属性用于验证 JWT 的签名是否正确。
在ASP.NET Core中,可以使用JwtSecurityToken类来创建JWT。例如:
var signingAlgorithm = SecurityAlgorithms.HmacSha256;
var claims = new[]
{
new Claim(JwtRegisteredClaimNames.Sub,"user_id"),
new Claim(ClaimTypes.Role,"Admin"),
new Claim("UserId","12"),
};
var secretByte = Encoding.UTF8.GetBytes(_configuration["Authentication:SecretKey"]);
var signingKey = new SymmetricSecurityKey(secretByte);
var signingCredentials = new SigningCredentials(signingKey, signingAlgorithm);
var token = new JwtSecurityToken(
issuer: _configuration["Authentication:Issuer"],
audience: _configuration["Authentication:Audience"],
claims,
notBefore: DateTime.UtcNow,
expires: DateTime.UtcNow.AddDays(1),
signingCredentials
);
var tokenStr = new JwtSecurityTokenHandler().WriteToken(token);
在这里,我们首先创建了一个声明(Claims)列表,其中包含用户ID、角色信息。然后,我们指定了JWT的过期时间和签名算法,并使用SymmetricSecurityKey类来指定密钥。最后,我们使用JwtSecurityTokenHandler类将token转换为字符串形式的jwt。
public bool ValidateAccessToken(string token)
{
var tokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.UTF8.GetBytes(_jwtConfig.SecretKey);
try
{
tokenHandler.ValidateToken(token, new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = _jwtConfig.Issuer,
ValidAudience = _jwtConfig.Audience,
IssuerSigningKey = new SymmetricSecurityKey(key)
}, out var validatedToken);
}
catch (Exception)
{
return false;
}
return true;
}
}
上面代码中,我们使用 JwtSecurityTokenHandler 类来验证 JWT 的真实性和完整性。其中,我们使用 TokenValidationParameters 来配置验证参数,包括是否验证 JWT 发行人、接收方、有效期等信息,以及使用哪个密钥对其进行数字签名。如果验证通过,则返回 true,否则返回 false。
JWT授权是指根据JWT中包含的声明信息来验证用户是否具有访问特定资源的权限。在使用JWT授权时,我们在JWT中添加了一些声明信息,例如用户所属角色、权限等,服务器可以通过这些信息来验证用户是否有权访问特定资源。
创建传入DTO:LoginDto 、返回DTO:LoginOutDto类
public class LoginDto
{
public string UserName { get; set; }
public string Pwd { get; set; }
}
public class LoginOutDto
{
public int Code { get; set; }
public string Msg { get; set; }
public string access_token { get; set; }
public string refresh_token { get; set; }
}
在用户登录时,我们需要对用户提供的用户名和密码进行验证,并生成访问令牌和刷新令牌。下面是一个简单的示例,演示如何在ASP.NET Core中实现用户登录验证,并生成JWT令牌。
[HttpPost("login")]
public LoginOutDto Login([FromBody] LoginDto input)
{
var dto = new LoginOutDto();
try
{
if (input.UserName != "admin" || input.Pwd != "bb123456")
{
dto.Code = 500;
dto.Msg = "用户名或密码不正确";
dto.access_token = string.Empty;
return dto;
}
// 生成访问令牌
var accessToken = _jwtService.GenerateAccessToken();
// 生成刷新令牌
var refreshToken = _jwtService.GenerateRefreshToken();
dto.Code = 200;
dto.Msg = "登录成功";
dto.access_token = accessToken;
dto.refresh_token = refreshToken;
}
catch (Exception ex)
{
dto.Code = 500;
dto.Msg = "登录失败:" + ex.Message;
}
return dto;
}
在上面的示例中,我们通过调用_jwtService.GenerateAccessToken和_jwtService.GenerateRefreshToken方法来生成访问令牌和刷新令牌,并将刷新令牌保存到数据库或其他持久化存储中,以便后续使用。
在用户登录后,访问令牌会在一定时间后过期,此时用户需要使用刷新令牌来获取新的访问令牌,而无需重新登录。下面是一个简单的示例,演示如何在ASP.NET Core中实现刷新令牌功能。
[HttpPost("refresh")]
public IActionResult RefreshToken(UserModel model)
{
// 验证刷新令牌是否有效
var isValidRefreshToken = ValidateAccessToken(model.RefreshToken);
if (!isValidRefreshToken)
{
return BadRequest(new { message = "Invalid refresh token" });
}
// 生成新的访问令牌
var accessToken = _jwtService.GenerateAccessToken(model);
// 返回新的访问令牌给客户端
return Ok(new
{
access_token = accessToken
});
}
在上面的示例中,我们通过调用_jwtService.GenerateAccessToken方法来生成新的访问令牌,并将其返回给客户端。在生成新的访问令牌时,我们可以使用之前保存的用户信息,例如用户名等
下面是一个包含生成 JWT,解析 JWT,鉴权,授权和策略的完整示例。请注意,此示例仅供参考,请根据实际需求进行修改。
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
var secretByte = Encoding.UTF8.GetBytes(Configuration["Authentication:SecretKey"]);
options.TokenValidationParameters = new TokenValidationParameters()
{
ValidateIssuer = true,
ValidIssuer = Configuration["Authentication:Issuer"],
ValidateAudience = true,
ValidAudience = Configuration["Authentication:Audience"],
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(secretByte)
};
});
// 注入IJwtService服务
services.AddSingleton<IJwtService, JwtService>();
services.AddControllers();
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "JWT.Demo", Version = "v1" });
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseSwagger();
app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "JWT.Demo v1"));
}
app.UseHttpsRedirection();
app.UseRouting();
// 身份验证
app.UseAuthentication();
// 授权
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
}
public interface IJwtService
{
/// <summary>
/// 生成JWT
/// </summary>
/// <returns></returns>
string GenerateAccessToken();
/// <summary>
/// 刷新Token
/// </summary>
/// <returns></returns>
string GenerateRefreshToken();
/// <summary>
/// 验证JWT
/// </summary>
/// <param name="token"></param>
/// <returns></returns>
bool ValidateAccessToken(string token);
}
public class JwtService : IJwtService
{
private readonly IConfiguration _configuration;
public JwtService(IConfiguration configuration)
{
_configuration = configuration;
}
/// <summary>
/// 生成JWT
/// </summary>
/// <returns></returns>
public string GenerateAccessToken()
{
var signingAlgorithm = SecurityAlgorithms.HmacSha256;
var claims = new[]
{
new Claim(JwtRegisteredClaimNames.Sub,"user_id"),
new Claim(ClaimTypes.Role,"Admin"),
new Claim("UserId","12"),
};
var secretByte = Encoding.UTF8.GetBytes(_configuration["Authentication:SecretKey"]);
var signingKey = new SymmetricSecurityKey(secretByte);
var signingCredentials = new SigningCredentials(signingKey, signingAlgorithm);
var token = new JwtSecurityToken(
issuer: _configuration["Authentication:Issuer"],
audience: _configuration["Authentication:Audience"],
claims,
notBefore: DateTime.UtcNow,
expires: DateTime.UtcNow.AddDays(1),
signingCredentials
);
var tokenStr = new JwtSecurityTokenHandler().WriteToken(token);
return tokenStr;
}
/// <summary>
/// 刷新Token
/// </summary>
/// <returns></returns>
public string GenerateRefreshToken()
{
var randomNumber = new byte[32];
using (var rng = new RNGCryptoServiceProvider())
{
rng.GetBytes(randomNumber);
return Convert.ToBase64String(randomNumber);
}
}
/// <summary>
/// 验证JWT
/// </summary>
/// <param name="token"></param>
/// <returns></returns>
public bool ValidateAccessToken(string token)
{
var tokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.UTF8.GetBytes(_configuration["Authentication:SecretKey"]);
try
{
tokenHandler.ValidateToken(token, new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = _configuration["Authentication:Issuer"],
ValidAudience = _configuration["Authentication:Audience"],
IssuerSigningKey = new SymmetricSecurityKey(key)
}, out var validatedToken);
}
catch (Exception)
{
return false;
}
return true;
}
}
[ApiController]
[Route("auth")]
public class AuthenticateController : ControllerBase
{
private readonly IConfiguration _configuration;
private readonly IJwtService _jwtService;
public AuthenticateController(IConfiguration configuration, IJwtService jwtService)
{
_configuration = configuration;
_jwtService = jwtService;
}
[HttpPost("login")]
public LoginOutDto Login([FromBody] LoginDto input)
{
var dto = new LoginOutDto();
try
{
if (input.UserName != "admin" || input.Pwd != "bb123456")
{
dto.Code = 500;
dto.Msg = "用户名或密码不正确";
dto.access_token = string.Empty;
return dto;
}
// 生成访问令牌
var accessToken = _jwtService.GenerateAccessToken();
// 生成刷新令牌
var refreshToken = _jwtService.GenerateRefreshToken();
dto.Code = 200;
dto.Msg = "登录成功";
dto.access_token = accessToken;
dto.refresh_token = refreshToken;
}
catch (Exception ex)
{
dto.Code = 500;
dto.Msg = "登录失败:" + ex.Message;
}
return dto;
}
[HttpPost("refresh")]
public IActionResult RefreshToken(string token)
{
// 验证刷新令牌是否有效
var isValidRefreshToken = _jwtService.ValidateAccessToken(token);
if (!isValidRefreshToken)
{
return BadRequest(new { message = "Invalid refresh token" });
}
// 生成新的访问令牌
var accessToken = _jwtService.GenerateAccessToken();
// 返回新的访问令牌给客户端
return Ok(new
{
access_token = accessToken
});
}
}
密钥管理:在使用JWT时,密钥是非常重要的,泄露密钥会导致安全问题。因此,密钥的生成、存储和更新都必须谨慎处理。
过期时间:在生成JWT时,要指定合适的过期时间,避免JWT过期后仍然可以使用。
签名算法:签名算法的选择很重要,不同的签名算法具有不同的安全性和效率。建议采用HMAC+
SHA256或RSA算法。
在.NET 5 中使用 JWT 进行授权鉴权是一种安全、可靠的身份验证方式。通过添加 JWT 鉴权服务、使用 Authorize 属性启用 JWT 授权、生成和验证 JWT、使用 UseAuthentication 和 UseAuthorization 中间件来启用身份验证和授权,并为不同的 API 设置不同的授权策略,可以轻松地实现 JWT 的授权鉴权功能。
给定这段代码defcreate@upgrades=User.update_all(["role=?","upgraded"],:id=>params[:upgrade])redirect_toadmin_upgrades_path,:notice=>"Successfullyupgradeduser."end我如何在该操作中实际验证它们是否已保存或未重定向到适当的页面和消息? 最佳答案 在Rails3中,update_all不返回任何有意义的信息,除了已更新的记录数(这可能取决于您的DBMS是否返回该信息)。http://ar.ru
我想安装一个带有一些身份验证的私有(private)Rubygem服务器。我希望能够使用公共(public)Ubuntu服务器托管内部gem。我读到了http://docs.rubygems.org/read/chapter/18.但是那个没有身份验证-如我所见。然后我读到了https://github.com/cwninja/geminabox.但是当我使用基本身份验证(他们在他们的Wiki中有)时,它会提示从我的服务器获取源。所以。如何制作带有身份验证的私有(private)Rubygem服务器?这是不可能的吗?谢谢。编辑:Geminabox问题。我尝试“捆绑”以安装新的gem..
我希望我的UserPrice模型的属性在它们为空或不验证数值时默认为0。这些属性是tax_rate、shipping_cost和price。classCreateUserPrices8,:scale=>2t.decimal:tax_rate,:precision=>8,:scale=>2t.decimal:shipping_cost,:precision=>8,:scale=>2endendend起初,我将所有3列的:default=>0放在表格中,但我不想要这样,因为它已经填充了字段,我想使用占位符。这是我的UserPrice模型:classUserPrice回答before_val
我有一个表单,其中有很多字段取自数组(而不是模型或对象)。我如何验证这些字段的存在?solve_problem_pathdo|f|%>... 最佳答案 创建一个简单的类来包装请求参数并使用ActiveModel::Validations。#definedsomewhere,atthesimplest:require'ostruct'classSolvetrue#youcouldevencheckthesolutionwithavalidatorvalidatedoerrors.add(:base,"WRONG!!!")unlesss
我有一些非常大的模型,我必须将它们迁移到最新版本的Rails。这些模型有相当多的验证(User有大约50个验证)。是否可以将所有这些验证移动到另一个文件中?说app/models/validations/user_validations.rb。如果可以,有人可以提供示例吗? 最佳答案 您可以为此使用关注点:#app/models/validations/user_validations.rbrequire'active_support/concern'moduleUserValidationsextendActiveSupport:
当我的预订模型通过rake任务在状态机上转换时,我试图找出如何跳过对ActiveRecord对象的特定实例的验证。我想在reservation.close时跳过所有验证!叫做。希望调用reservation.close!(:validate=>false)之类的东西。仅供引用,我们正在使用https://github.com/pluginaweek/state_machine用于状态机。这是我的预订模型的示例。classReservation["requested","negotiating","approved"])}state_machine:initial=>'requested
我有一个服务模型/表及其注册表。在表单中,我几乎拥有服务的所有字段,但我想在验证服务对象之前自动设置其中一些值。示例:--服务Controller#创建Action:defcreate@service=Service.new@service_form=ServiceFormObject.new(@service)@service_form.validate(params[:service_form_object])and@service_form.saverespond_with(@service_form,location:admin_services_path)end在验证@ser
这里有一个很好的答案解释了如何在Ruby中下载文件而不将其加载到内存中:https://stackoverflow.com/a/29743394/4852737require'open-uri'download=open('http://example.com/image.png')IO.copy_stream(download,'~/image.png')我如何验证下载文件的IO.copy_stream调用是否真的成功——这意味着下载的文件与我打算下载的文件完全相同,而不是下载一半的损坏文件?documentation说IO.copy_stream返回它复制的字节数,但是当我还没有下
1.postman介绍Postman一款非常流行的API调试工具。其实,开发人员用的更多。因为测试人员做接口测试会有更多选择,例如Jmeter、soapUI等。不过,对于开发过程中去调试接口,Postman确实足够的简单方便,而且功能强大。2.下载安装官网地址:https://www.postman.com/下载完成后双击安装吧,安装过程极其简单,无需任何操作3.使用教程这里以百度为例,工具使用简单,填写URL地址即可发送请求,在下方查看响应结果和响应状态码常用方法都有支持请求方法:getpostputdeleteGet、Post、Put与Delete的作用get:请求方法一般是用于数据查询,
大家好,我正在尝试设置一个开发环境,并且我一直在关注以下教程:Linktotutorial我做得不是很好,除了最基本的版本控制内容外,我对终端命令没有任何实际经验。我点击了第一个链接并尝试运行source~/.bash_profile我得到了错误;mkdir:/usr/local/rbenv/shims:权限被拒绝mkdir:/usr/local/rbenv/versions:权限被拒绝现在每次我加载终端时都会出现错误。bash_profile的内容;exportPATH=/usr/local/rbenv/bin:$PATHexportRBENV_ROOT=/usr/local/rbe