asp.net-mvc – 如何在ASP.NET MVC4中使用具有唯一标识符URL的OpenID提供程序

前端之家收集整理的这篇文章主要介绍了asp.net-mvc – 如何在ASP.NET MVC4中使用具有唯一标识符URL的OpenID提供程序前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。
在ASP.NET MVC4中实现的新SimpleMembershipProvider允许对两个流行的OpenID提供商(Google和Yahoo)和三个OAuth提供商(Microsoft,Facebook,Twitter)进行简单的内置支持.

DotNetOpenAuth.AspNet.Clients中实现的提供者与SimpleMembershipProvider一起使用的提供商都使用静态URL进行身份服务,即所有用户使用相同的知名URL来访问提供商.用户的OpenID标识符与用于访问身份服务的URL分开.

例如,所有用户的Google OpenID服务网址均为https://www.google.com/accounts/o8/id.

这适用于MVC4中的SimpleMembershipProvider,在您的MVC应用程序启动时,身份提供者的URL需要被知道,不变和注册.

问题是,其他OpenID提供者通常使用用户唯一的OpenID标识符作为访问身份服务的URL.

例如,AOL和wordpress分别使用https://openid.aol.com/{username}和https:// {username} .wordpress.com.

如果您将SimpleMembershipProvider替换为您自己的ExtendedMembershipProvider实现,那么您可以滚动自己的提供程序实现,但是它不会与MVC4帐户控制器开箱即用.

当提供者使用URL中的用户名唯一标识符时,如何使用SimpleMembershipProvider实现新的OpenID依赖方?

解决方法

我开发了以下解决方案,适用于我,我正在分享,以帮助他人,但我真的很想看看是否有一个更直接的方法或“最佳实践”,我错过了.

基本上,您需要实现一个OpenIdClient,它使用一个包含关键字__username__的URL的ProviderIdentifier进行初始化.

在运行时,提供者名称用户名将传递给帐户控制器,在该控制器中,提供者客户端按名称进行选择,用户名替换为__username__关键字,然后认证请求将发送给提供商.

OpenID客户端

由Microsoft提供的DotNetOpenAuth OpenID提供程序类继承了基类DotNetOpenAuth.AspNet.Clients.OpenIdClient,它实现了OpenID提供程序类所需的IAuthenticationClient接口.从source for the Google provider开始,因为它具有直接的实现,自定义它使一个GenericOpenIdClient类与提供者使用自定义URL.

要在运行时创建自定义URL,我们将接受OpenID用户名作为URI片段,并将URL中的所有__username__实例替换为用户提交的用户名.提供商需要在应用程序启动期间注册URL,因此,当用户名已知时,我们不能在运行时注册提供程序URL.

我们将使用OpenID Selector将表单提交给我们的Account控制器的ExternalLogin操作,其提供者表单值设置为格式提供者{username}中的提供者名称用户名. OpenId Selector具有内置的逻辑,用于将{username}的所有实例替换为向用户呈现的文本框的输入.在服务器端,我们将从用户名中分离提供程序名称,按照应用程序启动时注册名称查找提供者,并将GenericOpenIdClient.UserName属性设置为用户名提交的用户名.

当创建认证请求以发送到OpenID提供程序时,我们将检查GenericOpenIdClient.UserName属性,如果设置,我们将在发送请求之前使用用户名重新创建提供程序URL.为了做到这一点,我们需要重写RequestAuthentication()方法来创建使用我们的自定义URL的身份验证请求. __username__而不是{username},因为{和}不是主机名的有效字符,因此当我们需要将其注册为通用提供者标识符时,创建包含它们的URL变得有问题.

/GenericOpenIdClient.cs

namespace DotNetOpenAuth.AspNet.Clients
{
    using System;
    using System.Collections.Generic;
    using System.Web;
    using System.Xml.Linq;
    using DotNetOpenAuth.OpenId;
    using DotNetOpenAuth.OpenId.Extensions.AttributeExchange;
    using DotNetOpenAuth.OpenId.RelyingParty;

    public class GenericOpenIdClient : OpenIdClient
    {
        #region Constants and Fields

        /// <summary>
        /// The openid relying party.
        /// </summary>
        /// <remarks>
        /// Pass null as applicationStore to specify dumb mode. Create a protected field to use internally; we can't access the private base class field.
        /// </remarks>
        protected static readonly OpenIdRelyingParty RelyingParty = new OpenIdRelyingParty(applicationStore: null);

        /// <summary>
        /// The provider identifier.
        /// </summary>
        /// <remarks>
        /// Create a protected field to use internally; we can't access the private base class field.
        /// </remarks>
        protected readonly Identifier providerIdentifier;

        #endregion

        #region Constructors and Destructors

        public GenericOpenIdClient(string providerName,Identifier providerIdentifier)
            : base(providerName,providerIdentifier) 
        {
            this.providerIdentifier = providerIdentifier; // initialize our internal field as well
        }

        #endregion

        #region Public Properties

        public String UserName { get; set; }

        #endregion

        #region Protected Properties

        /// <summary>
        /// The provider Identifier with the "__username__" keyword replaced with the value of the UserName property.
        /// </summary>
        protected Identifier ProviderIdentifier
        {
            get
            {
                var customIdentifier = String.IsNullOrWhiteSpace(this.UserName) ?
                    this.providerIdentifier :
                    Identifier.Parse(HttpUtility.UrlDecode(this.providerIdentifier).Replace("__username__",this.UserName));
                return customIdentifier;
            }
        }

        #endregion

        #region Methods

        /// <summary>
        /// Gets the extra data obtained from the response message when authentication is successful.
        /// </summary>
        /// <param name="response">
        /// The response message. 
        /// </param>
        /// <returns>A dictionary of profile data; or null if no data is available.</returns>
        protected override Dictionary<string,string> GetExtraData(IAuthenticationResponse response)
        {
            FetchResponse fetchResponse = response.GetExtension<FetchResponse>();
            if (fetchResponse != null)
            {
                var extraData = new Dictionary<string,string>();
                extraData.AddItemIfNotEmpty("email",fetchResponse.GetAttributeValue(WellKnownAttributes.Contact.Email));
                extraData.AddItemIfNotEmpty("country",fetchResponse.GetAttributeValue(WellKnownAttributes.Contact.HomeAddress.Country));
                extraData.AddItemIfNotEmpty("firstName",fetchResponse.GetAttributeValue(WellKnownAttributes.Name.First));
                extraData.AddItemIfNotEmpty("lastName",fetchResponse.GetAttributeValue(WellKnownAttributes.Name.Last));

                return extraData;
            }

            return null;
        }

        public override void RequestAuthentication(HttpContextBase context,Uri returnUrl)
        {
            var realm = new Realm(returnUrl.GetComponents(UriComponents.SchemeAndServer,UriFormat.Unescaped));
            IAuthenticationRequest request = RelyingParty.CreateRequest(ProviderIdentifier,realm,returnUrl);

            // give subclasses a chance to modify request message,e.g. add extension attributes,etc.
            this.OnBeforeSendingAuthenticationRequest(request);

            request.RedirectToProvider();
        }

        /// <summary>
        /// Called just before the authentication request is sent to service provider.
        /// </summary>
        /// <param name="request">
        /// The request. 
        /// </param>
        protected override void OnBeforeSendingAuthenticationRequest(IAuthenticationRequest request)
        {
            // Attribute Exchange extensions
            var fetchRequest = new FetchRequest();
            fetchRequest.Attributes.Addrequired(WellKnownAttributes.Contact.Email);
            fetchRequest.Attributes.AddOptional(WellKnownAttributes.Contact.HomeAddress.Country);
            fetchRequest.Attributes.Addrequired(WellKnownAttributes.Name.First);
            fetchRequest.Attributes.Addrequired(WellKnownAttributes.Name.Last);

            request.AddExtension(fetchRequest);
        }

        #endregion
    }

