身份认证使用JWT,关于AspNetCore的身份认证和JWT可以看看我之前这篇博客
先安装nuget包
dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer
在Services
目录下新建一个AuthService
类,先留着不写代码,等把准备工作完成了再来。
用户模型
在 StarBlog.Data
项目的 Models
目录下新建 User
类
代码如下
namespace StarBlog.Data.Models;
public class User {
public string Id { get; set; }
public string Name { get; set; }
public string Password { get; set; }
}
配置
编辑appsettings.json
添加配置
Issuer
是token的发行者,我写了这个项目的名称Audience
是token的接受者(使用者),我写了管理后台的项目名称Key
是用来加密token的密码,找一个随机密码生成器生成一个就好了,注意位数要8的位数
"SecuritySettings": {
"Token": {
"Issuer": "starblog",
"Audience": "starblog-admin-ui",
"Key": "Pox40XC.D5>v^2B7+KAt%WfXaz0B6zC5"
}
}
PS:本文里这种把Key放在配置文件的做法并不安全,特别是在开源项目中,这种机密数据用环境变量来存更好,MSDN上有关于安全这方面的更详细的资料,如果上生产项目的话可以关注一下。这里为了不增加复杂度我就直接偷懒把Key放在appsettings.json
里了,见谅哈~
配置模型
写一个实体类,方便和配置绑定起来使用
在Models/Config
目录下新建SecuritySettings.cs
,代码如下
namespace StarBlog.Web.Models.Config;
public class SecuritySettings {
public Token Token { get; set; }
}
public class Token {
public string Issuer { get; set; }
public string Audience { get; set; }
public string Key { get; set; }
}
注册服务
为了Program.cs
文件内的代码整洁,我们用扩展方法的方式来注册服务
在Extensions
目录下新建ConfigureAppSettings.cs
,用来绑定配置,代码如下
using StarBlog.Web.Models.Config;
namespace StarBlog.Web.Extensions;
public static class ConfigureAppSettings {
public static void AddSettings(this IServiceCollection services, IConfiguration configuration) {
// 安全配置
services.Configure<SecuritySettings>(configuration.GetSection(nameof(SecuritySettings)));
}
}
继续新建ConfigureAuth.cs
文件,用来注册认证需要的一些服务,代码如下
using System.Text;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
using StarBlog.Web.Models.Config;
using StarBlog.Web.Services;
namespace StarBlog.Web.Extensions;
public static class ConfigureAuth {
public static void AddAuth(this IServiceCollection services, IConfiguration configuration) {
services.AddScoped<AuthService>();
services.AddAuthentication(options => {
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options => {
var secSettings = configuration.GetSection(nameof(SecuritySettings)).Get<SecuritySettings>();
options.TokenValidationParameters = new TokenValidationParameters {
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuer = true,
ValidateIssuerSigningKey = true,
ValidIssuer = secSettings.Token.Issuer,
ValidAudience = secSettings.Token.Audience,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(secSettings.Token.Key)),
ClockSkew = TimeSpan.Zero
};
});
}
}
这里面的AuthService
还没写,但不急,先来注册服务
编辑Program.cs
文件,添加这两行代码
builder.Services.AddSettings(builder.Configuration);
builder.Services.AddAuth(builder.Configuration);
身份认证关键代码
AspNetCore框架已经把身份认证的功能内置了,所以我们要做的事情很少,只需要生成一个JWT token给客户端就行了。
那就开始吧
模型
先在 ViewModels
目录下新建两个类,分别是 LoginToken
和 LoginUser
。
代码如下
namespace StarBlog.Web.ViewModels;
public class LoginToken {
public string Token { get; set; }
public DateTime Expiration { get; set; }
}
namespace StarBlog.Web.ViewModels;
public class LoginUser {
public string Username { get; set; }
public string Password { get; set; }
}
AuthService
先写上依赖注入
public class AuthService {
private readonly SecuritySettings _securitySettings;
private readonly IBaseRepository<User> _userRepo;
public AuthService(IOptions<SecuritySettings> options, IBaseRepository<User> userRepo) {
_securitySettings = options.Value;
_userRepo = userRepo;
}
}
然后我们要在 AuthService
里实现
- 生成token
- 从数据库获取用户
- 从token里取出用户信息
这几个功能
一个个来
生成token
首先是生成token,代码如下
public LoginToken GenerateLoginToken(User user) {
var claims = new List<Claim> {
new("username", user.Name),
new(JwtRegisteredClaimNames.Name, user.Id), // User.Identity.Name
new(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()), // JWT ID
};
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_securitySettings.Token.Key));
var signCredential = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
var jwtToken = new JwtSecurityToken(
issuer: _securitySettings.Token.Issuer,
audience: _securitySettings.Token.Audience,
claims: claims,
expires: DateTime.Now.AddDays(7),
signingCredentials: signCredential);
return new LoginToken {
Token = new JwtSecurityTokenHandler().WriteToken(jwtToken),
Expiration = TimeZoneInfo.ConvertTimeFromUtc(jwtToken.ValidTo, TimeZoneInfo.Local)
};
}
从数据库获取用户
代码如下
public User? GetUserById(string userId) {
return _userRepo.Where(a => a.Id == userId).ToOne();
}
public User? GetUserByName(string name) {
return _userRepo.Where(a => a.Name == name).ToOne();
}
从token里取出用户信息
因为客户端要提交token到服务端,JWT token里包含用户信息,所以我们可以直接从token里解密出用户信息,不需要访问数据库浪费IO性能。
代码如下
public User? GetUser(ClaimsPrincipal userClaim) {
var userId = userClaim.Identity?.Name;
var userName = userClaim.Claims.FirstOrDefault(c => c.Type == "username")?.Value;
if (userId == null || userName == null) return null;
return new User { Id = userId, Name = userName };
}
OK,最关键的部分完成了,接下来就是写个登录接口
登录接口
没啥东西,在 Apis
目录下新建 AuthController.cs
,然后直接上代码
using Microsoft.AspNetCore.Mvc;
using StarBlog.Web.Services;
using StarBlog.Web.ViewModels;
using StarBlog.Web.ViewModels.Response;
namespace StarBlog.Web.Controllers;
[ApiController]
[Route("Api/[controller]")]
public class AuthController : ControllerBase {
private readonly AuthService _authService;
public AuthController(AuthService authService) {
_authService = authService;
}
[HttpPost]
public ApiResponse<LoginToken> Login(LoginUser loginUser) {
var user = _authService.GetUserByName(loginUser.Username);
if (user==null) return ApiResponse.NotFound(Response);
if(loginUser.Password != user.Password) return ApiResponse.Unauthorized(Response);
return new ApiResponse<LoginToken>(_authService.GenerateLoginToken(user));
}
}
哦,再加个一个获取当前登录用户信息的接口。
代码如下
[Authorize]
[HttpGet]
public ActionResult<User> GetUser() {
var user = _authService.GetUser(User);
if (user == null) return NotFound();
return user;
}
加了 [Authorize]
特性,表示这个接口需要登录才能用
swagger小绿锁
其实就是swagger的请求过滤器,配置了token之后,可以在请求的时候带上token,访问需要登录的接口。
首先要安装一个新的nuget依赖
dotnet add package Swashbuckle.AspNetCore.Filters
这个组件的项目地址:https://github.com/mattfrear/Swashbuckle.AspNetCore.Filters
为了使Program.cs
的代码尽量简洁,我们依然新建一个扩展方法来放swagger的配置
在Extensions
目录下新建ConfigureSwagger
类
代码如下
using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.Filters;
namespace StarBlog.Web.Extensions;
public static class ConfigureSwagger {
public static void AddSwagger(this IServiceCollection services) {
services.AddSwaggerGen(options => {
var security = new OpenApiSecurityScheme {
Description = "JWT模式授权,请输入 \"Bearer {Token}\" 进行身份验证",
Name = "Authorization",
In = ParameterLocation.Header,
Type = SecuritySchemeType.ApiKey
};
options.AddSecurityDefinition("oauth2", security);
options.AddSecurityRequirement(new OpenApiSecurityRequirement { { security, new List<string>() } });
options.OperationFilter<AddResponseHeadersFilter>();
options.OperationFilter<AppendAuthorizeToSummaryOperationFilter>();
options.OperationFilter<SecurityRequirementsOperationFilter>();
var filePath = Path.Combine(System.AppContext.BaseDirectory, $"{typeof(Program).Assembly.GetName().Name}.xml");
options.IncludeXmlComments(filePath, true);
});
}
}
然后回到Program.cs
文件,添加这行代码就行
builder.Services.AddSwagger();