.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的基友,方便广大 ...
随机推荐
- httppost的用法
一,案例一 定义了一个list,该list的数据类型是NameValuePair(简单名称值对节点类型),这个代码多处用于Java像url发送Post请求.在发送post请求时用该list来存放参数. ...
- 化学元素周期表的英文全称 Periodic Table of the Elements
化学元素周期表的英文全称 Periodic Table of the Elements 缩写 PTE 拉丁文 英文 1 H 氢 Hydrogenium Hydrogen 2 He 氦 Helium ...
- 【项目】关于TeenCode第二代评测机的技术分析
晚上睡不着觉,仔细研读了洛谷的第四代评测机技术分析后,突然发现自己写的TeenCode评测机竟然有这么多地方可以改进,这不得不让我诞生了实现第二代TeenCode评测机的想法.[第一代评测机挺可怜的, ...
- Bootstrap解决页面缩小变形的办法
bootstrap布局是应用得很广泛的一种网页布局方法,例如:我们用一种中间内容很流行的布局分布:3-6-3式布局.代码如下 <style type="text/css"&g ...
- 前端HTML学习 table标签 知识点与使用
表格基本结构 <table> <tr> <td>单元格</td> </tr> </table> < tr >表示 行 ...
- JavaScript实现登录滑动验证
来自于GitHub, 如何快速访问GitHub 先附上效果图 划到一半停止回自己回去的 PS: 附上代码,有需要自己更改, <!DOCTYPE html> <html lang=&q ...
- java实现 洛谷 P1056 排座椅
import java.util.Arrays; import java.util.Map.Entry; import java.util.Scanner; import java.util.Tree ...
- java实现第五届蓝桥杯猜年龄
猜年龄 题目描述 小明带两个妹妹参加元宵灯会.别人问她们多大了,她们调皮地说:"我们俩的年龄之积是年龄之和的6倍".小明又补充说:"她们可不是双胞胎,年龄差肯定也不超过8 ...
- 团体天梯赛L1-041.寻找250
对方不想和你说话,并向你扔了一串数…… 而你必须从这一串数字中找到“250”这个高大上的感人数字. 输入格式: 输入在一行中给出不知道多少个绝对值不超过1000的整数,其中保证至少存在一个“250”. ...
- 借Adobe XD之力,自动生成Flutter代码
概述 今天,我们来聊聊一个专门为"懒人程序员"准备的工具--Adobe XD.使用它可以快速将原型设计图转换为可执行的Flutter代码, 而由于Flutter自身跨平台的特性,因 ...