    /// <summary>
    /// The dictionary extensions.
    /// </summary>
    internal static class DictionaryExtensions
    {
        /// <summary>
        /// Adds the value from an XDocument with the specified element name if it's not empty.
        /// </summary>
        /// <param name="dictionary">
        /// The dictionary. 
        /// </param>
        /// <param name="document">
        /// The document. 
        /// </param>
        /// <param name="elementName">
        /// Name of the element. 
        /// </param>
        public static void AddDataIfNotEmpty(
            this Dictionary<string,string> dictionary,XDocument document,string elementName)
        {
            var element = document.Root.Element(elementName);
            if (element != null)
            {
                dictionary.AddItemIfNotEmpty(elementName,element.Value);
            }
        }

        /// <summary>
        /// Adds a key/value pair to the specified dictionary if the value is not null or empty.
        /// </summary>
        /// <param name="dictionary">
        /// The dictionary. 
        /// </param>
        /// <param name="key">
        /// The key. 
        /// </param>
        /// <param name="value">
        /// The value. 
        /// </param>
        public static void AddItemIfNotEmpty(this IDictionary<string,string key,string value)
        {
            if (key == null)
            {
                throw new ArgumentNullException("key");
            }

            if (!string.IsNullOrEmpty(value))
            {
                dictionary[key] = value;
            }
        }
    }
}

注册由微软提供的新的DotNetOpenAuth类中提供的提供程序,请取消注释现有的Microsoft,Twitter和Google提供商,并添加一个注册内置的Yahoo提供商的电话.我们即将实施的OpenID提供程序不需要键,但如果要使用它们,则需要从OAuth提供程序(Microsoft,Facebook和Twitter)获取密钥. OpenID Selector软件包中提供的其他提供程序可以添加到您的喜好中.

/App_Start/AuthConfig.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using DotNetOpenAuth.AspNet.Clients;
using DotNetOpenAuth.OpenId.RelyingParty;
using Microsoft.Web.WebPages.OAuth;
using Mvc4ApplicationOpenAuth.Models;

namespace Mvc4ApplicationOpenAuth
{
    public static class AuthConfig
    {
        public static void RegisterAuth()
        {
            // To let users of this site log in using their accounts from other sites such as Microsoft,and Twitter,// you must update this site. For more information visit http://go.microsoft.com/fwlink/?LinkID=252166

            //OAuthWebSecurity.RegisterMicrosoftClient(
            //    clientId: "",//    clientSecret: "");

            //OAuthWebSecurity.RegisterTwitterClient(
            //    consumerKey: "",//    consumerSecret: "");

            //OAuthWebSecurity.RegisterFacebookClient(
            //    appId: "",//    appSecret: "");

            OAuthWebSecurity.RegisterGoogleClient();
            OAuthWebSecurity.RegisterYahooClient();
            OAuthWebSecurity.RegisterClient(new GenericOpenIdClient("Aol","https://openid.aol.com/__username__"),"Aol",new Dictionary());
            OAuthWebSecurity.RegisterClient(new GenericOpenIdClient("LiveJournal","https://__username__.livejournal.com/"),"LiveJournal",new Dictionary());
            OAuthWebSecurity.RegisterClient(new GenericOpenIdClient("wordpress","https://__username__.wordpress.com/"),"wordpress",new Dictionary());
            OAuthWebSecurity.RegisterClient(new GenericOpenIdClient("Blogger","https://__username__.blogspot.com/"),"Blogger",new Dictionary());
            OAuthWebSecurity.RegisterClient(new GenericOpenIdClient("VeriSign","https://__username__.pip.verisignlabs.com/"),"VeriSign",new Dictionary());
            OAuthWebSecurity.RegisterClient(new GenericOpenIdClient("ClaimID","https://claimid.com/__username__"),"ClaimID",new Dictionary());
            OAuthWebSecurity.RegisterClient(new GenericOpenIdClient("ClickPass","https://clickpass.com/public/__username__"),"ClickPass",new Dictionary());
            OAuthWebSecurity.RegisterClient(new GenericOpenIdClient("Google Profile","https://www.google.com/profiles/__username__"),"Google Profile",new Dictionary());
            OAuthWebSecurity.RegisterClient(new GenericOpenIdClient("MyOpenID","https://__username__.myopenid.com/"),"MyOpenID",new Dictionary());
        }
    }
}

