认证授权服务的创建

以下内容以密码授权方式为例。

创建模拟访问DB各数据源类

为模拟测试准备的数据源。

/// 假设的用户模型
public class TestUser
{
public string id { get; set; } = string.Empty;
public string username { get; set; } = string.Empty;
public string password { get; set; } = string.Empty;
public string nickname { get; set; } = string.Empty;
public string gender { get; set; } = string.Empty;
public string email { get; set; } = string.Empty;
public string phone { get; set; } = string.Empty;
public string address { get; set; } = string.Empty;
}
/// 假设的DB数据
public class DB
{
/// Scope数据源方法(4.x 时 很重要!!!)
public static IEnumerable<ApiScope> ApiScopes => new ApiScope[]
{
new ApiScope("add","新增"),
new ApiScope("search","查询"),
new ApiScope("shopping","购物"),
};
/// ApiResource 数据源方法
/// 需要被认证授权的资源(服务站点)数据源
public static IEnumerable<ApiResource> GetApiResources => new ApiResource[]
{
new ApiResource("user", "会员服务")
{
// v4.x 时 很重要!!!
Scopes = { "add", "search" },
// 指定此资源中,需要的身份(用户)信息(因此后续会存于Token中)
UserClaims={ JwtClaimTypes.NickName }
},
new ApiResource("product", "产品服务")
{
Scopes = { "add", "shopping" },
UserClaims = { JwtClaimTypes.Name, JwtClaimTypes.NickName, "email", "depart", "role"}
},
new ApiResource("order", "订单服务")
{
Scopes = { "add", "shopping"},
UserClaims = { JwtClaimTypes.Gender, "zip" }
}
};
/// 身份资源配置数据源方法
/// 它定义了一个身份可以具备的所有属性
/// 一个 IdentityResource = 一组 Claim;如下的:Profile、org等
public static IEnumerable<IdentityResource> IdentityResources => new IdentityResource[]
{
// 必须项
new IdentityResources.OpenId(),
new IdentityResources.Profile(),
// 扩展项
new IdentityResources.Email(),
new IdentityResources.Phone(),
new IdentityResources.Address(),
// 自定义追加项
new IdentityResource("org",new string[]{"depart","role"}),
new IdentityResource("zip",new string[]{"zip"})
};
/// 客户端数据源方法
public static IEnumerable<Client> Clients => new Client[]
{
new Client
{
ClientId = "Cli-c",
ClientName="客户端-C-密码方式认证",
AllowedGrantTypes = GrantTypes.ResourceOwnerPassword,
ClientSecrets = { new Secret("secret_code".Sha256()) },
// 支持token过期后自动刷新token,增强体验
AllowOfflineAccess = true,
AccessTokenLifetime = 360000,
AllowedScopes = { // Client.Scopes = Scope + IdentityResource
// 以下为Scope数据源中必须具备的
"add", "search", "shopping",
// 以下为IdentityResource数据源中必须具备的
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
JwtClaimTypes.Email, "org","zip",
// 为配合 AllowOfflineAccess 属性
IdentityServerConstants.StandardScopes.OfflineAccess
}
}
};
/// 用户数据源方法
public static IEnumerable<TestUser> Users => new TestUser[] {
new TestUser{
id = "10001", username = "sol", password = "123", nickname = "Sol",
email = "sol@domain.com", phone="13888888888", gender = "男", address="jingan"
},
new TestUser{
id = "10002", username = "song", password = "123", nickname = "Song",
email = "song@domain.com", phone="13888888888", gender = "女", address="jingan"
}
};
/// 用户是否激活方法
public static bool GetUserActive(string userid)
{
return Users.Any(a => a.id == userid);
}
}

为 Client 实现 IClientStore 接口

/// 客户端数据查询
public class ClientStore : IClientStore
{
// 客户端验证方法
public Task<Client> FindClientByIdAsync(string clientId)
{
// 数据库查询 Client 信息
var client = DB.Clients.FirstOrDefault(c => c.ClientId == clientId) ?? new Client();
client.AccessTokenLifetime = 36000;
return Task.FromResult(client);
}
}

