Sayfalar

18 Ekim 2012 Perşembe

OpenId Destekleyen Sosyal Medya İkonlarıyla Kullanıcıları Bir Siteye Üye Yapmak

JavascriptOpenID Selector
Sıklıkla gördüğümüz "Facebook hesabınızla giriş yapın", "Google hesabınızla üye olun" gibi butonlarla kullanıcıları tek tıklama ile sistemlerine üye yapan sitelerin sayısı gün geçtikçe artıyor. Bununla beraner OpenId kullanan sosyal ağ sitelerinin sayısı da artıyor. Hal böyle olunca, nasıl paylaşım desteği veren sosyal ağların paylaş butonlarını tek bir yerde toplayan eklentiler çıktıysa, OpenId destekleyen siteler ile giriş yapmak için sosyal ağ butonlarının tek bir yerde toplayan eklentiler de kaçınılmaz oldu. Bu makalede bu OpenId seçicilerden en bilineni olan Javascript OpenID Selector ile ASP.NET üzerinde bir üyelik entegrasyonunun nasıl yapılacağını anlatacağım.

Google, Yahoo, Blogger, ... gibi sosyal ağların herhangi birinde açtığınız bir hesap ile başka sitelere üye olabiliyor ve oturum açabiliyoruz. Buradaki ortak nokta aslında OpenId denen bir teknoloji. Bu teknoloji özünde hesabınızın ait olduğu sunucu üzerinde sunulan bir API sayesinde, bu API'yi kullanmayı bilen istemci (üye olunmak veya oturum açılmak istenen) sitelerin sunucu sitede kayıtlı kullanıcı bilgilerinin talep ettiklerine kullanıcı izni doğrultusunda ulaşmasını sağlar.

Örneğin Google'a üyesiniz ve bir takım profil bilgileri girdiniz. Tesadüfen rastladığınız xyz.com sitesine de üye olacaksınız. Sitedeki üyelik formunu açtığımızda zorunlu ad, soyad ve e-posta alanlarının dışında isteğe bağlı doğum yılı, şehir gibi bilgiler de olduğunu gördük. Bu siteye üye olabilmek için en az ad, soyad ve e-posta bilgileri gerekmekte.

Ancak bu sitede kayıt formu gibi "Google ile giriş yap" butonu bulunmakta, size OpenId desteklediğini ve bu formu doldurtmak yerine Google hesabınızla oturum açabileceğinizi söylemektedir.
İşte siz bu butona tıkladığınızda; xyz.com sitesi Google üzerinde bulunan API'yi kullanarak Google'dan kendisine üyelik için gerekli minimum bilgilerinizi ve isteğe bağlı diğer bilgilerinizi sorgulayacak izinleri vermenizi isteyecek. Google oturumunuz açık değilse oturum açmanız gerekecek. Böylece Google kendi kullanıcı veritabanında sizin profil bilgilerinizin hangilerinin xyz.com ile paylaşılabileceğini işaretleyecek, daha sonra talep ettiğinde bu bilgileri kendisine iletecek. Bu işlemin tamamlanabilmesi için zorunlu olarak istenen alanların tamamına izin vermeniz gerekir ancak isteğe bağlı izinleri vermeyebilirsiniz.
İzinlerin de verilmesinden sonra pencere xyz.com sitesine Postback yapar ve bir yanıt gelir. Bu yanıtta Google'daki kullanıcı ID bilgileriniz ve diğer istenilen bilgiler bulunur. xyz.com bu bilgileri kullanarak kendi veritabanında otomatik olarak bir kullanıcı yaratır ve Google'daki bilgilerinizi kullanarak Üyelik formunda karşılık gelen alanları doldurur. Böylece sanki üyelik formunu doldurmuşsunuz gibi ilerleyebilirsiniz.

Şimdi böyle bir teknolojiyi kendi web uygulamamıza nasıl entegre edeceğimizi görelim. Bu entegrasyon için kullanacağımız iki adet temel bileşen şunlar:


Bu bileşenleri indirdikten sonra Visual Studio'ya geçip yeni bir ASP.NET Web Application oluşturalım. Üyelik sistemi için ASP.NET Membership Provider kullanacağız. Bunun kurulumu ve ayarlaması ile ilgili bilgiler internette ve MSDN'de bulunabilir.