最后,我们需要解析由OpenID Selector提交给Account控制器的ExternalLogin操作的提供者表单值,以检查指示用户名存在的“;”分隔符.如果是这样,那么我们解析出提供者的名字和用户名.

/Controllers/AccountController.cs

[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public ActionResult ExternalLogin(string provider,string returnUrl)
{
    if (provider.Contains(';'))
    {
        string[] providerParts = provider.Split(';');
        if (providerParts.Length == 2)
        {
            AuthenticationClientData clientData;
            if (OAuthWebSecurity.TryGetOAuthClientData(providerParts[0],out clientData))
            {
                var genericClient = clientData.AuthenticationClient as GenericOpenIdClient;
                if (genericClient != null)
                {
                    provider = providerParts[0];
                    genericClient.UserName = providerParts[1];
                }
            }
        }
    }

    return new ExternalLoginResult(provider,Url.Action("ExternalLoginCallback",new { ReturnUrl = returnUrl }));
}

用户界面

使用开源的OpenID Selector,UI实现变得更加简单. Download OpenID Selector,并自定义它用于OAuthWebSecurity类.

>在您的Web应用程序中创建一个新的openid文件夹:/ Content / openid
>复制css,images,images.large和images.small文件
openid-selector下载到/ Content / openid文件夹,然后包含
项目中的文件.
>从openid-selector下载的js文件夹中,将openid-jquery.js和openid-en.js复制到您的Web应用程序的/ Scripts文件夹中,然后添加项目文件.
>打开openid-en.js文件自定义它,以便提供者的URL是
您将在AuthConfig.cs文件添加的提供者名称.对于
提供者使用自定义URL,使用格式Provider; {username}:

/Scripts/openid-en.js

var providers_large = {
    google : {
        name : 'Google',url : 'Google'
    },facebook : {
        name : 'Facebook',url : 'Facebook',},twitter: {
        name: 'Twitter',url: 'Twitter'
    },microsoft : {
        name : 'Microsoft',url : 'Microsoft'
    },yahoo : {
        name : 'Yahoo',url : 'Yahoo'
    },aol : {
        name : 'Aol',label : 'Enter your Aol screenname.',url : 'Aol;{username}'
    }
};

var providers_small = {
    livejournal: {
        name : 'LiveJournal',label : 'Enter your Livejournal username.',url: 'LiveJournal;{username}'
    },wordpress : {
        name : 'wordpress',label : 'Enter your wordpress.com username.',url: 'wordpress;{username}'
    },blogger : {
        name : 'Blogger',label : 'Your Blogger account',url: 'Blogger;{username}'
    },verisign : {
        name : 'VeriSign',label : 'Your VeriSign username',url: 'VeriSign;{username}'
    },claimid : {
        name : 'ClaimID',label : 'Your ClaimID username',url: 'ClaimID;{username}'
    },clickpass : {
        name : 'ClickPass',label : 'Enter your ClickPass username',url: 'ClickPass;{username}'
    },google_profile : {
        name : 'Google Profile',label : 'Enter your Google Profile username',url: 'Google Profile;{username}'
    },myopenid: {
        name: 'MyOpenID',label: 'Enter your MyOpenID username.',url: 'MyOpenID;{username}'
    }
};

openid.locale = 'en';
openid.sprite = 'en'; // reused in german& japan localization
openid.demo_text = 'In client demo mode. Normally would have submitted OpenID:';
openid.signin_text = 'Log in';
openid.image_title = 'Log in with {provider}';
openid.no_sprite = true;
openid.img_path = '/Content/openid/images/';

OpenID Selector不附带Microsoft或Twitter的图像,因此下载您最喜欢的MicrosoftTwitter(蓝色的白色)徽标,将它们转换为GIF(以100×60像素为单位),然后将其放在/Content/openid/images.large文件夹中.如果要单独使用单个图像,请阅读OpenID Selector README.txt文件中的说明.设置openid.no_sprite = false;在openid-en.js中,如果你使用sprite.

将JS和CSS文件注册为新的包.打开/App_Start/BundleConfig.cs并在RegisterBundles()方法添加以下脚本和样式包.

/App_Start/BundleConfig.cs

bundles.Add(new ScriptBundle("~/bundles/openid").Include(
    "~/Scripts/openid-jquery.js","~/Scripts/openid-en.js"));

bundles.Add(new StyleBundle("~/Content/css/openid").Include("~/Content/openid/css/openid-shadow.css"));

我更喜欢OpenID Selector的“阴影”样式,所以我选择使用openid-shadow.css CSS文件,并自定义以下类来在MVC4登录模板中工作.

/Content/css/openid/openid-shadow.css

/*#openid_form {
    width: 590px;
}*/

#openid_highlight {
    padding: 0px;
    background-color: #FFFCC9;
    float: left;
    border-radius: 5px; 
    -moz-border-radius: 5px;
    -webkit-border-radius: 5px;
}

