401 .NET 8 和 Vue.js 应用程序中 JWT 身份验证出现未经授权错误

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

我正在制作一个小型应用程序,使用 .NET 8 作为后端,Vue 作为前端。

目前我有登录功能。用户登录后,他们的数据就会加载到“主”屏幕上。

我正在尝试使用 JWT 实现登录以提高安全性,并学习如何执行此操作,因为这在大型应用程序中是一个很好的做法,但我一直遇到 401 未经授权的错误并且无法解决它。

在我的 appsettings.json 文件中,我有 JWT 所需的参数:

"Jwt": {
  "Key": "8D9E3B5F7C2A4E9D1F3C6A7E9B2D4C8F",
  "Issuer": "http://localhost:5251",
  "Audience": "http://localhost:8080"
}

使用该密钥,我对用户登录时生成的令牌进行签名:

// Login method
[HttpPost("login")]
public async Task<IActionResult> Login([FromBody] LoginDto loginDto)
{
  var user = await _context.Users.FirstOrDefaultAsync(u => u.UserName == 
  loginDto.UserName);
            
  if (user == null || !BCrypt.Net.BCrypt.Verify(loginDto.Password, user.Password))
  {
     return Unauthorized(new { message = "Invalid username or password." });
  }

  // Create the JWT token
  var tokenHandler = new JwtSecurityTokenHandler();
  var key = _configuration["Jwt:Key"];

  if (string.IsNullOrEmpty(key))
  {
     throw new InvalidOperationException("JWT Key is not configured.");
  }

  var keyBytes = Encoding.UTF8.GetBytes(key);
  var tokenDescriptor = new SecurityTokenDescriptor
  {
     Subject = new ClaimsIdentity(
     [
         new(ClaimTypes.NameIdentifier, user.Id.ToString()),
         new(ClaimTypes.Name, user.UserName),
     ]),
     Expires = DateTime.UtcNow.AddDays(7),
     SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(keyBytes), SecurityAlgorithms.HmacSha256Signature),
     Issuer = _configuration["Jwt:Issuer"], 
     Audience = _configuration["Jwt:Audience"]
  };
  var token = tokenHandler.CreateToken(tokenDescriptor);
  var tokenString = tokenHandler.WriteToken(token);

  Console.WriteLine("Token: " + tokenString);
  Console.WriteLine("Issuer: " + _configuration["Jwt:Issuer"]);
  Console.WriteLine("Audience: " + _configuration["Jwt:Audience"]);

  return Ok(new { token = tokenString });
}

在本地存储中,我看到令牌已存储。 [在此插入图片描述]

Local Storage showing bearer token

在“网络”选项卡中,我看到令牌位于请求中,而响应告诉我它无效。 [在此插入图片描述]

request and response headers

我已经在 jwt.io 上验证了生成的令牌是正确的,并且除了我用于登录的用户标识符之外,我还看到“发行者”和“受众”是正确的。

在客户端,我从响应中收集服务器上生成的令牌并将其存储在本地存储中以供访问:

async handleSubmit() {
  try {
      const url = this.isRegistering 
      ? 'http://localhost:5251/auth/register' 
      : 'http://localhost:5251/auth/login'; 

      const response = await axios.post(url, { 
         username : this.username,
         password : this.password
      });

      if (this.isRegistering) {
          this.setMessage(response.data.message || "Registration successful! You can now log in.", false);
          this.clearFields(); 
      } else {
          const token = response.data.token;

          if(token) {
             localStorage.setItem('token', token)
             this.setMessage("Login successful!", false)
             this.login({ username: this.username })
             this.$router.push({ name: 'Home' });
          }
          else {
             this.setMessage('Login failed. Token not received', true);
          }

      }               
  } catch (error) {
      this.setMessage(error.response?.data?.message || "An error occurred. Please try again.", true);
       console.error('Error:', error.response.data);
  }
},

进入主页后,我从客户端访问本地存储以检索令牌并确认当前登录的用户是正确的:

methods: {
    async fetchTasks() {
      try {
        console.log("token", localStorage.getItem("token"));
        const response = await axios.get('http://localhost:5251/api/task/upcoming', {
            headers: {
                Authorization: `Bearer ${localStorage.getItem('token')}`,
            },
        });
        this.tasks = response.data;
      } catch (error) {
        console.error('Error fetching tasks:', error);
      }
    },
  },

这是后端方法:

[Authorize]
[Route("api/[controller]")]
[ApiController]
public class TaskController(TaskManagerContext context) : ControllerBase
{
   private readonly TaskManagerContext _context = context;

   // Method to get the next 3 tasks for a user
   [HttpGet("upcoming")]
   public async Task<IActionResult> GetUpcomingTasks()
   {
      var userIdClaim = User.Claims.FirstOrDefault(c => c.Type == ClaimTypes.NameIdentifier);
      if (userIdClaim == null)
      {
         return Unauthorized();
      }
      var userId = int.Parse(userIdClaim.Value);

      // Query tasks for the authenticated user
      var upcomingTasks = await _context.Tasks
          .Where(t => t.UserId == userId)
          .OrderBy(t => t.Date)
          .Take(3)
          .ToListAsync();

      return Ok(upcomingTasks);
   }

}

