SharePoint 2013: 关于配置Claims Authentication的一切

网上关于如何在SharePoint中配置Claims based authentication的文章非常多,在SharePoint 2010时代,我自己也写过几篇。不过,这些文章大部分都只集中于一个特定的主题,比如怎么配置ADFS和SharePoint之间的Trust,怎么部署自己开发的Claim Provider等等,很少有文章就如何完整地部署一个Claim AuthN的环境,而不只是配置Web Application,还有怎么配置User Profile, My Site等等,给出一个完整的说明。我的上一个项目正好是关于Claim AuthN的,这篇文章算是对项目的一个总结。

配置Identity Provider

使用Claims based authentication的第一步,当然是配置SharePoint和Identity Provider之间的信任关系了。网上这样的文档非常多。如果是ADFS,可以参考官方文档,照着来基本错不了。其他的Identity Provider,配置起来也是大同小异,没什么可说的。我自己以前曾经写过一篇如何配置自己开发的Identity Provider的文章,也可以参考。多个Web Application是可以共用一个Trusted Identity Provider设置的,只要配置好相应的realm就可以了。为Trusted Token Issuer添加新的realm,可以用下面的PowerShell代码:

$uri = New-Object System.Uri("http://intranet.contoso.com")
$ap = Get-SPTrustedIdentityTokenIssuer
$ap.ProviderRealms.Add($uri, "urn:SharePoint:Intranet")
$ap.Update()

配置Claim Provider

由于默认的Claim Provider其实什么都不做,一旦启用了Claims AuthN,用户的权限管理就会有麻烦。一般启用了Claims AuthN之后,都需要部署定制的Claim Provider,来做用户搜索或名字解析。关于Claim Provider的开发和部署也没有太多好说的,网上的资料很多,Codeplex上也有相应的开源项目。在Claim Provider部署成功之后,可以用下面的PowerShell命令,来关联Trusted Identity Provider和Claim Provider。

$sts = Get-SPTrustedIdentityTokenIssuer
$sts.ClaimProviderName = "ClaimProviderInternalName"
$sts.Update()

配置User Profile

配置好Web Application和Claim Provider之后,基本上Claims based authentication就可以用了。但是,如果想使用User Profile,例如想显示用户的display name,支持People Search等,就还需要配置User Profile Synchronization。

配置User Profile Synchronization Connection的时候,Athentication Provider Type需要选择Trusted Claims Provider Authentication以及相应的Trusted Identity Provider。User Profile里的Claim User Identifier属性需要map到作为Identifier的Claim上,比如Trusted Claims Provider配置为使用email地址作为Identifier,那么在User Profile里,Claim User Identifier就需要map到mail这个AD属性上。参考下面两幅图:

另外,如果想在My Site URL里避免名字冲突,或者不想将用户的Windows Logon Name显示在My Site URL里的话,可以改变User name这个属性的mapping。例如,如果想在My Site URL里使用email地址的话,可以将User name map到AD的mail属性,参考下图。

完成了上面的设置之后,执行一次Full Sync,用户的User Profile就会得到更新了。

最后

完成了上面所有的配置之后,一个完整的,基于Claims based authentication的SharePoint环境基本上就完成了。如果想要在People Picker中隐藏默认的Windows Authentication的选项,可以执行一个简单的PowerShell命令,有详细的官方文档介绍,这里就不赘述了。

为SharePoint 2010中的FBA创建自定义登录页面

本文英文版在这里

SharePoint 2010中默认的FBA登录页面非常简单,只提供了一个Asp.Net的Login控件,让用户输入用户名和密码。在大多数情况下,我们需要定制这个页面以满足一些安全需求,比如为登录页面加上验证码等等。

由于SharePoint 2010里的FBA已经变成了基于Claims认证的方式,因此在实现自定义的登录页面时就与MOSS 2007里的做法完全不同了。最显著的一点就是,像Steve Peschka说的,以前的FormsAuthentication类不会再被用到了。现在我们需要用SharePoint的STS来做认证和处理Claims。好在SharePoint提供了相应的借口,让这一切变得容易了许多。这这篇文章里,我将演示如何创建自定义登录页面,并保持默认的master page和样式。