为 ApiResource 实现 IResourceStore 接口

从中可以理出 IdentityResource、ApiResource、ApiScope 三者的关系。

/// <summary>
/// 各个资源数据的查询方法
/// 包括:IdentityResource、ApiResource、ApiScope 三项资源
/// </summary>
public class ResourceStore : IResourceStore
{
public Task<IEnumerable<ApiResource> FindApiResourcesByNameAsync(IEnumerable<string> apiResourceNames)
{
if (apiResourceNames == null) throw new ArgumentNullException(nameof(apiResourceNames));
var result = DB.GetApiResources.Where(r => apiResourceNames.Contains(r.Name));
return Task.FromResult(result);
}
public Task<IEnumerable<ApiResource> FindApiResourcesByScopeNameAsync(IEnumerable<string> scopeNames)
{
if (scopeNames == null) throw new ArgumentNullException(nameof(scopeNames));
var result = DB.GetApiResources.Where(t => t.Scopes.Any(item => scopeNames.Contains(item)));
return Task.FromResult(result);
}
public Task<IEnumerable<ApiScope> FindApiScopesByNameAsync(IEnumerable<string> scopeNames)
{
if (scopeNames == null) throw new ArgumentNullException(nameof(scopeNames));
var result = DB.ApiScopes.Where(w => scopeNames.Contains(w.Name));
return Task.FromResult(result);
}
public Task<IEnumerable<IdentityResource> FindIdentityResourcesByScopeNameAsync(IEnumerable<string> scopeNames)
{
if (scopeNames == null) throw new ArgumentNullException(nameof(scopeNames));
var result = DB.IdentityResources.Where(w => scopeNames.Contains(w.Name));
return Task.FromResult(result);
}
public Task<Resources> GetAllResourcesAsync()
{
return Task.FromResult(new Resources(DB.IdentityResources, DB.GetApiResources, DB.ApiScopes));
}
}

密码方式验证用户,实现 IResourceOwnerPasswordValidator 接口

/// <summary>
/// 密码方式认证过程
/// </summary>
public class ResourceOwnerPasswordValidator : IResourceOwnerPasswordValidator
{
/// <summary>
/// 1、验证 用户是否合法
/// 2、设定 身份基本信息
/// 3、设定 返回给调用者的 Response 结果信息
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
public Task ValidateAsync(ResourceOwnerPasswordValidationContext context)
{
try
{
//验证用户,用户名和密码是否正确
var user = DB.Users.FirstOrDefault(u => u.username == context.UserName && u.password == context.Password); if (user != null)
{
#region 设置 身份(用户)基本信息
// 身份信息的相关属性,带入到ids4中
var claimList = new List<Claim>()
{
// Claim 多(自定义)属性
new Claim(JwtClaimTypes.Name,user.username),
new Claim(JwtClaimTypes.NickName,user.nickname),
new Claim(JwtClaimTypes.Email,user.email),
new Claim(JwtClaimTypes.Gender,user.gender),
new Claim(JwtClaimTypes.PhoneNumber,user.phone),
new Claim("zip","200000")
}; // 追加Claim自定义用户属性
string[] roles = new string[] { "SupperManage", "manage", "admin", "member" };
string[] departs = new string[] { "销售部", "人事部", "总经理办公室" };
foreach (var rolename in roles)
{
claimList.Add(new Claim(JwtClaimTypes.Role, rolename));
}
foreach (var departname in departs)
{
claimList.Add(new Claim("depart", departname));
}
#endregion #region 设置 返回给调用者的Response信息 // 在以下 GrantValidationResult 类中
// 1、通过以上已组装的 ClaimList,再追加上系统必须的Claim项,组装成最终的Claims
// 2、用 Claims ==> 创建出 ClaimsIdentity ==> 再创建出 ClaimsPrincipal
// 以完成 Response 的 json 结果 返回给 调用者 context.Result = new GrantValidationResult(
subject: user.id,
claims: claimList,
authenticationMethod: "db_pwdmode",
// Response 的 json 自定义追加项
customResponse: new Dictionary<string, object> {
{ "custom_append_author", "认证授权请求的Response自定义追加效果" },
{ "custom_append_discription", "认证授权请求的Response自定义追加效果" }
}
);
#endregion
}
else if (user == null)
{
context.Result = new GrantValidationResult(
TokenRequestErrors.InvalidGrant,
"用户认证失败,账号或密码不存在;无效的自定义证书。"
);
}
}
catch (Exception ex)
{
context.Result = new GrantValidationResult()
{
IsError = true,
Error = ex.Message
};
}
return Task.CompletedTask;
}
}

