有时想快速搭建一个简单应用,并集成登录功能时,总是会被认证授权绕来绕去,一直想着要搞个授权中心,却把最为简单快捷的方式抛掷脑后。
认证与授权说来说去还是四个核心步骤,登录退出,登录有效后请求资源,请求人是谁与请求人有没有权限请求。
JWT
JSON Web Token(JWT)是目前最流行的跨域身份验证解决方案。其本身只是一种格式或是协议,集成到框架中,然后便按照这种格式或协议来传递信息。
当使用认证授权时,将具有用户信息的令牌以JWT格式的呈现,命名为Id token或是Access token。
项目准备
准备一个Asp.Net Core 6.0的WebApi(前端实现不考虑)。按照如上几个用例挨个实现(退出用例不考虑)
安装Nuget包
<ItemGroup>
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.2.3" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="6.0.3" />
<PackageReference Include="IdentityModel" Version="6.0.0" />
</ItemGroup>
生成JWT格式令牌
增加Account控制器
[ApiController]
[Route("[controller]")]
public class AccountController : ControllerBase
{
}
增加JwtOption
该部分信息用户Jwt格式中所需要的
public class JwtOptions
{
public const string Name = "Jwt";
public string Audience { get; set; }
public string Issuer { get; set; }
public double ExpiresMinutes { get; set; } = 30d;
public Encoding Encoding { get; set; } = Encoding.UTF8;
public string SymmetricSecurityKeyString { get; set; }
public SymmetricSecurityKey SymmetricSecurityKey => new(Encoding.GetBytes(SymmetricSecurityKeyString));
}
服务注册
builder.Services.Configure<AuthConfigOptions>(builder.Configuration.GetSection(AuthConfigOptions.Name));
配置信息
appsettings.json中增加该块配置
{
"Jwt": {
"Audience": "http://localhost:5105",
"Issuer": "http://localhost:5105",
"ExpiresMinutes": 30,
"SymmetricSecurityKeyString": "Symmetric Security Key"
}
}
注入Option
[ApiController]
[Route("[controller]")]
public class AccountController : ControllerBase
{
private readonly JwtOptions _jwtOptions;
public AccountController(IOptionsSnapshot<JwtOptions> jwtOptions)
{
_jwtOptions = jwtOptions.Value;
}
}
增加SignIn方法
此处只模拟存在一个用户,将该用户通过Jwt格式存储信息并颁发token。
[AllowAnonymous]
[HttpPost("Login")]
public IActionResult SignIn([FromBody] SignInDto dto)
{
//db query...
//return Unauthorized();
//user info
var user = new UserModel()
{
Id = Guid.NewGuid(),
UserName = dto.UserName,
Email = "test@test.com"
};
// 1 定义需要的Cliam信息
var claims = new[]
{
new Claim(JwtClaimTypes.Id, user.Id.ToString("N")),
new Claim(JwtClaimTypes.Name, user.UserName),
new Claim(JwtClaimTypes.Email, user.Email)
};
// 2 设置SecretKey
var secretKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_jwtOptions.SymmetricSecurityKeyString));
// 3 设置加密算法
var algorithm = SecurityAlgorithms.HmacSha256;
// 4 生成签名凭证信息
var signingCredentials = new SigningCredentials(secretKey, algorithm);
// 5 设置token过期时间
var expires = DateTime.Now.AddMinutes(_jwtOptions.ExpiresMinutes);
// 6 生成token
var securityToken = new JwtSecurityToken(
claims: claims,
issuer: _jwtOptions.Issuer,
audience: _jwtOptions.Audience,
notBefore: DateTime.Now,
expires: expires,
signingCredentials: signingCredentials
);
var jwtSecurityTokenHandler = new JwtSecurityTokenHandler();
var token = jwtSecurityTokenHandler.WriteToken(securityToken);
return Ok(new { token });
}
生成token
请求资源
默认模板生成时自带了一个WeatherForecast控制器,此处将其作为资源,对其添加Authorize特性,控制资源。
[ApiController]
[Route("[controller]")]
[Authorize]
public class WeatherForecastController : ControllerBase
{
}
因模板中管道部分默认带上了UseAuthorization,因此再次请求WeatherForecast的方法则会报错,没有为Authorization配置相关服务。
增加服务配置
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.RequireHttpsMetadata = false;
options.SaveToken = true;
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = false,
ValidIssuer = jwtOptions.Issuer,
ValidateAudience = false,
ValidAudience = jwtOptions.Audience,
ValidateLifetime = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtOptions.SymmetricSecurityKeyString)),
};
});
增加Authentication中间件
app.UseAuthentication();
访问资源
Authorization中间件与Filter区别
在控制器/方法上加Authorize特性,有相应的Filter处理是否有权限,为什么存在了一个Authorization中间件去提前验证?
答:Filter的处理属于MVC的职责范围,而Authorization则是中间件的职责范围,可以认为是总闸与分闸。
2022-04-17,望技术有成后能回来看见自己的脚步