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();

結構搭建

下面把項目基本結構搭建好,做好介面,後面實現:

image-20210927232821355

以下是各個類的定義:

// 用戶註冊請求參數
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中的RegisterAsyncLoginAsync方法了。這裡主要用到identity中的UserManagerUserManager封裝了很多用戶操作的現成方法。

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"));

測試一下

image-20210927234957931

隨便輸入abc進行註冊,返回了一些密碼規則的錯誤:

image-20210927235203175

這個規則在註冊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:

image-20210927235821636

使用剛剛註冊的帳號測試登錄,也沒有問題:

image-20210927235940868

最後

本篇完成了identity的登錄,註冊,獲取token,下一篇將介紹如何使用refresh token。

參考:

ASP.NET Core 簡介 Identity | Microsoft Docs

Mohamad Lawand – DEV Community