Я уже некоторое время стучу головой. Я установил Identity, сопоставленный с MySQL DB для моего API.
Я загружаю свои роли и одного пользователя при запуске:
if (!_roleManager.RoleExistsAsync("Admin").Result)
{
Role role = new Role();
role.Name = "Admin";
IdentityResult roleResult = _roleManager.CreateAsync(role).Result;
}
if (_userManager.FindByNameAsync("myuser").Result == null)
{
User user = new User();
user.UserName = "myuser";
user.Email = "myuser@test.com";
// Not secure, to be moved
user.PasswordHash = SecurePasswordHasher.Hash("testtest");
IdentityResult result = _userManager.CreateAsync(user).Result;
if (result.Succeeded)
{
_userManager.AddToRoleAsync(user, "Admin").Wait();
}
}
Таблицы Aspnetroles
, Aspnetusers
и Aspnetuserroles
заполнены правильно. При входе в систему получите свой токен на предъявителя и попробуйте получить доступ к конечной точке API, например:
[HttpGet]
[Authorize]
public async Task<IActionResult> Index()
{
// Get objects
var objects = _repository.FindAll();
// Return response
return Ok(objects);
}
Работает нормально. Без токена я получаю 401 Несанкционированный. Все нормально. Но когда я пытаюсь получить доступ к конечной точке, как эта:
[HttpGet]
[Authorize(Roles = "Admin")]
public async Task<IActionResult> Index()
{
// Get objects
var objects = _repository.FindAll();
// Return response
return Ok(objects);
}
Это не работает, я получаю 403 Запрещено. Что бы я хотел, если бы у меня была неправильная роль, но это не так. Если я назначу роль null
вместо "Admin"
, то это сработает! Поэтому я предполагаю, что что-то не так, и приложение не может получить роль моего пользователя.
DbContext:
public class AppContext : IdentityDbContext<User, Role, int, UserClaim, UserRole, UserLogin, RoleClaim, UserToken>
{
private Settings Settings { get; }
// Constructor
public AppContext()
{
// Build the MySql settings:
Settings = new Settings("dev")
.Configure(new MysqlSettings())
.Build();
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
if(!optionsBuilder.IsConfigured)
{
optionsBuilder.UseLazyLoadingProxies();
optionsBuilder.UseMySql(Settings.GetRequiredConf<MysqlSettings>().Connection +
"TreatTinyAsBoolean=false;");
}
}
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
builder.Entity<Role>().ToTable("role");
builder.Entity<User>().ToTable("user");
builder.Entity<UserClaim>().ToTable("user_claim");
builder.Entity<UserToken>().ToTable("user_token");
builder.Entity<UserLogin>().ToTable("user_login");
builder.Entity<RoleClaim>().ToTable("role_claim");
builder.Entity<UserRole>(entity =>
{
entity.ToTable("user_role");
entity.HasOne(d => d.User)
.WithOne(p => (Data.Models.Identity.UserRole)p.UserRole)
.HasForeignKey<UserRole>(d => d.UserId)
.OnDelete(DeleteBehavior.Cascade)
.HasConstraintName("FK_user_role_user_UserId");
entity.HasOne(d => d.Role)
.WithOne(p => (Data.Models.Identity.UserRole)p.UserRole)
.HasForeignKey<UserRole>(d => d.RoleId)
.OnDelete(DeleteBehavior.Cascade)
.HasConstraintName("FK_user_role_role_RoleId");
});
}
}
Запуск:
public void ConfigureServices(IServiceCollection services)
{
// Add the environment to the services
services.AddSingleton(Environment);
// Add the settings to the services
services.AddSingleton(Settings);
// Add repository manager
services.AddRepositoryManager();
// Add Db context
services.AddSingleton(Context);
// Mvc service
services.AddMvc(options =>
{
options.Filters.Add(new AuthorizeFilter());
})
.AddJsonOptions(options =>
{
options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
})
.SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
// Identity
services.AddIdentity<User, Role>(options =>
{
options.Tokens.PasswordResetTokenProvider = TokenOptions.DefaultEmailProvider;
options.Tokens.EmailConfirmationTokenProvider = TokenOptions.DefaultEmailProvider;
options.Password.RequireDigit = false;
options.Password.RequiredLength = 8;
options.Password.RequireLowercase = true;
options.Password.RequireNonAlphanumeric = false;
options.Password.RequireUppercase = false;
})
.AddEntityFrameworkStores<BreathBalanzContext>()
.AddDefaultTokenProviders();
// Custom password hasher
services.AddScoped<IPasswordHasher<User>, CustomPasswordHasher>();
// Authentication service
services.AddAuthentication(options =>
{
options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(o =>
{
o.Authority = Settings.GetRequiredConf<AuthSettings>().Address;
o.Audience = "Api";
o.RequireHttpsMetadata = false;
});
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env, UserManager<User> userManager, RoleManager<Role> roleManager)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseAuthentication();
// Seeding the roles and users here
Seeder.SeedData(userManager, roleManager);
app.UseMvc();
}
У меня действительно заканчиваются идеи. Первоначально отложенная загрузка не была настроена, я думал, что это было причиной проблемы, но это не помогло (хотя отложенная загрузка работает правильно сейчас). Я не уверен, что я пропустил.
EDIT:
Простое _context.Set<User>().Find(id)
возвращает следующее, что должно доказать, что Lazy Loading работает и, следовательно, должно быть достаточно для Identity, чтобы получить роль моего пользователя?
{
"userClaim": [],
"userToken": [],
"userLogin": [],
"userRole": [
{
"role": {
"userRole": [],
"roleClaim": [],
"id": 3,
"name": "Admin",
"normalizedName": "ADMIN",
"concurrencyStamp": "34d3e595-035c-4f4b-9e5e-99c8f4221701"
},
"userId": 11,
"roleId": 3
}
],
"id": 11,
"userName": "myuser",
"normalizedUserName": "MYUSER",
"email": "myuser@dtest.com",
"normalizedEmail": "MYUSER@TEST.COM",
"emailConfirmed": false,
"passwordHash": "$MYHASH$V1$10000$v19jvXvMQeU5KtwWg2vs0f3Ou6J8+ANWJaXjPEkM9eEvQ6pm",
"securityStamp": "LRITM53N625U4SSFQLCPIYN2UFJHMYJP",
"concurrencyStamp": "1897bc87-fcd0-4759-ad8c-d21210351e04",
"phoneNumber": null,
"phoneNumberConfirmed": false,
"twoFactorEnabled": false,
"lockoutEnd": null,
"lockoutEnabled": true,
"accessFailedCount": 0
}