{"id":1152,"date":"2025-05-27T14:47:56","date_gmt":"2025-05-27T06:47:56","guid":{"rendered":"https:\/\/www.hyy.net\/?p=1152"},"modified":"2025-05-27T14:47:56","modified_gmt":"2025-05-27T06:47:56","slug":"ultimate-asp-net-core-web-api-27-jwt-identity-and-refresh-token","status":"publish","type":"post","link":"https:\/\/diji.net\/?p=1152","title":{"rendered":"Ultimate ASP.NET Core Web API 27 JWT, IDENTITY, AND REFRESH TOKEN"},"content":{"rendered":"<p>27 JWT, IDENTITY, AND REFRESH TOKEN<br \/>\n27  JWT\u3001\u8eab\u4efd\u8ba4\u8bc1\u548c\u5237\u65b0\u4ee4\u724c<\/p>\n<p>User authentication is an important part of any application. It refers to the process of confirming the identity of an application\u2019s users. Implementing it properly could be a hard job if you are not familiar with the process.\u200c<br \/>\n\u7528\u6237\u8eab\u4efd\u9a8c\u8bc1\u662f\u4efb\u4f55\u5e94\u7528\u7a0b\u5e8f\u7684\u91cd\u8981\u7ec4\u6210\u90e8\u5206\u3002\u5b83\u662f\u6307\u786e\u8ba4\u5e94\u7528\u7a0b\u5e8f\u7528\u6237\u8eab\u4efd\u7684\u8fc7\u7a0b\u3002\u5982\u679c\u60a8\u4e0d\u719f\u6089\u8be5\u8fc7\u7a0b\uff0c\u6b63\u786e\u5b9e\u65bd\u5b83\u53ef\u80fd\u662f\u4e00\u9879\u8270\u5de8\u7684\u5de5\u4f5c\u3002<\/p>\n<p>Also, it could take a lot of time that could be spent on different features of an application.<br \/>\n\u6b64\u5916\uff0c\u53ef\u80fd\u9700\u8981\u82b1\u8d39\u5927\u91cf\u65f6\u95f4\uff0c\u8fd9\u4e9b\u65f6\u95f4\u53ef\u80fd\u4f1a\u82b1\u5728\u5e94\u7528\u7a0b\u5e8f\u7684\u4e0d\u540c\u529f\u80fd\u4e0a\u3002<\/p>\n<p>So, in this section, we are going to learn about authentication and authorization in ASP.NET Core by using Identity and JWT (Json Web Token). We are going to explain step by step how to integrate Identity in the existing project and then how to implement JWT for the authentication and authorization actions.<br \/>\n\u56e0\u6b64\uff0c\u5728\u672c\u8282\u4e2d\uff0c\u6211\u4eec\u5c06\u4f7f\u7528\u8eab\u4efd\u548c JWT\uff08Json Web \u4ee4\u724c\uff09\u4e86\u89e3 ASP.NET Core \u4e2d\u7684\u8eab\u4efd\u9a8c\u8bc1\u548c\u6388\u6743\u3002\u6211\u4eec\u5c06\u9010\u6b65\u89e3\u91ca\u5982\u4f55\u5728\u73b0\u6709\u9879\u76ee\u4e2d\u96c6\u6210 Identity\uff0c\u7136\u540e\u5982\u4f55\u4e3a\u8eab\u4efd\u9a8c\u8bc1\u548c\u6388\u6743\u4f5c\u5b9e\u65bd JWT\u3002<\/p>\n<p>ASP.NET Core provides us with both functionalities, making implementation even easier.<br \/>\nASP.NET Core \u4e3a\u6211\u4eec\u63d0\u4f9b\u4e86\u8fd9\u4e24\u79cd\u529f\u80fd\uff0c\u4f7f\u5b9e\u65bd\u53d8\u5f97\u66f4\u52a0\u5bb9\u6613\u3002<\/p>\n<p>Finally, we are going to learn more about the refresh token flow and implement it in our Web API project.<br \/>\n\u6700\u540e\uff0c\u6211\u4eec\u5c06\u4e86\u89e3\u6709\u5173\u5237\u65b0\u4ee4\u724c\u6d41\u7684\u66f4\u591a\u4fe1\u606f\uff0c\u5e76\u5728\u6211\u4eec\u7684 Web API \u9879\u76ee\u4e2d\u5b9e\u73b0\u5b83\u3002<\/p>\n<p>So, let\u2019s start with Identity integration.<br \/>\n\u90a3\u4e48\uff0c\u8ba9\u6211\u4eec\u4ece Identity integration \u5f00\u59cb\u3002<\/p>\n<h2>27.1 Implementing Identity in ASP.NET Core Project<\/h2>\n<p>27.1 \u5728 ASP.NET Core Project \u4e2d\u5b9e\u73b0\u8eab\u4efd\u8ba4\u771f<\/p>\n<p>Asp.NET Core Identity is the membership system for web applications that includes membership, login, and user data. It provides a rich set of services that help us with creating users, hashing their passwords, creating a database model, and the authentication overall.\u200cThat said, let\u2019s start with the integration process.<br \/>\nAsp.NET Core Identity \u662f Web \u5e94\u7528\u7a0b\u5e8f\u7684\u6210\u5458\u8d44\u683c\u7cfb\u7edf\uff0c\u5305\u62ec\u6210\u5458\u8d44\u683c\u3001\u767b\u5f55\u540d\u548c\u7528\u6237\u6570\u636e\u3002\u5b83\u63d0\u4f9b\u4e86\u4e00\u7ec4\u4e30\u5bcc\u7684\u670d\u52a1\uff0c\u53ef\u5e2e\u52a9\u6211\u4eec\u521b\u5efa\u7528\u6237\u3001\u5bf9\u4ed6\u4eec\u7684\u5bc6\u7801\u8fdb\u884c\u54c8\u5e0c\u5904\u7406\u3001\u521b\u5efa\u6570\u636e\u5e93\u6a21\u578b\u4ee5\u53ca\u6574\u4f53\u8eab\u4efd\u9a8c\u8bc1\u3002\u4e5f\u5c31\u662f\u8bf4\uff0c\u8ba9\u6211\u4eec\u4ece\u96c6\u6210\u8fc7\u7a0b\u5f00\u59cb\u3002<\/p>\n<p>The first thing we have to do is to install the Microsoft.AspNetCore.Identity.EntityFrameworkCore library in the Entities project:<br \/>\n\u6211\u4eec\u8981\u505a\u7684\u7b2c\u4e00\u4ef6\u4e8b\u662f\u5728 Entities \u9879\u76ee\u4e2d\u5b89\u88c5 Microsoft.AspNetCore.Identity.EntityFrameworkCore \u5e93\uff1a<\/p>\n<p><img decoding=\"async\" src=\"\/images\/ultimateaspnetcorewebapi6\/2701.gif\" alt=\"alt text\" \/><\/p>\n<p>After the installation, we are going to create a new User class in the Entities\/Models folder:<br \/>\n\u5b89\u88c5\u540e\uff0c\u6211\u4eec\u5c06\u5728 Entities\/Models \u6587\u4ef6\u5939\u4e2d\u521b\u5efa\u4e00\u4e2a\u65b0\u7684 User \u7c7b\uff1a<\/p>\n<pre><code>public class User : IdentityUser { public string FirstName { get; set; } public string LastName { get; set; } }<\/code><\/pre>\n<p>Our class inherits from the IdentityUser class that has been provided by the ASP.NET Core Identity. It contains different properties and we can extend it with our own as well.<br \/>\n\u6211\u4eec\u7684\u7c7b\u7ee7\u627f\u81ea ASP.NET Core Identity \u63d0\u4f9b\u7684 IdentityUser \u7c7b\u3002\u5b83\u5305\u542b\u4e0d\u540c\u7684\u5c5e\u6027\uff0c\u6211\u4eec\u4e5f\u53ef\u4ee5\u4f7f\u7528\u81ea\u5df1\u7684\u5c5e\u6027\u6765\u6269\u5c55\u5b83\u3002<\/p>\n<p>After that, we have to modify the RepositoryContext class:<br \/>\n\u4e4b\u540e\uff0c\u6211\u4eec\u5fc5\u987b\u4fee\u6539 RepositoryContext \u7c7b\uff1a<\/p>\n<pre><code>public class RepositoryContext : IdentityDbContext&lt;User&gt; { public RepositoryContext(DbContextOptions options) : base(options) { } protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); modelBuilder.ApplyConfiguration(new CompanyConfiguration()); modelBuilder.ApplyConfiguration(new EmployeeConfiguration()); } public DbSet&lt;Company&gt; Companies { get; set; } public DbSet&lt;Employee&gt; Employees { get; set; } }<\/code><\/pre>\n<p>So, our class now inherits from the IdentityDbContext class and not DbContext because we want to integrate our context with Identity. For this, we have to include the Identity.EntityFrameworkCore namespace:<br \/>\n\u56e0\u6b64\uff0c\u6211\u4eec\u7684\u7c7b\u73b0\u5728\u7ee7\u627f\u81ea IdentityDbContext \u7c7b\uff0c\u800c\u4e0d\u662f DbContext\uff0c\u56e0\u4e3a\u6211\u4eec\u5e0c\u671b\u5c06\u4e0a\u4e0b\u6587\u4e0e Identity \u96c6\u6210\u3002\u4e3a\u6b64\uff0c\u6211\u4eec\u5fc5\u987b\u5305\u542b Identity.EntityFrameworkCore \u547d\u540d\u7a7a\u95f4\uff1a<\/p>\n<pre><code>using Microsoft.AspNetCore.Identity.EntityFrameworkCore;<\/code><\/pre>\n<p>We don\u2019t have to install the library in the Repository project since we already did that in the Entities project, and Repository has the reference to Entities.<br \/>\n\u6211\u4eec\u4e0d\u5fc5\u5728 Repository \u9879\u76ee\u4e2d\u5b89\u88c5\u5e93\uff0c\u56e0\u4e3a\u6211\u4eec\u5df2\u7ecf\u5728 Entities \u9879\u76ee\u4e2d\u5b89\u88c5\u4e86\u8be5\u5e93\uff0c\u5e76\u4e14 Repository \u5177\u6709\u5bf9 Entities \u7684\u5f15\u7528\u3002<\/p>\n<p>Additionally, we call the OnModelCreating method from the base class. This is required for migration to work properly.<br \/>\n\u6b64\u5916\uff0c\u6211\u4eec\u8fd8\u4ece\u57fa\u7c7b\u8c03\u7528 OnModelCreating \u65b9\u6cd5\u3002\u8fd9\u662f\u8fc1\u79fb\u6b63\u5e38\u5de5\u4f5c\u6240\u5fc5\u9700\u7684\u3002<\/p>\n<p>Now, we have to move on to the configuration part.<br \/>\n\u73b0\u5728\uff0c\u6211\u4eec\u5fc5\u987b\u8fdb\u5165\u914d\u7f6e\u90e8\u5206\u3002<\/p>\n<p>To do that, let\u2019s create a new extension method in the ServiceExtensions class:<br \/>\n\u4e3a\u6b64\uff0c\u8ba9\u6211\u4eec\u5728 ServiceExtensions \u7c7b\u4e2d\u521b\u5efa\u4e00\u4e2a\u65b0\u7684\u6269\u5c55\u65b9\u6cd5\uff1a<\/p>\n<pre><code>public static void ConfigureIdentity(this IServiceCollection services) { var builder = services.AddIdentity&lt;User, IdentityRole&gt;(o =&gt; { o.Password.RequireDigit = true; o.Password.RequireLowercase = false; o.Password.RequireUppercase = false; o.Password.RequireNonAlphanumeric = false; o.Password.RequiredLength = 10; o.User.RequireUniqueEmail = true; }) .AddEntityFrameworkStores&lt;RepositoryContext&gt;() .AddDefaultTokenProviders(); }<\/code><\/pre>\n<p>With the AddIdentity method, we are adding and configuring Identity for the specific type; in this case, the User and the IdentityRole type. We use different configuration parameters that are pretty self-explanatory on their own. Identity provides us with even more features to configure, but these are sufficient for our example.<br \/>\n\u4f7f\u7528 AddIdentity \u65b9\u6cd5\uff0c\u6211\u4eec\u5c06\u4e3a\u7279\u5b9a\u7c7b\u578b\u6dfb\u52a0\u548c\u914d\u7f6e Identity;\u5728\u672c\u4f8b\u4e2d\u4e3a User \u548c IdentityRole \u7c7b\u578b\u3002\u6211\u4eec\u4f7f\u7528\u4e0d\u540c\u7684\u914d\u7f6e\u53c2\u6570\uff0c\u8fd9\u4e9b\u53c2\u6570\u672c\u8eab\u5c31\u5f88\u5bb9\u6613\u7406\u89e3\u3002Identity \u4e3a\u6211\u4eec\u63d0\u4f9b\u4e86\u66f4\u591a\u9700\u8981\u914d\u7f6e\u7684\u529f\u80fd\uff0c\u4f46\u8fd9\u4e9b\u529f\u80fd\u5bf9\u4e8e\u6211\u4eec\u7684\u793a\u4f8b\u6765\u8bf4\u5df2\u7ecf\u8db3\u591f\u4e86\u3002<\/p>\n<p>Then, we add EntityFrameworkStores implementation with the default token providers.<br \/>\n\u7136\u540e\uff0c\u6211\u4eec\u6dfb\u52a0\u5177\u6709\u9ed8\u8ba4\u4ee4\u724c\u63d0\u4f9b\u7a0b\u5e8f\u7684 EntityFrameworkStores \u5b9e\u73b0\u3002<\/p>\n<p>Now, let\u2019s modify the Program class:<br \/>\n\u73b0\u5728\uff0c\u8ba9\u6211\u4eec\u4fee\u6539 Program \u7c7b\uff1a<\/p>\n<pre><code>builder.Services.AddAuthentication(); \nbuilder.Services.ConfigureIdentity();<\/code><\/pre>\n<p>And, let\u2019s add the authentication middleware to the application\u2019s request pipeline:<br \/>\n\u7136\u540e\uff0c\u8ba9\u6211\u4eec\u5c06\u8eab\u4efd\u9a8c\u8bc1\u4e2d\u95f4\u4ef6\u6dfb\u52a0\u5230\u5e94\u7528\u7a0b\u5e8f\u7684\u8bf7\u6c42\u7ba1\u9053\u4e2d\uff1a<\/p>\n<pre><code>app.UseAuthorization();\napp.UseAuthentication();<\/code><\/pre>\n<p>That\u2019s it. We have prepared everything we need.<br \/>\n\u5c31\u662f\u8fd9\u6837\u3002\u6211\u4eec\u5df2\u7ecf\u51c6\u5907\u597d\u4e86\u6211\u4eec\u9700\u8981\u7684\u4e00\u5207\u3002<\/p>\n<h2>27.2 Creating Tables and Inserting Roles<\/h2>\n<p>27.2 \u521b\u5efa table \u548c\u63d2\u5165\u89d2\u8272<\/p>\n<p>Creating tables is quite an easy process. All we have to do is to create and apply migration. So, let\u2019s create a migration:\u200c<br \/>\n\u521b\u5efa\u8868\u683c\u662f\u4e00\u4e2a\u975e\u5e38\u7b80\u5355\u7684\u8fc7\u7a0b\u3002\u6211\u4eec\u6240\u8981\u505a\u7684\u5c31\u662f\u521b\u5efa\u5e76\u5e94\u7528\u8fc1\u79fb\u3002\u90a3\u4e48\uff0c\u8ba9\u6211\u4eec\u521b\u5efa\u4e00\u4e2a\u8fc1\u79fb\uff1a<\/p>\n<pre><code>PM&gt; Add-Migration CreatingIdentityTables<\/code><\/pre>\n<p>And then apply it:<br \/>\n\u7136\u540e\u5e94\u7528\u5b83\uff1a<\/p>\n<pre><code>PM&gt; Update-Database<\/code><\/pre>\n<p>If we check our database now, we are going to see additional tables:<br \/>\n\u5982\u679c\u6211\u4eec\u73b0\u5728\u68c0\u67e5\u6211\u4eec\u7684\u6570\u636e\u5e93\uff0c\u6211\u4eec\u5c06\u770b\u5230\u989d\u5916\u7684\u8868\uff1a<\/p>\n<p><img decoding=\"async\" src=\"\/images\/ultimateaspnetcorewebapi6\/2702.jpg\" alt=\"alt text\" \/><\/p>\n<p>For our project, the AspNetRoles, AspNetUserRoles, and AspNetUsers tables will be quite enough. If you open the AspNetUsers table, you will see additional FirstName and LastName columns.<br \/>\n\u5bf9\u4e8e\u6211\u4eec\u7684\u9879\u76ee\uff0cAspNetRoles\u3001AspNetUserRoles \u548c AspNetUsers \u8868\u5c31\u8db3\u591f\u4e86\u3002\u5982\u679c\u6253\u5f00 AspNetUsers \u8868\uff0c\u5c06\u770b\u5230\u5176\u4ed6 FirstName \u548c LastName \u5217\u3002<\/p>\n<p>Now, let\u2019s insert several roles in the AspNetRoles table, again by using migrations. The first thing we are going to do is to create the RoleConfiguration class in the Repository\/Configuration folder:<br \/>\n\u73b0\u5728\uff0c\u8ba9\u6211\u4eec\u518d\u6b21\u4f7f\u7528\u8fc1\u79fb\u5728 AspNetRoles \u8868\u4e2d\u63d2\u5165\u591a\u4e2a\u89d2\u8272\u3002\u6211\u4eec\u8981\u505a\u7684\u7b2c\u4e00\u4ef6\u4e8b\u662f\u5728 Repository\/Configuration \u6587\u4ef6\u5939\u4e2d\u521b\u5efa RoleConfiguration \u7c7b\uff1a<\/p>\n<pre><code>public class RoleConfiguration : IEntityTypeConfiguration&lt;IdentityRole&gt; { public void Configure(EntityTypeBuilder&lt;IdentityRole&gt; builder) {builder.HasData( new IdentityRole { Name = &quot;Manager&quot;, NormalizedName = &quot;MANAGER&quot; }, new IdentityRole { Name = &quot;Administrator&quot;, NormalizedName = &quot;ADMINISTRATOR&quot; } ); }<\/code><\/pre>\n<p>For this to work, we need the following namespaces included:<br \/>\n\u4e3a\u6b64\uff0c\u6211\u4eec\u9700\u8981\u5305\u542b\u4ee5\u4e0b\u547d\u540d\u7a7a\u95f4\uff1a<\/p>\n<pre><code>using Microsoft.AspNetCore.Identity; \nusing Microsoft.EntityFrameworkCore;\nusing Microsoft.EntityFrameworkCore.Metadata.Builders;<\/code><\/pre>\n<p>And let\u2019s modify the OnModelCreating method in the RepositoryContext class:<br \/>\n\u8ba9\u6211\u4eec\u4fee\u6539 RepositoryContext \u7c7b\u4e2d\u7684 OnModelCreating \u65b9\u6cd5\uff1a<\/p>\n<pre><code>protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); modelBuilder.ApplyConfiguration(new CompanyConfiguration()); modelBuilder.ApplyConfiguration(new EmployeeConfiguration()); modelBuilder.ApplyConfiguration(new RoleConfiguration()); }<\/code><\/pre>\n<p>Finally, let\u2019s create and apply migration:<br \/>\n\u6700\u540e\uff0c\u8ba9\u6211\u4eec\u521b\u5efa\u5e76\u5e94\u7528\u8fc1\u79fb\uff1a<\/p>\n<pre><code>PM&gt; Add-Migration AddedRolesToDb\nPM&gt; Update-Database<\/code><\/pre>\n<p>If you check the AspNetRoles table, you will find two new roles created.<br \/>\n\u5982\u679c\u68c0\u67e5 AspNetRoles \u8868\uff0c\u60a8\u5c06\u53d1\u73b0\u521b\u5efa\u4e86\u4e24\u4e2a\u65b0\u89d2\u8272\u3002<\/p>\n<h2>27.3 User Creation<\/h2>\n<p>27.3 \u7528\u6237\u521b\u5efa<\/p>\n<p>To create\/register a new user, we have to create a new controller:\u200c<br \/>\n\u8981\u521b\u5efa\/\u6ce8\u518c\u65b0\u7528\u6237\uff0c\u6211\u4eec\u5fc5\u987b\u521b\u5efa\u4e00\u4e2a\u65b0\u7684\u63a7\u5236\u5668\uff1a<\/p>\n<pre><code>[Route(&quot;api\/authentication&quot;)] [ApiController] public class AuthenticationController : ControllerBase { private readonly IServiceManager _service; public AuthenticationController(IServiceManager service) =&gt; _service = service; }<\/code><\/pre>\n<p>So, nothing new here. We have the basic setup for our controller with IServiceManager injected.<br \/>\n\u6240\u4ee5\uff0c\u8fd9\u91cc\u6ca1\u4ec0\u4e48\u65b0\u9c9c\u4e8b\u3002\u6211\u4eec\u6709\u4e86\u6ce8\u5165\u4e86 IServiceManager \u7684\u63a7\u5236\u5668\u7684\u57fa\u672c\u8bbe\u7f6e\u3002<\/p>\n<p>The next thing we have to do is to create a UserForRegistrationDto record in the Shared\/DataTransferObjects folder:<br \/>\n\u63a5\u4e0b\u6765\u6211\u4eec\u8981\u505a\u7684\u662f\u5728 Shared\/DataTransferObjects \u6587\u4ef6\u5939\u4e2d\u521b\u5efa\u4e00\u4e2a UserForRegistrationDto \u8bb0\u5f55\uff1a<\/p>\n<pre><code>public record UserForRegistrationDto { public string? FirstName { get; init; } public string? LastName { get; init; } [Required(ErrorMessage = &quot;Username is required&quot;)] public string? UserName { get; init; } [Required(ErrorMessage = &quot;Password is required&quot;)] public string? Password { get; init; } public string? Email { get; init; } public string? PhoneNumber { get; init; } public ICollection&lt;string&gt;? Roles { get; init; } }<\/code><\/pre>\n<p>Then, let\u2019s create a mapping rule in the MappingProfile class:<br \/>\n\u7136\u540e\uff0c\u8ba9\u6211\u4eec\u5728 MappingProfile \u7c7b\u4e2d\u521b\u5efa\u4e00\u4e2a\u6620\u5c04\u89c4\u5219\uff1a<\/p>\n<pre><code>CreateMap&lt;UserForRegistrationDto, User&gt;();<\/code><\/pre>\n<p>Since we want to extract all the registration\/authentication logic to the service layer, we are going to create a new IAuthenticationService interface inside the Service.Contracts project:<br \/>\n\u7531\u4e8e\u6211\u4eec\u5e0c\u671b\u5c06\u6240\u6709\u6ce8\u518c\/\u8eab\u4efd\u9a8c\u8bc1\u903b\u8f91\u63d0\u53d6\u5230\u670d\u52a1\u5c42\uff0c\u56e0\u6b64\u6211\u4eec\u5c06\u5728 Service.Contracts \u9879\u76ee\u4e2d\u521b\u5efa\u4e00\u4e2a\u65b0\u7684 IAuthenticationService \u63a5\u53e3\uff1a<\/p>\n<pre><code>public interface IAuthenticationService { Task&lt;IdentityResult&gt; RegisterUser(UserForRegistrationDto userForRegistration); }<\/code><\/pre>\n<p>This method will execute the registration logic and return the identity result to the caller.<br \/>\n\u8be5\u65b9\u6cd5\u5c06\u6267\u884c\u6ce8\u518c\u903b\u8f91\uff0c\u5e76\u5c06\u8eab\u4efd\u7ed3\u679c\u8fd4\u56de\u7ed9\u8c03\u7528\u65b9\u3002<\/p>\n<p>Now that we have the interface, we need to create an implementation service class inside the Service project:<br \/>\n\u73b0\u5728\u6211\u4eec\u6709\u4e86\u63a5\u53e3\uff0c\u6211\u4eec\u9700\u8981\u5728 Service \u9879\u76ee\u4e2d\u521b\u5efa\u4e00\u4e2a\u5b9e\u73b0\u670d\u52a1\u7c7b\uff1a<\/p>\n<pre><code>internal sealed class AuthenticationService : IAuthenticationService { private readonly ILoggerManager _logger; private readonly IMapper _mapper; private readonly UserManager&lt;User&gt; _userManager; private readonly IConfiguration _configuration; public AuthenticationService(ILoggerManager logger, IMapper mapper, UserManager&lt;User&gt; userManager, IConfiguration configuration) { _logger = logger;_mapper = mapper; _userManager = userManager; _configuration = configuration; } }<\/code><\/pre>\n<p>This code is pretty familiar from the previous service classes except for the UserManager class. This class is used to provide the APIs for managing users in a persistence store. It is not concerned with how user information is stored. For this, it relies on a UserStore (which in our case uses Entity Framework Core).<br \/>\n\u6b64\u4ee3\u7801\u4e0e\u524d\u9762\u7684\u670d\u52a1\u7c7b\u975e\u5e38\u76f8\u4f3c\uff0c\u4f46 UserManager \u7c7b\u9664\u5916\u3002\u6b64\u7c7b\u7528\u4e8e\u63d0\u4f9b\u7528\u4e8e\u7ba1\u7406\u6301\u4e45\u6027\u5b58\u50a8\u4e2d\u7684\u7528\u6237\u7684 API\u3002\u5b83\u4e0d\u5173\u5fc3\u7528\u6237\u4fe1\u606f\u7684\u5b58\u50a8\u65b9\u5f0f\u3002\u4e3a\u6b64\uff0c\u5b83\u4f9d\u8d56\u4e8e UserStore\uff08\u5728\u6211\u4eec\u7684\u4f8b\u5b50\u4e2d\u4f7f\u7528 Entity Framework Core\uff09\u3002<\/p>\n<p>Of course, we have to add some additional namespaces:<br \/>\n\u5f53\u7136\uff0c\u6211\u4eec\u5fc5\u987b\u6dfb\u52a0\u4e00\u4e9b\u989d\u5916\u7684\u547d\u540d\u7a7a\u95f4\uff1a<\/p>\n<pre><code>using AutoMapper; using Contracts; using Entities.Models; using Microsoft.AspNetCore.Identity; using Microsoft.Extensions.Configuration; using Service.Contracts;<\/code><\/pre>\n<p>Great. Now, we can implement the RegisterUser method:<br \/>\n\u73b0\u5728\uff0c\u6211\u4eec\u53ef\u4ee5\u5b9e\u73b0 RegisterUser \u65b9\u6cd5\uff1a<\/p>\n<pre><code>public async Task&lt;IdentityResult&gt; RegisterUser(UserForRegistrationDto userForRegistration) { var user = _mapper.Map&lt;User&gt;(userForRegistration); var result = await _userManager.CreateAsync(user, userForRegistration.Password); if (result.Succeeded) await _userManager.AddToRolesAsync(user, userForRegistration.Roles); return result; }<\/code><\/pre>\n<p>So we map the DTO object to the User object and call the CreateAsync method to create that specific user in the database. The CreateAsync method will save the user to the database if the action succeeds or it will return error messages as a result.<br \/>\n\u56e0\u6b64\uff0c\u6211\u4eec\u5c06 DTO \u5bf9\u8c61\u6620\u5c04\u5230 User \u5bf9\u8c61\uff0c\u5e76\u8c03\u7528 CreateAsync \u65b9\u6cd5\u5728\u6570\u636e\u5e93\u4e2d\u521b\u5efa\u8be5\u7279\u5b9a\u7528\u6237\u3002\u5982\u679c\u4f5c\u6210\u529f\uff0cCreateAsync \u65b9\u6cd5\u4f1a\u5c06\u7528\u6237\u4fdd\u5b58\u5230\u6570\u636e\u5e93\uff0c\u5426\u5219\u5b83\u5c06\u8fd4\u56de\u9519\u8bef\u6d88\u606f\u3002<\/p>\n<p>After that, if a user is created, we add that user to the named roles \u2014 the ones sent from the client side \u2014 and return the result.<br \/>\n\u4e4b\u540e\uff0c\u5982\u679c\u521b\u5efa\u4e86\u4e00\u4e2a\u7528\u6237\uff0c\u6211\u4eec\u5c06\u8be5\u7528\u6237\u6dfb\u52a0\u5230\u547d\u540d\u89d2\u8272\uff08\u4ece\u5ba2\u6237\u7aef\u53d1\u9001\u7684\u89d2\u8272\uff09\u5e76\u8fd4\u56de\u7ed3\u679c\u3002<\/p>\n<p>If you want, before calling AddToRoleAsync or AddToRolesAsync, you can check if roles exist in the database. But for that, you have to inject RoleManager<TRole> and use the RoleExistsAsync method.<br \/>\n\u5982\u679c\u9700\u8981\uff0c\u5728\u8c03\u7528 AddToRoleAsync \u6216 AddToRolesAsync \u4e4b\u524d\uff0c\u53ef\u4ee5\u68c0\u67e5\u6570\u636e\u5e93\u4e2d\u662f\u5426\u5b58\u5728\u89d2\u8272\u3002\u4f46\u4e3a\u6b64\uff0c\u60a8\u5fc5\u987b\u6ce8\u5165 RoleManager \u5e76\u4f7f\u7528 RoleExistsAsync \u65b9\u6cd5\u3002<\/p>\n<p>We want to provide this service to the caller through ServiceManager and for that, we have to modify the IServiceManager interface first:<br \/>\n\u6211\u4eec\u5e0c\u671b\u901a\u8fc7 ServiceManager \u5411\u8c03\u7528\u65b9\u63d0\u4f9b\u6b64\u670d\u52a1\uff0c\u4e3a\u6b64\uff0c\u6211\u4eec\u5fc5\u987b\u5148\u4fee\u6539 IServiceManager \u63a5\u53e3\uff1a<\/p>\n<pre><code>public interface IServiceManager { ICompanyService CompanyService { get; } IEmployeeService EmployeeService { get; } IAuthenticationService AuthenticationService { get; } }<\/code><\/pre>\n<p>And then the ServiceManager class:<br \/>\n\u7136\u540e\u662f ServiceManager \u7c7b\uff1a<\/p>\n<pre><code>public sealed class ServiceManager : IServiceManager { private readonly Lazy&lt;ICompanyService&gt; _companyService; private readonly Lazy&lt;IEmployeeService&gt; _employeeService; private readonly Lazy&lt;IAuthenticationService&gt; _authenticationService; public ServiceManager(IRepositoryManager repositoryManager, ILoggerManager logger, IMapper mapper, IEmployeeLinks employeeLinks, UserManager&lt;User&gt; userManager, IConfiguration configuration) { _companyService = new Lazy&lt;ICompanyService&gt;(() =&gt; new CompanyService(repositoryManager, logger, mapper)); _employeeService = new Lazy&lt;IEmployeeService&gt;(() =&gt; new EmployeeService(repositoryManager, logger, mapper, employeeLinks)); _authenticationService = new Lazy&lt;IAuthenticationService&gt;(() =&gt; new AuthenticationService(logger, mapper, userManager, configuration)); } public ICompanyService CompanyService =&gt; _companyService.Value; public IEmployeeService EmployeeService =&gt; _employeeService.Value; public IAuthenticationService AuthenticationService =&gt; _authenticationService.Value; }<\/code><\/pre>\n<p>Finally, it is time to create the RegisterUser action:<br \/>\n\u6700\u540e\uff0c\u662f\u65f6\u5019\u521b\u5efa RegisterUser\u4f5c\u4e86\uff1a<\/p>\n<pre><code>[HttpPost] [ServiceFilter(typeof(ValidationFilterAttribute))] public async Task&lt;IActionResult&gt; RegisterUser([FromBody] UserForRegistrationDto userForRegistration) { var result = await _service.AuthenticationService.RegisterUser(userForRegistration); if (!result.Succeeded){ foreach (var error in result.Errors) { ModelState.TryAddModelError(error.Code, error.Description); } return BadRequest(ModelState); } return StatusCode(201); }<\/code><\/pre>\n<p>We are implementing our existing action filter for the entity and model validation on top of our action. Then, we call the RegisterUser method and accept the result. If the registration fails, we iterate through each error add it to the ModelState and return the BadRequest response. Otherwise, we return the 201 created status code.<br \/>\n\u6211\u4eec\u6b63\u5728\u4e3a\u4f5c\u4e4b\u4e0a\u7684\u5b9e\u4f53\u548c\u6a21\u578b\u9a8c\u8bc1\u5b9e\u73b0\u73b0\u6709\u7684\u4f5c\u7b5b\u9009\u5668\u3002\u7136\u540e\uff0c\u6211\u4eec\u8c03\u7528 RegisterUser \u65b9\u6cd5\u5e76\u63a5\u53d7\u7ed3\u679c\u3002\u5982\u679c\u6ce8\u518c\u5931\u8d25\uff0c\u6211\u4eec\u5c06\u904d\u5386\u6bcf\u4e2a\u9519\u8bef\uff0c\u5c06\u5176\u6dfb\u52a0\u5230 ModelState \u5e76\u8fd4\u56de BadRequest \u54cd\u5e94\u3002\u5426\u5219\uff0c\u6211\u4eec\u5c06\u8fd4\u56de 201 created \u72b6\u6001\u4ee3\u7801\u3002<\/p>\n<p>Before we continue with testing, we should increase a rate limit from 3 to 30 (ServiceExtensions class, ConfigureRateLimitingOptions method) just to not stand in our way while we\u2019re testing the different features of our application.<br \/>\n\u5728\u7ee7\u7eed\u6d4b\u8bd5\u4e4b\u524d\uff0c\u6211\u4eec\u5e94\u8be5\u5c06\u901f\u7387\u9650\u5236\u4ece 3 \u589e\u52a0\u5230 30\uff08ServiceExtensions \u7c7b\uff0cConfigureRateLimitingOptions \u65b9\u6cd5\uff09\uff0c\u4ee5\u514d\u5728\u6d4b\u8bd5\u5e94\u7528\u7a0b\u5e8f\u7684\u4e0d\u540c\u529f\u80fd\u65f6\u6210\u4e3a\u6211\u4eec\u7684\u969c\u788d\u3002<\/p>\n<p>Now we can start with testing.Let\u2019s send a valid request first:<br \/>\n\u73b0\u5728\u6211\u4eec\u53ef\u4ee5\u4ece\u6d4b\u8bd5\u5f00\u59cb\u3002\u8ba9\u6211\u4eec\u5148\u53d1\u9001\u4e00\u4e2a\u6709\u6548\u7684\u8bf7\u6c42\uff1a<\/p>\n<p><a href=\"https:\/\/localhost:5001\/api\/authentication\">https:\/\/localhost:5001\/api\/authentication<\/a><\/p>\n<p><img decoding=\"async\" src=\"\/images\/ultimateaspnetcorewebapi6\/2703.jpg\" alt=\"alt text\" \/><\/p>\n<p>And we get 201, which means that the user has been created and added to the role. We can send additional invalid requests to test our Action and Identity features.<br \/>\n\u6211\u4eec\u5f97\u5230 201\uff0c\u8fd9\u610f\u5473\u7740\u7528\u6237\u5df2\u88ab\u521b\u5efa\u5e76\u6dfb\u52a0\u5230\u89d2\u8272\u4e2d\u3002\u6211\u4eec\u53ef\u4ee5\u53d1\u9001\u5176\u4ed6\u65e0\u6548\u8bf7\u6c42\u6765\u6d4b\u8bd5\u6211\u4eec\u7684 Action \u548c Identity \u529f\u80fd\u3002<\/p>\n<p>If the model is invalid:<br \/>\n\u5982\u679c\u6a21\u578b\u65e0\u6548\uff1a<\/p>\n<p><a href=\"https:\/\/localhost:5001\/api\/authentication\">https:\/\/localhost:5001\/api\/authentication<\/a><\/p>\n<p><img decoding=\"async\" src=\"\/images\/ultimateaspnetcorewebapi6\/2704.jpg\" alt=\"alt text\" \/><\/p>\n<p>If the password is invalid:<br \/>\n\u5982\u679c\u5bc6\u7801\u65e0\u6548\uff1a<\/p>\n<p><a href=\"https:\/\/localhost:5001\/api\/authentication\">https:\/\/localhost:5001\/api\/authentication<\/a><\/p>\n<p><img decoding=\"async\" src=\"\/images\/ultimateaspnetcorewebapi6\/2705.jpg\" alt=\"alt text\" \/><\/p>\n<p>Finally, if we want to create a user with the same user name and email:<\/p>\n<p>\u6700\u540e\uff0c\u5982\u679c\u6211\u4eec\u60f3\u521b\u5efa\u4e00\u4e2a\u5177\u6709\u76f8\u540c\u7528\u6237\u540d\u548c\u7535\u5b50\u90ae\u4ef6\u7684\u7528\u6237\uff1a<br \/>\n<a href=\"https:\/\/localhost:5001\/api\/authentication\">https:\/\/localhost:5001\/api\/authentication<\/a><\/p>\n<p><img decoding=\"async\" src=\"\/images\/ultimateaspnetcorewebapi6\/2706.jpg\" alt=\"alt text\" \/><\/p>\n<p>Excellent. Everything is working as planned. We can move on to the JWT implementation.<br \/>\n\u975e\u5e38\u597d\u3002\u4e00\u5207\u90fd\u5728\u6309\u8ba1\u5212\u8fdb\u884c\u3002\u6211\u4eec\u53ef\u4ee5\u7ee7\u7eed\u8fdb\u884c JWT \u5b9e\u73b0\u3002<\/p>\n<h2>27.4 Big Picture<\/h2>\n<p>27.4 \u5927\u5c40<\/p>\n<p>Before we get into the implementation of authentication and authorization, let\u2019s have a quick look at the big picture. There is an application that has a login form. A user enters their username and password and presses the login button. After pressing the login button, a client (e.g., web browser) sends the user\u2019s data to the server\u2019s API endpoint:\u200c<br \/>\n\u5728\u6211\u4eec\u5f00\u59cb\u5b9e\u73b0\u8eab\u4efd\u9a8c\u8bc1\u548c\u6388\u6743\u4e4b\u524d\uff0c\u8ba9\u6211\u4eec\u5feb\u901f\u4e86\u89e3\u4e00\u4e0b\u5927\u5c40\u3002\u6709\u4e00\u4e2a\u5e94\u7528\u7a0b\u5e8f\u5177\u6709\u767b\u5f55\u8868\u5355\u3002\u7528\u6237\u8f93\u5165\u5176\u7528\u6237\u540d\u548c\u5bc6\u7801\uff0c\u7136\u540e\u6309\u767b\u5f55\u6309\u94ae\u3002\u6309\u4e0b\u767b\u5f55\u6309\u94ae\u540e\uff0c\u5ba2\u6237\u7aef\uff08\u4f8b\u5982 Web \u6d4f\u89c8\u5668\uff09\u5c06\u7528\u6237\u7684\u6570\u636e\u53d1\u9001\u5230\u670d\u52a1\u5668\u7684 API \u7aef\u70b9\uff1a<\/p>\n<p><img decoding=\"async\" src=\"\/images\/ultimateaspnetcorewebapi6\/2707.jpg\" alt=\"alt text\" \/><\/p>\n<p>When the server validates the user\u2019s credentials and confirms that the user is valid, it\u2019s going to send an encoded JWT to the client. A JSON web token is a JavaScript object that can contain some attributes of the logged-in user. It can contain a username, user subject, user roles, or some other useful information.<br \/>\n\u5f53\u670d\u52a1\u5668\u9a8c\u8bc1\u7528\u6237\u7684\u51ed\u8bc1\u5e76\u786e\u8ba4\u7528\u6237\u6709\u6548\u65f6\uff0c\u5b83\u5c06\u5411\u5ba2\u6237\u7aef\u53d1\u9001\u7f16\u7801\u7684 JWT\u3002JSON Web \u4ee4\u724c\u662f\u4e00\u4e2a JavaScript \u5bf9\u8c61\uff0c\u53ef\u4ee5\u5305\u542b\u5df2\u767b\u5f55\u7528\u6237\u7684\u67d0\u4e9b\u5c5e\u6027\u3002\u5b83\u53ef\u4ee5\u5305\u542b\u7528\u6237\u540d\u3001\u7528\u6237\u4e3b\u9898\u3001\u7528\u6237\u89d2\u8272\u6216\u5176\u4ed6\u4e00\u4e9b\u6709\u7528\u7684\u4fe1\u606f\u3002<\/p>\n<h2>27.5 About JWT<\/h2>\n<p>27.5 \u5173\u4e8e JWT<\/p>\n<p>JSON web tokens enable a secure way to transmit data between two parties in the form of a JSON object. It\u2019s an open standard and it\u2019s a popular mechanism for web authentication. In our case, we are going to use JSON web tokens to securely transfer a user\u2019s data between the client and the server.\u200c<br \/>\nJSON Web \u4ee4\u724c\u652f\u6301\u4ee5 JSON \u5bf9\u8c61\u7684\u5f62\u5f0f\u5728\u4e24\u65b9\u4e4b\u95f4\u4f20\u8f93\u6570\u636e\u7684\u5b89\u5168\u65b9\u5f0f\u3002\u5b83\u662f\u4e00\u4e2a\u5f00\u653e\u6807\u51c6\uff0c\u4e5f\u662f\u4e00\u79cd\u6d41\u884c\u7684 Web \u8eab\u4efd\u9a8c\u8bc1\u673a\u5236\u3002\u5728\u6211\u4eec\u7684\u4f8b\u5b50\u4e2d\uff0c\u6211\u4eec\u5c06\u4f7f\u7528 JSON Web \u4ee4\u724c\u5728\u5ba2\u6237\u7aef\u548c\u670d\u52a1\u5668\u4e4b\u95f4\u5b89\u5168\u5730\u4f20\u8f93\u7528\u6237\u7684\u6570\u636e\u3002<\/p>\n<p>JSON web tokens consist of three basic parts: the header, the payload, and the signature.<br \/>\nJSON Web \u4ee4\u724c\u7531\u4e09\u4e2a\u57fa\u672c\u90e8\u5206\u7ec4\u6210\uff1a\u6807\u5934\u3001\u6709\u6548\u8d1f\u8f7d\u548c\u7b7e\u540d\u3002<\/p>\n<p>One real example of a JSON web token:<br \/>\nJSON Web \u4ee4\u724c\u7684\u4e00\u4e2a\u771f\u5b9e\u793a\u4f8b\uff1a<\/p>\n<p><img decoding=\"async\" src=\"\/images\/ultimateaspnetcorewebapi6\/2708.jpg\" alt=\"alt text\" \/><\/p>\n<p>Every part of all three parts is shown in a different color. The first part of JWT is the header, which is a JSON object encoded in the base64 format. The header is a standard part of JWT and we don\u2019t have to worry about it. It contains information like the type of token and the name of the algorithm:<br \/>\n\u6240\u6709\u4e09\u4e2a\u90e8\u5206\u7684\u6bcf\u4e2a\u90e8\u5206\u90fd\u4ee5\u4e0d\u540c\u7684\u989c\u8272\u663e\u793a\u3002JWT \u7684\u7b2c\u4e00\u90e8\u5206\u662f\u6807\u5934\uff0c\u5b83\u662f\u4ee5 base64 \u683c\u5f0f\u7f16\u7801\u7684 JSON \u5bf9\u8c61\u3002\u6807\u5934\u662f JWT \u7684\u6807\u51c6\u90e8\u5206\uff0c\u6211\u4eec\u4e0d\u5fc5\u62c5\u5fc3\u5b83\u3002\u5b83\u5305\u542b\u4ee4\u724c\u7c7b\u578b\u548c\u7b97\u6cd5\u540d\u79f0\u7b49\u4fe1\u606f\uff1a<\/p>\n<pre><code>{ &quot;alg&quot;: &quot;HS256&quot;, &quot;typ&quot;: &quot;JWT&quot; }<\/code><\/pre>\n<p>After the header, we have a payload which is also a JavaScript object encoded in the base64 format. The payload contains some attributes about the logged-in user. For example, it can contain the user id, the user subject, and information about whether a user is an admin user or not.<br \/>\n\u5728\u6807\u5934\u4e4b\u540e\uff0c\u6211\u4eec\u6709\u4e00\u4e2a\u6709\u6548\u8d1f\u8f7d\uff0c\u5b83\u4e5f\u662f\u4e00\u4e2a\u4ee5 base64 \u683c\u5f0f\u7f16\u7801\u7684 JavaScript \u5bf9\u8c61\u3002\u6709\u6548\u8d1f\u8f7d\u5305\u542b\u6709\u5173\u5df2\u767b\u5f55\u7528\u6237\u7684\u4e00\u4e9b\u5c5e\u6027\u3002\u4f8b\u5982\uff0c\u5b83\u53ef\u4ee5\u5305\u542b\u7528\u6237 ID\u3001\u7528\u6237\u4e3b\u9898\u4ee5\u53ca\u6709\u5173\u7528\u6237\u662f\u5426\u4e3a\u7ba1\u7406\u5458\u7528\u6237\u7684\u4fe1\u606f\u3002<\/p>\n<p>JSON web tokens are not encrypted and can be decoded with any base64 decoder, so please never include sensitive information in the Payload:<br \/>\nJSON Web \u4ee4\u724c\u672a\u52a0\u5bc6\uff0c\u53ef\u4ee5\u4f7f\u7528\u4efb\u4f55 base64 \u89e3\u7801\u5668\u8fdb\u884c\u89e3\u7801\uff0c\u56e0\u6b64\u8bf7\u4e0d\u8981\u5728\u6709\u6548\u8d1f\u8f7d\u4e2d\u5305\u542b\u654f\u611f\u4fe1\u606f\uff1a<\/p>\n<pre><code>{ &quot;sub&quot;: &quot;1234567890&quot;, &quot;name&quot;: &quot;John Doe&quot;, &quot;iat&quot;: 1516239022 }<\/code><\/pre>\n<p>Finally, we have the signature part. Usually, the server uses the signature part to verify whether the token contains valid information, the information which the server is issuing. It is a digital signature that gets generated by combining the header and the payload. Moreover, it\u2019s based on a secret key that only the server knows:<br \/>\n\u6700\u540e\uff0c\u6211\u4eec\u6709\u7b7e\u540d\u90e8\u5206\u3002\u901a\u5e38\uff0c\u670d\u52a1\u5668\u4f7f\u7528\u7b7e\u540d\u90e8\u5206\u6765\u9a8c\u8bc1\u4ee4\u724c\u662f\u5426\u5305\u542b\u6709\u6548\u4fe1\u606f\uff0c\u5373\u670d\u52a1\u5668\u9881\u53d1\u7684\u4fe1\u606f\u3002\u5b83\u662f\u901a\u8fc7\u7ec4\u5408\u6807\u5934\u548c\u6709\u6548\u8d1f\u8f7d\u751f\u6210\u7684\u6570\u5b57\u7b7e\u540d\u3002\u6b64\u5916\uff0c\u5b83\u57fa\u4e8e\u53ea\u6709\u670d\u52a1\u5668\u77e5\u9053\u7684\u5bc6\u94a5\uff1a<\/p>\n<p><img decoding=\"async\" src=\"\/images\/ultimateaspnetcorewebapi6\/2709.jpg\" alt=\"alt text\" \/><\/p>\n<p>So, if malicious users try to modify the values in the payload, they have to recreate the signature; for that purpose, they need the secret key only known to the server. On the server side, we can easily verify if the values are original or not by comparing the original signature with a new signature computed from the values coming from the client.<br \/>\n\u56e0\u6b64\uff0c\u5982\u679c\u6076\u610f\u7528\u6237\u5c1d\u8bd5\u4fee\u6539\u6709\u6548\u8d1f\u8f7d\u4e2d\u7684\u503c\uff0c\u5219\u5fc5\u987b\u91cd\u65b0\u521b\u5efa\u7b7e\u540d;\u4e3a\u6b64\uff0c\u4ed6\u4eec\u9700\u8981\u53ea\u6709\u670d\u52a1\u5668\u77e5\u9053\u7684\u5bc6\u94a5\u3002\u5728\u670d\u52a1\u5668\u7aef\uff0c\u6211\u4eec\u53ef\u4ee5\u901a\u8fc7\u5c06\u539f\u59cb\u7b7e\u540d\u4e0e\u6839\u636e\u6765\u81ea\u5ba2\u6237\u7aef\u7684\u503c\u8ba1\u7b97\u7684\u65b0\u7b7e\u540d\u8fdb\u884c\u6bd4\u8f83\uff0c\u8f7b\u677e\u9a8c\u8bc1\u503c\u662f\u5426\u4e3a\u539f\u59cb\u503c\u3002<\/p>\n<p>So, we can easily verify the integrity of our data just by comparing the digital signatures. This is the reason why we use JWT.<br \/>\n\u56e0\u6b64\uff0c\u6211\u4eec\u53ea\u9700\u6bd4\u8f83\u6570\u5b57\u7b7e\u540d\u5373\u53ef\u8f7b\u677e\u9a8c\u8bc1\u6570\u636e\u7684\u5b8c\u6574\u6027\u3002\u8fd9\u5c31\u662f\u6211\u4eec\u4f7f\u7528 JWT \u7684\u539f\u56e0\u3002<\/p>\n<h2>27.6 JWT Configuration<\/h2>\n<p>27.6 JWT \u914d\u7f6e<\/p>\n<p>Let\u2019s start by modifying the appsettings.json file:\u200c<br \/>\n\u8ba9\u6211\u4eec\u4ece\u4fee\u6539 appsettings.json \u6587\u4ef6\u5f00\u59cb\uff1a<\/p>\n<pre><code>{ &quot;Logging&quot;: { &quot;LogLevel&quot;: { &quot;Default&quot;: &quot;Information&quot;, &quot;Microsoft.AspNetCore&quot;: &quot;Warning&quot;, } }, &quot;ConnectionStrings&quot;: { &quot;sqlConnection&quot;: &quot;server=.; database=CompanyEmployee; Integrated Security=true&quot; }, &quot;JwtSettings&quot;: { &quot;validIssuer&quot;: &quot;CodeMazeAPI&quot;, &quot;validAudience&quot;: &quot;https:\/\/localhost:5001&quot; }, &quot;AllowedHosts&quot;: &quot;*&quot; }<\/code><\/pre>\n<p>We just store the issuer and audience information in the appsettings.json file. We are going to talk more about that in a minute. As you probably remember, we require a secret key on the server-side. So, we are going to create one and store it in the environment variable because this is much safer than storing it inside the project.<br \/>\n\u6211\u4eec\u53ea\u5c06\u53d1\u884c\u8005\u548c\u53d7\u4f17\u4fe1\u606f\u5b58\u50a8\u5728 appsettings.json \u6587\u4ef6\u4e2d\u3002\u6211\u4eec\u7a0d\u540e\u5c06\u8be6\u7ec6\u8ba8\u8bba\u8fd9\u4e2a\u95ee\u9898\u3002\u60a8\u53ef\u80fd\u8fd8\u8bb0\u5f97\uff0c\u6211\u4eec\u9700\u8981\u670d\u52a1\u5668\u7aef\u7684\u5bc6\u94a5\u3002\u56e0\u6b64\uff0c\u6211\u4eec\u5c06\u521b\u5efa\u4e00\u4e2a\u5e76\u5c06\u5176\u5b58\u50a8\u5728\u73af\u5883\u53d8\u91cf\u4e2d\uff0c\u56e0\u4e3a\u8fd9\u6bd4\u5c06\u5176\u5b58\u50a8\u5728\u9879\u76ee\u4e2d\u8981\u5b89\u5168\u5f97\u591a\u3002<\/p>\n<p>To create an environment variable, we have to open the cmd window as an administrator and type the following command:<br \/>\n\u8981\u521b\u5efa\u73af\u5883\u53d8\u91cf\uff0c\u6211\u4eec\u5fc5\u987b\u4ee5\u7ba1\u7406\u5458\u8eab\u4efd\u6253\u5f00 cmd \u7a97\u53e3\u5e76\u952e\u5165\u4ee5\u4e0b\u547d\u4ee4\uff1a<\/p>\n<pre><code>setx SECRET &quot;CodeMazeSecretKey&quot; \/M<\/code><\/pre>\n<p>This is going to create a system environment variable with the name SECRET and the value CodeMazeSecretKey. By using \/M we specify that we want a system variable and not local.<br \/>\n\u8fd9\u5c06\u521b\u5efa\u4e00\u4e2a\u540d\u79f0\u4e3a SECRET \u4e14\u503c\u4e3a CodeMazeSecretKey \u7684\u7cfb\u7edf\u73af\u5883\u53d8\u91cf\u3002\u901a\u8fc7\u4f7f\u7528 \/M\uff0c\u6211\u4eec\u6307\u5b9a\u6211\u4eec\u60f3\u8981\u4e00\u4e2a\u7cfb\u7edf\u53d8\u91cf\uff0c\u800c\u4e0d\u662f\u5c40\u90e8\u53d8\u91cf\u3002<\/p>\n<p>Great. We can now modify the ServiceExtensions class:<br \/>\n\u6211\u4eec\u73b0\u5728\u53ef\u4ee5\u4fee\u6539 ServiceExtensions \u7c7b\uff1a<\/p>\n<pre><code>public static void ConfigureJWT(this IServiceCollection services, IConfiguration configuration) { var jwtSettings = configuration.GetSection(&quot;JwtSettings&quot;); var secretKey = Environment.GetEnvironmentVariable(&quot;SECRET&quot;); services.AddAuthentication(opt =&gt; { opt.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; opt.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; }) .AddJwtBearer(options =&gt; { options.TokenValidationParameters = new TokenValidationParameters { ValidateIssuer = true, ValidateAudience = true, ValidateLifetime = true, ValidateIssuerSigningKey = true, ValidIssuer = jwtSettings[&quot;validIssuer&quot;], ValidAudience = jwtSettings[&quot;validAudience&quot;], IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(secretKey)) }; }); }<\/code><\/pre>\n<p>First, we extract the JwtSettings from the appsettings.json file and extract our environment variable (If you keep getting null for the secret key, try restarting the Visual Studio or even your computer).<br \/>\n\u9996\u5148\uff0c\u6211\u4eec\u4ece appsettings.json \u6587\u4ef6\u4e2d\u63d0\u53d6 JwtSettings \u5e76\u63d0\u53d6\u73af\u5883\u53d8\u91cf\uff08\u5982\u679c\u5bc6\u94a5\u4e00\u76f4\u4e3a null\uff0c\u8bf7\u5c1d\u8bd5\u91cd\u65b0\u542f\u52a8 Visual Studio \u751a\u81f3\u8ba1\u7b97\u673a\uff09\u3002<\/p>\n<p>Then, we register the JWT authentication middleware by calling the method AddAuthentication on the IServiceCollection interface. Next, we specify the authentication scheme JwtBearerDefaults.AuthenticationScheme as well as ChallengeScheme. We also provide some parameters that will be used while validating JWT. For this to work, we have to install the Microsoft.AspNetCore.Authentication.JwtBearer library.<br \/>\n\u7136\u540e\uff0c\u6211\u4eec\u901a\u8fc7\u5728 IServiceCollection \u63a5\u53e3\u4e0a\u8c03\u7528 AddAuthentication \u65b9\u6cd5\u6765\u6ce8\u518c JWT \u8eab\u4efd\u9a8c\u8bc1\u4e2d\u95f4\u4ef6\u3002\u63a5\u4e0b\u6765\uff0c\u6211\u4eec\u6307\u5b9a\u8eab\u4efd\u9a8c\u8bc1\u65b9\u6848 JwtBearerDefaults.AuthenticationScheme \u4ee5\u53ca ChallengeScheme\u3002\u6211\u4eec\u8fd8\u63d0\u4f9b\u4e86\u4e00\u4e9b\u53c2\u6570\uff0c\u8fd9\u4e9b\u53c2\u6570\u5c06\u5728\u9a8c\u8bc1 JWT \u65f6\u4f7f\u7528\u3002\u4e3a\u6b64\uff0c\u6211\u4eec\u5fc5\u987b\u5b89\u88c5 Microsoft.AspNetCore.Authentication.JwtBearer \u5e93\u3002<\/p>\n<p>For this to work, we require the following namespaces:<br \/>\n\u4e3a\u6b64\uff0c\u6211\u4eec\u9700\u8981\u4ee5\u4e0b\u547d\u540d\u7a7a\u95f4\uff1a<\/p>\n<pre><code>using Microsoft.AspNetCore.Authentication.JwtBearer; \nusing Microsoft.AspNetCore.Identity;\nusing Microsoft.IdentityModel.Tokens; \nusing System.Text;<\/code><\/pre>\n<p>Excellent. We\u2019ve successfully configured the JWT authentication.<br \/>\n\u975e\u5e38\u597d\u3002\u6211\u4eec\u5df2\u6210\u529f\u914d\u7f6e JWT \u8eab\u4efd\u9a8c\u8bc1\u3002<\/p>\n<p>According to the configuration, the token is going to be valid if:<br \/>\n\u6839\u636e\u914d\u7f6e\uff0c\u5982\u679c\u6ee1\u8db3\u4ee5\u4e0b\u6761\u4ef6\uff0c\u5219\u4ee4\u724c\u5c06\u6709\u6548\uff1a<\/p>\n<p>\u2022 The issuer is the actual server that created the token (ValidateIssuer=true)<br \/>\n\u9881\u53d1\u8005\u662f\u521b\u5efa\u4ee4\u724c\u7684\u5b9e\u9645\u670d\u52a1\u5668 \uff08ValidateIssuer=true\uff09<\/p>\n<p>\u2022 The receiver of the token is a valid recipient (ValidateAudience=true)<br \/>\n\u4ee4\u724c\u7684\u63a5\u6536\u8005\u662f\u6709\u6548\u7684\u63a5\u6536\u8005 \uff08ValidateAudience=true\uff09<\/p>\n<p>\u2022 The token has not expired (ValidateLifetime=true)<br \/>\n\u4ee4\u724c\u5c1a\u672a\u8fc7\u671f \uff08ValidateLifetime=true\uff09<\/p>\n<p>\u2022 The signing key is valid and is trusted by the server (ValidateIssuerSigningKey=true)<br \/>\n\u7b7e\u540d\u5bc6\u94a5\u6709\u6548\u4e14\u53d7\u670d\u52a1\u5668\u4fe1\u4efb \uff08ValidateIssuerSigningKey=true\uff09<\/p>\n<p>Additionally, we are providing values for the issuer, the audience, and the secret key that the server uses to generate the signature for JWT.<br \/>\n\u6b64\u5916\uff0c\u6211\u4eec\u8fd8\u4e3a\u9881\u53d1\u8005\u3001\u53d7\u4f17\u548c\u670d\u52a1\u5668\u7528\u4e8e\u751f\u6210 JWT \u7b7e\u540d\u7684\u5bc6\u94a5\u63d0\u4f9b\u503c\u3002<\/p>\n<p>All we have to do is to call this method in the Program class:<br \/>\n\u6211\u4eec\u6240\u8981\u505a\u7684\u5c31\u662f\u5728 Program \u7c7b\u4e2d\u8c03\u7528\u6b64\u65b9\u6cd5\uff1a<\/p>\n<pre><code>builder.Services.ConfigureJWT(builder.Configuration);\nbuilder.Services.AddAuthentication(); \nbuilder.Services.ConfigureIdentity();<\/code><\/pre>\n<p>And that is it. We can now protect our endpoints.<br \/>\n\u5c31\u662f\u8fd9\u6837\u3002\u6211\u4eec\u73b0\u5728\u53ef\u4ee5\u4fdd\u62a4\u6211\u4eec\u7684\u7aef\u70b9\u3002<\/p>\n<h2>27.7 Protecting Endpoints<\/h2>\n<p>27.7 \u4fdd\u62a4\u7aef\u70b9<\/p>\n<p>Let\u2019s open the CompaniesController and add an additional attribute above the GetCompanies action:\u200c<br \/>\n\u8ba9\u6211\u4eec\u6253\u5f00 CompaniesController \u5e76\u5728 GetCompanies\u4f5c\u4e0a\u65b9\u6dfb\u52a0\u4e00\u4e2a\u9644\u52a0\u5c5e\u6027\uff1a<\/p>\n<pre><code>[HttpGet(Name = &quot;GetCompanies&quot;)]\n[Authorize] \npublic async Task&lt;IActionResult&gt; GetCompanies()<\/code><\/pre>\n<p>The [Authorize] attribute specifies that the action or controller that it is applied to requires authorization. For it to be available we need an additional namespace:<br \/>\n[Authorize] \u5c5e\u6027\u6307\u5b9a\u5e94\u7528\u8be5\u5c5e\u6027\u7684\u4f5c\u6216\u63a7\u5236\u5668\u9700\u8981\u6388\u6743\u3002\u4e3a\u4e86\u4f7f\u5176\u53ef\u7528\uff0c\u6211\u4eec\u9700\u8981\u4e00\u4e2a\u989d\u5916\u7684\u547d\u540d\u7a7a\u95f4\uff1a<\/p>\n<pre><code>using Microsoft.AspNetCore.Authorization;<\/code><\/pre>\n<p>Now to test this, let\u2019s send a request to get all companies:<br \/>\n\u73b0\u5728\u4e3a\u4e86\u6d4b\u8bd5\u8fd9\u4e00\u70b9\uff0c\u8ba9\u6211\u4eec\u53d1\u9001\u4e00\u4e2a\u8bf7\u6c42\u4ee5\u83b7\u53d6 all companies\uff1a<\/p>\n<p><a href=\"https:\/\/localhost:5001\/api\/companies\">https:\/\/localhost:5001\/api\/companies<\/a><\/p>\n<p><img decoding=\"async\" src=\"\/images\/ultimateaspnetcorewebapi6\/2710.jpg\" alt=\"alt text\" \/><\/p>\n<p>We see the protection works. We get a 401 Unauthorized response, which is expected because an unauthorized user tried to access the protected endpoint. So, what we need is for our users to be authenticated and to have a valid token.<br \/>\n\u6211\u4eec\u770b\u5230\u4fdd\u62a4\u5de5\u4f5c\u6b63\u5e38\u3002\u6211\u4eec\u6536\u5230 401 Unauthorized \u54cd\u5e94\uff0c\u8fd9\u662f\u610f\u6599\u4e4b\u4e2d\u7684\uff0c\u56e0\u4e3a\u672a\u7ecf\u6388\u6743\u7684\u7528\u6237\u8bd5\u56fe\u8bbf\u95ee\u53d7\u4fdd\u62a4\u7684\u7ec8\u7aef\u8282\u70b9\u3002\u56e0\u6b64\uff0c\u6211\u4eec\u9700\u8981\u7684\u662f\u8ba9\u6211\u4eec\u7684\u7528\u6237\u7ecf\u8fc7\u8eab\u4efd\u9a8c\u8bc1\u5e76\u62e5\u6709\u6709\u6548\u7684\u4ee4\u724c\u3002<\/p>\n<h2>27.8 Implementing Authentication<\/h2>\n<p>27.8 \u5b9e\u73b0\u8eab\u4efd\u9a8c\u8bc1<\/p>\n<p>Let\u2019s begin with the UserForAuthenticationDto record:\u200c<br \/>\n\u8ba9\u6211\u4eec\u4ece UserForAuthenticationDto \u8bb0\u5f55\u5f00\u59cb\uff1a<\/p>\n<pre><code>public record UserForAuthenticationDto { [Required(ErrorMessage = &quot;User name is required&quot;)] public string? UserName { get; init; } [Required(ErrorMessage = &quot;Password name is required&quot;)] public string? Password { get; init; } }<\/code><\/pre>\n<p>To continue, let\u2019s modify the IAuthenticationService interface:<br \/>\n\u8981\u7ee7\u7eed\uff0c\u8ba9\u6211\u4eec\u4fee\u6539 IAuthenticationService \u63a5\u53e3\uff1a<\/p>\n<pre><code>public interface IAuthenticationService { Task&lt;IdentityResult&gt; RegisterUser(UserForRegistrationDto userForRegistration); Task&lt;bool&gt; ValidateUser(UserForAuthenticationDto userForAuth); Task&lt;string&gt; CreateToken(); }<\/code><\/pre>\n<p>Next, let\u2019s add a private variable in the AuthenticationService class:<br \/>\n\u63a5\u4e0b\u6765\uff0c\u8ba9\u6211\u4eec\u5728 AuthenticationService \u7c7b\u4e2d\u6dfb\u52a0\u4e00\u4e2a\u79c1\u6709\u53d8\u91cf\uff1a<\/p>\n<pre><code>private readonly UserManager&lt;User&gt; _userManager; private readonly IConfiguration _configuration; private User? _user;<\/code><\/pre>\n<p>Before we continue to the interface implementation, we have to install System.IdentityModel.Tokens.Jwt library in the Service project. Then, we can implement the required methods:<br \/>\n\u5728\u7ee7\u7eed\u63a5\u53e3\u5b9e\u73b0\u4e4b\u524d\uff0c\u6211\u4eec\u5fc5\u987b\u5728 Service \u9879\u76ee\u4e2d\u5b89\u88c5 System.IdentityModel.Tokens.Jwt \u5e93\u3002\u7136\u540e\uff0c\u6211\u4eec\u53ef\u4ee5\u5b9e\u73b0\u6240\u9700\u7684\u65b9\u6cd5\uff1a<\/p>\n<pre><code>public async Task&lt;bool&gt; ValidateUser(UserForAuthenticationDto userForAuth) { _user = await _userManager.FindByNameAsync(userForAuth.UserName); var result = (_user != null &amp;&amp; await _userManager.CheckPasswordAsync(_user, userForAuth.Password)); if (!result) _logger.LogWarn($&quot;{nameof(ValidateUser)}: Authentication failed. Wrong user name or password.&quot;); return result; } public async Task&lt;string&gt; CreateToken() { var signingCredentials = GetSigningCredentials(); var claims = await GetClaims(); var tokenOptions = GenerateTokenOptions(signingCredentials, claims); return new JwtSecurityTokenHandler().WriteToken(tokenOptions); } private SigningCredentials GetSigningCredentials() { var key = Encoding.UTF8.GetBytes(Environment.GetEnvironmentVariable(&quot;SECRET&quot;)); var secret = new SymmetricSecurityKey(key); return new SigningCredentials(secret, SecurityAlgorithms.HmacSha256); } private async Task&lt;List&lt;Claim&gt;&gt; GetClaims() { var claims = new List&lt;Claim&gt; { new Claim(ClaimTypes.Name, _user.UserName) }; var roles = await _userManager.GetRolesAsync(_user); foreach (var role in roles) { claims.Add(new Claim(ClaimTypes.Role, role)); } return claims; }private JwtSecurityToken GenerateTokenOptions(SigningCredentials signingCredentials, List&lt;Claim&gt; claims) { var jwtSettings = _configuration.GetSection(&quot;JwtSettings&quot;); var tokenOptions = new JwtSecurityToken ( issuer: jwtSettings[&quot;validIssuer&quot;], audience: jwtSettings[&quot;validAudience&quot;], claims: claims, expires: DateTime.Now.AddMinutes(Convert.ToDouble(jwtSettings[&quot;expires&quot;])), signingCredentials: signingCredentials ); return tokenOptions; }<\/code><\/pre>\n<p>For this to work, we require a few more namespaces:<br \/>\n\u4e3a\u6b64\uff0c\u6211\u4eec\u9700\u8981\u66f4\u591a\u7684\u547d\u540d\u7a7a\u95f4\uff1a<\/p>\n<pre><code>using System.IdentityModel.Tokens.Jwt; \nusing Microsoft.IdentityModel.Tokens; \nusing System.Text;\nusing System.Security.Claims;<\/code><\/pre>\n<p>Now we can explain the code.<br \/>\n\u73b0\u5728\u6211\u4eec\u53ef\u4ee5\u89e3\u91ca\u4ee3\u7801\u3002<\/p>\n<p>In the ValidateUser method, we fetch the user from the database and check whether they exist and if the password matches. The <code>UserManager&lt;TUser&gt;<\/code> class provides the FindByNameAsync method to find the user by user name and the CheckPasswordAsync to verify the user\u2019s password against the hashed password from the database. If the check result is false, we log a message about failed authentication. Lastly, we return the result.<br \/>\n\u5728 ValidateUser \u65b9\u6cd5\u4e2d\uff0c\u6211\u4eec\u4ece\u6570\u636e\u5e93\u4e2d\u83b7\u53d6\u7528\u6237\uff0c\u5e76\u68c0\u67e5\u4ed6\u4eec\u662f\u5426\u5b58\u5728\u4ee5\u53ca\u5bc6\u7801\u662f\u5426\u5339\u914d\u3002<code>UserManager&lt;TUser&gt;<\/code> \u7c7b\u63d0\u4f9b FindByNameAsync \u65b9\u6cd5\uff0c\u7528\u4e8e\u6309\u7528\u6237\u540d\u67e5\u627e\u7528\u6237\uff0c\u5e76\u63d0\u4f9b CheckPasswordAsync \u65b9\u6cd5\uff0c\u7528\u4e8e\u6839\u636e\u6570\u636e\u5e93\u4e2d\u7684\u54c8\u5e0c\u5bc6\u7801\u9a8c\u8bc1\u7528\u6237\u7684\u5bc6\u7801\u3002\u5982\u679c\u68c0\u67e5\u7ed3\u679c\u4e3a false\uff0c\u6211\u4eec\u5c06\u8bb0\u5f55\u6709\u5173\u8eab\u4efd\u9a8c\u8bc1\u5931\u8d25\u7684\u6d88\u606f\u3002\u6700\u540e\uff0c\u6211\u4eec\u8fd4\u56de\u7ed3\u679c\u3002<\/p>\n<p>The CreateToken method does exactly that \u2014 it creates a token. It does that by collecting information from the private methods and serializing token options with the WriteToken method.<br \/>\nCreateToken \u65b9\u6cd5\u6b63\u662f\u8fd9\u6837\u505a\u7684 \u2014 \u5b83\u521b\u5efa\u4e00\u4e2a\u4ee4\u724c\u3002\u5b83\u901a\u8fc7\u4ece\u79c1\u6709\u65b9\u6cd5\u6536\u96c6\u4fe1\u606f\u5e76\u4f7f\u7528 WriteToken \u65b9\u6cd5\u5e8f\u5217\u5316\u4ee4\u724c\u9009\u9879\u6765\u5b9e\u73b0\u6b64\u76ee\u7684\u3002<\/p>\n<p>We have three private methods as well. The GetSignInCredentials method returns our secret key as a byte array with the security algorithm. The GetClaims method creates a list of claims with the user name inside and all the roles the user belongs to. The last method, GenerateTokenOptions, creates an object of the JwtSecurityToken type with all of the required options. We can see the expires parameter as one of the token options. We would extract it from the appsettings.json file as well, but we don\u2019t have it there. So, we have to add it:<br \/>\n\u6211\u4eec\u8fd8\u6709\u4e09\u4e2a\u79c1\u6709\u65b9\u6cd5\u3002GetSignInCredentials \u65b9\u6cd5\u5c06\u5bc6\u94a5\u4f5c\u4e3a\u5305\u542b\u5b89\u5168\u7b97\u6cd5\u7684\u5b57\u8282\u6570\u7ec4\u8fd4\u56de\u3002GetClaims \u65b9\u6cd5\u521b\u5efa\u4e00\u4e2a\u58f0\u660e\u5217\u8868\uff0c\u5176\u4e2d\u5305\u542b\u7528\u6237\u540d\u4ee5\u53ca\u7528\u6237\u6240\u5c5e\u7684\u6240\u6709\u89d2\u8272\u3002\u6700\u540e\u4e00\u4e2a\u65b9\u6cd5 GenerateTokenOptions \u521b\u5efa\u4e00\u4e2a JwtSecurityToken \u7c7b\u578b\u7684\u5bf9\u8c61\uff0c\u5176\u4e2d\u5305\u542b\u6240\u6709\u5fc5\u9700\u7684\u9009\u9879\u3002\u6211\u4eec\u53ef\u4ee5\u5c06 expires \u53c2\u6570\u89c6\u4e3a token \u9009\u9879\u4e4b\u4e00\u3002\u6211\u4eec\u4e5f\u4f1a\u4ece appsettings.json \u6587\u4ef6\u4e2d\u63d0\u53d6\u5b83\uff0c\u4f46\u6211\u4eec\u6ca1\u6709\u5b83\u3002\u6240\u4ee5\uff0c\u6211\u4eec\u5fc5\u987b\u6dfb\u52a0\u5b83\uff1a<\/p>\n<pre><code>&quot;JwtSettings&quot;: { &quot;validIssuer&quot;: &quot;CodeMazeAPI&quot;, &quot;validAudience&quot;: &quot;https:\/\/localhost:5001&quot;, &quot;expires&quot;: 5 }<\/code><\/pre>\n<p>Finally, we have to add a new action in the AuthenticationController:<br \/>\n\u6700\u540e\uff0c\u6211\u4eec\u5fc5\u987b\u5728 AuthenticationController \u4e2d\u6dfb\u52a0\u4e00\u4e2a\u65b0\u4f5c\uff1a<\/p>\n<pre><code>[HttpPost(&quot;login&quot;)] [ServiceFilter(typeof(ValidationFilterAttribute))] public async Task&lt;IActionResult&gt; Authenticate([FromBody] UserForAuthenticationDto user) { if (!await _service.AuthenticationService.ValidateUser(user)) return Unauthorized(); return Ok(new { Token = await _service .AuthenticationService.CreateToken() }); }<\/code><\/pre>\n<p>There is nothing special in this controller. If validation fails, we return the 401 Unauthorized response; otherwise, we return our created token:<br \/>\n\u8fd9\u4e2a\u63a7\u5236\u5668\u6ca1\u6709\u4ec0\u4e48\u7279\u522b\u4e4b\u5904\u3002\u5982\u679c\u9a8c\u8bc1\u5931\u8d25\uff0c\u6211\u4eec\u5c06\u8fd4\u56de 401 Unauthorized \u54cd\u5e94;\u5426\u5219\uff0c\u6211\u4eec\u5c06\u8fd4\u56de\u521b\u5efa\u7684 token\uff1a<\/p>\n<p><a href=\"https:\/\/localhost:5001\/api\/authentication\/login\">https:\/\/localhost:5001\/api\/authentication\/login<\/a><\/p>\n<p><img decoding=\"async\" src=\"\/images\/ultimateaspnetcorewebapi6\/2711.jpg\" alt=\"alt text\" \/><\/p>\n<p>Excellent. We can see our token generated. Now, let\u2019s send invalid credentials:<br \/>\n\u975e\u5e38\u597d\u3002\u6211\u4eec\u53ef\u4ee5\u770b\u5230\u6211\u4eec\u7684 Token \u5df2\u751f\u6210\u3002\u73b0\u5728\uff0c\u8ba9\u6211\u4eec\u53d1\u9001\u65e0\u6548\u7684\u51ed\u636e\uff1a<\/p>\n<p><a href=\"https:\/\/localhost:5001\/api\/authentication\/login\">https:\/\/localhost:5001\/api\/authentication\/login<\/a><\/p>\n<p><img decoding=\"async\" src=\"\/images\/ultimateaspnetcorewebapi6\/2712.jpg\" alt=\"alt text\" \/><\/p>\n<p>And we get a 401 Unauthorized response.<br \/>\n\u6211\u4eec\u6536\u5230\u4e86 401 Unauthorized \u54cd\u5e94\u3002<\/p>\n<p>Right now if we send a request to the GetCompanies action, we are still going to get the 401 Unauthorized response even though we have successful authentication. That\u2019s because we didn\u2019t provide our token in a request header and our API has nothing to authorize against. To solve that, we are going to create another GET request, and in the Authorization header choose the header type and paste the token from the previous request:<br \/>\n\u73b0\u5728\uff0c\u5982\u679c\u6211\u4eec\u5411 GetCompanies\u4f5c\u53d1\u9001\u8bf7\u6c42\uff0c\u5373\u4f7f\u6211\u4eec\u5df2\u6210\u529f\u8fdb\u884c\u8eab\u4efd\u9a8c\u8bc1\uff0c\u6211\u4eec\u4ecd\u4f1a\u6536\u5230 401 Unauthorized \u54cd\u5e94\u3002\u90a3\u662f\u56e0\u4e3a\u6211\u4eec\u6ca1\u6709\u5728\u8bf7\u6c42\u6807\u5934\u4e2d\u63d0\u4f9b\u4ee4\u724c\uff0c\u5e76\u4e14\u6211\u4eec\u7684 API \u6ca1\u6709\u4ec0\u4e48\u53ef\u4ee5\u6388\u6743\u7684\u3002\u4e3a\u4e86\u89e3\u51b3\u8fd9\u4e2a\u95ee\u9898\uff0c\u6211\u4eec\u5c06\u521b\u5efa\u53e6\u4e00\u4e2a GET \u8bf7\u6c42\uff0c\u5e76\u5728 Authorization \u6807\u5934\u4e2d\u9009\u62e9\u6807\u5934\u7c7b\u578b\u5e76\u7c98\u8d34\u4e0a\u4e00\u4e2a\u8bf7\u6c42\u7684\u4ee4\u724c\uff1a<\/p>\n<p><a href=\"https:\/\/localhost:5001\/api\/companies\">https:\/\/localhost:5001\/api\/companies<\/a><\/p>\n<p><img decoding=\"async\" src=\"\/images\/ultimateaspnetcorewebapi6\/2713.jpg\" alt=\"alt text\" \/><\/p>\n<p>Now, we can send the request again:<br \/>\n\u73b0\u5728\uff0c\u6211\u4eec\u53ef\u4ee5\u518d\u6b21\u53d1\u9001\u8bf7\u6c42\uff1a<\/p>\n<p><a href=\"https:\/\/localhost:5001\/api\/companies\">https:\/\/localhost:5001\/api\/companies<\/a><\/p>\n<p><img decoding=\"async\" src=\"\/images\/ultimateaspnetcorewebapi6\/2714.jpg\" alt=\"alt text\" \/><\/p>\n<p>Excellent. It works like a charm.<br \/>\n\u975e\u5e38\u597d\u3002\u5b83\u5c31\u50cf\u4e00\u4e2a\u5409\u7965\u5c0f\u9970\u7269\u3002<\/p>\n<h2>27.9 Role-Based Authorization<\/h2>\n<p>27.9 \u57fa\u4e8e\u89d2\u8272\u7684\u6388\u6743<\/p>\n<p>Right now, even though authentication and authorization are working as expected, every single authenticated user can access the GetCompanies action. What if we don\u2019t want that type of behavior? For example, we want to allow only managers to access it. To do that, we have to make one simple change:\u200c<br \/>\n\u73b0\u5728\uff0c\u5373\u4f7f\u8eab\u4efd\u9a8c\u8bc1\u548c\u6388\u6743\u6309\u9884\u671f\u5de5\u4f5c\uff0c\u6bcf\u4e2a\u7ecf\u8fc7\u8eab\u4efd\u9a8c\u8bc1\u7684\u7528\u6237\u90fd\u53ef\u4ee5\u8bbf\u95ee GetCompanies\u4f5c\u3002\u5982\u679c\u6211\u4eec\u4e0d\u5e0c\u671b\u51fa\u73b0\u8fd9\u79cd\u884c\u4e3a\u600e\u4e48\u529e\uff1f\u4f8b\u5982\uff0c\u6211\u4eec\u5e0c\u671b\u4ec5\u5141\u8bb8\u7ba1\u7406\u8005\u8bbf\u95ee\u5b83\u3002\u4e3a\u6b64\uff0c\u6211\u4eec\u5fc5\u987b\u8fdb\u884c\u4e00\u4e2a\u7b80\u5355\u7684\u66f4\u6539\uff1a<\/p>\n<pre><code>[HttpGet(Name = &quot;GetCompanies&quot;)] \n[Authorize(Roles = &quot;Manager&quot;)] \npublic async Task&lt;IActionResult&gt; GetCompanies()<\/code><\/pre>\n<p>And that is it. To test this, let\u2019s create another user with the Administrator role (the second role from the database):<br \/>\n\u5c31\u662f\u8fd9\u6837\u3002\u4e3a\u4e86\u6d4b\u8bd5\u8fd9\u4e00\u70b9\uff0c\u8ba9\u6211\u4eec\u521b\u5efa\u53e6\u4e00\u4e2a\u5177\u6709 Administrator \u89d2\u8272\u7684\u7528\u6237\uff08\u6570\u636e\u5e93\u4e2d\u7684\u7b2c\u4e8c\u4e2a\u89d2\u8272\uff09\uff1a<\/p>\n<p><img decoding=\"async\" src=\"\/images\/ultimateaspnetcorewebapi6\/2715.jpg\" alt=\"alt text\" \/><\/p>\n<p>We get 201. After we send an authentication request for Jane Doe, we are going to get a new token. Let\u2019s use that token to send the request towards the GetCompanies action:<br \/>\n\u6211\u4eec\u5f97\u5230 201\u3002\u5728\u6211\u4eec\u53d1\u9001 Jane Doe \u7684\u8eab\u4efd\u9a8c\u8bc1\u8bf7\u6c42\u540e\uff0c\u6211\u4eec\u5c06\u83b7\u53d6\u65b0\u4ee4\u724c\u3002\u8ba9\u6211\u4eec\u4f7f\u7528\u8be5\u4ee4\u724c\u5c06\u8bf7\u6c42\u53d1\u9001\u5230 GetCompanies\u4f5c\uff1a<br \/>\n<a href=\"https:\/\/localhost:5001\/api\/companies\">https:\/\/localhost:5001\/api\/companies<\/a><\/p>\n<p><img decoding=\"async\" src=\"\/images\/ultimateaspnetcorewebapi6\/2716.jpg\" alt=\"alt text\" \/><\/p>\n<p>We get a 403 Forbidden response because this user is not allowed to access the required endpoint. If we log in with John Doe and use his token, we are going to get a successful response for sure. Of course, we don\u2019t have to place an Authorize attribute only on top of the action; we can place it on the controller level as well. For example, we can place just [Authorize] on the controller level to allow only authorized users to access all the actions in that controller; also, we can place the [Authorize (Role=\u2026)] on top of any action in that controller to state that only a user with that specific role has access to that action.<br \/>\n\u6211\u4eec\u6536\u5230 403 Forbidden \u54cd\u5e94\uff0c\u56e0\u4e3a\u4e0d\u5141\u8bb8\u6b64\u7528\u6237\u8bbf\u95ee\u6240\u9700\u7684\u7ec8\u7aef\u8282\u70b9\u3002\u5982\u679c\u6211\u4eec\u4f7f\u7528 John Doe \u767b\u5f55\u5e76\u4f7f\u7528\u4ed6\u7684\u4ee4\u724c\uff0c\u6211\u4eec\u80af\u5b9a\u4f1a\u5f97\u5230\u6210\u529f\u7684\u54cd\u5e94\u3002\u5f53\u7136\uff0c\u6211\u4eec\u4e0d\u5fc5\u4ec5\u5728\u4f5c\u9876\u90e8\u653e\u7f6e Authorize \u5c5e\u6027;\u6211\u4eec\u4e5f\u53ef\u4ee5\u5c06\u5176\u653e\u5728\u63a7\u5236\u5668\u7ea7\u522b\u3002\u4f8b\u5982\uff0c\u6211\u4eec\u53ef\u4ee5\u53ea\u5c06 [Authorize] \u653e\u5728\u63a7\u5236\u5668\u7ea7\u522b\uff0c\u4ee5\u4ec5\u5141\u8bb8\u6388\u6743\u7528\u6237\u8bbf\u95ee\u8be5\u63a7\u5236\u5668\u4e2d\u7684\u6240\u6709\u4f5c;\u6b64\u5916\uff0c\u6211\u4eec\u53ef\u4ee5\u5c06 [Authorize \uff08Role=...\uff09] \u653e\u5728\u8be5\u63a7\u5236\u5668\u4e2d\u4efb\u4f55\u4f5c\u7684\u9876\u90e8\uff0c\u4ee5\u58f0\u660e\u53ea\u6709\u5177\u6709\u8be5\u7279\u5b9a\u89d2\u8272\u7684\u7528\u6237\u624d\u80fd\u8bbf\u95ee\u8be5\u4f5c\u3002<\/p>\n<p>One more thing. Our token expires after five minutes after the creation point. So, if we try to send another request after that period (we probably have to wait 5 more minutes due to the time difference between servers, which is embedded inside the token \u2013 this can be overridden with the ClockSkew property in the TokenValidationParameters object ), we are going to get the 401 Unauthorized status for sure. Feel free to try.<br \/>\n\u8fd8\u6709\u4e00\u4ef6\u4e8b\u3002\u6211\u4eec\u7684\u4ee4\u724c\u5728\u521b\u5efa\u70b9\u540e 5 \u5206\u949f\u540e\u8fc7\u671f\u3002\u56e0\u6b64\uff0c\u5982\u679c\u6211\u4eec\u5c1d\u8bd5\u5728\u8be5\u65f6\u95f4\u6bb5\u4e4b\u540e\u53d1\u9001\u53e6\u4e00\u4e2a\u8bf7\u6c42\uff08\u7531\u4e8e\u670d\u52a1\u5668\u4e4b\u95f4\u7684\u65f6\u5dee\uff0c\u6211\u4eec\u53ef\u80fd\u4e0d\u5f97\u4e0d\u518d\u7b49\u5f85 5 \u5206\u949f\uff0c\u8fd9\u5d4c\u5165\u5728\u4ee4\u724c\u4e2d - \u8fd9\u53ef\u4ee5\u7528 TokenValidationParameters \u5bf9\u8c61\u4e2d\u7684 ClockSkew \u5c5e\u6027\u8986\u76d6\uff09\uff0c\u6211\u4eec\u80af\u5b9a\u4f1a\u5f97\u5230 401 Unauthorized \u72b6\u6001\u3002\u8bf7\u968f\u610f\u5c1d\u8bd5\u3002<\/p>\n","protected":false},"excerpt":{"rendered":"<p>27 JWT, IDENTITY, AND REFRESH TOKEN 27 JWT\u3001\u8eab\u4efd\u8ba4\u8bc1\u548c\u5237\u65b0\u4ee4\u724c User authentication is an important part of any application. It refers to the process of confirming the identity of an application\u2019s users. Implementing it properly could be a hard job if you are not familiar with the process.\u200c \u7528\u6237\u8eab\u4efd\u9a8c\u8bc1\u662f\u4efb\u4f55\u5e94\u7528\u7a0b\u5e8f\u7684\u91cd\u8981\u7ec4\u6210\u90e8\u5206\u3002\u5b83\u662f\u6307\u786e\u8ba4\u5e94\u7528\u7a0b\u5e8f\u7528\u6237\u8eab\u4efd\u7684\u8fc7\u7a0b\u3002\u5982\u679c\u60a8\u4e0d\u719f\u6089\u8be5\u8fc7\u7a0b\uff0c\u6b63\u786e\u5b9e\u65bd\u5b83\u53ef\u80fd\u662f\u4e00\u9879\u8270\u5de8\u7684\u5de5\u4f5c\u3002 Also, it could take a lot of time [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[],"class_list":["post-1152","post","type-post","status-publish","format-standard","hentry","category-uncategorized"],"_links":{"self":[{"href":"https:\/\/diji.net\/index.php?rest_route=\/wp\/v2\/posts\/1152","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/diji.net\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/diji.net\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/diji.net\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/diji.net\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=1152"}],"version-history":[{"count":0,"href":"https:\/\/diji.net\/index.php?rest_route=\/wp\/v2\/posts\/1152\/revisions"}],"wp:attachment":[{"href":"https:\/\/diji.net\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=1152"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/diji.net\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=1152"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/diji.net\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=1152"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}