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

nhu*_*uvy 2 c# jwt asp.net-core asp.net-core-webapi asp.net-core-identity

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

\n
using Microsoft.AspNetCore.Authentication.JwtBearer;\nusing Microsoft.AspNetCore.Authorization;\nusing Microsoft.AspNetCore.Builder;\nusing Microsoft.AspNetCore.Hosting;\nusing Microsoft.AspNetCore.Http;\nusing Microsoft.AspNetCore.Identity;\nusing Microsoft.EntityFrameworkCore;\nusing Microsoft.Extensions.Configuration;\nusing Microsoft.Extensions.DependencyInjection;\nusing Microsoft.Extensions.FileProviders;\nusing Microsoft.Extensions.Hosting;\nusing Microsoft.IdentityModel.Tokens;\nusing shadow.Data;\nusing shadow.Models;\nusing shadow.Services;\nusing System.IO;\nusing System.Text;\n\nnamespace shadow\n{\n    public class Startup\n    {\n        public Startup(IConfiguration configuration)\n        {\n            Configuration = configuration;\n        }\n\n        public IConfiguration Configuration { get; }\n\n        public void ConfigureServices(IServiceCollection services)\n        {\n            services.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));\n            services.AddHttpContextAccessor();\n            //services.AddCors();\n            services.AddCors(options =>\n            {\n                options.AddPolicy("CorsPolicy",\n                    builder => builder.AllowAnyOrigin()\n                        .AllowAnyMethod()\n                        .AllowAnyHeader());\n            });\n            services.AddSingleton<IUriService>(o =>\n            {\n                var accessor = o.GetRequiredService<IHttpContextAccessor>();\n                var request = accessor.HttpContext.Request;\n                var uri = string.Concat(request.Scheme, "://", request.Host.ToUriComponent());\n                return new UriService(uri);\n            });\n            services.AddIdentity<ApplicationUser, IdentityRole>(options =>\n            {\n                //options.Password.RequireDigit = true;\n                //options.Password.RequireLowercase = true;\n                options.Password.RequiredLength = 6;\n            }).AddEntityFrameworkStores<ApplicationDbContext>().AddDefaultTokenProviders();\n            services\n                .AddAuthentication(auth =>\n                {\n                    auth.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;\n                    auth.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;\n                })\n\n                  .AddJwtBearer(options =>\n                {\n                    options.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters\n                    {\n                        ValidateIssuer = true,\n                        ValidateAudience = true,\n                        ValidAudience = Configuration["AuthSettings:Audience"],\n                        ValidIssuer = Configuration["AuthSettings:Issuer"],\n                        RequireExpirationTime = true,\n                        IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["AuthSettings:Key"])),\n                        ValidateIssuerSigningKey = true\n                    };\n                });\n            services.AddAuthorization(options =>\n            {\n                options.FallbackPolicy = new AuthorizationPolicyBuilder()\n                    .RequireAuthenticatedUser()\n                    .Build();\n\n            });\n            services.AddScoped<IUserService, UserService>();\n            services.AddTransient<IMailService, SendGridMailService>();            \n            services.AddControllers();\n            services.AddRazorPages();\n            services.AddControllers(options => options.SuppressAsyncSuffixInActionNames = false);\n            services.AddMvc().AddJsonOptions(options =>\n            {\n                //options.JsonSerializerOptions.DictionaryKeyPolicy = JsonNamingPolicy.CamelCase;\n                options.JsonSerializerOptions.PropertyNamingPolicy = null;\n            });\n        }\n\n        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)\n        {\n            if (env.IsDevelopment())\n            {\n                app.UseDeveloperExceptionPage();\n            }\n            else\n            {\n                app.UseHsts();\n            }\n            //app.UseHttpsRedirection();\n            app.UseRouting();\n            // app.UseCors(options => options.WithOrigins("http://localhost:4200")\n            //.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader());\n            app.UseCors("CorsPolicy");\n            app.UseAuthentication();\n            app.UseAuthorization();\n            // Server: https://shorten.news/static-file/content/10992.mp3\n            // Local: http://localhost:5000/static-file/content/10992.mp3\n            app.UseStaticFiles(new StaticFileOptions\n            {\n                FileProvider = new PhysicalFileProvider(\n                    // Path.Combine(env.ContentRootPath, @"c:\\audio\\")),\n                    // Path.Combine(@"c:\\audio\\")),\n                    Path.Combine(@"D:\\shadow_backend\\Upload\\files\\")\n                ),\n                RequestPath = "/static-file",\n                OnPrepareResponse = context =>\n                {\n                    context.Context.Response.Headers["Access-Control-Allow-Origin"] = "*";\n                }\n            });\n\n            app.UseEndpoints(endpoints =>\n            {\n                endpoints.MapControllers();\n            });\n            app.Run(async (context) =>\n            {\n                await context.Response.WriteAsync("cound not find anything");\n            });\n        }\n    }\n\n}\n
Run Code Online (Sandbox Code Playgroud)\n

文件IUserService.cs

\n
using Microsoft.AspNetCore.WebUtilities;\nusing Microsoft.Extensions.Configuration;\nusing Microsoft.IdentityModel.Tokens;\nusing shadow.Models;\nusing shadow.Shared;\nusing System;\nusing System.IdentityModel.Tokens.Jwt;\nusing System.Linq;\nusing System.Security.Claims;\nusing System.Text;\nusing System.Threading.Tasks;\n\nnamespace shadow.Services\n{\n\n    public interface IUserService\n    {\n\n        Task<UserManagerResponse> RegisterUserAsync(RegisterViewModel model);\n        Task<UserManagerResponse> LoginUserAsync(LoginViewModel model);\n        Task<UserManagerResponse> LogoutUserAsync(LoginViewModel model);\n        Task<UserManagerResponse> ConfirmEmailAsync(string userId, string token);\n        Task<UserManagerResponse> ForgetPasswordAsync(string email);\n        Task<UserManagerResponse> ResetPasswordAsync(ResetPasswordViewModel model);\n        Task<UserManagerResponse> ChangePasswordAsync(ChangePasswordViewModel model);\n    }\n\n    public class UserService : IUserService\n    {\n        private Microsoft.AspNetCore.Identity.UserManager<ApplicationUser> _userManger;\n        private IConfiguration _configuration;\n        private IMailService _mailService;\n\n        public UserService(Microsoft.AspNetCore.Identity.UserManager<ApplicationUser> userManager, IConfiguration configuration, IMailService mailService)\n        {\n            _userManger = userManager;\n            _configuration = configuration;\n            _mailService = mailService;\n        }\n\n        public async Task<UserManagerResponse> RegisterUserAsync(RegisterViewModel model)\n        {\n            if (model == null)\n            {\n                throw new NullReferenceException("Reigster Model is null");\n            }\n            if (model.Password != model.ConfirmPassword)\n            {\n                return new UserManagerResponse\n                {\n                    Message = "Confirm password doesn\'t match the password",\n                    IsSuccess = false,\n                };\n            }\n            var identityUser = new ApplicationUser\n            {\n                Email = model.Email,\n                UserName = model.Email,\n                About = model.About,\n                SecondMobile = model.SecondMobile,\n                Fullname = model.Fullname,\n                AliasName = model.AliasName,\n                Created = DateTime.Now,\n                Modified = DateTime.Now\n            };\n            var result = await _userManger.CreateAsync(identityUser, model.Password);\n\n            if (result.Succeeded)\n            {\n                var confirmEmailToken = await _userManger.GenerateEmailConfirmationTokenAsync(identityUser);\n                var encodedEmailToken = Encoding.UTF8.GetBytes(confirmEmailToken);\n                var validEmailToken = WebEncoders.Base64UrlEncode(encodedEmailToken);\n                string url = $"{_configuration["AppUrl"]}/api/auth/ConfirmEmail?userId={identityUser.Id}&token={validEmailToken}";\n                await _mailService.SendEmailAsync(identityUser.Email, "Confirm your email", $"<h1>Welcome to Trustee app</h1>" +\n                    $"<p>Please confirm your email by <a href=\'{url}\'>clicking here</a></p>");\n                return new UserManagerResponse\n                {\n                    Message = "User created successfully!",\n                    IsSuccess = true,\n                };\n            }\n            return new UserManagerResponse\n            {\n                Message = "User did not create",\n                IsSuccess = false,\n                Errors = result.Errors.Select(e => e.Description)\n            };\n        }\n\n        /// <summary>\n        /// \xc4\x90\xc4\x83ng nh\xe1\xba\xadp.\n        /// </summary>\n        /// <param name="model"></param>\n        /// <returns></returns>\n        public async Task<UserManagerResponse> LoginUserAsync(LoginViewModel model)\n        {\n            var user = await _userManger.FindByEmailAsync(model.Email);\n            if (user == null)\n            {\n                return new UserManagerResponse\n                {\n                    Message = "There is no user with that Email address",\n                    IsSuccess = false,\n                };\n            }\n            var result = await _userManger.CheckPasswordAsync(user, model.Password);\n            if (!result)\n            {\n                return new UserManagerResponse\n                {\n                    Message = "Invalid password",\n                    IsSuccess = false,\n                };\n            }\n            var claims = new[]\n            {\n                new Claim("Email", model.Email),\n                new Claim(ClaimTypes.NameIdentifier, user.Id),\n            };\n            var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration["AuthSettings:Key"]));\n            var token = new JwtSecurityToken(\n                issuer: _configuration["AuthSettings:Issuer"],\n                audience: _configuration["AuthSettings:Audience"],\n                claims: claims,\n                expires: DateTime.Now.AddDays(30),\n                signingCredentials: new SigningCredentials(key, SecurityAlgorithms.HmacSha256));\n            string tokenAsString = new JwtSecurityTokenHandler().WriteToken(token);\n            return new UserManagerResponse\n            {\n                Message = tokenAsString,\n                IsSuccess = true,\n                ExpireDate = token.ValidTo\n            };\n        }\n\n        // \xc4\x90\xc4\x83ng xu\xe1\xba\xa5t.\n        public async Task<UserManagerResponse> LogoutUserAsync(LoginViewModel model)\n        {\n            var user = await _userManger.FindByEmailAsync(model.Email);\n            if (user == null)\n            {\n                return new UserManagerResponse\n                {\n                    Message = "There is no user with that Email address",\n                    IsSuccess = false,\n                };\n            }\n            var result = await _userManger.CheckPasswordAsync(user, model.Password);\n            if (!result)\n            {\n                return new UserManagerResponse\n                {\n                    Message = "Invalid password",\n                    IsSuccess = false,\n                };\n            }\n            var claims = new[]\n            {\n                new Claim("Email", model.Email),\n                new Claim(ClaimTypes.NameIdentifier, user.Id),\n            };\n            var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration["AuthSettings:Key"]));\n            var token = new JwtSecurityToken(\n                issuer: _configuration["AuthSettings:Issuer"],\n                audience: _configuration["AuthSettings:Audience"],\n                claims: claims,\n                expires: DateTime.Now.AddDays(30),\n                signingCredentials: new SigningCredentials(key, SecurityAlgorithms.HmacSha256));\n            string tokenAsString = new JwtSecurityTokenHandler().WriteToken(token);\n            return new UserManagerResponse\n            {\n                Message = tokenAsString,\n                IsSuccess = true,\n                ExpireDate = token.ValidTo\n            };\n        }\n\n        public async Task<UserManagerResponse> ConfirmEmailAsync(string userId, string token)\n        {\n            var user = await _userManger.FindByIdAsync(userId);\n            if (user == null)\n            {\n                return new UserManagerResponse { IsSuccess = false, Message = "User not found" };\n            }\n            var decodedToken = WebEncoders.Base64UrlDecode(token);\n            string normalToken = Encoding.UTF8.GetString(decodedToken);\n            var result = await _userManger.ConfirmEmailAsync(user, normalToken);\n            if (result.Succeeded)\n            {\n                return new UserManagerResponse { Message = "Email confirmed successfully!", IsSuccess = true };\n            }\n            return new UserManagerResponse\n            {\n                IsSuccess = false,\n                Message = "Email did not confirm",\n                Errors = result.Errors.Select(e => e.Description)\n            };\n        }\n\n        public async Task<UserManagerResponse> ForgetPasswordAsync(string email)\n        {\n            var user = await _userManger.FindByEmailAsync(email);\n            if (user == null)\n            {\n                return new UserManagerResponse { IsSuccess = false, Message = "No user associated with email", };\n            }\n            var token = await _userManger.GeneratePasswordResetTokenAsync(user);\n            var encodedToken = Encoding.UTF8.GetBytes(token);\n            var validToken = WebEncoders.Base64UrlEncode(encodedToken);\n            string url = $"{_configuration["AppUrl"]}/ResetPassword?email={email}&token={validToken}";\n            await _mailService.SendEmailAsync(email, "Reset Password", "<h1>Follow the instructions to reset your password</h1>" +\n                $"<p>To reset your password <a href=\'{url}\'>Click here</a></p>");\n            return new UserManagerResponse\n            {\n                IsSuccess = true,\n                Message = "Reset password URL has been sent to the email successfully!"\n            };\n        }\n\n        public async Task<UserManagerResponse> ResetPasswordAsync(ResetPasswordViewModel model)\n        {\n            var user = await _userManger.FindByEmailAsync(model.Email);\n            if (user == null)\n            {\n                return new UserManagerResponse { IsSuccess = false, Message = "No user associated with email", };\n            }\n            if (model.NewPassword != model.ConfirmPassword)\n            {\n                return new UserManagerResponse { IsSuccess = false, Message = "Password doesn\'t match its confirmation", };\n            }\n            var decodedToken = WebEncoders.Base64UrlDecode(model.Token);\n            string normalToken = Encoding.UTF8.GetString(decodedToken);\n            var result = await _userManger.ResetPasswordAsync(user, normalToken, model.NewPassword);\n            if (result.Succeeded)\n            {\n                return new UserManagerResponse { Message = "Password has been reset successfully!", IsSuccess = true };\n            }\n            return new UserManagerResponse\n            {\n                Message = "Something went wrong",\n                IsSuccess = false,\n                Errors = result.Errors.Select(e => e.Description)\n            };\n        }\n    \n        public async Task<UserManagerResponse> ChangePasswordAsync(ChangePasswordViewModel model)\n        {\n            var tokenString = model.Token;            \n            var jwtEncodedString = tokenString.Substring(7); // trim \'Bearer \' from the start since its just a prefix for the token string\n            var token = new JwtSecurityToken(jwtEncodedString: jwtEncodedString);\n            string email = token.Claims.First(c => c.Type == "Email").Value;\n            Console.WriteLine("email => " + email);\n            var user = await _userManger.FindByEmailAsync(email);\n            if (user == null)\n            {\n                return new UserManagerResponse { IsSuccess = false, Message = "No user associated with email", };\n            }\n            if (model.NewPassword != model.ConfirmPassword)\n            {\n                return new UserManagerResponse { IsSuccess = false, Message = "Password doesn\'t match its confirmation", };\n            }\n            var token2 = await _userManger.GeneratePasswordResetTokenAsync(user);\n            var result = await _userManger.ResetPasswordAsync(user, token2, model.NewPassword);\n            if (result.Succeeded)\n            {\n                return new UserManagerResponse { Message = "Password has been changed successfully!", IsSuccess = true };\n            }\n            return new UserManagerResponse\n            {\n                Message = "Something went wrong",\n                IsSuccess = false,\n                Errors = result.Errors.Select(e => e.Description)\n            };\n        }     \n    }\n\n}\n
Run Code Online (Sandbox Code Playgroud)\n

文件appsettings.json

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

在控制器中

\n
using Microsoft.AspNetCore.Authorization;\nusing Microsoft.AspNetCore.Http;\nusing Microsoft.AspNetCore.Mvc;\nusing Microsoft.Extensions.Configuration;\nusing shadow.Data;\nusing shadow.DTO;\nusing shadow.Models;\nusing shadow.Services;\nusing System;\nusing System.Collections.Generic;\nusing System.IO;\nusing System.Linq;\nusing System.Threading.Tasks;\n\nnamespace shadow.Controllers\n{\n    [Route("[controller]")]\n    [ApiController]\n    public class AssetItemController : ControllerBase\n    {\n        private IUserService _userService;\n        private IMailService _mailService;\n        private IConfiguration _configuration;\n        private Appli

Riw*_*wen 7

您实际上并没有向令牌添加角色。每个角色都应该是一个主张,就像这样。

new Claim(ClaimTypes.Role, "<role name">));

例如。

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);
Run Code Online (Sandbox Code Playgroud)

然后添加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));
Run Code Online (Sandbox Code Playgroud)

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

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