asp.net core使用identity+jwt保護你的webapi(二)——獲取jwt token
前言
上一篇已經介紹了identity在web api中的基本配置,本篇來完成用戶的註冊,登錄,獲取jwt token。
開始
開始之前先配置一下jwt相關服務。
配置JWT
首先NuGet安裝包:
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="5.0.10" />
appsettings.json中添加jwt配置:
"JwtSettings": {
"SecurityKey": "qP1yR9qH2xS0vW2lA3gI4nF0zA7fA3hB",
"ExpiresIn": "00:10:00"
}
為了方便,新建一個配置類JwtSettings
:
public class JwtSettings
{
public string SecurityKey { get; set; }
public TimeSpan ExpiresIn { get; set; }
}
在Startup中配置jwt:
public void ConfigureServices(IServiceCollection services)
{
//省略......
var jwtSettings = Configuration.GetSection(nameof(JwtSettings)).Get<JwtSettings>();
services.AddSingleton(jwtSettings);
var tokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = false,
ValidateAudience = false,
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(jwtSettings.SecurityKey)),
ClockSkew = TimeSpan.Zero,
};
services
.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options => { options.TokenValidationParameters = tokenValidationParameters; });
}
最後別忘了UseAuthentication
:
app.UseAuthentication(); // add
app.UseAuthorization();
結構搭建
下面把項目基本結構搭建好,做好介面,後面實現:
以下是各個類的定義:
// 用戶註冊請求參數
public class RegisterRequest
{
public string UserName { get; set; }
public string Password { get; set; }
public string Address { get; set; }
}
// 用戶登錄請求參數
public class LoginRequest
{
public string UserName { get; set; }
public string Password { get; set; }
}
// 註冊 登錄 成功後返回 token
public class TokenResponse
{
[JsonPropertyName("access_token")]
public string AccessToken { get; set; }
[JsonPropertyName("token_type")]
public string TokenType { get; set; }
}
// 登錄 註冊 失敗時返回錯誤資訊public class FailedResponse{ public IEnumerable<string> Errors { get; set; }}
// IUserService 介面public interface IUserService{ Task<TokenResult> RegisterAsync(string username, string password, string address); Task<TokenResult> LoginAsync(string username, string password);}
// UserService 實現public class UserService : IUserService{ public Task<TokenResult> RegisterAsync(string username, string password, string address) { throw new System.NotImplementedException(); } public Task<TokenResult> LoginAsync(string username, string password) { throw new System.NotImplementedException(); }}
// TokenResult 定義public class TokenResult{ public bool Success => Errors == null || !Errors.Any(); public IEnumerable<string> Errors { get; set; } public string AccessToken { get; set; } public string TokenType { get; set; }}
最後是UserController
:
[Route("api/[controller]")][ApiController]public class UserController : ControllerBase{ private readonly IUserService _userService; public UserController(IUserService userService) { _userService = userService; } [HttpPost("Register")] public async Task<IActionResult> Register(RegisterRequest request) { var result = await _userService.RegisterAsync(request.UserName, request.Password, request.Address); if (!result.Success) { return BadRequest(new FailedResponse() { Errors = result.Errors }); } return Ok(new TokenResponse { AccessToken = result.AccessToken, TokenType = result.TokenType }); } [HttpPost("Login")] public async Task<IActionResult> Login(LoginRequest request) { var result = await _userService.LoginAsync(request.UserName, request.Password); if (!result.Success) { return Unauthorized(new FailedResponse() { Errors = result.Errors }); } return Ok(new TokenResponse { AccessToken = result.AccessToken, TokenType = result.TokenType }); }}
service實現
上面已經做好了基本的結構,接下來就是實現UserService
中的RegisterAsync
和LoginAsync
方法了。這裡主要用到identity中的UserManager
,UserManager
封裝了很多用戶操作的現成方法。
在UserService
中先做一個私有方法,根據user創建jwt token;用戶註冊,登錄成功後調用此方法得到token返回即可:
private TokenResult GenerateJwtToken(AppUser user){ var key = Encoding.ASCII.GetBytes(_jwtSettings.SecurityKey); var tokenDescriptor = new SecurityTokenDescriptor { Subject = new ClaimsIdentity(new[] { new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString("N")), new Claim(JwtRegisteredClaimNames.Sub, user.Id.ToString()) }), IssuedAt = DateTime.UtcNow, NotBefore = DateTime.UtcNow, Expires = DateTime.UtcNow.Add(_jwtSettings.ExpiresIn), SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature) }; var jwtTokenHandler = new JwtSecurityTokenHandler(); var securityToken = jwtTokenHandler.CreateToken(tokenDescriptor); var token = jwtTokenHandler.WriteToken(securityToken); return new TokenResult() { AccessToken = token, TokenType = "Bearer" };}
註冊方法實現:
public async Task<TokenResult> RegisterAsync(string username, string password, string address){ var existingUser = await _userManager.FindByNameAsync(username); if (existingUser != null) { return new TokenResult() { Errors = new[] {"user already exists!"}, //用戶已存在 }; } var newUser = new AppUser() {UserName = username, Address = address}; var isCreated = await _userManager.CreateAsync(newUser, password); if (!isCreated.Succeeded) { return new TokenResult() { Errors = isCreated.Errors.Select(p => p.Description) }; } return GenerateJwtToken(newUser);}
登錄方法實現:
public async Task<TokenResult> LoginAsync(string username, string password){ var existingUser = await _userManager.FindByNameAsync(username); if (existingUser == null) { return new TokenResult() { Errors = new[] {"user does not exist!"}, //用戶不存在 }; } var isCorrect = await _userManager.CheckPasswordAsync(existingUser, password); if (!isCorrect) { return new TokenResult() { Errors = new[] {"wrong user name or password!"}, //用戶名或密碼錯誤 }; } return GenerateJwtToken(existingUser);}
最後,別忘了註冊UserService
:
services.AddScoped<IUserService, UserService>();
swagger配置
為了方便測試,可以配置一下swagger
NuGet安裝包:
<PackageReference Include="Swashbuckle.AspNetCore" Version="5.6.3" />
ConfigureServices:
services.AddSwaggerGen(c =>{ c.SwaggerDoc("v1", new OpenApiInfo { Title = "Sample.Api", Version = "v1", Description = "Sample.Api Swagger Doc" }); c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme { Description = "Input the JWT like: Bearer {your token}", Name = "Authorization", In = ParameterLocation.Header, Type = SecuritySchemeType.ApiKey, BearerFormat = "JWT", Scheme = "Bearer" }); c.AddSecurityRequirement(new OpenApiSecurityRequirement { { new OpenApiSecurityScheme { Reference = new OpenApiReference { Type = ReferenceType.SecurityScheme, Id = "Bearer" } }, Array.Empty<string>() } });});
app.UseSwagger();app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "Sample.Api v1"));
測試一下
隨便輸入abc進行註冊,返回了一些密碼規則的錯誤:
這個規則在註冊identity服務時可以配置:
services.AddIdentityCore<AppUser>(options =>{ options.Password.RequireDigit = true; options.Password.RequireLowercase = false; options.Password.RequireUppercase = false; options.Password.RequireNonAlphanumeric = false;}).AddEntityFrameworkStores<AppDbContext>();
identityOptions
還支援一些其他配置。
下面註冊成功後返回了token:
使用剛剛註冊的帳號測試登錄,也沒有問題:
最後
本篇完成了identity的登錄,註冊,獲取token,下一篇將介紹如何使用refresh token。
參考: