運行環境
Vue 使用的是D2admin:https://doc.d2admin.fairyever.com/zh/
Github地址:https://github.com/Fengddd/PermissionAdmin.git
Net Core的環境:Webapi 使用的是:NET Core SDK2.1 IdentityServer和Ocelot:NET Core SDK2.2
Github地址:https://github.com/Fengddd/IdentityServerOcelotDemo.git
我也是在初學階段,所以有些地方可能描述的不是很清楚,可以下載源碼查看,也可能大家一起交流,學習
建立IdentityServer4項目
根據 https://www.cnblogs.com/edisonchou/p/identityserver4_foundation_and_quickstart_01.html 根據Edison Zhou大佬的博客,配置好 QuickStart UI 以及IdentityServer的依賴項
新建IdentityServer4的配置文件IdentityConfig
public static IEnumerable
GetIdentityResources() { return new IdentityResource[] { new IdentityResources.OpenId(), new IdentityResources.Profile(), new IdentityResource("delimitClaim","delimitClaim",new List
(){ "role", "name"}), //在Claims添加role 和 name信息 }; } public static IEnumerable
GetApiResource() { return new List
{ new ApiResource("identityServerApi", "identityServerApi"), }; } public static IEnumerable
GetClients() { new Client { ClientId = "js", //客戶端Id ClientName = "JavaScript Client", //客戶端名稱 AllowedGrantTypes = GrantTypes.Code, //授權模式 //AllowedGrantTypes = GrantTypes.Implicit, RequirePkce = true, RequireClientSecret = false, RequireConsent = false, //禁用 consent 頁面確認 AllowAccessTokensViaBrowser = true, AlwaysIncludeUserClaimsInIdToken = true, RedirectUris = { "http://localhost:8080/#/IdentityServerCallBack", //登陸后回調頁面 "http://localhost:8080/#/IdentityServerRefreshToken" //刷新Token的頁面 }, PostLogoutRedirectUris = { "http://localhost:8080/#/IdentityServerClient" },//注銷退出后跳轉的頁面(登錄頁) AllowedCorsOrigins = { "http://localhost:8080" }, //跨域 AccessTokenLifetime = 60, //AccessToken 的有效時間 AllowedScopes = { IdentityServerConstants.StandardScopes.OpenId, IdentityServerConstants.StandardScopes.Profile, "identityServerApi", //授權的Scopes "delimitClaim" //Claims 信息 }, AllowOfflineAccess = true, } }
有時我們需要自定義驗證以及自定義一些Claim的信息,所以需要實現 IProfileService 接口
public virtual Task GetProfileDataAsync(ProfileDataRequestContext context)
{
context.LogProfileRequest(Logger);
//判斷是否有請求Claim信息
if (context.RequestedClaimTypes.Any())
{
var userClaims = new List<Claim>
{
new Claim("demo1", "測試1"),
new Claim("demo2", "測試2"),
};
List<TestUser> userList = new List<TestUser>()
{
new TestUser(){SubjectId = "cfac01a9-ba15-4678-bccb-cc22d7896362",Password = "123456",Username="李鋒",Claims = userClaims},
new TestUser(){SubjectId = "cfac01a9-ba15-4678-bccb-cc22d7855555",Password = "123456",Username="張三"},
};
TestUserStore userStore = new TestUserStore(userList);
//根據用戶唯一標識查找用戶信息
var user = userStore.FindBySubjectId(context.Subject.GetSubjectId());
if (user != null)
{
//調用此方法以后內部會進行過濾,只將用戶請求的Claim加入到 context.IssuedClaims 集合中 這樣我們的請求方便能正常獲取到所需Claim
context.AddRequestedClaims(user.Claims);
}
//context.IssuedClaims=userClaims;
}
context.LogIssuedClaims(Logger);
return Task.CompletedTask;
}
/// <summary>
/// 驗證用戶是否有效 例如:token創建或者驗證
/// </summary>
/// <param name="context">The context.</param>
/// <returns></returns>
public virtual Task IsActiveAsync(IsActiveContext context)
{
Logger.LogDebug("IsActive called from: {caller}", context.Caller);
var userClaims = new List<Claim>
{
new Claim("demo1", "測試1"),
new Claim("demo2", "測試2"),
};
List<TestUser> userList = new List<TestUser>()
{
new TestUser(){SubjectId = "cfac01a9-ba15-4678-bccb-cc22d7896362",Password = "123456",Username="李鋒",Claims = userClaims},
new TestUser(){SubjectId = "cfac01a9-ba15-4678-bccb-cc22d7855555",Password = "123456",Username="張三"},
};
TestUserStore userStore = new TestUserStore(userList);
var user = userStore.FindBySubjectId(context.Subject.GetSubjectId());
context.IsActive = user?.IsActive == true;
return Task.CompletedTask;
}
其中關於Claims的地方這里做測試所以直接實例出來的數據,這里可以通過讀取數據庫進行驗證,以及添加Claims的信息
在Startup 的ConfigureServices 注入IdentityServer信息 ,Configure下注冊UseIdentityServer中間件
services.AddIdentityServer()
.AddDeveloperSigningCredential()
.AddInMemoryIdentityResources(IdentityConfig.GetIdentityResources())
.AddInMemoryApiResources(IdentityConfig.GetApiResource())
.AddInMemoryClients(IdentityConfig.GetClients())
//.AddTestUsers(IdentityConfig.GetUsers().ToList())
.AddProfileService<IdentityProfileService>(); //使用的是Code模式,使用自定義Claims信息
//.AddResourceOwnerValidator<IdentityResourceOwnerPasswordValidator>();
app.UseIdentityServer();
然后找到QuickStart中的登錄方法,這里修改為從數據庫讀取驗證用戶信息
建立Vue項目
安裝依賴項:oidc-client :npm install oidc-client --save axios:npm install axios --save
添加 IdentityServerClient 頁面
添加 IdentityServerCallBack頁面
添加 IdentityServerCallBack頁面
訪問IdentityServerClient 頁面時會自動跳轉到IdentityServer4的登錄頁面,輸入賬號密碼,驗證成功之后,會跳轉到IdentityServerCallBack頁面,然后在IdentityServerCallBack頁面設置路由,跳轉到目標頁面
這里主要講哈前端的配置,建議看https://www.cnblogs.com/FireworksEasyCool/p/10576911.html教程和oidc-client官方文檔https://github.com/IdentityModel/oidc-client-js/wiki
注意使用:自動刷新Token使用自動刷新Token需要accessTokenExpiringNotificationTime和automaticSilentRenew 一起設置,當AccssToken要過期前:accessTokenExpiringNotificationTime設置的時間,會去請求
IdentityServer4 connect/token接口,刷新Token,請求到Token以后會觸發addUserLoaded事件,我們可以把Token 保存在瀏覽器的緩存中(cookies,localstorage)中,然后在Axios中全局攔截請求,添加headers信息
Authorization 是token的信息,X-Claims是Claims的信息,傳遞Authorization 是為了IdentityServer 進行驗證授權,X-Claims是為了后面結合Ocelot,攜帶一些Ocelot下游需要的參數進去
建立OcelotGateWay項目
添加Ocelot.json文件"ReRoutes": [
{
"DownstreamPathTemplate": "/api/{url}",
"DownstreamScheme": "http",
"DownstreamHostAndPorts": [
{
"Host": "localhost",
"Port": 44375
}
],
"UpstreamPathTemplate": "/api/{url}",
"UpstreamHttpMethod": [ "Get", "Post" ],
"Priority": 2,
"AuthenticationOptions": {
"AuthenticationProviderKey": "IdentityServerKey",
"AllowScopes": [ "identityServerApi", "delimitClaim" ]
},
"RateLimitOptions": {
"ClientWhiteList": [ //白名單
],
"EnableRateLimiting": true, //啟用限流
"Period": "1m",
"PeriodTimespan": 30,
"Limit": 5
},
"QoSOptions": {
"ExceptionsAllowedBeforeBreaking": 3,
"DurationOfBreak": 3000,
"TimeoutValue": 5000
}
]
}
AuthenticationOptions 表示認證的信息:IdentityServer4驗證信息,AuthenticationProviderKey 填寫 AddIdentityServerAuthentication 的key 一致,然后配置跨域,注冊中間件
services.AddAuthentication()
.AddIdentityServerAuthentication("IdentityServerKey", options =>
{
options.Authority = "http://localhost:17491";
options.ApiName = "identityServerApi";
options.SupportedTokens = IdentityServer4.AccessTokenValidation.SupportedTokens.Both;
options.RequireHttpsMetadata = false;
});
services.AddOcelot()
.AddConsul()
.AddPolly();
//配置跨域處理
services.AddCors(options =>
{
options.AddPolicy("any", builder =>
{
builder.AllowAnyOrigin() //允許任何來源的主機訪問
.AllowAnyMethod()
.AllowAnyHeader()
.AllowCredentials();//指定處理cookie
});
});
//配置Cors
app.UseCors("any");
app.UseAuthentication();
public static IWebHostBuilder CreateWebHostBuilder(string[] args)
{
return WebHost.CreateDefaultBuilder(args)
.ConfigureAppConfiguration((hostingContext, config) =>
{
config
.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath)
.AddOcelot(hostingContext.HostingEnvironment) //ocelot合並配置文件,不能出現同樣的一個端口,多個路由
.AddEnvironmentVariables();
})
.UseStartup<Startup>();
}
學習的資料鏈接,參考資料
Edison Zhou: https://www.cnblogs.com/edisonchou/p/integration_authentication-authorization_service_foundation.html
曉晨Master:https://www.cnblogs.com/stulzq/category/1060023.html
solenovex :https://www.cnblogs.com/cgzl/tag/identity%20server%204/
滅蒙鳥:https://www.jianshu.com/p/fde63052a3a5
.Net框架學苑:https://www.cnblogs.com/letyouknowdotnet/category/1481970.html