首页 > 其他分享 >IdentityServer4 - v4.x .Net中的实践应用

IdentityServer4 - v4.x .Net中的实践应用

时间:2022-12-28 20:13:45浏览次数:36  
标签:Task string app DB v4 new Net public IdentityServer4

认证授权服务的创建

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

创建模拟访问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" },
                // 指定资源中,可取得的身份(用户)信息
	        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" }
            }
	};
	/// 身份资源配置数据源方法
	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 = { "add", "search", "shopping",
	            IdentityServerConstants.StandardScopes.OpenId,
	            IdentityServerConstants.StandardScopes.Profile,
	            JwtClaimTypes.Email, "org","zip",
	            IdentityServerConstants.StandardScopes.OfflineAccess
	        }
	    }
	};
	/// 用户数据源方法
	public static IEnumerable<TestUser> Users => new TestUser[] {
	    new TestUser{
	        id = "10001", username = "sol", password = "123", nickname = "Sol",
                email = "[email protected]", phone="13888888888", gender = "男", address="jingan"
	    },
	    new TestUser{
	        id = "10002", username = "song", password = "123", nickname = "Song",
                email = "[email protected]", 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));
    }
}

用户信息 Profile 的接口实现

/// <summary>
/// 认证通过的用户资料信息 的处理,后续公布到Token中
/// </summary>
public class UserProfileService : IProfileService
{
    public Task GetProfileDataAsync(ProfileDataRequestContext context)
    {
        // 把需要公开到Token中的用户claim信息,放到指定的IssuedClaims中,为后续生成 Token 所用
        var userid = context.Subject.GetSubjectId();
        if (userid != null)
        {
            var claims = context.Subject.Claims.ToList();

            // 此方法,会依据Client请求的Scope(Claim),过滤Claim后的集合放入到 IssuedClaims 中
            context.AddRequestedClaims(claims);

            // 不按 Client.Scope 的过滤,所有的用户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;
    }
}

密码方式验证用户,实现 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;
    }
}

认证授权服务配置

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) + UserClaim(openid+profile+org+email)

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

数据源DB类 Client、IdentityResource 中已定义了 openid+profile+org+email,所以access_token中包含了此几种用户信息。

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

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

解析Token数据图例:

上图显示:

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

name/nickname/email/role/...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服务中设定必须授权的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
    }};
}

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

 

标签:Task,string,app,DB,v4,new,Net,public,IdentityServer4
From: https://www.cnblogs.com/Sol-wang/p/17010169.html

相关文章

  • API对象--Deployment(chrono《kubernetes入门实战课》笔记整理)
    【概念介绍】“Deployment”,顾名思义,它是专门用来部署应用程序的,能够让应用永不宕机,多用来发布无状态的应用,是Kubernetes里最常用也是最有用的一个对象。之前学习过的两......
  • 谈谈Kubernetes开源社区和未来走向
    分享下Kubernetes社区资深成员与项目维护者「张磊」对于这个话题的思考。你好,我是张磊。今天我和你分享的主题是:谈谈Kubernetes开源社区和未来走向。在前面的文章中,我......
  • .NET和JavaScript控件丨Infragistics功能简介
    使用InfragisticsUltimateUI/UX工具包简化开发,提供综合的企业级UI控件库和使用Indigo.Design的UX设计-开发协作工具-一个完整的设计到代码系统-集成原型、设计系统......
  • .NET Core 学习笔记
    .net是一个开发平台。包含.netframwork、netcore等,具体开发的语言主要是C#一、.netframwork和.netcore二者的区别①、.netframework是系统基本安装,相互影响(所......
  • asp.net core 的一个CURD项目,.NET6
         项目视频地址https://www.youtube.com/watch?v=T-e554Zt3n4    效果图     第一步先安装vistalstudio2022https://visualstudio.......
  • WEB应用安全测试丨Acunetix功能简介
    快速查找并修复使您的Web应用程序面临攻击风险的漏洞。享受更多的安心——无需花费更多有限的时间。  产品功能发现与爬行01、发现所有......
  • .net core-利用PdfSharpCore 操作PDF实例
    .netcore-利用PdfSharpCore操作PDF实例 前序使用PdfSharpCore请注意使用XGraphics基类,与System.Drawing的Graphics类似,XGraphics提供XColor(颜色)、XPen(画笔)、XBru......
  • 一分钟搞定Netty 三大组件,如果搞不定,再看3遍
    1.三大组件简介Channel与BufferJavaNIO系统的核心在于:通道(Channel)和缓冲区(Buffer)。通道表示打开到IO设备(例如:文件、套接字)的连接。若需要使用NIO系......
  • 逆向工程 .NET 逆向
    逆向工程.NET逆向1.NET逆向阅读https://blog.csdn.net/cnhk1225/article/details/53568996然后回答问题:Whatdoesthiscodedo?Optimizingcsc.NETcompiler......
  • .NET CORE 发布到IIS HTTP Error 500.30 - 502
    第一种情况:HTTPError500.30-ASP.NETCoreappfailedtostart    根据ASP.NETCore模块|MicrosoftDocs可知,默认配置是进程内托管,设为InProcess,显示指......