用户信息 Profile 的接口实现

/// <summary>
/// 认证通过的用户资料信息 的处理,后续公布到Token中
/// </summary>
public class UserProfileService : IProfileService
{
// 把需要公开到Token中的用户claim信息,放到指定的IssuedClaims中,为后续生成 Token 所用
public Task GetProfileDataAsync(ProfileDataRequestContext context)
{
var userid = context.Subject.GetSubjectId();
if (userid != null)
{
var claims = context.Subject.Claims.ToList(); // 此方法,会依据Client请求的Scope(IdentityResource.Claims),过滤Claim后的集合放入到 IssuedClaims 中
// 1、Client.Scope(IdentityResource)找到身份中的Claims
// 2、与用户信息生成的Claims匹配,将结果放入IssuedClaims中
context.AddRequestedClaims(claims); // 不按 Client.Scope(IdentityResource.Claims) 的过滤,所有的用户claim全部放入
// context.IssuedClaims = claims.ToList();
}
return Task.CompletedTask;
} public Task IsActiveAsync(IsActiveContext context)
{
string userid = context.Subject.GetSubjectId();
// 查询 DB,ids4需要知道 用户是否已激活
context.IsActive = DB.GetUserActive(userid);
return Task.CompletedTask;
}
}

认证授权服务配置

public class Program
{
public static void Main(string[] args)
{
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers(); #region IdentityServer 的配置
builder.Services.AddIdentityServer()
// 支持开发环境的签名证书
.AddDeveloperSigningCredential()
// 分别注册各自接口的实现类
.AddResourceStore().AddClientStore().AddResourceOwnerValidator().AddProfileService();
// 可追加的扩展
//.AddExtensionGrantValidator<微信自定义扩展模式>();
#endregion
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(); var app = builder.Build();
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseRouting(); #region 使用 ids4 服务
// 它需要在 [路由] 之后,[授权] 之前。
app.UseIdentityServer();
app.UseAuthorization();
#endregion app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
app.Run();
}
}

认证授权服务请求效果

从上图看出:用户密码验证成功、客户端密钥Secret验证成功。

这里重点解释下Scope:

Client参数Scope中包含了: Scope(shopping) + IdentityResource(openid+profile+org+email+zip)

ApiResource 数据源中的产品服务、订单服务都包含了shopping,所以access_token可以访问这两个服务。

Client/IdentityResource/ApiResource 数据源中已定义了 openid+profile+org+email+zip,所以access_token中包含了此用户信息。

认证授权服务 /connect/userinfo 取得的身份信息图例:

上图结果显示:Client.Scope(IdentityResource.Claims) 匹配到的 ApiResources.UserClaims 合并的结果

解析Token数据图例:

上图显示:

aud:已授权的(Client.Scope匹配到的)ApiResource服务名称集合(product/order)

name/email/role/zip/...的Claims:已授权服务(product/order)UserClaims的合并结果

client_id:申请的客户端标识

nbf/exp:认证授权时间/token过期时间

Token访问授权服务

