【发布时间】:2021-08-03 08:27:19
【问题描述】:
我在 VS Code 中使用 HTML/Javascript,并在 Visual Studio 2019 中使用 Dot net core 3.1 进行 Web API 开发。还在 Windows 10 专业版上使用 IIS 来测试 API。
- 使用以下代码开发登录页面。一旦用户输入用户名和密码,点击登录按钮,就会调用 Web api“TestAuthService”。
function fnlogin() {
const uname = document.getElementById('uname').value;
const pwd = document.getElementById('pwd').value;
const logindata = {
username: uname,
password: pwd
}
const loginurl = 'http://localhost:8091/api/Auth/Login';
authenticate(loginurl, logindata);
}
async function authenticate(loginurl, logindata) {
console.log(logindata)
const response = await fetch(loginurl , {
method: "POST",
mode: "cors",
body: JSON.stringify(logindata),
headers: { "Content-type" : "application/json, charset=UTF-8"}
});
const rdata = await response.json();
console.log(rdata);
if (!rdata.success) {
document.getElementById("loginMessage").innerHTML = rdata.message;
return;
}
const inMemoryToken = rdata.data
localStorage.setItem('user', JSON.stringify(rdata));
window.location.href = "http://127.0.0.1:5500/Weatherinfo.html";
}
- API TestAuthService 在 IIS 中发布在 localhost:8091。成功登录后,将向 javascript 返回一个 JWT。这工作正常。 JWT 通过 Javascript 存储在 chrome 浏览器的 localStorage 中。 认证控制器代码如下:
[Route("api/[controller]")]
[ApiController]
public class AuthController : ControllerBase
{
private readonly IAuthRepository _authRepo;
public AuthController(IAuthRepository authRepo)
{
_authRepo = authRepo;
}
[HttpPost("Register")]
public async Task<ActionResult<ServiceResponse<int>>> Register(UserRegisterDto request)
{
var response = await _authRepo.Register(
new User { Username = request.Username }, request.Password
);
if (!response.Success)
{
return BadRequest(response);
}
return Ok(response);
}
[HttpPost("Login")]
public async Task<ActionResult<ServiceResponse<string>>> Login(UserLoginDto request)
{
var response = await _authRepo.Login(
request.Username, request.Password
);
if (!response.Success)
{
return BadRequest(response);
}
return Ok(response);
}
AuthRepository 代码如下:
public class AuthRepository : IAuthRepository
{
private readonly AppDbContext _context;
private readonly IConfiguration _configuration;
public AuthRepository(AppDbContext context, IConfiguration configuration)
{
_configuration = configuration;
_context = context;
}
public async Task<ServiceResponse<string>> Login(string username, string password)
{
var response = new ServiceResponse<string>();
var user = await _context.Users.FirstOrDefaultAsync(x => x.Username.ToLower().Equals(username.ToLower()));
if (user == null)
{
response.Success = false;
response.Message = "User not found.";
}
else if (!VerifyPasswordHash(password, user.PasswordHash, user.PasswordSalt))
{
response.Success = false;
response.Message = "Wrong password.";
}
else
{
response.Data = CreateToken(user);
}
return response;
}
public async Task<ServiceResponse<User>> Register(User user, string password)
{
ServiceResponse<User> response = new ServiceResponse<User>();
if (await UserExists(user.Username))
{
response.Success = false;
response.Message = "User already exists.";
return response;
}
CreatePasswordHash(password, out byte[] passwordHash, out byte[] passwordSalt);
user.PasswordHash = passwordHash;
user.PasswordSalt = passwordSalt;
_context.Users.Add(user);
await _context.SaveChangesAsync();
response.Data = user;
return response;
}
public async Task<bool> UserExists(string username)
{
if (await _context.Users.AnyAsync(x => x.Username.ToLower().Equals(username.ToLower())))
{
return true;
}
return false;
}
private void CreatePasswordHash(string password, out byte[] passwordHash, out byte[] passwordSalt)
{
using (var hmac = new System.Security.Cryptography.HMACSHA512())
{
passwordSalt = hmac.Key;
passwordHash = hmac.ComputeHash(System.Text.Encoding.UTF8.GetBytes(password));
}
}
private bool VerifyPasswordHash(string password, byte[] passwordHash, byte[] passwordSalt)
{
using (var hmac = new System.Security.Cryptography.HMACSHA512(passwordSalt))
{
var computedHash = hmac.ComputeHash(System.Text.Encoding.UTF8.GetBytes(password));
for (int i = 0; i < computedHash.Length; i++)
{
if (computedHash[i] != passwordHash[i])
{
return false;
}
}
return true;
}
}
private string CreateToken(User user)
{
var claims = new List<Claim>
{
new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
new Claim(ClaimTypes.Name, user.Username)
};
var key = new SymmetricSecurityKey(System.Text.Encoding.UTF8.GetBytes(_configuration.GetSection("AppSettings:Token").Value));
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha512Signature);
var tokendDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(claims),
Expires = System.DateTime.Now.AddDays(1),
SigningCredentials = creds
};
var tokenHandler = new JwtSecurityTokenHandler();
var token = tokenHandler.CreateToken(tokendDescriptor);
return tokenHandler.WriteToken(token);
}
- 如果登录成功,会显示Weatherinfo.html页面,该页面有“获取天气数据”按钮,点击后会调用另一个web api“天气预报”
<button type="button" onclick="return getWeather();">Get Weather data</button>
<table id="weatherdata">
<thead>
</thead>
<tbody id="weatherdatalist">
</tbody>
</table>
<script>
async function getWeather() {
const url = 'http://localhost:5861/weatherforecast';
const localstorage_user = JSON.parse(localStorage.getItem('user'));
const inMemToken = localstorage_user.data
console.log(inMemToken)
/*
const response = await fetch(url);
*/
const response = await fetch(url, {
headers: {
Authorization: 'Bearer ${inMemToken}'
}
});
const data = await response.json();
console.log(data)
display(data);
}
function display(data) {
let tab = "";
data.forEach(element => {
tab += `<tr>
<td > ${element.date} </td>
<td> ${element.temperatureC} </td>
<td> ${element.temperatureF} </td>
<td> ${element.summary} </td>
</tr>`;
});
document.getElementById("weatherdatalist").innerHTML = tab;
}
</script>
- 天气预报 api 有以下代码
启动类
public void ConfigureServices(IServiceCollection services)
{
services.AddCors(o => o.AddPolicy("AllowOrigins", builder =>
{
builder.WithOrigins("http://localhost:5500", "http://127.0.0.1:5500")
.AllowAnyMethod()
.AllowAnyHeader();
}));
services.AddControllers();
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = false,
ValidateIssuer = false,
ValidateAudience = false
};
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseAuthentication();
app.UseRouting();
app.UseCors("AllowOrigins");
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
控制器如下
[ApiController]
[Route("[controller]")]
[Authorize]
public class WeatherForecastController : ControllerBase
{
private static readonly string[] Summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};
private readonly ILogger<WeatherForecastController> _logger;
public WeatherForecastController(ILogger<WeatherForecastController> logger)
{
_logger = logger;
}
[HttpGet]
public IEnumerable<WeatherForecast> Get()
{
var rng = new Random();
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = DateTime.Now.AddDays(index),
TemperatureC = rng.Next(-20, 55),
Summary = Summaries[rng.Next(Summaries.Length)]
})
.ToArray();
}
- 问题是 weatherforcast api 失败并出现 HTTP 错误 401(未经授权)
我不确定我是否在下面的代码中正确传递了 JWT
const response = await fetch(url, {
headers: {
Authorization: 'Bearer ${inMemToken}'
}
});
【问题讨论】:
-
您能分享您的身份验证控制器登录方法吗?我们需要知道您是如何生成令牌的。另外,我建议您可以尝试使用 postman 来测试生成的令牌是否正确,并可以发送到身份验证 api。
-
我在#2中添加了代码
-
我使用Postman验证生成的token,使用jwt.io验证token
-
您确保令牌是正确的,并且您可以使用该令牌访问 Web api 对吗?
-
我刚刚使用 Postman GET 请求进行了测试,并将不记名令牌粘贴到 Postman 的 Auth 部分。我得到 200 OK 响应,但在 Postman 响应正文部分看不到天气数据。相反,我在那里看到了实际的 weatherinfo.html 页面,
标签: javascript asp.net-core .net-core