ASP.NET Core 身份 [Authorize(Roles ="ADMIN")] 不起作用

Posted

技术标签:

【中文标题】ASP.NET Core 身份 [Authorize(Roles ="ADMIN")] 不起作用【英文标题】:ASP.NET Core Identity [Authorize(Roles ="ADMIN")] not work 【发布时间】:2021-01-11 16:00:48 【问题描述】:

我正在使用 .NET 版本 5.0.100-rc.1.20452.10、ASP.NET Core Web API、Microsoft SQL Server 2019、JWT 令牌。我有Startup.cs

using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.Hosting;
using Microsoft.IdentityModel.Tokens;
using shadow.Data;
using shadow.Models;
using shadow.Services;
using System.IO;
using System.Text;

namespace shadow

    public class Startup
    
        public Startup(IConfiguration configuration)
        
            Configuration = configuration;
        

        public IConfiguration Configuration  get; 

        public void ConfigureServices(IServiceCollection services)
        
            services.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
            services.AddHttpContextAccessor();
            //services.AddCors();
            services.AddCors(options =>
            
                options.AddPolicy("CorsPolicy",
                    builder => builder.AllowAnyOrigin()
                        .AllowAnyMethod()
                        .AllowAnyHeader());
            );
            services.AddSingleton<IUriService>(o =>
            
                var accessor = o.GetRequiredService<IHttpContextAccessor>();
                var request = accessor.HttpContext.Request;
                var uri = string.Concat(request.Scheme, "://", request.Host.ToUriComponent());
                return new UriService(uri);
            );
            services.AddIdentity<ApplicationUser, IdentityRole>(options =>
            
                //options.Password.RequireDigit = true;
                //options.Password.RequireLowercase = true;
                options.Password.RequiredLength = 6;
            ).AddEntityFrameworkStores<ApplicationDbContext>().AddDefaultTokenProviders();
            services
                .AddAuthentication(auth =>
                
                    auth.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
                    auth.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
                )

                  .AddJwtBearer(options =>
                
                    options.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters
                    
                        ValidateIssuer = true,
                        ValidateAudience = true,
                        ValidAudience = Configuration["AuthSettings:Audience"],
                        ValidIssuer = Configuration["AuthSettings:Issuer"],
                        RequireExpirationTime = true,
                        IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["AuthSettings:Key"])),
                        ValidateIssuerSigningKey = true
                    ;
                );
            services.AddAuthorization(options =>
            
                options.FallbackPolicy = new AuthorizationPolicyBuilder()
                    .RequireAuthenticatedUser()
                    .Build();

            );
            services.AddScoped<IUserService, UserService>();
            services.AddTransient<IMailService, SendGridMailService>();            
            services.AddControllers();
            services.AddRazorPages();
            services.AddControllers(options => options.SuppressAsyncSuffixInActionNames = false);
            services.AddMvc().AddJsonOptions(options =>
            
                //options.JsonSerializerOptions.DictionaryKeyPolicy = JsonNamingPolicy.CamelCase;
                options.JsonSerializerOptions.PropertyNamingPolicy = null;
            );
        

        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        
            if (env.IsDevelopment())
            
                app.UseDeveloperExceptionPage();
            
            else
            
                app.UseHsts();
            
            //app.UseHttpsRedirection();
            app.UseRouting();
            // app.UseCors(options => options.WithOrigins("http://localhost:4200")
            //.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader());
            app.UseCors("CorsPolicy");
            app.UseAuthentication();
            app.UseAuthorization();
            // Server: https://shorten.news/static-file/content/10992.mp3
            // Local: http://localhost:5000/static-file/content/10992.mp3
            app.UseStaticFiles(new StaticFileOptions
            
                FileProvider = new PhysicalFileProvider(
                    // Path.Combine(env.ContentRootPath, @"c:\audio\")),
                    // Path.Combine(@"c:\audio\")),
                    Path.Combine(@"D:\shadow_backend\Upload\files\")
                ),
                RequestPath = "/static-file",
                OnPrepareResponse = context =>
                
                    context.Context.Response.Headers["Access-Control-Allow-Origin"] = "*";
                
            );

            app.UseEndpoints(endpoints =>
            
                endpoints.MapControllers();
            );
            app.Run(async (context) =>
            
                await context.Response.WriteAsync("cound not find anything");
            );
        
    


文件IUserService.cs

using Microsoft.AspNetCore.WebUtilities;
using Microsoft.Extensions.Configuration;
using Microsoft.IdentityModel.Tokens;
using shadow.Models;
using shadow.Shared;
using System;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Security.Claims;
using System.Text;
using System.Threading.Tasks;

namespace shadow.Services


    public interface IUserService
    

        Task<UserManagerResponse> RegisterUserAsync(RegisterViewModel model);
        Task<UserManagerResponse> LoginUserAsync(LoginViewModel model);
        Task<UserManagerResponse> LogoutUserAsync(LoginViewModel model);
        Task<UserManagerResponse> ConfirmEmailAsync(string userId, string token);
        Task<UserManagerResponse> ForgetPasswordAsync(string email);
        Task<UserManagerResponse> ResetPasswordAsync(ResetPasswordViewModel model);
        Task<UserManagerResponse> ChangePasswordAsync(ChangePasswordViewModel model);
    

    public class UserService : IUserService
    
        private Microsoft.AspNetCore.Identity.UserManager<ApplicationUser> _userManger;
        private IConfiguration _configuration;
        private IMailService _mailService;

        public UserService(Microsoft.AspNetCore.Identity.UserManager<ApplicationUser> userManager, IConfiguration configuration, IMailService mailService)
        
            _userManger = userManager;
            _configuration = configuration;
            _mailService = mailService;
        

        public async Task<UserManagerResponse> RegisterUserAsync(RegisterViewModel model)
        
            if (model == null)
            
                throw new NullReferenceException("Reigster Model is null");
            
            if (model.Password != model.ConfirmPassword)
            
                return new UserManagerResponse
                
                    Message = "Confirm password doesn't match the password",
                    IsSuccess = false,
                ;
            
            var identityUser = new ApplicationUser
            
                Email = model.Email,
                UserName = model.Email,
                About = model.About,
                SecondMobile = model.SecondMobile,
                Fullname = model.Fullname,
                AliasName = model.AliasName,
                Created = DateTime.Now,
                Modified = DateTime.Now
            ;
            var result = await _userManger.CreateAsync(identityUser, model.Password);

            if (result.Succeeded)
            
                var confirmEmailToken = await _userManger.GenerateEmailConfirmationTokenAsync(identityUser);
                var encodedEmailToken = Encoding.UTF8.GetBytes(confirmEmailToken);
                var validEmailToken = WebEncoders.Base64UrlEncode(encodedEmailToken);
                string url = $"_configuration["AppUrl"]/api/auth/ConfirmEmail?userId=identityUser.Id&token=validEmailToken";
                await _mailService.SendEmailAsync(identityUser.Email, "Confirm your email", $"<h1>Welcome to Trustee app</h1>" +
                    $"<p>Please confirm your email by <a href='url'>clicking here</a></p>");
                return new UserManagerResponse
                
                    Message = "User created successfully!",
                    IsSuccess = true,
                ;
            
            return new UserManagerResponse
            
                Message = "User did not create",
                IsSuccess = false,
                Errors = result.Errors.Select(e => e.Description)
            ;
        

        /// <summary>
        /// Đăng nhập.
        /// </summary>
        /// <param name="model"></param>
        /// <returns></returns>
        public async Task<UserManagerResponse> LoginUserAsync(LoginViewModel model)
        
            var user = await _userManger.FindByEmailAsync(model.Email);
            if (user == null)
            
                return new UserManagerResponse
                
                    Message = "There is no user with that Email address",
                    IsSuccess = false,
                ;
            
            var result = await _userManger.CheckPasswordAsync(user, model.Password);
            if (!result)
            
                return new UserManagerResponse
                
                    Message = "Invalid password",
                    IsSuccess = false,
                ;
            
            var claims = new[]
            
                new Claim("Email", model.Email),
                new Claim(ClaimTypes.NameIdentifier, user.Id),
            ;
            var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration["AuthSettings:Key"]));
            var token = new JwtSecurityToken(
                issuer: _configuration["AuthSettings:Issuer"],
                audience: _configuration["AuthSettings:Audience"],
                claims: claims,
                expires: DateTime.Now.AddDays(30),
                signingCredentials: new SigningCredentials(key, SecurityAlgorithms.HmacSha256));
            string tokenAsString = new JwtSecurityTokenHandler().WriteToken(token);
            return new UserManagerResponse
            
                Message = tokenAsString,
                IsSuccess = true,
                ExpireDate = token.ValidTo
            ;
        

        // Đăng xuất.
        public async Task<UserManagerResponse> LogoutUserAsync(LoginViewModel model)
        
            var user = await _userManger.FindByEmailAsync(model.Email);
            if (user == null)
            
                return new UserManagerResponse
                
                    Message = "There is no user with that Email address",
                    IsSuccess = false,
                ;
            
            var result = await _userManger.CheckPasswordAsync(user, model.Password);
            if (!result)
            
                return new UserManagerResponse
                
                    Message = "Invalid password",
                    IsSuccess = false,
                ;
            
            var claims = new[]
            
                new Claim("Email", model.Email),
                new Claim(ClaimTypes.NameIdentifier, user.Id),
            ;
            var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration["AuthSettings:Key"]));
            var token = new JwtSecurityToken(
                issuer: _configuration["AuthSettings:Issuer"],
                audience: _configuration["AuthSettings:Audience"],
                claims: claims,
                expires: DateTime.Now.AddDays(30),
                signingCredentials: new SigningCredentials(key, SecurityAlgorithms.HmacSha256));
            string tokenAsString = new JwtSecurityTokenHandler().WriteToken(token);
            return new UserManagerResponse
            
                Message = tokenAsString,
                IsSuccess = true,
                ExpireDate = token.ValidTo
            ;
        

        public async Task<UserManagerResponse> ConfirmEmailAsync(string userId, string token)
        
            var user = await _userManger.FindByIdAsync(userId);
            if (user == null)
            
                return new UserManagerResponse  IsSuccess = false, Message = "User not found" ;
            
            var decodedToken = WebEncoders.Base64UrlDecode(token);
            string normalToken = Encoding.UTF8.GetString(decodedToken);
            var result = await _userManger.ConfirmEmailAsync(user, normalToken);
            if (result.Succeeded)
            
                return new UserManagerResponse  Message = "Email confirmed successfully!", IsSuccess = true ;
            
            return new UserManagerResponse
            
                IsSuccess = false,
                Message = "Email did not confirm",
                Errors = result.Errors.Select(e => e.Description)
            ;
        

        public async Task<UserManagerResponse> ForgetPasswordAsync(string email)
        
            var user = await _userManger.FindByEmailAsync(email);
            if (user == null)
            
                return new UserManagerResponse  IsSuccess = false, Message = "No user associated with email", ;
            
            var token = await _userManger.GeneratePasswordResetTokenAsync(user);
            var encodedToken = Encoding.UTF8.GetBytes(token);
            var validToken = WebEncoders.Base64UrlEncode(encodedToken);
            string url = $"_configuration["AppUrl"]/ResetPassword?email=email&token=validToken";
            await _mailService.SendEmailAsync(email, "Reset Password", "<h1>Follow the instructions to reset your password</h1>" +
                $"<p>To reset your password <a href='url'>Click here</a></p>");
            return new UserManagerResponse
            
                IsSuccess = true,
                Message = "Reset password URL has been sent to the email successfully!"
            ;
        

        public async Task<UserManagerResponse> ResetPasswordAsync(ResetPasswordViewModel model)
        
            var user = await _userManger.FindByEmailAsync(model.Email);
            if (user == null)
            
                return new UserManagerResponse  IsSuccess = false, Message = "No user associated with email", ;
            
            if (model.NewPassword != model.ConfirmPassword)
            
                return new UserManagerResponse  IsSuccess = false, Message = "Password doesn't match its confirmation", ;
            
            var decodedToken = WebEncoders.Base64UrlDecode(model.Token);
            string normalToken = Encoding.UTF8.GetString(decodedToken);
            var result = await _userManger.ResetPasswordAsync(user, normalToken, model.NewPassword);
            if (result.Succeeded)
            
                return new UserManagerResponse  Message = "Password has been reset successfully!", IsSuccess = true ;
            
            return new UserManagerResponse
            
                Message = "Something went wrong",
                IsSuccess = false,
                Errors = result.Errors.Select(e => e.Description)
            ;
        
    
        public async Task<UserManagerResponse> ChangePasswordAsync(ChangePasswordViewModel model)
        
            var tokenString = model.Token;            
            var jwtEncodedString = tokenString.Substring(7); // trim 'Bearer ' from the start since its just a prefix for the token string
            var token = new JwtSecurityToken(jwtEncodedString: jwtEncodedString);
            string email = token.Claims.First(c => c.Type == "Email").Value;
            Console.WriteLine("email => " + email);
            var user = await _userManger.FindByEmailAsync(email);
            if (user == null)
            
                return new UserManagerResponse  IsSuccess = false, Message = "No user associated with email", ;
            
            if (model.NewPassword != model.ConfirmPassword)
            
                return new UserManagerResponse  IsSuccess = false, Message = "Password doesn't match its confirmation", ;
            
            var token2 = await _userManger.GeneratePasswordResetTokenAsync(user);
            var result = await _userManger.ResetPasswordAsync(user, token2, model.NewPassword);
            if (result.Succeeded)
            
                return new UserManagerResponse  Message = "Password has been changed successfully!", IsSuccess = true ;
            
            return new UserManagerResponse
            
                Message = "Something went wrong",
                IsSuccess = false,
                Errors = result.Errors.Select(e => e.Description)
            ;
             
    


