Azure AD多租户+身份验证过滤最佳实践

时间:2016-11-17 21:38:16

标签: azure active-directory authorization single-sign-on multi-tenant

我是C#和Azure AD的新手,所以请给我一些指导和资源,以了解如何最好地允许我的MVC应用程序的SSO和授权,以获得我认可的客户列表。我有一个小应用程序,我托管在Azure VM上,并使用OpenID Connect使单个租户Azure AD工作。我在Azure AD中创建了应用注册多租户。我为一些用户设置了一个新的组织帐户,以便在Azure AD中进行测试。我读了this guide,这很好,但在其示例

中停止记录
  

禁用颁发者验证以启用任何Azure AD租户登录

我找不到有关如何将租户定为我想要访问我的应用的组织的指南。即使两者都拥有Azure AD组织帐户,从域名CustomerCompany.com而不是SomeOtherCompany.com授权用户的最佳方式是什么?

我不想维护用户列表,因此Azure AD协作B2B内容不会填写账单(上传用户列表和发出邀请)。我希望客户维护自己的用户列表。

我想首先简单地允许来自我的客户的用户并拒绝所有其他用户。

  • 我是否有办法在Azure AD中创建我的组织与其他人之间的关系,并以某种方式允许/拒绝在AAD登录时使用这些关系对我的应用授权?
  • 是否存在来自Azure AD的tenantid值,我可以将其与SQL Server中保留的列表进行比较,以便在AAD登录后允许/拒绝授权?这通常是我必须从客户处获得的域名或某些GUID或其他价值吗?

如果客户希望将我的应用程序的使用限制在他们组织中的某些人,那么最佳做法是:

  • 让客户注册我的应用程序或以某种方式将其分配给他们在自己的Azure AD中设置的组,以便用户在管理员不允许的情况下不会进行身份验证? OR
  • 使用Graph API查询我服务器上的传入用户配置文件(组成员身份,部门,经理等),并根据该值允许/拒绝?

您可以在此处为初学者提供任何操作指南和最佳实践指南,我们将不胜感激。谢谢。

2 个答案:

答案 0 :(得分:1)

  

有没有办法可以在Azure AD中创建我的组织与其他人之间的关系,并在AAD登录时使用这些关系以某种方式允许/拒绝授权我的应用程序?

     

是否存在来自Azure AD的tenantid值,我可以将其与SQL Server中保留的列表进行比较,以便在AAD登录后允许/拒绝授权?这通常是我必须从客户处获得的域名或某些GUID或其他价值吗?

是。我们可以编写自定义代码来验证来自令牌的iss声明,以使其符合业务逻辑。以下是使用OpenIdConnect OWIN组件的代码供您参考:

 app.UseOpenIdConnectAuthentication(
            new OpenIdConnectAuthenticationOptions
            {
                ClientId = ClientId,
                Authority = Authority,
                TokenValidationParameters = new System.IdentityModel.Tokens.TokenValidationParameters
                {
                    // instead of using the default validation (validating against a single issuer value, as we do in line of business apps), 
                    // we inject our own multitenant validation logic
                    ValidateIssuer = false,
                },
                Notifications = new OpenIdConnectAuthenticationNotifications()
                {

                    // we use this notification for injecting our custom logic
                    SecurityTokenValidated = (context) =>
                    {
                        // retriever caller data from the incoming principal
                        string issuer = context.AuthenticationTicket.Identity.FindFirst("iss").Value;
                        string UPN = context.AuthenticationTicket.Identity.FindFirst(ClaimTypes.Name).Value;
                        string tenantID = context.AuthenticationTicket.Identity.FindFirst("http://schemas.microsoft.com/identity/claims/tenantid").Value;

                        if (
                            // the caller comes from an admin-consented, recorded issuer
                            (db.Tenants.FirstOrDefault(a => ((a.IssValue == issuer) && (a.AdminConsented))) == null)
                            // the caller is recorded in the db of users who went through the individual onboardoing
                            && (db.Users.FirstOrDefault(b =>((b.UPN == UPN) && (b.TenantID == tenantID))) == null)
                            )
                            // the caller was neither from a trusted issuer or a registered user - throw to block the authentication flow
                            throw new SecurityTokenValidationException();                            
                        return Task.FromResult(0);
                    },
                    AuthenticationFailed = (context) =>
                    {
                        context.OwinContext.Response.Redirect("/Home/Error?message=" + context.Exception.Message);
                        context.HandleResponse(); // Suppress the exception
                        return Task.FromResult(0);
                    }
                }
            });

here是一个有用的代码示例供您参考。

  

让客户注册我的应用程序或以某种方式将其分配给他们在自己的Azure AD中设置的组,以便用户在管理员不允许的情况下不会进行身份验证? OR

     

使用Graph API查询我服务器上的传入用户配置文件(组成员身份,部门,经理等),并根据该值允许/拒绝?

根据我的理解,它应该让自定义公司管理有权访问您的应用程序的用户,因为用户管理负责合作伙伴的公司。因此,在合作伙伴的公司决定启用/禁用用户之后,您的公司和应用程序不需要制作和额外的工作或更改。

要管理可以访问该应用程序的用户,合作伙伴的公司可以使用Requiring User Assignment功能。

答案 1 :(得分:0)

