DotNetOpenAuthのOpenId認証アダプタ作成
※8/25 OpenId用語訂正(RP⇒OP)
asp.net mvcアプリで、DotNetOpenAuthを使用したOpenId認証を行う際、
DotNetOpenAuthのサンプルコードのようにControllerで認証関係処理をゴリ書きするのが嫌だったので、その辺の処理をクラスに分離してみました。
FactoryクラスでCookie使ってたり、RP OP毎の(とりあえずはGoogleとYahoo)AuthAdapterクラス処理をちゃんと実装する必要はありますが、
ひと通り動くようになってるので公開しておきます。その他RP OPとして、はてなとライブドアで動作確認済です。
ログインページ(一部抜粋)
@{ ViewBag.Title = "ログイン"; var returnUrl = @Url.Content("~/Home"); if (ViewBag.ReturnUrl != null) { returnUrl = @Html.Encode(ViewBag.ReturnUrl as string); } } @if (!string.IsNullOrEmpty(ViewBag.Message)) { <h3 class="error">@ViewBag.Message</h3> } <form action="@Url.Content("~/OpenId/Authenticate?ReturnUrl=" + @returnUrl)" method="post"> <span>Id</span> <input id="OpenIdIdentifier" name="OpenIdIdentifier" type="text" value="" /> <br /> <input type="submit" value="認証" /> </form>
ログインコントローラー
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Mvc; using System.Web.Security; using AuthAdapter; namespace OpenIdLoginMVC3Sample.Controllers { public class OpenIdController : Controller { private EasyAuthAdapterFactory adapterFactory; // // GET: /OpenId/ public ActionResult Index() { ViewBag.ReturnUrl = "~/Home"; return View("Login"); } public ActionResult Logout() { FormsAuthentication.SignOut(); return Redirect("~/Home"); } public ActionResult Login() { return View(); } [ValidateInput(false)] public ActionResult Authenticate(string ReturnUrl) { adapterFactory = new EasyAuthAdapterFactory(this); adapterFactory.Initialize(Request["OpenIdIdentifier"]); var adapter = adapterFactory.Create(); var response = adapter.GetResponse(); switch(response.Status) { case AuthStatus.NotYet: try { string identifier = Request["OpenIdIdentifier"]; return adapter.CreateRequestAndRedirectingResponse(identifier); } catch (Exception ex) { ViewBag.Message = ex.Message; return View("Login"); } case AuthStatus.Authenticated: FormsAuthentication.SetAuthCookie(response.NickName, false); if (!string.IsNullOrEmpty(ReturnUrl)) { return Redirect(ReturnUrl); } else { return RedirectToAction("Index", "Home"); } case AuthStatus.Canceled: ViewBag.Message = "認証がキャンセルされました。"; return View("Login"); case AuthStatus.Failed: ViewBag.Message = "認証に失敗しました。"; return View("Login"); default: return new EmptyResult(); } } } }
認証アダプタ定義
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Mvc; using DotNetOpenAuth.Messaging; using DotNetOpenAuth.OpenId; using DotNetOpenAuth.OpenId.RelyingParty; using DotNetOpenAuth.OpenId.Extensions; using DotNetOpenAuth.OpenId.Extensions.SimpleRegistration; using DotNetOpenAuth.OpenId.Extensions.AttributeExchange; using System.Text.RegularExpressions; namespace AuthAdapter { /// <summary> /// 認証状態 /// </summary> public enum AuthStatus { /// <summary> /// 未認証 /// </summary> NotYet, /// <summary> /// 認証済み /// </summary> Authenticated, /// <summary> /// キャンセルされた /// </summary> Canceled, /// <summary> /// 失敗 /// </summary> Failed } /// <summary> /// 認証結果 /// </summary> public class AuthResult { private EasyAuthAdapter adapter; private IAuthenticationResponse lowResponse = null; private string nickname = null; public AuthResult(EasyAuthAdapter adapter) { this.adapter = adapter; } public AuthStatus Status { get{ lowResponse = lowResponse == null ? adapter.GetLowResponse() : lowResponse; if(lowResponse == null) return AuthStatus.NotYet; if(lowResponse.Status == AuthenticationStatus.Canceled) return AuthStatus.Canceled; if (lowResponse.Status == AuthenticationStatus.Authenticated) return AuthStatus.Authenticated; return AuthStatus.Failed; } } public string NickName { get{ if(nickname == null) nickname = adapter.GetNickName(lowResponse); return nickname; } } } /// <summary> /// 簡単認証アダプタファクトリ /// </summary> public interface IEasyAuthAdapterFactory { void Initialize(string adapterid); EasyAuthAdapter Create(); } /// <summary> /// ファクトリ実装 /// </summary> public class EasyAuthAdapterFactory : IEasyAuthAdapterFactory { //Cookieを使用してEasyAuthAdapterを生成する。 private Controller controller; private string adapterId = null; public EasyAuthAdapterFactory(Controller controller) { this.controller = controller; } public void Initialize(string adapterid) { var cookie = controller.Request.Cookies["adptid"]; if (cookie != null) adapterId = cookie.Value; if (adapterid != null) adapterId = this.adapterId != adapterid ? adapterid : this.adapterId; controller.Response.Cookies.Add(new HttpCookie("adptid", adapterId)); } public EasyAuthAdapter Create() { if (adapterId == null) return null; var uri = new Uri(adapterId); if (Regex.IsMatch(uri.Host, "google")) return new EasyAuthForGoogle(); if (Regex.IsMatch(uri.Host, "yahoo")) return new EasyAuthForYahoo(); else return new EasyAuthForOther(); } } /// <summary> /// 簡単認証アダプタ /// </summary> public class EasyAuthAdapter { protected OpenIdRelyingParty openid; public EasyAuthAdapter() { this.openid = new OpenIdRelyingParty(); } public virtual bool IsTarget(string domainOrUrl) { return true; } public virtual ActionResult CreateRequestAndRedirectingResponse(string identifier) { return null; } public AuthResult GetResponse() { return new AuthResult(this); } internal IAuthenticationResponse GetLowResponse() { return openid.GetResponse(); } internal string GetNickName(IAuthenticationResponse lowres) { if (lowres == null) return null; return CreateNickName(lowres); } protected virtual string CreateNickName(IAuthenticationResponse lowres) { return null; } } /// <summary> /// グーグル用 /// </summary> public class EasyAuthForGoogle : EasyAuthAdapter { public EasyAuthForGoogle() : base() { } public override ActionResult CreateRequestAndRedirectingResponse(string identifier) { Identifier id; if(!Identifier.TryParse(identifier, out id)) return null; try { //Todo:OP毎の実装 var req = openid.CreateRequest(id); var freq = new FetchRequest(); freq.Attributes.Add(new AttributeRequest("http://axschema.org/namePerson/friendly", true, 1)); freq.Attributes.Add(new AttributeRequest("http://axschema.org/contact/country/home", true, 1)); freq.Attributes.Add(new AttributeRequest("http://axschema.org/namePerson/first", true, 1)); freq.Attributes.Add(new AttributeRequest("http://axschema.org/pref/language", true, 1)); freq.Attributes.Add(new AttributeRequest("http://axschema.org/namePerson/last", true, 1)); req.AddExtension(freq); return req.RedirectingResponse.AsActionResult(); } catch (ProtocolException ex) { throw new Exception(ex.Message); } } protected override string CreateNickName(IAuthenticationResponse lowres) { //Todo:OP毎の実装 var fres = lowres.GetExtension(typeof(FetchResponse)) as FetchResponse; var nickname = fres != null ? fres.Attributes[0].Values[0] : lowres.FriendlyIdentifierForDisplay; return nickname; } } /// <summary> /// ヤフー用 /// </summary> public class EasyAuthForYahoo : EasyAuthAdapter { public EasyAuthForYahoo() : base() { } public override ActionResult CreateRequestAndRedirectingResponse(string identifier) { Identifier id; if (!Identifier.TryParse(identifier, out id)) return null; try { //Todo:OP毎の実装 var req = openid.CreateRequest(id); var freq = new FetchRequest(); freq.Attributes.Add(new AttributeRequest("http://axschema.org/namePerson/friendly", true, 1)); freq.Attributes.Add(new AttributeRequest("http://axschema.org/contact/country/home", true, 1)); freq.Attributes.Add(new AttributeRequest("http://axschema.org/namePerson/first", true, 1)); freq.Attributes.Add(new AttributeRequest("http://axschema.org/pref/language", true, 1)); freq.Attributes.Add(new AttributeRequest("http://axschema.org/namePerson/last", true, 1)); req.AddExtension(freq); return req.RedirectingResponse.AsActionResult(); } catch (ProtocolException ex) { throw new Exception(ex.Message); } } protected override string CreateNickName(IAuthenticationResponse lowres) { //Todo:OP毎の実装 var fres = lowres.GetExtension(typeof(FetchResponse)) as FetchResponse; var nickname = fres != null ? fres.Attributes[0].Values[0] : lowres.FriendlyIdentifierForDisplay; return nickname; } } /// <summary> /// そのほか /// </summary> public class EasyAuthForOther : EasyAuthAdapter { public EasyAuthForOther() : base() { } public override ActionResult CreateRequestAndRedirectingResponse(string identifier) { Identifier id; if (!Identifier.TryParse(identifier, out id)) return null; try { var req = openid.CreateRequest(id); return req.RedirectingResponse.AsActionResult(); } catch (ProtocolException ex) { throw new Exception(ex.Message); } } protected override string CreateNickName(IAuthenticationResponse lowres) { var nickname = lowres.FriendlyIdentifierForDisplay; int stIdx = nickname.LastIndexOf("/") + 1; return nickname.Substring(stIdx); } } }