文件appsettings.json


  "ConnectionStrings": 
    "DefaultConnection": "Server=.;Database=foo;User Id=sa; Password=SecrEt_STring;Trusted_Connection=False;MultipleActiveResultSets=True"
  ,
  "Logging": 
    "LogLevel": 
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    
  ,
  "AllowedHosts": "*",
  "AuthSettings": 
    "Key": "This is the key that we will use in the encryption",
    "Audience": "http://example.io",
    "Issuer": "http://example.io"
  ,
  "SendGridAPIKey": "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",
  "AppUrl": "http://localhost:5002"


在控制器中

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using shadow.Data;
using shadow.DTO;
using shadow.Models;
using shadow.Services;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;

namespace shadow.Controllers

    [Route("[controller]")]
    [ApiController]
    public class AssetItemController : ControllerBase
    
        private IUserService _userService;
        private IMailService _mailService;
        private IConfiguration _configuration;
        private ApplicationDbContext _db;

        public AssetItemController(IUserService userService, IMailService mailService, IConfiguration configuration, ApplicationDbContext context /*, SignInManager<IdentityUser> signInManager */)
        
            _userService = userService;
            _mailService = mailService;
            _configuration = configuration;
            _db = context;
            // _signInManager = signInManager;
        

        // Admin liệt kê tất cả các tài sản.
        [HttpGet]
        [Route("all")]
        [Authorize(Roles ="ADMIN")]        
        public async Task<ActionResult<AssetItem>> GetAllAssetItems()
        
            var a = await Task.Run(() => _db.AssetItems);
            if (a != null)
            
                return Ok(a.ToList());
            
            else
            
                return NoContent();
            
        

        // Liệt kê tất cả các tài sản theo user_id.
        [HttpGet]
        [Route("user/userId")]
        public async Task<ActionResult<IEnumerable<AssetItem>>> GetAllAssetItemsByUserId(string userId)
        
            var assetListByUserId = await Task.Run(() => _db.AssetItems.Where(x => x.UserId == userId).ToList());
            if (assetListByUserId != null)
            
                return Ok(assetListByUserId);
            
            else
            
                return NoContent();
            

        

        // Xem chi tiet asset_item theo id cua no.
        [HttpGet]
        [Route("assetItemId")]
        public async Task<ActionResult<IEnumerable<AssetItem>>> ViewAssetItemById(int assetItemId)
        
            var assetListByUserId = await Task.Run(() => _db.AssetItems.Where(x => x.Id == assetItemId).FirstOrDefault());
            if (assetListByUserId != null)
            
                return Ok(assetListByUserId);
            
            else
            
                return NoContent();
            
        

        // Xóa tài sản.
        [HttpDelete("id")]
        public async Task<ActionResult<string>> DeleteAssetItem(int id)
        
            var item = _db.AssetItems.Find(id);
            if (item != null)
            
                _db.AssetItems.Remove(item);
                await _db.SaveChangesAsync();
                return Ok("ok");
            
            else
            
                return NotFound();
            

        

        // POST http://localhost:5002/AssetItem
        [HttpPost]
        [ProducesResponseType(StatusCodes.Status200OK)]
        [ProducesResponseType(typeof(string), StatusCodes.Status400BadRequest)]
        [Authorize]
        public async Task<ActionResult<AssetItem>> Add([FromForm] AssetItemDTO input)
        
            if (ModelState.IsValid)
            
                AssetItem assetItem = new AssetItem();
                if (input.AssetTypeId != null)
                
                    assetItem.AssetTypeId = input.AssetTypeId;
                

                if (input.UserId != null)
                
                    assetItem.UserId = input.UserId;
                

                if (input.TrustedPersonId != null)
                
                    assetItem.TrustedPersonId = input.TrustedPersonId;
                

                if (input.TrustedContent != null)
                
                    assetItem.TrustedContent = input.TrustedContent;
                

                if (input.Description != null)
                
                    assetItem.Description = input.Description;
                

                assetItem.Created = DateTime.Now;
                if (input.file != null)
                
                    if (IsImageFile(input.file))
                    
                        string imgPath = await WriteFile(input.file);
                        assetItem.ImagePath = imgPath;
                    
                    else
                    
                        return BadRequest(new  message = "Invalid file extension" );
                    
                
                _db.AssetItems.Add(assetItem);
                await _db.SaveChangesAsync();
                return Ok(assetItem);
            
            else
            
                return BadRequest(ModelState);
            
        

        // Edit 1 tài sản.
        // PUT http://localhost:5002/AssetItem
        [HttpPut("id")]
        [ProducesResponseType(StatusCodes.Status200OK)]
        [ProducesResponseType(typeof(string), StatusCodes.Status400BadRequest)]
        [Authorize]
        public async Task<ActionResult<AssetItem>> Edit([FromForm] AssetItemDTO input, int id)
        
            if (ModelState.IsValid)
            
                var foo = _db.AssetItems.Find(id);
                if (!String.IsNullOrEmpty(input.TrustedContent))
                
                        foo.TrustedContent = input.TrustedContent;                    
                else              
                
                    foo.TrustedContent = null;
                

                if (input.file != null)
                
                    string imgPath = await WriteFile(input.file);
                    foo.ImagePath = imgPath;
                
                else
                
                    foo.ImagePath = null;
                

                if (!String.IsNullOrEmpty(input.Description))
                
                    foo.Description = input.Description;
                
                else
                
                    foo.Description = null;
                

                foo.AssetTypeId = input.AssetTypeId;

                if (input.TrustedPersonId != null)
                
                    foo.TrustedPersonId = input.TrustedPersonId;
                
                // Hiện chưa ghi nhận user_modified. Cần xem lại Requirement.
                if (!String.IsNullOrEmpty(input.UserModified))
                
                    foo.UserModified = input.UserModified;
                
                else
                
                    foo.UserModified = null;
                
                foo.Modified = DateTime.Now;
                _db.Update(foo);
                await _db.SaveChangesAsync();
                return Ok(foo);
            
            else
            
                return BadRequest(ModelState);
            
        

        private bool IsImageFile(IFormFile file)
        
            try
            
                var extension = "." + file.FileName.Split('.')[file.FileName.Split('.').Length - 1];
                return (extension == ".png" || extension == ".jpg" || extension == ".bmp" || extension == ".gif" || extension == ".tif");
            
            catch
            
                return false;
            
        

        private async Task<string> WriteFile(IFormFile file)
        
            string fileName = "";
            try
            
                var extension = "." + file.FileName.Split('.')[file.FileName.Split('.').Length - 1];
                fileName = DateTime.Now.Ticks + extension; //Create a new Name for the file due to security reasons.
                var pathBuilt = Path.Combine(Directory.GetCurrentDirectory(), "Upload\\file");
                if (!Directory.Exists(pathBuilt))
                
                    Directory.CreateDirectory(pathBuilt);
                
                var path = Path.Combine(Directory.GetCurrentDirectory(), "Upload\\files", fileName);           
                using (var stream = new FileStream(path, FileMode.Create))
                
                    await file.CopyToAsync(stream);
                
                return fileName;
            
            catch (Exception e)
            
                //
            
            return fileName;
        
    


