运行环境
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 地址:
我也是在初学阶段, 所以有些地方可能描述的不是很清楚, 可以下载源码查看, 也可能大家一起交流, 学习
建立 IdentityServer4 项目
根据 根据 Edison Zhou 大佬的博客, 配置好 QuickStart UI 以及 IdentityServer 的依赖项
新建 IdentityServer4 的配置文件 IdentityConfig
- public static IEnumerable<IdentityResource> GetIdentityResources()
- {
- return new IdentityResource[]
- {
- new IdentityResources.OpenId(),
- new IdentityResources.Profile(),
- new IdentityResource("delimitClaim","delimitClaim",new List<string>(){ "role", "name"}), // 在 Claims 添加 role 和 name 信息
- };
- }
- public static IEnumerable<ApiResource> GetApiResource()
- {
- return new List<ApiResource>
- {
- new ApiResource("identityServerApi", "identityServerApi"),
- };
- }
- public static IEnumerable<Client> 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();
Program 中添加
- 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: IdentityServer4 和 ocelot
晓晨 Master: IdentityServer4
solenovex : IdentityServer4 bibi 上还有 IdentityServer4 视频喔
灭蒙鸟: https://www.jianshu.com/p/fde63052a3a5 入门教程: JS 认证和 WebAPI
.Net 框架学苑: ocelot 一系列教程
来源: http://www.bubuko.com/infodetail-3170631.html