谢谢。我也在回答我自己的问题,为我的进展添加更多细节(希望它能帮助一些读者),并提出更详细的问题来了解它。

我现在可以将传入的经过身份验证的用户的图表数据与我保留的客户公司名称列表进行比较。但这并不理想。它取决于获取客户租户的DisplayName的图表api,它应与我的列表匹配。我担心这可能会改变,然后不允许合法的客户。我更喜欢使用Azure AD中的租户ID。但我不知道如何在注册时立即获得。我可以在第一个用户从该租户进行身份验证后获得该信息,但如何在该公司的任何人进行身份验证之前获取该客户的租户ID?我必须要求它,还是我自己可以得到它?

有没有更好的方法来过滤我的网络应用程序(IIS)的访问权限,只允许我的客户?这是我的详细问答。

请帮助初学者了解大男孩如何仅将他们的客户授权给IIS网络应用。这是我如何做的开始(在客户过滤部分中使用伪代码作弊,因为我想比较像TenantID(Issuer?)这样的GUID而不是像DisplayName这样的字符串):

public partial class Startup
{
    private static string clientId = ConfigurationManager.AppSettings["ida:ClientId"];
    private static string appKey = ConfigurationManager.AppSettings["ida:ClientSecret"];
    private static string aadInstance = ConfigurationManager.AppSettings["ida:AADInstance"];

    private static string postLogoutRedirectUri = ConfigurationManager.AppSettings["ida:PostLogoutRedirectUri"];
    public static string custTenantID = "";

    //for multi tenant sso, can be 'common' for consumer, 'organization' for work+school, or tenantID for single company
    public static readonly string Authority = aadInstance + "common/";

    //this is my model for the customer db
    public CustomerTenant tenant = new CustomerTenant();

    // This is the resource ID of the AAD Graph API.  We'll need this to request a token to call the Graph API.
    string graphResourceId = "https://graph.windows.net";


    public void ConfigureAuth(IAppBuilder app)
    {       
        ApplicationDbContext db = new ApplicationDbContext();

        app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);

        app.UseCookieAuthentication(new CookieAuthenticationOptions());

        app.UseOpenIdConnectAuthentication(
            new OpenIdConnectAuthenticationOptions
            {
                ClientId = clientId,
                Authority = Authority,
                PostLogoutRedirectUri = postLogoutRedirectUri,


                //for multi-tenant
                TokenValidationParameters = new System.IdentityModel.Tokens.TokenValidationParameters
                {
                    // instead of using the default validation (validating against a single issuer value, as we do in line of business apps)
                    // we inject our own multitenant validation logic
                    ValidateIssuer = false,
                },

                Notifications = new OpenIdConnectAuthenticationNotifications()
                {
                   // If there is a code in the OpenID Connect response, redeem it for an access token and refresh token, and store those away.

//AuthorizationCodeReceived is invoked after SecurityTokenValidated
//using this Notification to inject logic to authorize only my customers
                   AuthorizationCodeReceived = (context) =>
                   {
                       var code = context.Code;
                       ClientCredential credential = new ClientCredential(clientId, appKey);
                       string signedInUserID = context.AuthenticationTicket.Identity.FindFirst(ClaimTypes.NameIdentifier).Value;
                         Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext authContext = new Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext(Authority, new ADALTokenCache(signedInUserID));

                       AuthenticationResult result = authContext.AcquireTokenByAuthorizationCode(
                        code, new Uri(HttpContext.Current.Request.Url.GetLeftPart(UriPartial.Path)), credential, graphResourceId);

//look up the customer tenant's DisplayName to compare to my list
                       Uri servicePointUri = new Uri(graphResourceId);
                       Uri serviceRoot = new Uri(servicePointUri, custTenantID);
                       ActiveDirectoryClient client = new ActiveDirectoryClient(serviceRoot, async () => { return await Task.FromResult(result.AccessToken); });
                       string tenantName = client.TenantDetails.ExecuteAsync().Result.CurrentPage.First().DisplayName;

//pseudo-code:  compare this tenantName to db.CustomerTenants.CustomerName and if there is a match then authorize else do not authorize

                       return Task.FromResult(0);

                   },


                    //for multi-tenant sso
                    RedirectToIdentityProvider = (context) =>
                    {
                        string appBaseUrl = context.Request.Scheme + "://" + context.Request.Host + context.Request.PathBase;
                        context.ProtocolMessage.RedirectUri = appBaseUrl;

                        context.ProtocolMessage.PostLogoutRedirectUri = postLogoutRedirectUri;
                        return Task.FromResult(0);
                    },


                    SecurityTokenValidated = (context) =>
                    {
                        // retrieve caller data from the incoming principal
                        string issuer = context.AuthenticationTicket.Identity.FindFirst("iss").Value;
                        string UPN = context.AuthenticationTicket.Identity.FindFirst(ClaimTypes.Name).Value;
                        custTenantID = context.AuthenticationTicket.Identity.FindFirst("http://schemas.microsoft.com/identity/claims/tenantid").Value;


                        return Task.FromResult(0);
                    },

                    AuthenticationFailed = (context) =>
                    {

                   context.OwinContext.Response.Redirect("urlToMyErrorPage?message=" + context.Exception.Message);
                        context.HandleResponse(); // Suppress the exception
                        return Task.FromResult(0);
                    }
            }
        });

    }