数据库

select * from AspNetUsers;
select * from AspNetUserRoles;
select * from AspNetRoles;

结果

添加[Authorize(Roles ="ADMIN")]时,显示403 Forbidden

如何使按角色授权工作?

【问题讨论】:

能否提供生成 JWT 的方法的代码?另外,请查看 this link 以查看您的令牌是否确实包含角色信息。 我在问题中添加了更多内容。 请看我的回答。 我离职了,我会在周一反馈。 周末愉快! 【参考方案1】:

您实际上并未向令牌添加角色。每个角色都应该是一个声明,就像这样。

new Claim(ClaimTypes.Role, "&lt;role name"&gt;));

例如。

var claims = new[]

      new Claim("Email", model.Email),
      new Claim(ClaimTypes.NameIdentifier, user.Id),
;

var roles = await _userManger.GetRolesAsync(user); // note: you have a typo in "_userManger"

var claimsWithRoles = roles.Select(role => new Claim(ClaimTypes.Role, role));
var allClaims = claims.Concat(claimsWithRoles);

然后将 allClaims 添加到 JWT,如下所示:

var token = new JwtSecurityToken(
            issuer: _configuration["AuthSettings:Issuer"],
            audience: _configuration["AuthSettings:Audience"],
            claims: allClaims , // note how we add all claims, including the ones for roles, here
            expires: DateTime.Now.AddDays(30),
            signingCredentials: new SigningCredentials(key, SecurityAlgorithms.HmacSha256));

