我无法使用本地存储中的令牌进行授权。对 API 的请求返回 200,但导航到 [Authorize] 页面会产生 302

问题描述 投票:0回答:1

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。

asp.net-core asp.net-web-api jwt asp.net-core-mvc access-token
1个回答
0
投票

您的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"]))
    };
});
© www.soinside.com 2019 - 2024. All rights reserved.