随笔 - 59  文章 - 20  评论 - 61  阅读 - 85494

Blazor Server访问Identity Server 4单点登录2-集成Asp.Net角色

采用内存用户集合的模板创建的项目,是不能用于生产环境的,毕竟用户信息只存在于内存。Id4有一个模板是可以采用Asp.Net内置的用户数据库的,直接创建这个模板的项目即可。

 

创建AspNet用户数据库的Identity Server 4项目

在控制台进入解决方案目录,创建is4aspid模板项目。

D:\Software\gitee\blzid4\BlzId4Web>dotnet new is4aspid -n AspNetId4Web

已成功创建模板“IdentityServer4 with ASP.NET Core Identity”

 

正在处理创建后操作...

模板配置为运行以下操作:

说明:

手动说明: Seeds the initial user database

实际命令: dotnet run /seed

是否要运行此操作 [Y()|N()]?

y

正在运行命令:“dotnet run /seed”...

命令已成功。

 

参考前文《Blazor Server访问Identity Server 4单点登录》

https://www.cnblogs.com/sunnytrudeau/p/15145388.html

 

Config.cs新增2个客户端定义

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
new Client()
{
    ClientId="BlazorServer1",
    ClientName = "BlazorServer1",
    ClientSecrets=new []{new Secret("BlazorServer1.Secret".Sha256())},
 
    AllowedGrantTypes = GrantTypes.Code,
     
    AllowedCorsOrigins = { "https://localhost:5101" },
    RedirectUris = { "https://localhost:5101/signin-oidc" },
    PostLogoutRedirectUris = { "https://localhost:5101/signout-callback-oidc" },
 
    AllowedScopes = { "openid", "profile", "scope1" }
},
 
new Client()
{
    ClientId="BlazorServer2",
    ClientName = "BlazorServer2",
    ClientSecrets=new []{new Secret("BlazorServer2.Secret".Sha256())},
 
    AllowedGrantTypes = GrantTypes.Code,
 
    AllowedCorsOrigins = { "https://localhost:5201" },
    RedirectUris = { "https://localhost:5201/signin-oidc" },
    PostLogoutRedirectUris = { "https://localhost:5201/signout-callback-oidc" },
 
    AllowedScopes = { "openid", "profile", "scope1" }
},

 

  

 

创建Blazor Server项目

参考前文《Blazor Server访问Identity Server 4单点登录》,创见2Blazor Server客户端项目。

 

然后运行3个项目,测试验证id4单点登录。

 

增加角色

Asp.Net项目最常见的授权策略就是基于角色,默认创建的Identity Server 4项目是没有角色的,得手工加上。这个还相当麻烦。

 

首先要给用户增加角色,在SeedData增加函数AddRole

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
/// <summary>
        /// 给测试用户增加角色
        /// </summary>
        /// <param name="serviceProvider"></param>
        public static void AddRole(IServiceProvider serviceProvider)
        {
            using var scope = serviceProvider.CreateScope();
            using var roleManager = scope.ServiceProvider.GetRequiredService<RoleManager<IdentityRole>>();
            using var userMgr = scope.ServiceProvider.GetRequiredService<UserManager<ApplicationUser>>();
 
            bool roleExists = roleManager.RoleExistsAsync("Admin").Result;
            if (!roleExists)
            {
                var result = roleManager.CreateAsync(new IdentityRole("Admin"));
            }
 
            roleExists = roleManager.RoleExistsAsync("Guest").Result;
            if (!roleExists)
            {
                var result = roleManager.CreateAsync(new IdentityRole("Guest"));
            }
 
            var alice = userMgr.FindByNameAsync("alice").Result;
            if (alice != null)
            {
                var roles = userMgr.GetRolesAsync(alice).Result;
                if (!roles.Contains("Admin"))
                {
                    var result = userMgr.AddToRoleAsync(alice, "Admin");
                }
                if (!roles.Contains("Guest"))
                {
                    var result = userMgr.AddToRoleAsync(alice, "Guest");
                }
            }
 
            var bob = userMgr.FindByNameAsync("bob").Result;
            if (bob != null)
            {
                var roles = userMgr.GetRolesAsync(bob).Result;
                if (!roles.Contains("Guest"))
                {
                    var result = userMgr.AddToRoleAsync(bob, "Guest");
                }
            }
        }

 

  

id4项目初始化阶段检查执行,增加角色。

SeedData.AddRole(host.Services);

 

id4项目config增加role资源

 

1
2
3
4
5
6
7
public static IEnumerable<IdentityResource> IdentityResources =>
            new IdentityResource[]
            {
                new IdentityResources.OpenId(),
                new IdentityResources.Profile(),
                new IdentityResource("role", new string[]{JwtClaimTypes.Role })
            };

 

  

客户端配置AllowedScopes也增加role

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
new Client()
                {
                    ClientId="BlazorServer1",
                    ClientName = "BlazorServer1",
                    ClientSecrets=new []{new Secret("BlazorServer1.Secret".Sha256())},
 
                    AllowedGrantTypes = GrantTypes.Code,
 
                    AllowedCorsOrigins = { "https://localhost:5101" },
                    RedirectUris = { "https://localhost:5101/signin-oidc" },
                    PostLogoutRedirectUris = { "https://localhost:5101/signout-callback-oidc" },
 
                    //效果等同客户端项目配置options.GetClaimsFromUserInfoEndpoint = true
                    //AlwaysIncludeUserClaimsInIdToken = true,
 
                    AllowedScopes = { "openid", "profile", "scope1", "role", }
                },

 

  