我的Postman也有同样的问题。

在我的 Program.cs 中,我按照所需的顺序注册了服务:

using Microsoft.EntityFrameworkCore;
using backend.Data;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
using System.Text;
using backend.Services;

var builder = WebApplication.CreateBuilder(args);

// Database connection
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection") ?? throw new InvalidOperationException("Connection string 'DefaultConnection' not found.");
builder.Services.AddDbContext<TaskManagerContext>(options =>
    options.UseNpgsql(connectionString));

// Add services to the container.
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

// Add controllers support
builder.Services.AddControllersWithViews();
builder.Services.AddCors(options => {
    options.AddPolicy("AllowAllOrigins",
        builder => builder
                    .AllowAnyOrigin()
                    .AllowAnyMethod()
                    .AllowAnyHeader());
});

builder.Services.AddAuthentication(options =>
{
    options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
    options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
    options.TokenValidationParameters = new TokenValidationParameters
    {
        ValidateIssuer = true,
        ValidateAudience = true,
        ValidateLifetime = true,
        ValidateIssuerSigningKey = true,
        ValidIssuer = builder.Configuration["Jwt:Issuer"],
        ValidAudience = builder.Configuration["Jwt:Audience"],
        IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(builder.Configuration["Jwt:Key"]))
    };
});

var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseDeveloperExceptionPage();
}
else
{
    app.UseExceptionHandler("/Home/Error");
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

// Apply CORS policy
app.UseCors("AllowAllOrigins");

app.UseAuthentication();
app.UseAuthorization();

app.MapControllerRoute(
    name: "default",
    pattern: "{controller=Home}/{action=Index}/{id?}"
);

app.Run();

因此,我不知道如何继续。我已经对所有内容进行了多次检查,没有发现任何问题。我是否应该放弃使用 JWT 的想法,因为它在我的应用程序中不值得?欢迎任何帮助。

c# vue.js jwt asp.net-core-webapi .net-8.0
1个回答
0
投票

我在 .Net 8 中创建了一个新的 MVC 应用程序,因为您的应用程序中有

builder.Services.AddControllersWithViews();
app.MapControllerRoute
,但我无法重现您的问题。我将 Program.cs 中的配置连同 appsettings.json 一起实现到我的应用程序中。我使用您的代码将访问令牌生成到 HomeController 中,我还像您一样创建了一个 TaskController 。你可以看到我的代码和我使用的nuget包版本。至少我可以使用API工具成功调用API端点。

您能否像我使用硬代码信息一样先评论一下真正的身份验证模块,以便我们可以验证问题是否与身份验证或其他部分有关?我的 Program.cs 和 appsettings.json 使用与您相同的配置。

public class HomeController : Controller
{
    private readonly ILogger<HomeController> _logger;
    private readonly IConfiguration _configuration;

    public HomeController(ILogger<HomeController> logger, IConfiguration configuration)
    {
        _logger = logger;
        _configuration = configuration;
    }

    public IActionResult Index()
    {
        var tokenHandler = new JwtSecurityTokenHandler();
        var key = _configuration["Jwt:Key"];

        if (string.IsNullOrEmpty(key))
        {
            throw new InvalidOperationException("JWT Key is not configured.");
        }

        var keyBytes = Encoding.UTF8.GetBytes(key);
        var tokenDescriptor = new SecurityTokenDescriptor
        {
            Subject = new ClaimsIdentity(
            [
               new(ClaimTypes.NameIdentifier, "asdf"),
                 new(ClaimTypes.Name, "user_name"),
            ]),
            Expires = DateTime.UtcNow.AddDays(7),
            SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(keyBytes), SecurityAlgorithms.HmacSha256Signature),
            Issuer = _configuration["Jwt:Issuer"],
            Audience = _configuration["Jwt:Audience"]
        };
        var token = tokenHandler.CreateToken(tokenDescriptor);
        var tokenString = tokenHandler.WriteToken(token);
        ViewBag.Token = tokenString;
        return View();
    }
}

[Authorize]
[Route("api/[controller]")]
[ApiController]
public class TaskController(TaskManagerContext context) : ControllerBase
{
    private readonly TaskManagerContext _context = context;

    [HttpGet("upcoming")]
    public async Task<IActionResult> GetUpcomingTasks()
    {
        return Ok("success");
    }
}

<ItemGroup>
    <PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.7" />
    <PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="8.0.10" />
    <PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0" />
    <PackageReference Include="Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore" Version="6.0.6" />
    <PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="6.0.26" />
</ItemGroup>

enter image description here

© www.soinside.com 2019 - 2024. All rights reserved.