Java自学者论坛

 找回密码
 立即注册

手机号码,快捷登录

恭喜Java自学者论坛(https://www.javazxz.com)已经为数万Java学习者服务超过8年了!积累会员资料超过10000G+
成为本站VIP会员,下载本站10000G+会员资源,会员资料板块,购买链接:点击进入购买VIP会员

JAVA高级面试进阶训练营视频教程

Java架构师系统进阶VIP课程

分布式高可用全栈开发微服务教程Go语言视频零基础入门到精通Java架构师3期(课件+源码)
Java开发全终端实战租房项目视频教程SpringBoot2.X入门到高级使用教程大数据培训第六期全套视频教程深度学习(CNN RNN GAN)算法原理Java亿级流量电商系统视频教程
互联网架构师视频教程年薪50万Spark2.0从入门到精通年薪50万!人工智能学习路线教程年薪50万大数据入门到精通学习路线年薪50万机器学习入门到精通教程
仿小米商城类app和小程序视频教程深度学习数据分析基础到实战最新黑马javaEE2.1就业课程从 0到JVM实战高手教程MySQL入门到精通教程
查看: 830|回复: 0

入门教程:.NET开源OpenID Connect 和OAuth解决方案IdentityServer v3 MVC认证与授权(四)

[复制链接]
  • TA的每日心情
    奋斗
    2024-11-24 15:47
  • 签到天数: 804 天

    [LV.10]以坛为家III

    2053

    主题

    2111

    帖子

    72万

    积分

    管理员

    Rank: 9Rank: 9Rank: 9

    积分
    726782
    发表于 2021-4-13 13:33:27 | 显示全部楼层 |阅读模式

    本教程将搭建一个最小能够运行的IdentityServer。为简单起见,我们将identityserver和客户端放在同一Web应用程序-这可能不会是一个很现实的情况下,但可以让你不太复杂的开始。

    完整的源代码可以在这里找到。

    Part 1 - MVC MVC认证与授权

    在第一部分中我们将创建一个简单的MVC应用程序并添加认证通过identityserver它。然后,我们将有一个更仔细的看claims,claims的变化和授权.

    创建一个 web application

    在Visual Studio 2013中,创建一个标准的MVC应用程序和设置认证,“没有认证”。

    你可以在属性窗口启用SSL

     

    注意:不要忘记更新你的项目属性中的url

    添加 IdentityServer 引用

    IdentityServer基于OWIN/Katana作为NuGet包。要将其添加到新创建的应用程序上,安装以下2个包:

    install-package Microsoft.Owin.Host.Systemweb
    install-package Thinktecture.IdentityServer3

    IdentityServer配置——客户端

    IdentityServer需要一些关于客户端信息,这可以简单地提供使用客户端对象:

    public static class Clients
    {
        public static IEnumerable<Client> Get()
        {
            return new[]
            {
                new Client 
                {
                    Enabled = true,
                    ClientName = "MVC Client",
                    ClientId = "mvc",
                    Flow = Flows.Implicit,
    
                    RedirectUris = new List<string>
                    {
                        "https://localhost:44319/"
                    }
                }
            };
        }
    }

    IdentityServer配置——用户

    下一步我们将添加一些IdentityServer用户-这里通过提供一个简单的C#类完成,当然你可以从任何数据存储加载用户。我们提供了ASP.NET Identity 和MembershipReboot支持检索用户信息。

    public static class Users
    {
        public static List<InMemoryUser> Get()
        {
            return new List<InMemoryUser>
            {
                new InMemoryUser
                {
                    Username = "bob",
                    Password = "secret",
                    Subject = "1",
    
                    Claims = new[]
                    {
                        new Claim(Constants.ClaimTypes.GivenName, "Bob"),
                        new Claim(Constants.ClaimTypes.FamilyName, "Smith")
                    }
                }
            };
        }
    }

    添加 Startup.cs

    配置启动类。在这里,我们提供有关客户信息的用户,范围,签名证书和其他一些配置选项。生产要从Windows证书存储区或其他固定源负载签名证书。这里我们简单地添加到项目文件(你可以下载一个测试证书的地方。它添加该项目并将其属性【复制到输出目录】更改为始终复制。

    public class Startup
    {
        public void Configuration(IAppBuilder app)
        {
            app.Map("/identity", idsrvApp =>
                {
                    idsrvApp.UseIdentityServer(new IdentityServerOptions
                    {
                        SiteName = "Embedded IdentityServer",
                        SigningCertificate = LoadCertificate(),
    
                        Factory = InMemoryFactory.Create(
                            users  : Users.Get(),
                            clients: Clients.Get(),
                            scopes : StandardScopes.All)
                    });
                });
        }
    
        X509Certificate2 LoadCertificate()
        {
            return new X509Certificate2(
                string.Format(@"{0}\bin\identityServer\idsrv3test.pfx", AppDomain.CurrentDomain.BaseDirectory), "idsrv3test");
        }
    }

     在浏览器中输入以下地址以检查配置https://localhost:44319/identity/.well-known/openid-configuration

     

    注意:

    最后一件事,在配置文件中添加下面的代码,否则我们的一些嵌入式资产将不能正确使用IIS加载

    <system.webServer>
      <modules runAllManagedModulesForAllRequests="true" />
    </system.webServer>

     

    添加和配置OpenID Connect 中间件

    增加OIDC 认证的MVC应用程序中,我们需要添加两包:

    install-package Microsoft.Owin.Security.Cookies
    install-package Microsoft.Owin.Security.OpenIdConnect

    在startup.cs中配置默认认证类型为cookie

    app.UseCookieAuthentication(new CookieAuthenticationOptions
        {
            AuthenticationType = "Cookies"
        });

     

    使用嵌入的OpenID Connect Server

    app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
        {
            Authority = "https://localhost:44319/identity",
            ClientId = "mvc",
            RedirectUri = "https://localhost:44319/",
            ResponseType = "id_token",
    
            SignInAsAuthenticationType = "Cookies"
        });

    添加一个受保护的资源和Claims

    一个受保护的资源:

    [Authorize]
    public ActionResult About()
    {
        return View((User as ClaimsPrincipal).Claims);
    }

    相应的视图看起来像这样:

    @model IEnumerable<System.Security.Claims.Claim>
    
    <dl>
        @foreach (var claim in Model)
        {
            <dt>@claim.Type</dt>
            <dd>@claim.Value</dd>
        }
    </dl>

    Authentication and claims

    点击About链接将触发认证。identityserver将显示登录页面

    登录成功后可以看到登录信息:

     

    增加Role Claim 和 Scope

    在下一步中,我们要向我们的用户添加一些角色声明,我们将在以后使用它来进行授权。

    现在我们有了OIDC 标准scope-定义一个角色的scope包括claims,和一些标准属性:

    public static class Scopes
    {
        public static IEnumerable<Scope> Get()
        {
            var scopes = new List<Scope>
            {
                new Scope
                {
                    Enabled = true,
                    Name = "roles",
                    Type = ScopeType.Identity,
                    Claims = new List<ScopeClaim>
                    {
                        new ScopeClaim("role")
                    }
                }
            };
    
            scopes.AddRange(StandardScopes.All);
    
            return scopes;
        }
    }

     

    改变在Startup.cs的factory类使用定义的scope

    Factory = new IdentityServerServiceFactory()
        .UseInMemoryUsers(Users.Get())
        .UseInMemoryClients(Clients.Get())
        .UseInMemoryScopes(Scopes.Get()),

    下一步我们为bob添加几个Claim

    public static class Users
    {
        public static IEnumerable<InMemoryUser> Get()
        {
            return new[]
            {
                new InMemoryUser
                {
                    Username = "bob",
                    Password = "secret",
                    Subject = "1",
    
                    Claims = new[]
                    {
                        new Claim(Constants.ClaimTypes.GivenName, "Bob"),
                        new Claim(Constants.ClaimTypes.FamilyName, "Smith"),
                        new Claim(Constants.ClaimTypes.Role, "Geek"),
                        new Claim(Constants.ClaimTypes.Role, "Foo")
                    }
                }
            };
        }
    }

     

    改变中间件配置:

    app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
        {
            Authority = "https://localhost:44319/identity",
                        
            ClientId = "mvc",
            Scope = "openid profile roles",
            RedirectUri = "https://localhost:44319/",
            ResponseType = "id_token",
    
            SignInAsAuthenticationType = "Cookies"
        });

     

    成功验证后,您现在应该看到用户Claim集合中的角色Claim

    Claims 转换

    默认情况下那些Claims看起像这样:

    通过配置可以控制哪些claim需要被记录:

    app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
        {
            Authority = "https://localhost:44319/identity",
                        
            ClientId = "mvc",
            Scope = "openid profile roles",
            RedirectUri = "https://localhost:44319/",
            ResponseType = "id_token",
    
            SignInAsAuthenticationType = "Cookies",
            UseTokenLifetime = false,
    
            Notifications = new OpenIdConnectAuthenticationNotifications
            {
                SecurityTokenValidated = async n =>
                    {
                        var id = n.AuthenticationTicket.Identity;
    
                        // we want to keep first name, last name, subject and roles
                        var givenName = id.FindFirst(Constants.ClaimTypes.GivenName);
                        var familyName = id.FindFirst(Constants.ClaimTypes.FamilyName);
                        var sub = id.FindFirst(Constants.ClaimTypes.Subject);
                        var roles = id.FindAll(Constants.ClaimTypes.Role);
    
                        // create new identity and set name and role claim type
                        var nid = new ClaimsIdentity(
                            id.AuthenticationType,
                            Constants.ClaimTypes.GivenName,
                            Constants.ClaimTypes.Role);
    
                        nid.AddClaim(givenName);
                        nid.AddClaim(familyName);
                        nid.AddClaim(sub);
                        nid.AddClaims(roles);
    
                        // add some other app specific claim
                        nid.AddClaim(new Claim("app_specific", "some data"));                   
    
                        n.AuthenticationTicket = new AuthenticationTicket(
                            nid,
                            n.AuthenticationTicket.Properties);
                    }
            }
        });

    在添加上述代码后,我们的Claims现在看起来像这样:

    Authorization

    现在,我们有身份验证和一些声明,我们可以开始添加简单的授权规则。

    MVC有一个内置的属性称为[Authorize]身份验证的用户,您还可以使用此属性来诠释角色成员资格要求。我们不建议这种方法,因为这通常会导致代码,混合的关注,如业务/控制器逻辑和授权政策。我们建议将授权逻辑从控制器中分离,从而导致更清洁的代码和更好的可测性(在  here 阅读更多)。

    Resource Authorization

    要添加新的授权基础设施和新的属性,我们添加NuGet包:

    install-package Thinktecture.IdentityModel.Owin.ResourceAuthorization.Mvc

     

    [ResourceAuthorize("Read", "ContactDetails")]
    public ActionResult Contact()
    {
        ViewBag.Message = "Your contact page.";
    
        return View();
    }

     

    请注意,属性是不表达权限,我们单独的逻辑去控制权限:

    public class AuthorizationManager : ResourceAuthorizationManager
    {
        public override Task<bool> CheckAccessAsync(ResourceAuthorizationContext context)
        {
            switch (context.Resource.First().Value)
            {
                case "ContactDetails":
                    return AuthorizeContactDetails(context);
                default:
                    return Nok();
            }
        }
    
        private Task<bool> AuthorizeContactDetails(ResourceAuthorizationContext context)
        {
            switch (context.Action.First().Value)
            {
                case "Read":
                    return Eval(context.Principal.HasClaim("role", "Geek"));
                case "Write":
                    return Eval(context.Principal.HasClaim("role", "Operator"));
                default:
                    return Nok();
            }
        }
    }

     

     最后在Startup.cs中添加配置:

    app.UseResourceAuthorization(new AuthorizationManager());

     

     运行示例,并通过代码来熟悉验证的流程。

    Role Authorization

     通过重写AuthorizeAttribute控制返回的结果

    // Customized authorization attribute:
    public class AuthAttribute : AuthorizeAttribute
    {
        protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
        {
            if (filterContext.HttpContext.User.Identity.IsAuthenticated)
            {
                // 403 we know who you are, but you haven't been granted access
                filterContext.Result = new HttpStatusCodeResult(System.Net.HttpStatusCode.Forbidden);
            }
            else
            {
                // 401 who are you? go login and then try again
                filterContext.Result = new HttpUnauthorizedResult();
            }
        }
    }
    
    // Usage:
    [Auth(Roles = "Geek")]
    public ActionResult About()
    {
        // ...
    }

    其他的授权和处理访问被拒绝的情况

    通过在HomeController中添加一个新的Action来进行更多的授权:

    [ResourceAuthorize("Write", "ContactDetails")]
    public ActionResult UpdateContact()
    {
        ViewBag.Message = "Update your contact details!";
    
        return View();
    }

     

     当你试图访问这个地址的时候,你会看到一个被禁止的错误页面。

     事实上,如果用户已经通过认证,你会看到不同的响应。如果不是MVC将重定向到登录页面,如果通过验证,您会看到禁止响应。这是由设计(阅读更多  here)。

     你可以通过检查403个状态码来处理这个被禁止的情况,我们提供了一个这样的过滤框:

    [ResourceAuthorize("Write", "ContactDetails")]
    [HandleForbidden]
    public ActionResult UpdateContact()
    {
        ViewBag.Message = "Update your contact details!";
    
        return View();
    }

     

    添加HandleForbidden 后,看起是这样:

     

     你也可以使用授权管理命令检查权限,这样更灵活:

     

    [HandleForbidden]
    public ActionResult UpdateContact()
    {
        if (!HttpContext.CheckAccess("Write", "ContactDetails", "some more data"))
        {
            // either 401 or 403 based on authentication state
            return this.AccessDenied();
        }
    
        ViewBag.Message = "Update your contact details!";
        return View();
    }

     

    添加注销功能

    添加注销功能很简单,直接创建一个Action并且调用 Request.GetOwinContext().Authentication.SignOut()方法即可。

    public ActionResult Logout()
    {
        Request.GetOwinContext().Authentication.SignOut();
        return Redirect("/");
    }
    

    这个方法会通知identityserver endsession 节点,它将清除身份验证Cookie并终止您的会话:

    通常,现在最安全的事情是简单地关闭浏览器窗口,以清除所有的会话数据。

    有时候我们需要注销后以匿名的方式保持访问网站,这需要一些步骤,首先你需要登记注销手续后,返回一个有效的URL是完整的。这是在Client中定义的(注意PostLogoutRedirectUris的设置):

    new Client 
    {
        Enabled = true,
        ClientName = "MVC Client",
        ClientId = "mvc",
        Flow = Flows.Implicit,
    
        RedirectUris = new List<string>
        {
            "https://localhost:44319/"
        },
        PostLogoutRedirectUris = new List<string>
        {
            "https://localhost:44319/"
        }
    }

    下一步,客户已经证明身份注销端点来确保我们重定向到正确的URL(而不是一些垃圾邮件/钓鱼页面)。这是通过发送在身份验证过程中接收的客户端发送的初始标识令牌的。到目前为止,我们已经注销了这个令牌,现在是时候改变claims 转换逻辑来保存它。这是通过添加这行代码来完成我们的securitytokenvalidated通知:

    // keep the id_token for logout
    nid.AddClaim(new Claim("id_token", n.ProtocolMessage.IdToken));

    最后一步,我们将附加一个id_token用于和identityserver通信。这是通过使用中间件来做的:

    RedirectToIdentityProvider = n =>
        {
            if (n.ProtocolMessage.RequestType == OpenIdConnectRequestType.LogoutRequest)
            {
                var idTokenHint = n.OwinContext.Authentication.User.FindFirst("id_token");
    
                if (idTokenHint != null)
                {
                    n.ProtocolMessage.IdTokenHint = idTokenHint.Value;
                }
            }
    
            return Task.FromResult(0);
        }

    做好这些事情后,identityserver注销页面会给用户一个链接返回到调用应用程序:

    提示:在IdentityServerOptions 配置项中有个AuthenticationOptions配置项,你可以将他赋值为EnablePostSignOutAutoRedirect,登出后将自动重定向到客户端。

    添加Google账号登陆

    下一步我们要启用第三方身份验证。这是通过添加额外Owin认证中间件identityserver -在我们的例子将使用谷歌。

    首先需要在Google开发者控制台https://console.developers.google.com创建一个项目:

    下一步启用Google+ API

    下一步配置与电子邮件地址和产品名称

    下一步创建一个应用程序

    在创建客户端应用程序后,会得到一个 client id 和 client secret。把这两个值配置到Owin中间件中

    private void ConfigureIdentityProviders(IAppBuilder app, string signInAsType)
    {
        app.UseGoogleAuthentication(new GoogleOAuth2AuthenticationOptions
            {
                AuthenticationType = "Google",
                Caption = "Sign-in with Google",
                SignInAsAuthenticationType = signInAsType,
    
                ClientId = "...",
                ClientSecret = "..."
            });
    }
    

    下一步配置IdentityServer授权选项,使用上面的身份提供程序:

    idsrvApp.UseIdentityServer(new IdentityServerOptions
    {
        SiteName = "Embedded IdentityServer",
        SigningCertificate = LoadCertificate(),
    
        Factory = new IdentityServerServiceFactory()
            .UseInMemoryUsers(Users.Get())
            .UseInMemoryClients(Clients.Get())
            .UseInMemoryScopes(Scopes.Get()),
    
        AuthenticationOptions = new IdentityServer3.Core.Configuration.AuthenticationOptions
        {
            IdentityProviders = ConfigureIdentityProviders
        }
    });

    完成后在登录界面会有一个Google登录的按钮:

    注意:在使用谷歌账号登陆后,角色role claim 丢失了。这是有道理的,因为谷歌没有角色的概念,不是所有的身份提供程序将提供相同的claim 类型。

     

    哎...今天够累的,签到来了1...
    回复

    使用道具 举报

    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    QQ|手机版|小黑屋|Java自学者论坛 ( 声明:本站文章及资料整理自互联网,用于Java自学者交流学习使用,对资料版权不负任何法律责任,若有侵权请及时联系客服屏蔽删除 )

    GMT+8, 2024-12-22 16:15 , Processed in 0.077924 second(s), 29 queries .

    Powered by Discuz! X3.4

    Copyright © 2001-2021, Tencent Cloud.

    快速回复 返回顶部 返回列表