由于要修改默认页面,我不知道这样的做法是否受Microsoft官方支持,如果你要用在你的项目中,风险自负。Smile

创建自定义登录页面

如果我们留意一下会发现,默认的FBA登录页面是_forms/default.aspx。这样的设计有一个好处,就是如果我们修改了default.aspx,它不会影响别的Web app。下面我就在一个default.aspx页面上加了一个验证码功能。

 <asp:login id="loginControl"
   
FailureText="<%$Resources:wss,login_pageFailureText%>"
   
runat="server" width="100%" OnLoggingIn="signInControl_LoggingIn"
   
OnAuthenticate="signInControl_Authenticate"> <layouttemplate>
        <
asp:label id="FailureText" class="ms-error" runat="server"/>
        <
table width="100%">
        <
tr>
            <
td nowrap="nowrap">
                <
SharePoint:EncodedLiteral runat="server"
                   
text="<%$Resources:wss,login_pageUserName%>"
                   
EncodeMethod=’HtmlEncode’/>
            </
td>
            <
td width="100%">
                <
asp:textbox id="UserName"
                   
autocomplete="off"
                   
runat="server"
                   
class="ms-inputuserfield" width="99%" />
            </
td>
        </
tr>
        <
tr>
            <
td nowrap="nowrap">
                <
SharePoint:EncodedLiteral runat="server"
                   
text="<%$Resources:wss,login_pagePassword%>"
                   
EncodeMethod=’HtmlEncode’/>
            </
td>
            <
td width="100%">
                <
asp:textbox id="password" TextMode="Password"
                   
autocomplete="off" runat="server"
                   
class="ms-inputuserfield" width="99%"/>
            </
td>
        </
tr>
        <
tr>
            <
td nowrap
="nowrap">
                <
SharePoint:EncodedLiteral runat
="server"
                   
text="Secure Code:" EncodeMethod
=’HtmlEncode’/>
            </
td
>
            <
td width
="100%">
                <
asp:textbox id="secureCode" autocomplete
="off"
                   
runat="server" class="ms-inputuserfield" Width
="85%" />
                <
SharePoint:EncodedLiteral ID
="secureCodeLit"
                   
runat="server" Text="1234" EncodeMethod
="HtmlEncode" />
            </
td
>
        </
tr
>
        <
tr>
            <
td colspan="2" align="right">
                <
asp:button id="login" commandname="Login"
                   
text="<%$Resources:wss,login_pagetitle%>" runat="server" />
            </
td>
        </
tr>
        <
tr>
            <
td colspan="2">
                <
asp:checkbox id="RememberMe"
                   
text="<%$SPHtmlEncodedResources:wss,login_pageRememberMe%>"
                   
runat="server" />
            </
td>
        </
tr>
        </
table>
    </
layouttemplate>
</
asp:login>

我的想法是,当用户登录的时候,只有输入了正确的用户名密码和验证码,这里是1234,之后才能成功登录。页面运行的效果如下:

当然,此时验证码还没有作用,我们必须写一些代码来实现验证的功能。

创建Code Behind类实现验证和登录功能

接下来是为default.aspx实现一个类来实现验证和登录功能。在项目中添加一个类,可以命名为FormsSignInPage。接着是添加一些引用,首先是Microsoft.SharePoint.dll。由于我们要处理Claims,System.IdentityModel.dll和Microsoft.IdentityModel.dll也是必须的。另外,SharePoint有一个自己的处理Claims的Module,Microsoft.SharePoint.IdentityModel.dll。添加对它的引用时,需要定位到它所在的目录。引用添加完,看起来像下面这样。

我们的类,FormsSignInPage,当然可以从Asp.Net的Page类派生,但是如果我们看一下默认的登录页面,它是派生自IdentityModelSignInPageBase。这个类在Microsoft.SharePoint.IdentityModel.Pages名字空间下,它提供了一些属性和方法,在处理登录时很方便。所以,我决定我的FormsSignInPage也从这个类派生。