Önce CodeContracts.Unofficial kurulumunu yapalım. Kurulum ile ilgili açıklamalar NuGet sitesinde mevcut ancak eğer NuGet Package Manager extensionu kurulu ise oradan da aratarak bulabilirsiniz. Bu kütüphane aslında DotNetOpenAuth kütüphanelerinden ASP.NET için seçilmiş birkaç kütüphanenin paketlenmiş hali. Eğer NuGet ile kurulum yaparsanız projemize
  • DotNetOpenAuth.Core
  • DotNetOpenAuth.Core.UI
  • DotNetOpenAuth.OpenId
  • DotNetOpenAuth.OpenId.UI
  • DotNetOpenAuth.OpenId.RelyingParty
  • DotNetOpenAuth.OpenId.RelyingParty.UI

olacak şekilde altı adet .NET modül referansı eklenecek ve Web.config dosyamızda aşağıdaki bölümler oluşturulacak:


  
  
  


  
    
      
        
        
      
    
  
  
  
    
      
        
          
          
        
        
          
          
          
        
      
    
  


  
  
    
    
  

İstemci tarafında jQuery kütüphanesini kullanacağımızdan bunu da projeye bir şekilde eklememiz gerekir.

Sıra geldi ikinci bileşenimizi kurmaya. Javascript OpenID Selector plugin'ini indirdiğimizde dosyanın içinde birçok dosya ve klasör buluacağız. Bunlardan bizi ilgilendiren temel dosyalar ve klasörler:
  • js/openid-jquery.js
  • js/openid-en.js
  • css/openid.css
  • images/
  • images.large/
  • images.small/
Ancak openid-jquery.js dosyasını açtığımızda

...
$('#openid_form').submit(this.submit);
...
$('#openid_form').submit();
...

gibi iki farklı satırda sırasıyla, id değeri "openid_form" olan bir form submit edilirken "submit" adlı metodu çağıran ve bu formu submit etmeye çalışan kodlar görürüz.

Bu durum ASP.NET uygulamalarında problem yaratacaktır çünkü ASP.NET sayfalarında sadece tek bir runat="server" olan form bulunmalıdır ve bu formun id değeri de otomatik olarak sunucu tarafından verilmektedir. Ancak bu formun dışında id değeri "openid_form" olan bir form açarsak bu kez de aynı adresteki sunucu kodunu çalıştıramayacağız. Oysa biz sunucu tarafında kod yazacağız. Bu yüzden bu dosyadaki '#openid_form' ibarelerini aşağıdaki gibi değiştirmemiz gerekiyor:

$('#openid_form').submit(this.submit);  -->  $('form').submit(this.submit);
$('#openid_form').submit();             -->  $('#btnOpenIdSubmitter').click();

Böylece ilk değişiklikle zaten sadece bir tane bulunan formu yakalamış ve bu formu istemci tarafından gönderecek bir butonu programatik olarak tıklatmış olacağız.

Ayrıca yine bu dosyada ilk satırlarda resim klasötlerinin yolları, cookie expiration süresi gibi bazı konfigürasyon ayarlarını yapabiliriz.

Bunları projemize yerleştirdikten sonra projemize LoginWithOpenId.aspx adında bir sayfa ekleyelim.

Bu sayfanın HTML kodları arasında aşağıdaki kodlar bulunsun:



Burada dikkat edilen nokta btnOpenIdSubmitter adlı gizli submit butonu bulunması. OpenId selector bu sayfadaki bu butonu programatik olarak submitletecek. Yine bu sayfada aşağıdaki javascript kodlarının da bulunması gerekecek:


    
    


Bu noktadan sonra sunucu tarafına geçebiliriz. OnLoad metodunu aşağıdaki eziyoruz:

protected override void OnLoad(EventArgs e)
{
    base.OnLoad(e);
    if (!this.IsPostBack)
        this.HandleOpenIdProviderResponse();
    else
        this.HandleRelyingPartyRequest();
}

Burada sayfa ilk açıldığında kullanıcı bilgilerini alacağı sosyal paylaşım sitesine OpenId sorgusu yapan ve eğer sonuç dönerse bunu işleyen HandleOpenIdProviderResponse metodunu çağırır. Eğer onay bir de sosyal ağ ikonuna tıkladığında oluşacak postback sayesinde sosyal ağ sitesine talep ettiği bilgileri gönderen HandleRelyingPartyRequest metodunu çağırır. Bu noktada kullanıcı ilk izni vermişse oradan geri bu sayfaya postback yapılır ve yine HandleOpenIdProviderResponse çağrılmış olur. Aslında bu sayfadaki HandleRelyingPartyRequest metodu sadece sosyal ağ ikonuna tıklandığında çağrılır. Bu metotlar aşağıdaki gibidir:

