Web API 入口
[HttpPost("login")]
public async Task<IActionResult> Login([FromBody] LoginRequestDto loginRequest)
{
var token = await _authService.LoginAsync(loginRequest.UsernameOrEmail, loginRequest.Password, 900);
return Ok(new { AccessToken = token.AccessToken, Expiration = token.Expiration, RefreshToken = token.RefreshToken });
}
api返回的json
{
"accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1lIjoiYWRtaW4iLCJodHRwOi8vc2NoZW1hcy5taWNyb3NvZnQuY29tL3dzLzIwMDgvMDYvaWRlbnRpdHkvY2xhaW1zL3JvbGUiOiJBZG1pbiIsIm5iZiI6MTcxMjA1NDcwNSwiZXhwIjoxNzEyMDU1NjA1LCJpc3MiOiJ3d3cubXlhcGkuY29tIiwiYXVkIjoid3d3LmJpbG1lbW5lLmNvbSJ9.O8cY-lBV-uWNPo4R9TJEdmzV4R7TbCja3N7pslU5WRQ",
"expiration": "2024-04-02T11:00:05Z",
"refreshToken": "JBKlltji4txzErH660t+lydjEhxCKJbP0HlEbakxFJM="
}
MVC Enpoint - 请求被丢弃的 enpoint
[HttpPost]
public async Task<IActionResult> Login(LoginWebDto model)
{
var request = new HttpRequestMessage(HttpMethod.Post, "http://localhost:5026/api/Auth/login");
var loginData = new
{
UsernameOrEmail = model.UsernameOrEmail,
Password = model.Password
};
request.Content = new StringContent(JsonConvert.SerializeObject(loginData), System.Text.Encoding.UTF8,
"application/json");
var client = _httpClientFactory.CreateClient();
var response = await client.SendAsync(request);
if (response.IsSuccessStatusCode)
{
var content = await response.Content.ReadAsStringAsync();
var tokenResponse = JsonConvert.DeserializeObject<TokenResponseDto>(content);
var handler = new JwtSecurityTokenHandler();
var token = handler.ReadJwtToken(tokenResponse.AccessToken);
_httpContextAccessor.HttpContext.Session.SetString("AccessToken", tokenResponse.AccessToken);
_httpContextAccessor.HttpContext.Session.SetString("RefreshToken", tokenResponse.RefreshToken);
return RedirectToAction("Index", "Home");
}
else
{
ModelState.AddModelError(string.Empty, "Login failed. Incorrect username or password.");
return View(model);
}
}
Login.cshtml 查看
@model WebMVC.DTOs.Login.LoginWebDto
<!DOCTYPE html>
<html>
<head>
<title>Login</title>
</head>
<body>
<h1>Login</h1>
@using (Html.BeginForm("Login", "Account", FormMethod.Post, new { @class = "login-form", id = "login-form" }))
{
@Html.AntiForgeryToken()
<div>
<label asp-for="UsernameOrEmail">Username or Email:</label>
<input value="admin" asp-for="UsernameOrEmail" />
<span asp-validation-for="UsernameOrEmail"></span>
</div>
<div>
<label asp-for="Password">Password:</label>
<input value="Admin1." asp-for="Password" type="password" />
<span asp-validation-for="Password"></span>
</div>
<button type="submit">Login</button>
}
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script>
$(document).ready(function() {
$('#login-form').submit(function(event) {
var formData = {
UsernameOrEmail: $('input[name="UsernameOrEmail"]').val(),
Password: $('input[name="Password"]').val()
};
$.ajax({
type: 'POST',
url: 'http://localhost:5026/api/Auth/login',
data: JSON.stringify(formData),
contentType: 'application/json',
success: function(response) {
localStorage.setItem('accessToken', response.accessToken);
localStorage.setItem('refreshToken', response.refreshToken);
window.location.href = '/Home/Index';
},
error: function(xhr, status, error) {
console.error('Login failed:', error);
alert('Login failed. Incorrect username or password.');
}
});
return true;
});
$.ajaxSetup({
beforeSend: function(xhr) {
var accessToken = localStorage.getItem('accessToken');
if (accessToken) {
xhr.setRequestHeader('Authorization', 'Bearer ' + accessToken);
}
}
});
})
</script>
</body>
</html>
API程序.cs
using System.Configuration;
using System.Security.Claims;
using System.Text;
using System.Text.Json.Serialization;
using Application.Abstractions.Services;
using Domain.Entities.Identity;
using Infrastructure;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Microsoft.IdentityModel.Tokens;
using Microsoft.OpenApi.Models;
using Persistence;
using Persistence.Context;
using Persistence.Services;
var builder = WebApplication.CreateBuilder(args);
var configuration = builder.Configuration;
// Add services to the container.
builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.AddCors(options => options.AddDefaultPolicy(policy =>
policy.WithOrigins("http://localhost:5273", "https://localhost:5273").AllowAnyHeader().AllowAnyMethod()
.AllowCredentials()
));
builder.Services.AddSession();
builder.Services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));
builder.Services.AddControllersWithViews()
.AddJsonOptions(opt => opt.JsonSerializerOptions.ReferenceHandler = ReferenceHandler.IgnoreCycles);
builder.Services.AddIdentity<AppUser, IdentityRole>().AddEntityFrameworkStores<ApplicationDbContext>();
builder.Services.AddScoped<IUserService, UserService>();
builder.Services.AddScoped<IAuthService, AuthService>();
builder.Services.AddInfrastructureServices();
builder.Services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateAudience = true,
ValidateIssuer = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidAudience = builder.Configuration["Token:Audience"],
ValidIssuer = builder.Configuration["Token:Issuer"],
IssuerSigningKey =
new SymmetricSecurityKey(Encoding.UTF8.GetBytes(builder.Configuration["Token:SecurityKey"])),
LifetimeValidator = (notBefore, expires, securityToken, validationParameters) =>
expires != null ? expires > DateTime.UtcNow : false,
NameClaimType = ClaimTypes.Name,
RoleClaimType = ClaimTypes.Role,
};
});
builder.Services.AddAuthorization(options =>
{
options.AddPolicy("ElevatedRights", policy =>
policy.RequireRole("Admin"));
});
builder.Services.AddSwaggerGen(c =>
{
c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme()
{
Name = "Authorization",
Type = SecuritySchemeType.ApiKey,
Scheme = "Bearer",
BearerFormat = "JWT",
In = ParameterLocation.Header,
Description =
"JWT Authorization header using the Bearer scheme. \r\n\r\n Enter 'Bearer' [space] and then your token in the text input below.\r\n\r\nExample: \"Bearer eyJhbGciOiJIU125InR5cCI6IkpXVCJ9.eyJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1Law0aXR5L2NsYWltcy9uYW1laWRlbnRpZmllciI52MzA3ZGNkLTUyZWItNDAwZi04NWJlLTI3MGIxNWUwZjRlYiIsImh0dHA6Ly9zY2hlbWFzLnhtbHNvYXAub3JnL3dzLzIwMDUvMDUvaWRlbnRpdHkvY2xhaW1zL25hbWUiOiJhZG1pbjEiLCJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1L124kZW50aXR5L2NsYWltcy9lbWFpbGFkZHJlc3MiOiJhZG1pbkBleGFtcGxlLmNvbSIsImh0dHA6Ly9zY2hlbWFzLm1pY3Jvc29mdC5jb20vd3MvMjAwOC8wNi9pZGVudGl0eS9jbGFpbXMvcm9sZSI6IkFkbWluIiwiZXhwIjoxNzA4MTcxNDUxLCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjUwMjYiLCJhdWQiOiJodHRwOi8vbG9jYWxob3N0OjUwMjYifQ.G0aHe5M_D7QOEYnidH-s7Cf48Ftf512sEUCyLbIN\"",
});
c.AddSecurityRequirement(new OpenApiSecurityRequirement
{
{
new OpenApiSecurityScheme
{
Reference = new OpenApiReference
{
Type = ReferenceType.SecurityScheme,
Id = "Bearer"
}
},
new string[] { }
}
});
});
var app = builder.Build();
using (var scope = app.Services.CreateScope())
{
var services = scope.ServiceProvider;
var userManager = services.GetRequiredService<UserManager<AppUser>>();
var roleManager = services.GetRequiredService<RoleManager<IdentityRole>>();
SeedData.Initialize(services, userManager, roleManager).Wait();
services.GetRequiredService<ILogger<Program>>();
}
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseCors();
app.UseHttpsRedirection();
app.UseAuthentication();
app.UseAuthorization();
app.UseSession();
app.MapControllers();
app.Run();
MVC 程序.cs
using Domain.Entities.Identity;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Persistence.Context;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddHttpContextAccessor();
builder.Services.AddSession();
builder.Services.AddHttpClient();
builder.Services.AddControllersWithViews();
builder.Services.AddControllersWithViews().AddRazorRuntimeCompilation();
builder.Services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));
builder.Services.AddIdentity<AppUser, IdentityRole>(options =>
{
options.User.RequireUniqueEmail = true;
})
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
var app = builder.Build();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Home/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseSession();
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
app.Run();
我在使用本地存储中的令牌进行授权时遇到问题。对 API 端点的请求返回状态代码 200,但是当导航到标记为 [Authorize] 的页面时,它返回状态代码 302。
您的API项目使用JWT进行验证,而MVC项目使用Identity cookie验证。当你在没有登录的情况下访问MVC中的授权页面时,会被重定向到登录页面进行验证,所以会出现302。
您提供的代码中,似乎API项目中有一个生成token的方法,并配置了相关的token认证。在你看来,通过表单提交访问login方法调用Login,处理登录逻辑:创建httpClientFactory实例,去API获取token,保存到session中,成功返回home/index,但是失败。然后返回当前页面。 验证过程中,如果要验证token,应该从mvc项目中的session中获取token进行验证:
builder.Services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
options.Events = new JwtBearerEvents
{
OnMessageReceived = context =>
{
var accessToken = context.HttpContext.Session.GetString("AccessToken");
context.Token = accessToken;
return Task.CompletedTask;
}
};
options.SaveToken = true;
options.RequireHttpsMetadata = false;
options.TokenValidationParameters = new TokenValidationParameters()
{
ValidateIssuer = true,
ValidateAudience = true,
ValidAudience = configuration["JWT:ValidAudience"],
ValidIssuer = configuration["JWT:ValidIssuer"],
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(configuration["JWT:Secret"]))
};
});