当用户点击页面上的登录按钮时,有两个Login控件的事件需要处理,一个是LoggingIn,在这个事件中,我们可以处理验证码。另一个是Authenticate,在这个事件中可以实现真正的登录。

protected void signInControl_LoggingIn(objectsender, LoginCancelEventArgs e)
{
    LoginControl login = sender asLoginControl;
    login.UserName = login.UserName.Trim();
    if(string.IsNullOrEmpty(login.UserName))
    {
        ClaimsFormsPageMessage.Text = "The server could not sign you in. The user name cannot be empty.";
        e.Cancel = true;
    }
    if(string.IsNullOrEmpty(secureCode.Text) ||
        !string.Equals(secureCode.Text.ToLower(), secureCodeLit.Text.ToLower()))
    {
        ClaimsFormsPageMessage.Text = "The server could not sign you in. Please input correct secure code.";
        e.Cancel = true;
    }
}

private void EstablishSessionWithToken(SecurityToken securityToken)
{
    if (null == securityToken)
    {
        throw new ArgumentNullException("securityToken");
    }
    SPFederationAuthenticationModule fam = SPFederationAuthenticationModule.Current;
    if (null == fam)
    {
        throw new ArgumentException(null, "FederationAuthenticationModule");
    }

    fam.SetPrincipalAndWriteSessionToken(securityToken);
}

protected void signInControl_Authenticate(object sender, AuthenticateEventArgs e)
{
    SecurityToken token = null;
    LoginControl formsLoginControl = sender as LoginControl;

    if (null != (token = GetSecurityToken(formsLoginControl)))
    {
        EstablishSessionWithToken(token);
        e.Authenticated = true;
        base.RedirectToSuccessUrl();
    }
}

private SPIisSettings IisSettings
{
    get
   
{
               
        SPWebApplication webApp = SPWebApplication.Lookup(new Uri(SPContext.Current.Web.Url));

SPIisSettings settings = webApp.IisSettings[SPUrlZone.Default];
        return settings;
    }
}

private SecurityToken GetSecurityToken(LoginControl formsLoginControl)
{
    SecurityToken token = null;
    SPIisSettings iisSettings = IisSettings;
    Uri appliesTo = base.AppliesTo;

    if (string.IsNullOrEmpty(formsLoginControl.UserName) ||
        string.IsNullOrEmpty(formsLoginControl.Password))
        return null;

    SPFormsAuthenticationProvider authProvider = iisSettings.FormsClaimsAuthenticationProvider;
    token = SPSecurityContext.SecurityTokenForFormsAuthentication(
        appliesTo,
        authProvider.MembershipProvider,
        authProvider.RoleProvider,
        formsLoginControl.UserName,
        formsLoginControl.Password);

    return token;
}

代码的核心部分是执行登录的部分。SharePoint提供了SecurityTokenForFormsAuthentication专门供开发者处理Forms验证。我使用了SPIisSettings来取得当前Web App所使用的membership provider和roleship provider。

SPFormsAuthenticationProvider authProvider = iisSettings.FormsClaimsAuthenticationProvider;
token = SPSecurityContext.SecurityTokenForFormsAuthentication(
    appliesTo,
    authProvider.MembershipProvider,
    authProvider.RoleProvider,
    formsLoginControl.UserName,
    formsLoginControl.Password);

关联Default.aspx和FormsSignInPage

这部分比较简单,只要修改<%@ Page %>使它继承我们的FormsSignInPage就好了。

<%@PageLanguage="C#"AutoEventWireup="true"
  
Inherits="Morpheus.Demo.Pages.FormsSignInPage,FormsSignInPage, Version=1.0.0.0, Culture=neutral, PublicKeyToken=72d2bbe72853b8eb"
  
MasterPageFile="~/_layouts/simple.master"%>

然后我们就可以将我们的Assembly部署到GAC中,同时将default.aspx拷贝到_forms目录里。当执行登录时,如果用户没有输入正确的验证码,下面的错误会显示出来。

这个登录页面的代码可以从这里下载

配置SharePoint 2010和ADFS 2.0