.openid_large_btn {
    width: 100px;
    height: 60px;
/* fix for IE 6 only: http://en.wikipedia.org/wiki/CSS_filter#Underscore_hack */
    _width: 104px;
    _height: 64px;

    border: 2px solid #DDD;
    border-right: 2px solid #ccc;
    border-bottom: 2px solid #ccc;
    margin: 3px;
    padding: 3px;
    float: left;
    border-radius: 5px; 
    -moz-border-radius: 5px;
    -webkit-border-radius: 5px;
    Box-shadow: 2px 2px 4px #ddd;
    -moz-Box-shadow: 2px 2px 4px #ddd;
    -webkit-Box-shadow: 2px 2px 4px #ddd;
}

.openid_large_btn:hover {
    margin: 4px 3px 3px 6px;
    padding: 2px 3px 3px 0px;
    border: 2px solid #999;
    Box-shadow: none;
    -moz-Box-shadow: none;
    -webkit-Box-shadow: none;
}

要创建一个通用的地方,将CSS脚本添加页面的< head>标签,在< head>底部添加一个head部分.标签.

/Views/Shared/_Layout.cshtml

<head>
    <Meta charset="utf-8" />
    <title>@ViewBag.Title - My ASP.NET MVC Application</title>
    <link href="~/favicon.ico" rel="shortcut icon" type="image/x-icon" />
    <Meta name="viewport" content="width=device-width" />
    @Styles.Render("~/Content/css")
    @Scripts.Render("~/bundles/modernizr")
    @RenderSection("head",false)
</head>

然后,在/Views/Account/Login.cshtml文件中,通过将我们之前注册的OpenID包添加页面底部的相应部分来自定义登录视图.

/Views/Account/Login.cshtml

<section class="social" id="socialLoginForm">
    @Html.Action("ExternalLoginsList",new { ReturnUrl = ViewBag.ReturnUrl })
</section>

@section Head {        
    @Styles.Render("~/Content/css/openid")
}

@section Scripts {
    @Scripts.Render("~/bundles/jqueryval")
    @Scripts.Render("~/bundles/openid")
    <script type="text/javascript">
        $(function () {
            openid.init('provider');
        });
    </script>
}

UI的最后一个元素包括用OpenID Selector表单替换默认的ExternalLogin窗体.

/Views/Account/_ExternalLoginsListPartial.cshtml

using (Html.BeginForm("ExternalLogin","Account",new { ReturnUrl = ViewBag.ReturnUrl },FormMethod.Post,new { id = "openid_form" }))
{
    @Html.AntiForgeryToken()
    <input type="hidden" name="action" value="verify" />

    <h2>Use another service to log in.</h2>
    <br />
    <fieldset id="socialLoginList">
        <legend></legend>

        <div id="openid_choice">
            <div id="openid_btns"></div>
        </div>
        <div id="openid_input_area">
            <input id="provider" name="provider" type="text" value="" />
            <input id="openid_submit" type="submit" value="Log in"/>
        </div>
        <noscript>
            <p>OpenID is service that allows you to log-on to many different websites using a single indentity. Find out <a href="http://openid.net/what/">more about OpenID</a> and <a href="http://openid.net/get/">how to get an OpenID enabled account</a>.</p>
        </noscript>
    </fieldset>
}

猜你在找的asp.Net相关文章