IdentityServer4-HybridAndClientCredentials
一、服务器
Client设置:
1 new Client 2 { 3 ClientId = "mvc1", 4 ClientName = "后台管理MVC客户端", 5 ClientSecrets = { new Secret("mvc1".Sha256()) }, 6 7 AllowedGrantTypes = GrantTypes.HybridAndClientCredentials, 8 AllowOfflineAccess = true, 9 RequireConsent = false, 10 RedirectUris = { $"{ClientUrl}/signin-oidc",$"{LocalClientUrl}/signin-oidc"}, 11 PostLogoutRedirectUris = { $"{ClientUrl}/signout-callback-oidc",$"{LocalClientUrl}/signout-callback-oidc"}, 12 13 AllowedScopes = 14 { 15 IdentityServerConstants.StandardScopes.OpenId, 16 IdentityServerConstants.StandardScopes.Profile, 17 "IdServerAdmin_API" 18 }, 19 20 AlwaysIncludeUserClaimsInIdToken = true 21 }
Startup.cs:
/// <summary> /// 设置认证服务器 /// </summary> /// <param name="services"></param> private void SetIdentityServer(IServiceCollection services) { #region 认证服务器 var ServerUrl = Configuration.GetSection("AppSetting:ServerUrl").Value; var connectionString = Configuration.GetSection("AppSetting:ConnectionString").Value; //配置AccessToken的加密证书 var rsa = new RSACryptoServiceProvider(); //从配置文件获取加密证书 rsa.ImportCspBlob(Convert.FromBase64String(Configuration["AppSetting:SigningCredential"])); var idServer = services.AddIdentityServer(options => { options.IssuerUri = ServerUrl; options.PublicOrigin = ServerUrl; options.Discovery.ShowApiScopes = true; options.Discovery.ShowClaims = true; options.Events.RaiseSuccessEvents = true; options.Events.RaiseFailureEvents = true; options.Events.RaiseErrorEvents = true; }); //设置加密证书 idServer.AddSigningCredential(new RsaSecurityKey(rsa)); idServer.AddInMemoryApiResources(Config.GetApiResources()); idServer.AddInMemoryIdentityResources(Config.GetIdentityResources()); idServer.AddInMemoryClients(Config.GetClients()); services.AddTransient<IMyUserStore, MyUserStore>(); services.AddTransient<IProfileService, MyProfile>(); services.AddTransient<IResourceOwnerPasswordValidator, MyUserValidator>(); #endregion }
1 public class MyProfile : IProfileService 2 { 3 private readonly IMyUserStore _myUserStore; 4 public MyProfile(IMyUserStore myUserStore) 5 { 6 _myUserStore = myUserStore; 7 } 8 9 public Task GetProfileDataAsync(ProfileDataRequestContext context) 10 { 11 var subjectId = context.Subject.GetSubjectId(); 12 var user = _myUserStore.GetUserById(subjectId); 13 14 15 16 var claims = new List<Claim> 17 { 18 new Claim("role", user.Role), 19 new Claim("userguid", user.SubjectId), 20 new Claim("abc", "这是自定义的值。……。。…。……。……") 21 }; 22 23 var q = context.RequestedClaimTypes; 24 context.AddRequestedClaims(claims); 25 context.IssuedClaims.AddRange(claims); 26 27 return Task.FromResult(0); 28 } 29 30 public Task IsActiveAsync(IsActiveContext context) 31 { 32 var user = _myUserStore.GetUserById(context.Subject.GetSubjectId()); 33 context.IsActive = (user != null); 34 35 return Task.FromResult(0); 36 } 37 }
1 public interface IMyUserStore 2 { 3 JUser Find(string username, string userpass); 4 JUser GetUserById(string subjectId); 5 } 6 7 public class MyUserStore : IMyUserStore 8 { 9 readonly IOptions<AppSetting> _options; 10 readonly IMemoryCache _memoryCache; 11 12 private const string CACHENAME = "MyUserStore"; 13 14 public MyUserStore(IOptions<AppSetting> options, IMemoryCache m_memoryCache) 15 { 16 _options = options; 17 _memoryCache = m_memoryCache; 18 } 19 20 public List<JUser> GetList(bool reload=true) 21 { 22 if (reload) 23 { 24 _memoryCache.Remove(CACHENAME); 25 } 26 27 List<JUser> list; 28 if (!_memoryCache.TryGetValue(CACHENAME, out list)){ 29 using(MySqlConnection conn = new MySqlConnection(_options.Value.ConnectionString)) 30 { 31 list = conn.Query<JUser>("select * from juser").ToList(); 32 33 //添加超级用户 34 JUser jc = new JUser() 35 { 36 UserName = _options.Value.SuperUserName, 37 UserPass = StringHelper.GetMd5(_options.Value.SuperPassword), 38 SubjectId = "a36005e2-5984-41f5-aa91-8e93b479d88e", 39 Role = "IdServerAdmin" 40 }; 41 42 list.Add(jc); 43 } 44 _memoryCache.Set(CACHENAME, list); 45 } 46 return list; 47 } 48 49 50 51 public JUser Find(string username, string userpass) 52 { 53 var list = GetList(); 54 return list.SingleOrDefault(p => p.UserName == username && p.UserPass == StringHelper.GetMd5(userpass)); 55 } 56 57 public JUser GetUserById(string subjectId) 58 { 59 var list = GetList(); 60 return list.SingleOrDefault(p => p.SubjectId == subjectId); 61 }
1 public class MyUserValidator : IResourceOwnerPasswordValidator 2 { 3 readonly IMyUserStore _myUserStore; 4 5 public MyUserValidator(IMyUserStore myUserStore) 6 { 7 _myUserStore = myUserStore; 8 } 9 10 public Task ValidateAsync(ResourceOwnerPasswordValidationContext context) 11 { 12 var q = _myUserStore.Find(context.UserName, context.Password); 13 14 if (q != null) 15 { 16 //验证成功 17 //使用subject可用于在资源服务器区分用户身份等等 18 //获取:资源服务器通过User.Claims.Where(l => l.Type == "sub").FirstOrDefault(); 19 var claims = new List<Claim>(); 20 claims.Add(new Claim("role", q.Role)); 21 claims.Add(new Claim("userguid", q.SubjectId)); 22 23 context.Result = new GrantValidationResult(subject: $"{q.SubjectId}", authenticationMethod: "custom", claims: claims.AsEnumerable()); 24 } 25 else 26 { 27 //验证失败 28 context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant, "无效的用户凭证"); 29 } 30 return Task.FromResult(0); 31 } 32 }
二、客户端:
1 /// <summary> 2 /// 设置认证客户端 3 /// </summary> 4 /// <param name="services"></param> 5 private void SetIdentityClient(IServiceCollection services) 6 { 7 var ServerUrl = Configuration.GetSection("AppSetting:ServerUrl").Value; 8 var client_id = Configuration.GetSection("AppSetting:SuperClientId").Value; 9 var cient_secret = Configuration.GetSection("AppSetting:SuperClientSecret").Value; 10 11 //services.Configure<MvcOptions>(options => 12 //{ 13 // // Set LocalTest:skipSSL to true to skip SSL requrement in 14 // // debug mode. This is useful when not using Visual Studio. 15 // options.Filters.Add(new RequireHttpsAttribute()); 16 //}); 17 18 JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear(); 19 20 var idClient = services.AddAuthentication(options => 21 { 22 options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme; 23 options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme; 24 }) 25 .AddCookie() 26 .AddOpenIdConnect(options => 27 { 28 options.SignOutScheme = OpenIdConnectDefaults.AuthenticationScheme; 29 options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme; // cookie middle setup above 30 options.Authority = ServerUrl; // 认证服务器 31 options.RequireHttpsMetadata = true; // SSL Https模式 32 options.ClientId = client_id; // 客户端(位于认证服务器) 33 options.ClientSecret = cient_secret; // 客户端(位于认证服务器) 34 options.ResponseType = "code id_token"; // means Hybrid flow (id + access token) 35 36 options.GetClaimsFromUserInfoEndpoint = false; 37 options.SaveTokens = true; 38 options.TokenValidationParameters = new TokenValidationParameters 39 { 40 NameClaimType = "name", 41 RoleClaimType = "role" 42 }; 43 44 options.Scope.Clear(); 45 options.Scope.Add("openid"); 46 options.Scope.Add("profile"); 47 options.Scope.Add("IdServerAdmin_API"); 48 49 options.Events = new OpenIdConnectEvents() 50 { 51 OnMessageReceived = (context) => 52 { 53 return Task.FromResult(0); 54 }, 55 56 OnUserInformationReceived = (context) => 57 { 58 return Task.FromResult(0); 59 }, 60 OnRedirectToIdentityProvider = (context) => 61 { 62 //设置重定向地址,解决生产环境nginx+https访问,还是有问题。。。。。。。 63 context.Properties.RedirectUri = $"{ClientUrl}/signin-oidc"; 64 //context.ProtocolMessage.RedirectUri = $"{ClientUrl}/signin-oidc"; 65 return Task.FromResult(0); 66 }, 67 68 OnTokenValidated = (context) => 69 { 70 //context.Properties.RedirectUri = $"{ClientUrl}/signin-oidc"; 71 return Task.FromResult(0); 72 }, 73 }; 74 }); 75 }