Blazor Server项目增加获取rolescope,并且在id4用户信息端点拿到role之后要手工解析,因为id4返回的role可能是单个字符串,也可能是字符串数组,Blazor Server不认识。

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
//默认采用cookie认证方案,添加oidc认证方案
            services.AddAuthentication(options =>
                {
                    options.DefaultScheme = "cookies";
                    options.DefaultChallengeScheme = "oidc";
                })
                //配置cookie认证
                .AddCookie("cookies")
                .AddOpenIdConnect("oidc", options =>
                {
                    //id4服务的地址
                    options.Authority = "https://localhost:5001";
 
                    //id4配置的ClientId以及ClientSecrets
                    options.ClientId = "BlazorServer1";
                    options.ClientSecret = "BlazorServer1.Secret";
 
                    //认证模式
                    options.ResponseType = "code";
 
                    //保存token到本地
                    options.SaveTokens = true;
 
                    //很重要,指定从Identity Server的UserInfo地址来取Claim
                    //效果等同id4配置AlwaysIncludeUserClaimsInIdToken = true
                    options.GetClaimsFromUserInfoEndpoint = true;
 
                    //指定要取哪些资料(除Profile之外,Profile是默认包含的)
                    options.Scope.Add("scope1");
                    options.Scope.Add("role");
 
                    //这里是个ClaimType的转换,Identity Server的ClaimType和Blazor中间件使用的名称有区别,需要统一。
                    //User.Identity.Name=JwtClaimTypes.Name
                    options.TokenValidationParameters.NameClaimType = "name";
                    options.TokenValidationParameters.RoleClaimType = "role";
 
                    options.Events.OnUserInformationReceived = (context) =>
                    {
                        //id4返回的角色是字符串数组或者字符串,blazor server的角色是字符串,需要转换,不然无法获取到角色
                        ClaimsIdentity claimsId = context.Principal.Identity as ClaimsIdentity;
 
                        var roleElement = context.User.RootElement.GetProperty(JwtClaimTypes.Role);
                        if (roleElement.ValueKind == System.Text.Json.JsonValueKind.Array)
                        {
                            var roles = roleElement.EnumerateArray().Select(e => e.ToString());
                            claimsId.AddClaims(roles.Select(r => new Claim(JwtClaimTypes.Role, r)));
                        }
                        else
                        {
                            claimsId.AddClaim(new Claim(JwtClaimTypes.Role, roleElement.ToString()));
                        }
 
                        return Task.CompletedTask;
                    };
                });

 

  

测试获取角色

同时运行3个项目,在Blazor Server项目主页用alice登录,可以看到正确获取到了role

 

id4控制台信息可以看到,Blazor Server客户端请求用户信息时,返回了role字段。

[23:37:38 Debug] IdentityServer4.Validation.TokenValidator

Token validation success

{"ClientId": null, "ClientName": null, "ValidateLifetime": true, "AccessTokenType": "Jwt", "ExpectedScope": "openid", "TokenHandle": null, "JwtId": "3223AFCB3DB832A92A69A04470AE144F", "Claims": {"nbf": 1630856258, "exp": 1630859858, "iss": "https://localhost:5001", "aud": "https://localhost:5001/resources", "client_id": "BlazorServer1", "sub": "845589e3-2f6d-4175-a5c1-92cc589ead01", "auth_time": 1630856258, "idp": "local", "jti": "3223AFCB3DB832A92A69A04470AE144F", "sid": "7C09FC1C3A8B1D645FA6ECE46A690580", "iat": 1630856258, "scope": ["openid", "profile", "scope1", "role"], "amr": "pwd"}, "$type": "TokenValidationLog"}

 

[23:37:38 Debug] IdentityServer4.ResponseHandling.UserInfoResponseGenerator

Creating userinfo response

 

[23:37:38 Debug] IdentityServer4.ResponseHandling.UserInfoResponseGenerator

Scopes in access token: openid profile scope1 role

 

[23:37:38 Debug] IdentityServer4.ResponseHandling.UserInfoResponseGenerator

Requested claim types: sub name family_name given_name middle_name nickname preferred_username profile picture website gender birthdate zoneinfo locale updated_at role

 

[23:37:38 Information] IdentityServer4.ResponseHandling.UserInfoResponseGenerator

Profile service returned the following claim types: sub name given_name family_name website role role preferred_username

 

[23:37:38 Debug] IdentityServer4.Endpoints.UserInfoEndpoint

End userinfo request

 

 

 

 

测试角色授权

 

counter页面增加认证要求,只要登录用户都能访问

 

@attribute [Authorize]

 

 

 

FetchData页面增加Admin角色授权要求,只有Admin角色可以访问

 

@attribute [Authorize(Roles ="Admin")]

 

 

 

alice登录,两个页面都可以访问,用bob登录,访问counter页面没问题,访问FetchData页面提示没有权限

 

这样就可以用于真实的产品项目中去了。

 

DEMO代码地址:https://gitee.com/woodsun/blzid4

 

posted on   SunnyTrudeau  阅读(676)  评论(0编辑  收藏  举报
编辑推荐:
· AI与.NET技术实操系列:基于图像分类模型对图像进行分类
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
< 2025年3月 >
23 24 25 26 27 28 1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31 1 2 3 4 5

点击右上角即可分享
微信分享提示