我曾经为了向客户演示SharePoint 2010中的Claims-based Authentication是怎么回事,专门写了一个简单的IP-STS,花了许多功夫配置SharePoint和这个IP-STS,并写了两篇文章。我最初是想用ADFS 2.0的,可是看了它的文档被吓着了,里面概念和名词一大堆,我怕真的用ADFS 2.0配合SharePoint作演示的话,光它的相关概念就得解释半天了。

不过在生产环境中,如果需要做联合认证(federated authentication),典型的比如在两个没有信任关系的域之间作认证,很少有用户会自己开发IP-STS,大部分用户会选择使用现成的产品,当然,ADFS 2.0是无法忽略的首选产品。最近又有客户让我演示配置ADFS 2.0和SharePoint 2010,于是花了些时间在Lab环境里配置了一下,发向也没有想象的复杂。现在在我的Lab环境里,有这样一个Web App,我同时在它上面启用了4种认证方式,Windows认证,Forms认证,基于ADFS 2.0的STS,和自定义的STS。

image

总结一下配置ADFS 2.0和SharePoint的步骤:

  1. 在Account Partner域中安装并配置ADFS 2.0。它的联合认证服务需要一个FQDN和相应的证书。
  2. 在ADFS 2.0中设置RP,这个RP就是Resource Partner域中的SharePoint网站。
  3. 设置Claims Rule,选择要发给SharePoint的claims。
  4. 最后在SharePoint设置信任这个ADFS 2.0,并在相应的Web App上启用这个STS。

详细的步骤,可以参考Steve Peschka的这篇文章,图文并茂,非常详细。

Forms-based Authentication on a Claim-based Web App

Claim

在SharePoint 2010中创建Web App时,虽然我们可以选择用Classic mode还是Claim mode认证,但在Classic mode下只能使用Windows Authentication,如果要使用其它的认证方式,比如FBA,就必须选择Claim mode (虽然在Beta 2中,仍能在Classic mode下配置FBA,RTM中将不允许这样做)。

在Claim mode下,无论是用Windows Authentication,FBA,还是基于其它trusted identity providers的认证方式,identity最终都会被转换成Claim Identity来进行认证。这就使得我们不需要使用多个Authentication Provider来让一个Web App使用不同的认证方式,一个Authentication Provider就够了。

由于Claim mode的引入,在SharePoint 2010中配置FBA的步骤与MOSS 2007的有一些不同,简单总结在下面:

  1. 第一步当然是启用Claim mode。如果是新建Web App,在UI上直接选就可以。对已有的Web App,应该是没有UI可以修改(或者是我没找到),但下面的PowerShell脚本可以实现Classic和Claim的转换:$w = Get-SPWebApplication “http://<Url>/”$w.UseClaimsAuthentication = True;

    $w.Update()

    $w.ProvisionGlobally()

  2. 在Authentication Provider的设置中选择同时启用Windows Authentication和Forms Authentication,并指定相应的Membership和Role provider的名字。
  3. 修改该Web App的web.config,加入要使用的Membership和Role provider。注意不要修改defaultProvider设置,Claim的provider是默认的Provider。
  4. 修改好之后,通过Windows Authentication登录网站,添加一个FBA用户,看看用户名是否能被解析,如果不能,多半是provider的设置有问题。
  5. 这时虽然FBA用户名能够被解析,但如果使用Forms Authentication登录的话,并不会成功。这时因为SharePoint使用SecurityTokenService来将各种认证的identity转换为Claim identity。我们必须让这个Service知道如何验证FBA用户。因此,下一步很重要。
  6. SecurityTokenService有一个Service Application与SharePoint交互,修改这个Service App的Web.config,在其中加入我们的Membership和Role provider。Provider所使用的名字和设置应该与我们的Web App中的相同。SecurityTokenServiceApplication的文件目录可以通过IIS找到。
  7. 完成上面的步骤之后,应该就可以用Forms Authentication登录了。如果需要在Central Admin中解析FBA用户,则需要在Central Admin的Web.config中添加相应的Providers。

参考:http://technet.microsoft.com/en-us/library/ee806882(office.14).aspx