让我知道它是如何工作的。

编辑:抱歉,忘记AddRange 返回无效;

【讨论】:

我发现错误user-images.githubusercontent.com/1328316/… 已修复。请再检查一次。 试试claims.AddRange(roles.Select(role =&gt; new Claim(ClaimsIdentity.DefaultRoleClaimType, role))); 我发现错误user-images.githubusercontent.com/1328316/… @DoNhuVy 我认为这应该不是问题。您需要使用实际的 256 位密钥来验证签名,而不是预设的 your-256-bit-secret 值。在调试器中通常不需要这样做,但您可以粘贴您的密钥并对其进行测试。但是,您的令牌现在包含适当的角色,因此您应该能够使用端点。快速说明:可能希望从主要问题的appsettings.json 代码中删除 Sendgrid API 密钥。

以上是关于ASP.NET Core 身份 [Authorize(Roles ="ADMIN")] 不起作用的主要内容,如果未能解决你的问题,请参考以下文章

选择 webApi 模板时如何将 ASP.Net 身份添加到 Asp.Net Core?

无法更改 Asp 身份表名称.....Asp .Net Core 2?

如何先用asp.net身份框架数据库将asp.net mvc迁移到asp.net core

ASP.NET Core 中的 Jwt 令牌身份验证

Cookie 身份验证 ASP.NET Core

ASP.Net Core Cookie 身份验证