.NETCore微服务探寻(二) - 认证与授权
前言
一直以来对于.NETCore微服务相关的技术栈都处于一个浅尝辄止的了解阶段,在现实工作中也对于微服务也一直没有使用的业务环境,所以一直也没有整合过一个完整的基于.NETCore技术栈的微服务项目。正好由于最近刚好辞职,有了时间可以写写自己感兴趣的东西,所以在此想把自己了解的微服务相关的概念和技术框架使用实现记录在一个完整的工程中,由于本人技术有限,所以错误的地方希望大家指出。
目录
为什么需要认证授权服务
作为对外曝露的企业接口服务,当然不可能直接不需要任何的认证就可以随意访问接口,不然会面临巨大的安全隐患。由于单纯的认证和授权不需要与业务逻辑耦合并且访问频繁,所以可以单独划分为一个服务并根据具体业务做单独的服务优化(负载均衡等)
怎么创建认证与授权服务
.NETCore中除了官方的认证授权框架外,比较流行的是 IdentityServer4
什么是IdentityServer4
IdentityServer是用于ASP.NET Core 的免费,开源OpenID Connect和OAuth 2.0框架。IdentityServer4 由Dominick Baier和Brock Allen创建和维护,它整合了将基于令牌的身份验证,单点登录和API访问控制集成到您的应用程序中所需的所有协议实现和可扩展性点。IdentityServer4 已由OpenID Foundation正式认证,因此符合规范且可互操作。它是.NET Foundation的一部分,并根据其行为准则进行操作。它已获得Apache 2(OSI批准的许可证)的许可。
如何接入IdentityServer4
一、认证中心
新建 Auth Web项目作为认证中心
Auth项目 通过 Nuget 安装 IdentityServer
Auth项目 定义Api资源(ApiResource),身份资源(IdentityResource),客户端(Client)信息。
IdentityServer4可以通过持久化的方式定义这些信息,这里方便演示采用的是内存中定义
IdentityServerConfig.cs
using IdentityServer4.Models;
using System.Collections.Generic;
namespace ForDotNet.Auth.Config
{
/// <summary>
/// IdentityServer4配置信息
/// </summary>
public static class IdentityServerConfig
{
private const string AuthClientId = "Auth";
private const string Api1ClientId = "Api1";
/// <summary>
/// 获取api资源
/// </summary>
/// <returns></returns>
public static IEnumerable<ApiResource> GetApiResources()
{
return new List<ApiResource>()
{
new ApiResource("Auth","AuthApi"),
// new ApiResource("Api1","Api1"),
new ApiResource()
{
Name = "Api1",
DisplayName = "Api1Display",
Scopes = new Scope[]
{
new Scope("Api1","This Api1 Scope"),
new Scope ("Business","This is Business Scope"),
new Scope ("Admin","This Admin Scope")
}
}
};
}
/// <summary>
/// 获取客户端
/// </summary>
/// <returns></returns>
public static IEnumerable<Client> GetClients()
{
Client authClient = new Client()
{
ClientId = AuthClientId,
ClientSecrets = new List<Secret>()
{
GetSecret(AuthClientId)
},
AllowedGrantTypes = GrantTypes.ResourceOwnerPassword,
AllowedScopes = new string[]
{
"Auth"
}
};
Client api1Client = new Client()
{
ClientId = Api1ClientId,
ClientSecrets = new List<Secret>()
{
GetSecret(Api1ClientId)
},
AllowedGrantTypes = GrantTypes.ResourceOwnerPassword,
AllowedScopes = new string[]
{
"Api1"
}
};
return new List<Client>()
{
authClient,
api1Client
};
}
public static IEnumerable<IdentityResource> GetIdentityResources()
{
return new List<IdentityResource>()
{
new IdentityResources.OpenId()
};
}
#region 私有方法
/// <summary>
/// 获取Secret
/// </summary>
/// <param name="clientId">clientId</param>
/// <returns></returns>
private static Secret GetSecret(string clientId)
{
return new Secret(clientId.Sha256());
}
#endregion 私有方法
}
}
Startup.cs中注入这些服务
using ForDotNet.Auth.Config;
using ForDotNet.Common.Consul.Extensions;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using System.Collections.Generic;
namespace ForDotNet.Auth
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services
.AddIdentityServer() // 注册IdentityServer4
.AddDeveloperSigningCredential() // 采用开发者凭证
.AddInMemoryApiResources(IdentityServerConfig.GetApiResources()) // 添加Api资源
.AddInMemoryClients(IdentityServerConfig.GetClients()) // 添加客户端
.AddInMemoryIdentityResources(IdentityServerConfig.GetIdentityResources()) // 添加身份资源
.AddTestUsers(new List<IdentityServer4.Test.TestUser>() // 添加测试用户
{
new IdentityServer4.Test.TestUser ()
{
Username = "admin",
Password = "123",
SubjectId = "999"
}
});
// 注册服务发现服务
services.AddConsulServiceDiscovery();
services.AddControllers();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IHostApplicationLifetime life)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
// 注册IdentityServer
app.UseIdentityServer();
// 注册服务发现
app.UseConsulServiceDiscovery(life);
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
}
}
这里我们为了方便测试直接使用的是 AddTestUsers
添加的测试用户,如果我们需要自定义验证逻辑的话需要使用 AddResourceOwnerValidator<T>
,该方法接收一个实现了 IResourceOwnerPasswordValidator
接口的 T
类型。
定义一个 MyResourceOwnerValidator.cs
using IdentityServer4.Models;
using IdentityServer4.ResponseHandling;
using IdentityServer4.Validation;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;
namespace ForDotNet.Auth
{
/// <summary>
/// 我的校验逻辑
/// </summary>
public class MyResourceOwnerValidator : IResourceOwnerPasswordValidator
{
public MyResourceOwnerValidator()
{
// 可以注入服务
}
/// <summary>
/// 校验方法
/// </summary>
/// <param name="context">上下文信息(包含了用户名密码等信息)</param>
/// <returns></returns>
public async Task ValidateAsync(ResourceOwnerPasswordValidationContext context)
{
await Task.Run(()=>
{
//校验逻辑...
// 校验成功
if (DateTime.Now.Minute % 2 == 0)
{
context.Result = new GrantValidationResult(Guid.NewGuid().ToString(), "DIY",new List<Claim>()
{
new Claim ("DIYClaim","This is DIYClaim")
});
}
else
{
context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant, "认证失败",
new Dictionary<string, object>()
{
{ "Test","This Is Test" }
});
}
});
}
}
}
如果使用了AddTestUsers的话则AddResourceOwnerValidator中实现的逻辑不会生效
public void ConfigureServices(IServiceCollection services)
{
services
.AddIdentityServer() // 注册IdentityServer4
.AddDeveloperSigningCredential() // 采用开发者凭证
.AddInMemoryApiResources(IdentityServerConfig.GetApiResources()) // 添加Api资源
.AddInMemoryClients(IdentityServerConfig.GetClients()) // 添加客户端
.AddInMemoryIdentityResources(IdentityServerConfig.GetIdentityResources()) // 添加身份资源
.AddResourceOwnerValidator<MyResourceOwnerValidator>()
//.AddTestUsers(new List<IdentityServer4.Test.TestUser>() // 添加测试用户
//{
// new IdentityServer4.Test.TestUser ()
// {
// Username = "admin",
// Password = "123",
// SubjectId = "999"
// }
//});
// 注册服务发现服务
services.AddConsulServiceDiscovery();
services.AddControllers();
}
二、客户端(多个)
1.新建 Api 项目作为客户端
2.通过 Nuget安装 IdentityServer4.AccessTokenValidation
3.Api项目客户端 Startup.cs 中 注册认证
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using ForDotNet.Common.Consul.Extensions;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.HttpsPolicy;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
namespace ForDotNet.Web.Api
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services
.AddAuthentication("Bearer")
.AddIdentityServerAuthentication(options =>
{
options.Authority = "http://localhost:5800"; // issuer地址
options.SupportedTokens = IdentityServer4.AccessTokenValidation.SupportedTokens.Both;
options.ApiName = "Api1";
options.RequireHttpsMetadata = false; // 启用http
});
services.AddConsulServiceDiscovery();
services.AddControllers();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env,IHostApplicationLifetime life)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
// 启用认证
app.UseAuthentication();
app.UseConsulServiceDiscovery(life);
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
}
}
三、运行并测试
1.客户端需要权限访问的接口添加 Authorize
特性
2.直接访问 http://localhost:5500/api/test
,响应401未授权
3.访问 http://localhost:5800/connect/token
请求token
由于我们的自定义逻辑 在当前时间分钟数 % 2 == 0 的时候 认证通过,反之失败
成功:
失败:
4.将获取到的token 添加到请求头中 访问 http://localhost:5500/api/test
200 响应请求成功
使用Ocelot 接入IdentityServer4
上面讲述的是没有使用网关的时候使用IdentityServer4,如果我们使用Ocelot的时候,下游服务运行在内网中与公网隔离时如何使用IdentityServer做统一的认证呢。
1.移除下游Api项目的认证代码
2.Ocelot添加认证配置
Startup中添加IdentityServer4认证配置信息
Ocelot.json配置文件中添加认证信息
AllowScopes
限制可以访问的范围 为空表示不限制
3.测试访问
直接通过网关访问 http://localhost:5000/api/v1/test
,响应401 未授权
通过 http://localhost:5000/auth/token
获取token ,访问 http://localhost:5000/api/v1/test
200 请求成功
尝试更改 AllowScopes
为 Business 再次访问,显示 403 禁止访问
将 AllowScopes
改为 Api1 再次访问,显示 200 请求成功
然后查看我们请求token所属客户端的 AllowScopes
为
与我们的限制范围 一致
.NETCore微服务探寻(二) - 认证与授权的更多相关文章
- .NETCore微服务探寻(三) - 分布式日志
前言 一直以来对于.NETCore微服务相关的技术栈都处于一个浅尝辄止的了解阶段,在现实工作中也对于微服务也一直没有使用的业务环境,所以一直也没有整合过一个完整的基于.NETCore技术栈的微服务项目 ...
- .NETCore微服务探寻(三) - 远程过程调用(RPC)
前言 一直以来对于.NETCore微服务相关的技术栈都处于一个浅尝辄止的了解阶段,在现实工作中也对于微服务也一直没有使用的业务环境,所以一直也没有整合过一个完整的基于.NETCore技术栈的微服务项目 ...
- .NETCore微服务探寻(一) - 网关
前言 一直以来对于.NETCore微服务相关的技术栈都处于一个浅尝辄止的了解阶段,在现实工作中也对于微服务也一直没有使用的业务环境,所以一直也没有整合过一个完整的基于.NETCore技术栈的微服务项目 ...
- 7.【Spring Cloud Alibaba】微服务的用户认证与授权
有状态 vs 无状态 有状态 那么Session在何时创建呢? 当然还是在服务器端程序运行的过程中创建的,不同语言实现的应用程序有不同创建Session的方法,而在Java中是通过调用HttpServ ...
- .NetCore微服务Surging新手傻瓜式 入门教程 学习日志---结构简介(二)
原文:.NetCore微服务Surging新手傻瓜式 入门教程 学习日志---结构简介(二) 先上项目解决方案图: 以上可以看出项目结构可以划分为4大块,1是surging的核心底层,2,3,4都可以 ...
- (1)学习笔记 ) ASP.NET CORE微服务 Micro-Service ---- 什么是微服务架构,.netCore微服务选型
开发工具:VS2017 .Net Core 2.1 什么是微服务?单体结构: 缺点: 1)只能采用同一种技术,很难用不同的语言或者语言不同版本开发不同模块: 2)系统耦合性强,一旦其中一个模块有问题, ...
- (1).NET CORE微服务 Micro-Service ---- 什么是微服务架构,.netCore微服务选型
开发工具:VS2017 .Net Core 2.1 什么是微服务?单体结构: 缺点:1)只能采用同一种技术,很难用不同的语言或者语言不同版本开发不同模块:2)系统耦合性强,一旦其中一个模块有问题,整个 ...
- 什么是微服务架构,.netCore微服务选型
什么是微服务架构,.netCore微服务选型 https://www.cnblogs.com/uglyman/p/9182485.html 开发工具:VS2017 .Net Core 2.1 什么是微 ...
- .NetCore微服务Surging新手傻瓜式 入门教程 学习日志---先让程序跑起来(一)
原文:.NetCore微服务Surging新手傻瓜式 入门教程 学习日志---先让程序跑起来(一) 写下此文章只为了记录Surging微服务学习过程,并且分享给广大想学习surging的基友,方便广大 ...
随机推荐
- 13.Java连接Redis_Jedis_事务
Jedis事务我们使用JDBC连接Mysql的时候,每次执行sql语句之前,都需要开启事务:在MyBatis中,也需要使用openSession()来获取session事务对象,来进行sql执行.查询 ...
- redis使用技巧十连胜,学会工作六到飞起
Redis 在当前的技术社区里是非常热门的.从来自 Antirez 一个小小的个人项目到成为内存数据存储行业的标准,Redis已经走过了很长的一段路. 随之而来的一系列最佳实践,使得大多数人可以正确地 ...
- QueryRunner的添加与查询操作
Apache-DBUtils实现CRUD操作,commmons-dbutils是Apache组织提供的开源JDBC工具类, 封装了针对于数据库的增删改查操作,Class QueryRunner Tes ...
- 【Flume】知识总结
Flume是Cloudera提供的一个高可用的,高可靠的,分布式的海量日志采集.聚合和传输的系统,Flume支持在日志系统中定制各类数据发送方,用于收集数据:同时,Flume提供对数据进行简单处理,并 ...
- 使用turtle库画太极图
from turtle import * pensize(3) penup() pencolor("black") reset() speed(10) pendown() circ ...
- Java实现 LeetCode 795 区间子数组个数 (暴力分析)
795. 区间子数组个数 给定一个元素都是正整数的数组A ,正整数 L 以及 R (L <= R). 求连续.非空且其中最大元素满足大于等于L 小于等于R的子数组个数. 例如 : 输入: A = ...
- Java实现 LeetCode 696 计数二进制子串(暴力)
696. 计数二进制子串 给定一个字符串 s,计算具有相同数量0和1的非空(连续)子字符串的数量,并且这些子字符串中的所有0和所有1都是组合在一起的. 重复出现的子串要计算它们出现的次数. 示例 1 ...
- Java实现 LeetCode 670 最大交换(暴力)
670. 最大交换 给定一个非负整数,你至多可以交换一次数字中的任意两位.返回你能得到的最大值. 示例 1 : 输入: 2736 输出: 7236 解释: 交换数字2和数字7. 示例 2 : 输入: ...
- Java实现 LeetCode 659 分割数组为连续子序列 (哈希)
659. 分割数组为连续子序列 输入一个按升序排序的整数数组(可能包含重复数字),你需要将它们分割成几个子序列,其中每个子序列至少包含三个连续整数.返回你是否能做出这样的分割? 示例 1: 输入: [ ...
- Java实现 LeetCode 328 奇偶链表
328. 奇偶链表 给定一个单链表,把所有的奇数节点和偶数节点分别排在一起.请注意,这里的奇数节点和偶数节点指的是节点编号的奇偶性,而不是节点的值的奇偶性. 请尝试使用原地算法完成.你的算法的空间复杂 ...