Среда:
- Версия ASP.NET Core: 8.0
- Среда размещения: Azure
Токен JWT создан и успешно проверен в AuthenticationController. Однако когда я пытаюсь получить доступ к другим контроллерам, требующим авторизации, я получаю ответ 401 Unauthorized. Издатель JWT, аудитория и секрет правильно настроены в Azure и успешно используются в AuthenticationController.
В целях тестирования я добавил действие, которое хочу использовать. в другом контроллере (который не работает) внутри AuthenticationController, и он работает даже без атрибута [Authorize] на этом контроллере.
Код: Выделить всё
using Expenses.Data;
using expensesBE.Data.DTO.Interfaces;
using expensesBE.Data.DTO;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
using System.Text;
var builder = WebApplication.CreateBuilder(args);
// Carica le variabili d'ambiente
var jwtIssuer = Environment.GetEnvironmentVariable("JWT_ISSUER");
var jwtSecret = Environment.GetEnvironmentVariable("JWT_SECRET");
var jwtAudience = Environment.GetEnvironmentVariable("JWT_AUDIENCE");
if (string.IsNullOrEmpty(jwtIssuer) || string.IsNullOrEmpty(jwtSecret) || string.IsNullOrEmpty(jwtAudience))
{
throw new ArgumentNullException("JWT configuration is missing");
}
// Costruisci la stringa di connessione
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
// Add services to the container.
builder.Services.AddDbContext(options =>
options.UseSqlServer(connectionString));
// Configura Identity
builder.Services.AddDefaultIdentity(options =>
{
options.SignIn.RequireConfirmedAccount = true;
})
.AddRoles()
.AddEntityFrameworkStores();
// Configura l'autenticazione JWT
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 = jwtIssuer,
ValidAudience = jwtIssuer,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtSecret))
};
});
// Aggiungi i servizi personalizzati
builder.Services.AddScoped();
builder.Services.AddScoped();
builder.Services.AddSingleton();
// Configura CORS
builder.Services.AddCors(options =>
{
options.AddPolicy("AllowAllOrigins",
builder => builder
.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader());
});
// Add services to the container.
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.AddControllersWithViews();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Home/Error");
app.UseHsts();
}
app.UseSwagger(); // Enable Swagger
app.UseSwaggerUI(); // Enable Swagger UI
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseCors("AllowAllOrigins");
app.UseAuthentication();
app.UseAuthorization();
app.MapRazorPages();
app.MapControllers();
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
app.Run();
Код: Выделить всё
--using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
namespace expensesBE.Data.DTO.Utilities
{
public static class JwtGenerator
{
public static string GenerateUserToken(string username)
{
var claims = new Claim[]
{
new Claim(ClaimTypes.Name, username),
};
return GenerateToken(claims, DateTime.UtcNow.AddDays(1));
}
private static string GenerateToken(Claim[] claims, DateTime expires)
{
var tokenHandler = new JwtSecurityTokenHandler();
var secret = Environment.GetEnvironmentVariable("JWT_SECRET");
var issuer = Environment.GetEnvironmentVariable("JWT_ISSUER");
var audience = Environment.GetEnvironmentVariable("JWT_AUDIENCE");
var key = Encoding.ASCII.GetBytes(secret);
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(claims),
Expires = expires,
Issuer = issuer,
Audience = audience,
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
};
var token = tokenHandler.CreateToken(tokenDescriptor);
return tokenHandler.WriteToken(token);
}
}
}
Код: Выделить всё
--using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Authorization;
using expensesBE.Models;
using expensesBE.Data;
using System.Threading.Tasks;
using System.Linq;
using expensesBE.Data.DTO;
using Expenses.Data;
using Microsoft.EntityFrameworkCore;
using System.Security.Claims;
namespace expensesBE.Controllers
{
[ApiController]
[Route("api/[controller]")]
[Authorize] // Richiede l'autenticazione per tutte le azioni del controller
public class ExpensesController : ControllerBase
{
private readonly AppDbContext _context;
public ExpensesController(AppDbContext context)
{
_context = context;
}
// POST: api/expenses
[HttpPost("AddExpense")]
public async Task AddExpense([FromBody] Expense expense)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState); // Ritorna un BadRequest se il modello non è valido
}
var userId = User.FindFirst(ClaimTypes.Name)?.Value ?? User.FindFirst("unique_name")?.Value;
expense.UserId = userId; // Associa l'ID utente alla spesa
_context.Expenses.Add(expense);
await _context.SaveChangesAsync();
return Created("api/expenses/AddExpense", expense); // Ritorna CreatedAtAction
}
}
}
Код: Выделить всё
--using Microsoft.AspNetCore.Mvc;
using expensesBE.Data.DTO.CustomExceptions;
using Microsoft.AspNetCore.Identity;
using expensesBE.Data.DTO.Interfaces;
using expensesBE.Data.DTO;
using Expenses.Data;
using Microsoft.EntityFrameworkCore;
using Expenses.Data; // Aggiungi questo spazio dei nomi
using expensesBE.Models;
namespace expensesBE.Controllers
{
[ApiController]
[Route("[controller]")]
public class AuthenticationController : ControllerBase
{
private readonly IUserService _userService;
private readonly UserManager _userManager;
private readonly AppDbContext _dbContext;
public AuthenticationController(UserManager userManager, IUserService userService, AppDbContext dbContext)
{
_userManager = userManager;
_userService = userService;
_dbContext = dbContext;
}
[HttpPost("signup")]
public async Task SignUp([FromBody] UserDto userDto)
{
try
{
var result = await _userService.Signup(userDto);
return Created("", result);
}
catch (UsernameAlreadyExistsException e)
{
return StatusCode(409, e.Message);
}
}
[HttpPost("signin")]
public async Task SignIn([FromBody] UserDto userDto)
{
try
{
var result = await _userService.SignIn(userDto);
return Ok(result);
}
catch (InvalidUsernamePasswordException e)
{
return StatusCode(401, e.Message);
}
}
[HttpGet("ConfirmEmail")]
public async Task ConfirmEmail(string userId, string token)
{
if (string.IsNullOrEmpty(userId) || string.IsNullOrEmpty(token))
{
return BadRequest("UserId and Token are required");
}
var user = await _userManager.FindByIdAsync(userId);
if (user == null)
{
return NotFound("User not found");
}
var result = await _userManager.ConfirmEmailAsync(user, token);
if (result.Succeeded)
{
return Ok("Email confirmed successfully");
}
return BadRequest("Email confirmation failed");
}
[HttpPost("AddExpense")]
public async Task AddExpense([FromBody] Expense expense)
{
if (expense == null)
{
return BadRequest("Expense data is required");
}
// Verifica se l'UserId esiste nella tabella AspNetUsers
var userExists = await _userManager.FindByIdAsync(expense.UserId) != null;
if (!userExists)
{
return BadRequest("The specified UserId does not exist.");
}
// Aggiungi la nuova spesa al contesto del database
await _dbContext.Expenses.AddAsync(expense);
await _dbContext.SaveChangesAsync();
return CreatedAtAction(nameof(AddExpense), new { id = expense.Id }, expense);
}
}
}
Тестирование с помощью Postman: я использовал Postman для отправки запросов к конечной точке AddExpense с помощью JWT. токен, включенный в заголовок авторизации как предъявитель . Несмотря на это, я получил ответ 401 Unauthorized.
Проверка утверждений токена: я проверил токен с помощью jwt.io, чтобы убедиться, что он содержит правильные утверждения и правильно подписан. Согласно веб-сайту, токен действителен.
Альтернативное тестирование: для тестирования я временно переместил действие AddExpense в AuthenticationController и подтвердил, что оно работает без [ Authorize], что указывает на то, что проблема связана с обработкой авторизации, а не с самим токеном.
Подробнее здесь: https://stackoverflow.com/questions/787 ... except-the