前言
上一篇已經介紹了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。
參考: