|
@@ -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
|
+```
|