创建一个API产品服务,配置产品服务

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers(); #region Authentication 授权认证
builder.Services.AddAuthorization();
builder.Services.AddAuthentication(options =>
{
// 数据格式设定,以 IdentityServer 风格为准
options.DefaultScheme = IdentityServerAuthenticationDefaults.AuthenticationScheme;
options.DefaultAuthenticateScheme = IdentityServerAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = IdentityServerAuthenticationDefaults.AuthenticationScheme;
options.DefaultForbidScheme = IdentityServerAuthenticationDefaults.AuthenticationScheme;
options.DefaultSignInScheme = IdentityServerAuthenticationDefaults.AuthenticationScheme;
options.DefaultSignOutScheme = IdentityServerAuthenticationDefaults.AuthenticationScheme;
})
.AddIdentityServerAuthentication(options =>
{
options.Authority = "http://localhost:5007"; // IdentityServer 授权服务地址
options.RequireHttpsMetadata = false; // 不需要https
options.ApiName = "product"; // 当前服务名称(与认证授权服务中 ApiResources 的名称对应)
});
#endregion builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
var app = builder.Build();
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseRouting(); #region IdentityServer4 注册
// 放在路由之后,授权之前
app.UseAuthentication();
app.UseAuthorization();
#endregion app.MapControllers();
app.Run();

API服务中设定Authorize必须授权的Action:

/// 获取当前身份信息
[HttpGet, Authorize(Roles = "SupperManage")]
public IEnumerable<object> Get()
{
/// 授权后的身份(用户)信息(从Token中提取的用户属性信息)
var Principal = HttpContext.User; /// 返回 获取到的身份(用户)信息
return new List<object> { new
{
UserId = Principal.Claims.FirstOrDefault(oo => oo.Type == "sub")?.Value,
UserName = Principal.Claims.FirstOrDefault(oo => oo.Type == JwtClaimTypes.Name)?.Value,
NickName = Principal.Claims.FirstOrDefault(oo => oo.Type == JwtClaimTypes.NickName)?.Value,
Email = Principal.Claims.FirstOrDefault(oo => oo.Type == JwtClaimTypes.Email)?.Value,
Zip = Principal.Claims.FirstOrDefault(oo => oo.Type == "zip")?.Value
}};
}

Action取得当前身份(用户)部分信息效果图:

IdentityServer4 - v4.x .Net中的实践应用的更多相关文章

  1. Mysql事务探索及其在Django中的实践(二)

    继上一篇<Mysql事务探索及其在Django中的实践(一)>交代完问题的背景和Mysql事务基础后,这一篇主要想介绍一下事务在Django中的使用以及实际应用给我们带来的效率提升. 首先 ...

  2. 05-雷海林-mysql备份原理与在TDSQL中的实践

    05-雷海林-mysql备份原理与在TDSQL中的实践 下载地址: http://files.cnblogs.com/files/MYSQLZOUQI/05-%E9%9B%B7%E6%B5%B7%E6 ...

  3. ceph在品高云中的实践

    ceph简介 ceph是业界目前人气最高的开源存储项目之一,关于其定义在官网是这样的:"Ceph is a unified, distributed storage system desig ...

  4. 一致性Hash算法在数据库分表中的实践

    最近有一个项目,其中某个功能单表数据在可预估的未来达到了亿级,初步估算在90亿左右.与同事详细讨论后,决定采用一致性Hash算法来完成数据库的自动扩容和数据迁移.整个程序细节由我同事完成,我只是将其理 ...

  5. 华为云对Kubernetes在Serverless Container产品落地中的实践经验

    华为云容器实例服务,它基于 Kubernetes 打造,对最终用户直接提供 K8S 的 API.正如前面所说,它最大的优点是用户可以围绕 K8S 直接定义运行应用. 这里值得一提是,我们采用了全物理机 ...

  6. React 与 Redux 在生产环境中的实践总结

    React 与 Redux 在生产环境中的实践总结 前段时间使用 React 与 Redux 重构了我们360netlab 的 开放数据平台.现将其中一些技术实践经验总结如下: Universal 渲 ...

  7. immutable.js 在React、Redux中的实践以及常用API简介

    immutable.js 在React.Redux中的实践以及常用API简介 学习下 这个immutable Data 是什么鬼,有什么优点,好处等等 mark :  https://yq.aliyu ...

  8. Immutable.js 以及在 react+redux 项目中的实践

    来自一位美团大牛的分享,相信可以帮助到你. 原文链接:https://juejin.im/post/5948985ea0bb9f006bed7472?utm_source=tuicool&ut ...

  9. TypeScript在react项目中的实践

    前段时间有写过一个TypeScript在node项目中的实践. 在里边有解释了为什么要使用TS,以及在Node中的一个项目结构是怎样的. 但是那仅仅是一个纯接口项目,碰巧赶上近期的另一个项目重构也由我 ...

  10. TypeScript在node项目中的实践

    TypeScript在node项目中的实践 TypeScript可以理解为是JavaScript的一个超集,也就是说涵盖了所有JavaScript的功能,并在之上有着自己独特的语法.最近的一个新项目开 ...

