nek*_*kno 11 openid asp.net-mvc dotnetopenauth asp.net-mvc-4 asp.net-4.5
SimpleMembershipProviderASP.NET MVC4中实现的新功能允许为两个流行的OpenID提供商(谷歌和雅虎)和三个OAuth提供商(微软,Facebook,Twitter)提供简单的内置支持.
提供者实现了DotNetOpenAuth.AspNet.Clients与SimpleMembershipProvider所有使用静态URL 一起使用的身份服务 - 也就是说,所有用户使用相同的知名URL来访问提供者.用户的OpenID标识符与用于访问身份服务的URL分开.
例如,Google的OpenID服务网址适用https://www.google.com/accounts/o8/id于所有用户.
这适用SimpleMembershipProvider于MVC4,其中身份提供者的URL需要在MVC应用程序启动时已知,不变并注册.
问题是,其他OpenID提供商通常使用用户的唯一OpenID标识符作为访问身份服务的URL.
例如,AOL和WordPress 分别使用https://openid.aol.com/{username}和https://{username}.wordpress.com.
如果你SimpleMembershipProvider用你自己的a实现替换ExtendedMembershipProvider,那么你可以推出自己的提供程序实现,但是它不能与开箱即用的MVC4 Account控制器一起使用.
SimpleMembershipProvider当提供程序在URL中使用带有用户名的唯一标识符时,如何使用the实现新的OpenID依赖方?
nek*_*kno 18
我开发了以下适用于我的解决方案,我将分享它以帮助其他人,但我真的想看看是否有更直接的方法或"我最不需要的"最佳实践.
基本上,您需要实现一个OpenIdClient使用ProviderIdentifier包含关键字的URL 初始化的__username__.
在运行时,将提供程序名称和用户名传递给Account控制器,其中按名称选择提供程序客户端,并__username__在将身份验证请求发送到提供程序之前替换用户名.
Microsoft提供的DotNetOpenAuth OpenID提供程序类继承了基类DotNetOpenAuth.AspNet.Clients.OpenIdClient,该基类实现了IAuthenticationClientOpenID提供程序类所需的接口.从Google提供商的源代码开始,因为它具有直接的实现,因此需要对其进行自定义以创建GenericOpenIdClient与使用自定义URL的提供程序一起使用的类.
要在运行时创建自定义URL,我们将接受OpenID用户名作为URI片段,并将__username__URL中的所有实例替换为用户提交的用户名.在应用程序启动期间,提供程序需要在URL中注册,因此我们不能仅在用户名已知时在运行时注册提供程序URL.
我们将使用OpenID Selector将表单提交给Account控制器的ExternalLogin操作,其中provider表单值设置为提供程序名称和用户名格式provider;{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> dictionary, string key, string value)
{
if (key == null)
{
throw new ArgumentNullException("key");
}
if (!string.IsNullOrEmpty(value))
{
dictionary[key] = value;
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
要注册Microsoft提供的新DotNetOpenAuth类中内置的提供程序,请取消注释现有的Microsoft,Facebook,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, Facebook, 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());
}
}
}
Run Code Online (Sandbox Code Playgroud)
最后,我们需要解析由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 }));
}
Run Code Online (Sandbox Code Playgroud)
使用开源OpenID选择器可以更轻松地实现UI实现.下载OpenID选择器并对其进行自定义以与OAuthWebSecurity类一起使用.
openid在您的网络应用中创建一个新文件夹:/Content/openidcss,images,images.large,并images.small从文件夹
openid-selector下载到/Content/openid的文件夹,然后在项目中包含的文件. js文件夹,复制openid-jquery.js和openid-en.js你的Web应用程序的/Scripts文件夹,然后包括文件的项目. openid-en.js文件并对其进行自定义,以使提供者URL成为您将在AuthConfig.cs文件中添加的提供者名称.对于自定义网址提供商,使用格式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/';
Run Code Online (Sandbox Code Playgroud)
OpenID Selector doesn’t come with images for Microsoft or Twitter, so download your favorite Microsoft and Twitter (blue on white) logos, convert them to GIFs at 100x60 pixels, then drop them in the /Content/openid/images.large folder. Read the instructions in the OpenID Selector README.txt file if you want to use a single sprite image instead if separate images. Set openid.no_sprite = false; in openid-en.js if you use the sprite.
Register the JS and CSS files as a new bundle. Open /App_Start/BundleConfig.cs and add the following script and style bundles in the RegisterBundles() method.
/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"));
Run Code Online (Sandbox Code Playgroud)
I prefer the OpenID Selector's "shadow" style, so I elected use just the openid-shadow.css CSS file and customized the following classes to work in the MVC4 Login template.
/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;
}
Run Code Online (Sandbox Code Playgroud)
To create a generic place to add CSS scripts to the page's <head> tag, add a head section at the bottom of the <head> tag.
/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>
Run Code Online (Sandbox Code Playgroud)
Then, in the /Views/Account/Login.cshtml file, customize the Login view by adding the OpenID bundles we registered previously to the appropriate sections at the bottom of the page.
/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>
}
Run Code Online (Sandbox Code Playgroud)
The last element of the UI involves replacing the default ExternalLogin form with the OpenID Selector form.
/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>
}
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
5154 次 |
| 最近记录: |