private void HandleRelyingPartyRequest()
{
    try
    {
        var openid_identifier = Request.Form["openid_identifier"];
        if (openid_identifier == null)
            return;

        using (var openid = new OpenIdRelyingParty())
        {
            var request = openid.CreateRequest(openid_identifier);

            var fetchRequest = new FetchRequest();
            fetchRequest.Attributes.AddRequired(WellKnownAttributes.Contact.Email);
            fetchRequest.Attributes.AddRequired(WellKnownAttributes.Name.First);
            fetchRequest.Attributes.AddRequired(WellKnownAttributes.Name.Last);
            fetchRequest.Attributes.AddOptional(WellKnownAttributes.Contact.HomeAddress.Country);
            request.AddExtension(fetchRequest);

            request.RedirectToProvider();
        }
    }
    catch (ProtocolException) { throw; }
    catch (WebException) { throw; }
}

private void HandleOpenIdProviderResponse()
{
    try
    {
        using (var openid = new OpenIdRelyingParty())
        {
            var response = openid.GetResponse();

            if (response == null) return;

            switch (response.Status)
            {
                case AuthenticationStatus.Authenticated:

                    Identifier userId = response.ClaimedIdentifier;
                    var fetchResponse = response.GetExtension();
                    loginOrSignWithWithOpenId(userId, fetchResponse);
                    break;
                case AuthenticationStatus.Canceled:
                    this.loginCanceledLabel.Visible = true;
                    break;
                case AuthenticationStatus.Failed:
                    this.loginFailedLabel.Visible = true;
                    break;
            }
        }
    }
    catch (ProtocolException) { throw; }
    catch (WebException) { throw; }
}

Burada kullanılan loginOrSignWithWithOpenId metodu karşıdan aldığımız bilgilere bizim sistemimizde karşılık gelen üye yoksa bu üye oluşturulur. Ardından oturumu açılmamışsa oturum açılır.

private void loginOrSignWithWithOpenId(string providerUserId, FetchResponse fetchResponse)
{
    loginOrSignWithWithOpenId(providerUserId, fetchResponse.GetAttributeValue(WellKnownAttributes.Contact.Email), fetchResponse.GetAttributeValue(WellKnownAttributes.Name.First), fetchResponse.GetAttributeValue(WellKnownAttributes.Name.Last));
}

private void loginOrSignWithWithOpenId(string providerUserId, string email, string firstName, string lastName)
{
    var user = Membership.GetUser(providerUserId)
            ?? Membership.CreateUser(providerUserId, Membership.GeneratePassword(Membership.MinRequiredPasswordLength, Membership.MinRequiredNonAlphanumericCharacters), email);

    if (Membership.ValidateUser(user.UserName, user.GetPassword()))
    {
        var p = CustomProfile.GetProfile(user.UserName);

        p.FirstName = firstName;
        p.LastName = lastName;
        p.IsOpenId = true;
        p.Save();

        Roles.AddUserToRole(user.UserName, Global.STR_Role_Users);

        FormsAuthentication.RedirectFromLoginPage(providerUserId, false);
    }
}

Burada Membership veya User tablolarında ad/soyad gibi bilgilerin saklanamamasında dolayı profil gibi başka bir yerde saklanması için özel bir sınıf oluşturabiliriz:

public class CustomProfile : ProfileBase
{
    [SettingsAllowAnonymous(true)]
    public string FirstName { get { return (string)this.GetPropertyValue("FirstName"); } set { this.SetPropertyValue("FirstName", value); } }
    [SettingsAllowAnonymous(true)]
    public string LastName { get { return (string)this.GetPropertyValue("LastName"); } set { this.SetPropertyValue("LastName", value); } }

    [SettingsAllowAnonymous(true)]
    public bool IsOpenId { get { return (bool)this.GetPropertyValue("IsOpenId"); } set { this.SetPropertyValue("IsOpenId", value); } }

    /// 
    /// Get the profile of the currently logged-on user.
    ///      
    public static CustomProfile GetProfile()
    {
        return (CustomProfile)HttpContext.Current.Profile;
    }
    /// 
    /// Gets the profile of a specific user.
    /// 
    /// The user name of the user whose profile you want to retrieve.    public static CustomProfile GetProfile(string userName)     {         return (CustomProfile)Create(userName);     } } 

Böylece entegrasyonumuzu tamamlamış bulunuyoruz. Bu kodu çalıştırıp debug ederek nereler ne zaman çalışıyor, giden ve gelen veriler neler ayrıntılı bir şekilde görebilirsiniz. Bir sonraki makalede bu sistemde bulunmayan Facebook ile giriş entegrasyonu yapmayı anlatacağım.

Hiç yorum yok:

Yorum Gönder