随机推荐

  1. SQL语句——为什么分组函数(多行处理函数)不能用在where语句之后?

    在SQL语句中,常见的分组查询函数为: sum()求和 avg()求平均值 min()求最小值 max()求最大值 count()求数目 在分组函数中有几个重要的特征: 1.分组函数进行时自动忽略nu ...

  2. TF-GNN踩坑记录(二)

    引言 最近在尝试构建GraphTensor时,尝试把默认的特征名hidden_state改成其他时,出现了奇怪的报错. 报错 WARNING:tensorflow:Gradients do not e ...

  3. Qt Quick 用cmake怎么玩子项目

    以下内容为本人的著作,如需要转载,请声明原文链接微信公众号「englyf」https://mp.weixin.qq.com/s/o-_aGqreuQda-ZmKktvxwA 以往在公司开发众多的项目中 ...

  4. 5 why 分析法,一种用于归纳抽象出解决方案的好方法

    最近在看了<微信背后的产品观 - 张小龙手抄版>,其中有段话如下: 用户需求是零散的,解决方案是归纳抽象的过程 那如何归纳抽象呢?是否有一定的实践方法论呢?经过一轮探讨和学习,有这些答案: ...

  5. 京东云开发者|ElasticSearch降本增效常见的方法

    Elasticsearch在db_ranking 的排名又(双叒叕)上升了一位,如图1-1所示;由此可见es在存储领域已经蔚然成风且占有非常重要的地位. 随着Elasticsearch越来越受欢迎,企 ...

  6. ML-L1、L2 正则化

    出现过拟合时,使用正则化可以将模型的拟合程度降低一点点,使曲线变得缓和. L1正则化(LASSO) 正则项是所有参数的绝对值的和.正则化不包含theta0,因为他只是偏置,而不影响曲线的摆动幅度. \ ...

  7. C# 语法分析器(二)LR(0) 语法分析

    系列导航 (一)语法分析介绍 (二)LR(0) 语法分析 (三)LALR 语法分析 (四)二义性文法 (五)错误恢复 (六)构造语法分析器 首先,需要介绍下 LALR 语法分析的基础:LR(0) 语法 ...

  8. MySQL的下载、安装、配置

    下载 官方下载地址:下载地址: 找到免费社区版本 进入到下面页面的时候,下载对应的MySQL,我这里选择Windows. 点击Download ,如下图: 后面他会提示你登录注册啥的,我们选择不需要, ...

  9. .NET 7 中 LINQ 的疯狂性能提升

    LINQ 是 Language INtegrated Query 单词的首字母缩写,翻译过来是语言集成查询.它为查询跨各种数据源和格式的数据提供了一致的模型,所以叫集成查询.由于这种查询并没有制造新的 ...

  10. 匿名方法、Lambda表达和自定义泛型委托以及Func、Action系统泛型委托

    1.匿名方法的概念:一个方法没有具体的名称,而只有关键字delegate.方法参数.方法体.这种方法是匿名方法. 匿名方法的好处:将具体方法和委托直接关联在一起,如果我们基于委托只需要一个方法的时候, ...