Przeglądaj źródła

IdentityServer4在API中的实现

master
qdjjx 5 lat temu
rodzic
commit
1bb798f9cc

+ 319
- 0
实践/后端/项目/20IdentityServer在API中的实现.md Wyświetl plik

@@ -0,0 +1,319 @@
1
+在研究IdentityServer4的过程中遇到过两个问题:
2
+
3
+- 在IdentityServer4通常的资料是针对Web的,而不是通过接口
4
+- .Net Core 2.1 2.0有些地方和.NET和.Net Core 2.0都不一样
5
+
6
+正好发现一个项目是在 .Net Core写,并且IdentityServer4提供接口。项目地址:https://github.com/marklaygo/Angular-IdentityServer-WebAPI
7
+
8
+首先来看`Config.cs`
9
+```
10
+public static class Config
11
+{
12
+    //User
13
+    public static List<TestUser> GetUsers()
14
+    {
15
+        return new List<TestUser>{
16
+            new TestUser{
17
+                SubjectId="1",
18
+                Username="",
19
+                Password=""
20
+            }
21
+        };
22
+    }
23
+
24
+    //IdentityResource
25
+    public static IEnumerable<IdentityResource> GetIdentityResources()
26
+    {
27
+        return new IdentityResource[]{
28
+            new IdentityResources.OpenId(),
29
+            new IdentityResources.Profile();
30
+        };
31
+    }
32
+
33
+    //API
34
+    public static IEnumerable<ApiResource> GetApis()
35
+    {
36
+        return new ApiResource[]{
37
+            new ApiResource("api1", "webApi"),
38
+            new ApiResource("accountApi", "AccountApi") //这里的api给验证服务器自己用,因为验证服务器通过接口开发给外界
39
+        };
40
+    }
41
+
42
+    //Client
43
+    public static IEnumerable<Client> GetClients()
44
+    {
45
+        renturn new Client[]
46
+        {
47
+            new Client
48
+            {
49
+                ClientId="Angular",
50
+                AllowedGrantTypes = GrantTypes.ResourceOwnerPassword,
51
+                RequireClientSecret = fasle,
52
+                AllowAccessTokenViaBrowser = true,
53
+                AccessTokenLifetime = 300,
54
+                AllowOfflineAccess = true,
55
+                AllowedScopes = {
56
+                    IdentityServerConstants.StandardScopes.OpenId,//客户端要的和IdentityServer4给的保持一致
57
+                    IdentityServerConstatns.StandardScopes.Profile,//客户端要的和IdentityServer4给的保持一致
58
+                    "api1",//api告诉Client
59
+                    "accountApi"//api告诉Client
60
+                }
61
+            }
62
+        };
63
+    }
64
+}
65
+```
66
+
67
+再来看上下文,一定是继承`IdentityDbContext`。
68
+
69
+```
70
+public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
71
+{
72
+    public class ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options){
73
+
74
+    }
75
+
76
+    protected override void OnModelCreating(ModelBuilder builder)
77
+    {
78
+        base.OnModelCreating(builder);
79
+    }
80
+}
81
+
82
+public class ApplicationUser : IdentityUser
83
+{
84
+
85
+}
86
+
87
+```
88
+
89
+有关对外开放的claim,通过实现`IProfileService`接口。
90
+
91
+```
92
+public class IdentityProfileSerivce  : IProfileService
93
+{
94
+    private readonly IUserClaimsPrincipalFactory<ApplicationUser> _claimsFactory;
95
+    private readonly UserManager<ApplicationUser> _userManager;
96
+
97
+    public IdentityProfileService(IUserClaimsPrincipalFactory<ApplicaitonUser> claimsFactory, UserManager<ApplicationUser> userManager)
98
+    {
99
+        _claimsFactory = claimsFactory;
100
+        _userManager = userManager;
101
+    }
102
+
103
+    public async Task GetProfileDataAsync(ProfileDataRequestContext context)
104
+    {
105
+        var subjectId = context.Subject.GetSubjectId();
106
+        var user = awati _userManager.FindByIdAsync(subjectId);
107
+        var principal = await _claimsFactory.CreateAsync(user);
108
+        var claims = principal.Claims.ToList();
109
+        claims.Add(new Claim(JwtClaimTypes.Role, "test_role"));
110
+        context.IssuedClaims = claim;
111
+    }
112
+
113
+    public async Task IsActiveAsync(IsActiveContext context)
114
+    {
115
+        var subjectId = context.Subject.GetSubjectId();
116
+        var user = awati _userManager.FindByIdAsync(subjectId);
117
+        context.IsActive = user != null;
118
+    }
119
+}
120
+```
121
+
122
+`Startup.cs`配置文件
123
+
124
+```
125
+public class Startup
126
+{
127
+    public IConfiguration Configuration{get;}
128
+    public IHostingEnvironment Environment{get;}
129
+
130
+    public Startup(IConfiguraiotn configuraiotn, IHostingEnvironment environment)
131
+    {
132
+        Configuraiton = configuration;
133
+        Envrionment = environment;
134
+    }
135
+
136
+    public void ConfigureService(IServiceCollection services)
137
+    {
138
+        //上下文
139
+        services.AddDbContext<ApplicationDbContext>(options => options.UserSqlServer(Configuration.GetConnectionString("DefaultConnection")));
140
+
141
+        //Identity
142
+        servcies.AddIdentity<ApplicationUser, IdentityRole>()
143
+            .AddEntityFrameworkeStore<ApplicationDbContext>()
144
+            .AddDefaultTokenProviders();
145
+
146
+        //有关Password的配置
147
+        services.COnfigure<IdentityOptions>(options => {
148
+            options.Password.RequrieDigit = false;
149
+            options.Password.RequrieLength =4;
150
+            options.Password.ReqruieNonAlphanumeric = false;
151
+            options.Password.RequireUppercase = false;
152
+            options.Password.RequrieLowercase = false;
153
+        })
154
+
155
+        //mvc
156
+        services.AddMvcCore()
157
+            .AddAuthorization()
158
+            .AddJsonFormatters();
159
+
160
+        //对外开发的claim
161
+        services.AddTransient<IProfileService, IdentityProfileService>();
162
+
163
+        //http://localhost:5000验证服务器的地址
164
+        //验证服务器的配置
165
+        var builder = service.AddIdentityServer()
166
+            .AddInMemoryIndentityRecouse(Config.GetIdenityResource())
167
+            .AddInMemoryApiResource(Config.GetApis())
168
+            .AddInMemoryClients(Config.GetClients())
169
+            .AddAspNetIdentity<ApplicationUser>()
170
+            .AddProfileService<IdentityProfileService>();
171
+
172
+        //验证服务器接口本身也需要验证
173
+        services.AddAuthetnication("Bearer")
174
+            .AddJwtBearer("Bearer", options => {
175
+                options.Authority = "http://localhost:5000";
176
+                options.RequireHttpsMetadata = false;
177
+                options.Audience = "accountApi";
178
+                options.TokenValidatioinParamsters = new TokenValidationParameters()
179
+                {
180
+                    ClockSkew = TimeSpan.FromMinutes(0)
181
+                }
182
+            });
183
+
184
+        //cors
185
+        services.AddCors(options => {
186
+
187
+                options.AddPolicy("default", policy => {
188
+                    policy.WithOrigin("http://localhost:4200")
189
+                        .AllowAnyHeader()
190
+                        .AllowAnyMethod();
191
+                });
192
+        });
193
+
194
+        if(Environment.IsDevelopment())
195
+        {
196
+            builder.AddDeveloperSigningCredential();
197
+        }
198
+        else
199
+        {
200
+            throw new Exception("need to configure key material");
201
+        }
202
+
203
+    }
204
+
205
+    public void Configure(IApplicationBuilder app, ApplicationDbContext context)
206
+    {
207
+        if(Environment.IsDevelopment())
208
+        {
209
+            app.UseDeveloperExceptionPage();
210
+        }
211
+
212
+        app.UseCors("default");
213
+        app.UseIdentityServer();
214
+        app.UseAuthentication();
215
+        app.UseMvc();
216
+
217
+        //种子化数据
218
+        try
219
+        {
220
+            context.Database.Migrate();
221
+            context.Users.RemoveRange(contexts.Users.ToList());
222
+            context.SaveChanges();
223
+
224
+            var user = new ApplicationUser
225
+            {
226
+                UserName = "",
227
+                NormalizedUserName = "",
228
+                Email = "",
229
+                NormalizedEmail = "",
230
+                EmailConfirmed = true,
231
+                LockoutEnabled = fasle,
232
+                SecurityStamp = Guid.NewGuid().ToString()
233
+            };
234
+
235
+            if(!context.Users.Any(u =>u.UserName == user.UserName))
236
+            {
237
+                var passwordHasher = new PasswordHasher<ApplicatonUser>();
238
+                var hased = passwordHasher.HashPassword(user,"");
239
+                user.PasswordHash = hased;
240
+                var userStore = new UserStore<ApplicationUser>(context);
241
+                userStore.CreateAsync(user);
242
+            }
243
+        }
244
+        catch(Exception ex)
245
+        {
246
+
247
+        }
248
+    }
249
+}
250
+```
251
+
252
+模型
253
+```
254
+public class RegisterViewModel
255
+{
256
+    [Required]
257
+    [EmailAddress]
258
+    publci string Email{get;set;}
259
+
260
+    [Requried]
261
+    [DataType(DataType.Password)]
262
+    public string Password{get;set;}
263
+}
264
+
265
+public class ChangePasswordViewModel
266
+{
267
+    [Required]
268
+    [DataType(DataType.EmailAddress)]
269
+    public string Email{get;set;}
270
+
271
+    [Required]
272
+    [DataType(DataType.Password)]
273
+    public string OldPassword{get;set;}
274
+
275
+    [Requried]
276
+    [DataType(DataType.Password)]
277
+    public string NewPassword{get;set;}
278
+}
279
+```
280
+
281
+最后来到有关用户管理的控制器。
282
+```
283
+[Route("api/[controller]")]
284
+publci class AccountController : ControllerBase
285
+{
286
+    private readonly UserManager<ApplicationUser> _userManager;
287
+    private readonly IUserClaimPrincipalFactory<ApplicatioinUser> _claimsFactory;
288
+
289
+    //构造函数略
290
+
291
+    [HttpPost("Register")]
292
+    public async Task<IActionResult> Register([FromBody]RegisterViewModel model)
293
+    {
294
+        if(ModelState.IsValid)
295
+        {
296
+            var user = new ApplicationUser {UserName = model.Email, Email = model.Email};
297
+            var result = awati _userManager.CreateAsync(user, model.Password);
298
+            return new JsonResult(result);
299
+        }
300
+        return BadRequest();
301
+    }
302
+
303
+    [HttpPost("ChangePassword")]
304
+    [Authorize(AuthenticationSchemes = "Bearer")]
305
+    public async Task<IActionResult> ChangePassword([FromBody]ChangePasswordViewModel model)
306
+    {
307
+        if(ModelState.IsValid)
308
+        {
309
+            var user = await _userManager.FindByEmailAsync(model.Email);
310
+            if(user!=null)
311
+            {
312
+                var result = await _userManager.ChangePasswordAsync(user, model.OldPassword, model.NewPassword);
313
+                return new JsonResult(result);
314
+            }
315
+        }
316
+        return BadRequest();
317
+    }
318
+}
319
+```

Ładowanie…
Anuluj
Zapisz