私は自分に合った次のソリューションを開発しました。他の人に役立つ場合に備えて共有していますが、私が見逃しているより直接的な方法または「ベストプラクティス」があるかどうかを確認したいと思います。
基本的に、キーワードを含むURLを持つOpenIdClient
で初期化されるを実装する必要があります。ProviderIdentifier
__username__
実行時に、プロバイダー名とユーザー名がコントローラーに渡されAccount
、プロバイダークライアントが名前で選択され、認証要求がプロバイダーに送信される前に、ユーザー名が__username__
キーワードに置き換えられます。
OpenIDクライアント
Microsoftが提供するDotNetOpenAuthOpenIDプロバイダークラスは、OpenIDプロバイダークラスに必要なインターフェイスDotNetOpenAuth.AspNet.Clients.OpenIdClient
を実装する基本クラスを継承します。実装が簡単なGoogleプロバイダーIAuthenticationClient
のソースから始めて、カスタムURLを使用してプロバイダーと連携するクラスを作成するようにカスタマイズします。GenericOpenIdClient
実行時にカスタムURLを作成するには、OpenIDユーザー名をURIフラグメントとして受け入れ、__username__
URL内のすべてのインスタンスをユーザーが送信したユーザー名に置き換えます。アプリケーションの起動時にプロバイダーをURLに登録する必要があるため、ユーザー名がわかっている実行時にプロバイダーのURLを登録するだけでは不十分です。
OpenID Selectorを使用して、フォームの値をプロバイダー名とユーザー名に設定された形式で、Account
コントローラーのExternalLogin
アクションにフォームを送信します。OpenId Selectorには、のすべてのインスタンスをユーザーに表示されるテキストボックスからの入力に置き換えるロジックが組み込まれています。サーバー側では、プロバイダー名をユーザー名から分割し、アプリケーションの起動時に登録されたプロバイダーから名前でプロバイダーを検索し、ユーザーが送信したユーザー名にプロパティを設定します。provider
provider;{username}
{username}
GenericOpenIdClient.UserName
OpenIDプロバイダーに送信する認証リクエストが作成されると、GenericOpenIdClient.UserName
プロパティがチェックされ、設定されている場合は、リクエストを送信する前にユーザー名を使用してプロバイダーのURLが再作成されます。そのためにはRequestAuthentication()
、カスタムURLを使用して認証リクエストを作成するメソッドをオーバーライドする必要があります。はホスト名の有効な文字ではないため、ここ__username__
の代わりにが使用されます。したがって、それらを含むURLを作成することは、それらを汎用プロバイダー識別子として登録する必要がある場合に問題になります。{username}
{
}
/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;
}
}
}
}
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());
}
}
}
Account
最後に、OpenID Selectorによってコントローラーの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 }));
}
UI
UIの実装は、オープンソースのOpenIDSelectorを使用するとはるかに簡単になります。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の画像が付属していないため、お気に入りのMicrosoftおよびTwitter(白地に青)のロゴをダウンロードし、100x60ピクセルのGIFに変換して、/Content/openid/images.large
フォルダーにドロップします。README.txt
個別の画像の代わりに単一のスプライト画像を使用する場合は、OpenIDSelectorファイルの手順をお読みください。スプライトを使用する場合は設定openid.no_sprite = false;
します。openid-en.js
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"));
私はOpenIDSelectorの「シャドウ」スタイルを好むので、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;
}
ページの<head>
タグにCSSスクリプトを追加する一般的な場所を作成する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>
次に、ファイルで、以前に登録したOpenIDバンドルをページ下部の適切なセクションに追加して/Views/Account/Login.cshtml
、ビューをカスタマイズします。Login
/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の最後の要素には、デフォルトのExternalLogin
フォームをOpenIDSelectorフォームに置き換えることが含まれます。
/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>
}