.NET’te Single Sign-On (SSO)

Single Sign-On (SSO), aynı veya farklı domain altındaki, farklı subdomain altındaki web uygulamalarında sözkonusu olup her uygulama için yeniden giriş yapmak yerine tek bir yerden giriş yapıp tüm uygulamalara otomatik olarak logon olabilme özelliğidir. SSO, farklı uygulamalara aynı kullanıcı adı ve şifreyle girildiği için kullanıcılara zaman kazandırır. Microsoft’un passport (Windows Live ID) veya birçok firmanın destek verdiği OpenId veya Sun’ın Java Identity Manager projesi, farklı web uygulamalardaki servislere tek bir noktadan giriş yapılması için geliştirilmiştir. Örneğin MSN Messenger, MSN Hotmail, MSN Müzik gibi passport network sitelerindeki servislerden yaralanmak için Windows Live ID’ye erişmek yeterli olmaktadır. Aynı şekilde özellikle intranet uygulamalarında kullanıcı Active Directory(AD) üzerinden login olduktan sonra AD yetkisi altındaki diğer sistemlere yeniden güvenlik bilgilerini girmeksizin Bu yazıda .NET tabanlı web uygulamalarımızda sözü geçen servislere benzer bir özelliği nasıl kuracağımızı örneklendireceğiz.

SSO yöntemini kullanabileceğimiz farklı senaryolar bulunmaktadır. Bunları aşağıdaki şekilde özetleyebiliriz;

  1. Ana uygulama ve onun altındaki uygulamalar (virtual sub-directory) arasında SSO
  2. Farklı yetki tanımlarına sahip olma durumunda SSO
  3. Aynı domain altındaki iki sub-domain arasında SSO
  4. .NET Framework’ün farklı sürümleri altında çalışan uygulamalar için SSO
  5. Farklı domain altındaki uygulamalar için SSO
  6. Mixed-mode authentication (Forms ve Windows) için SSO

1) Ana uygulama ve onun altındaki uygulamalar arasında SSO
“Alisveris” ve “Forum” isminde iki .NET uygulamasının olduğunu ve “Forum” uygulamasının “Alisveris” uygulaması altında virtual sub-directory olarak çalıştığını (http://www.alisveris.com/forum) düşünelim. Her iki uygulama da “Forms authentication” tabanlı güvenlik mekanizmasını kullanmaktadır. Bilindiği gibi uygulama içerisinde login işlemini kontrol etmek ve kullanıcıyı güvenli bir şekilde içeri almak için Global.asax.cs dosyasında Application_AuthenticateRequest yordamı kullanılır. Eğer kullanıcının girdiği güvenlik bilgileri doğruysa kullanıcı temsil edecek güvenlik kodu (örneğin kullanıcı ID’si) FormsAuthentication.RedirectFromLoginPage() yordamına parametre olarak gönderilir. Kullanıcıya ait sözkonusu güvenlik kodu, istemci tarafında cookie olarak saklanır. Sunucu ile istemci arasındaki her konuşmadan bu cookie ikili arasında taşınır. RedirectFromLoginPage() metodu çağrıldığı zaman güvenlik kodu şifrelenerek bir cookie içerisine aktarılır. Bu cookie’nin nasıl oluşturulacağı Web.config dosyasında aşağıdaki etiketlerle belirtilir.

Burada iki önemli nitelik bulunmaktadır;name ve protection. name deyimi, istemci tarafında oluşturulacak cookie’nin adını belirtir. protection ise bu cookie’nin güvenliğinin nasıl sağlanacağını belirtir. All, Encryption, Validation, None değerlerden birini alır. Encryption seçeneği, şifreleme, Validation seçeneği işaretleme, All seçeneği ise hem şifreleme hem de işaretleme yapar. All değeri, varsayılan seçenek olup cookie’deki verilerin en üst seviyede güvenli olmasını sağlar.
Her iki uygulamanın güvenlik bilgilerinin yazılığı cookie’yi paylaşması için her iki uygulamada da cookie adı ve güvenlik seviyesinin aynı olması gerekir.

Oluşturulan cookie’nin şifrelemesi elementi aracılığıyla gerçekleştirilir. Varsayılan machine.config dosyasındaki anahtar tanımlamaları kullanılır. Eğer bu projelere özgü bir anahtar oluşturulacaksa projelerin web.config dosyasında elementinin yeniden tanımlanması gerekir. Bu element için kullanılacak nitelikler şu şekildedir.

validationKey : İçeriği doğrulamak için kullanılan anahtar.
decryptionKey : İçeriği şifrelemek için kullanılan anahtar.
validationKey ve decryptionKey niteliklerini “AutoGenerate” olarak tanımlanması durumunda ASP.NET Runtime, otomatik bir anahtar üretir.
validation : Şifreleme algoritması(MD5 | SHA1 | 3DES)
IsolateApps seçeneği, her uygulama için farklı bir anahtarın oluşturulacağını belirtir. Birden fazla uygulamanın cookie içeriğini aynı anahtarla şifrelemesi ve çözmesi için IsolateApps seçeneği kaldırılır veya daha iyi bir çözüm olarak SSO’yu kullanacak tüm uygulamaların web.config dosyasında aşağıdaki gibi aynı anahtarı kullanmaktır.
machineKey değeri, uygulamanın çalıştığı ortamdan bağımsız oluşturulan bir anahtardır. Herhangi bir key generator aracını kullanarak o an için tekil bir anahtar oluşturulabilir. Bu adresteki araç bu amaç için kullanılabilir.

Bu senaryoda her iki uygulamada da User.Identity.Name ifadesi aynı değeri gösterir.
2) Farklı yetki tanımlarına sahip olma durumunda SSO (Kullanıcı eşleştirme)
“Alisveris” ve “Forum” uygulamalarının birbirinden farklı yetkilendirme mekanizmalarını kullandıklarını örneğin “Alisveris”in kendi veritabanı üzerinden yetkilendirme yaptığını buna karşın “Forum”un Membership API veya başka bir form authentication kullandığını düşünelim. Böyle bir senaryoda “Alisveris” uygulamasında otomatik olarak oluşturulmuş olan cookie, “Forum” uygulaması tarafından kullanılamayacaktır. Çünkü “Aliveris” için oluşturulmuş olan kullanıcı adı (user name), “Forum” uygulaması için birşey ifade etmeyecektir. Bu senaryoda her iki uygulamanın cookie isimleri farklı olacaktır.

Bu durumda özellikle “Forum” uygulaması için ikinci bir yetkilendirme cookie’si oluşturmak gerekir. Ayrıca “Alisveris” kullanıcısı ile “Forum” kullanıcısı arasında bir ilişkilendirme yapmak gerekir. Örneğin “Alisveris” uygulamasında “alisveris_akaymaz” isimli bir kullanıcının olduğunu ve bu kullanıcının “Forum” uygulamasındaki “forum_akaymaz” kullanıcısına denk geldiğini varsayalım. Her iki uygulamanın web.config’teki cookie isimlerinin “Alisveris” uygulamasında cookie isimlerine bağlı olarak şu şekilde bir tanımlama yapmalıyız.

FormsAuthenticationTicket bilet = new FormsAuthenticationTicket(1,
    "forum_akaymaz", DateTime.Now,
    DateTime.Now.AddYears(1), true, "");

HttpCookie oCk = new HttpCookie(".ForumBilgi");
oCk.Value = FormsAuthentication.Encrypt(bilet);
oCk.Expires = bilet.Expiration;
HttpContext.Current.Response.Cookies.Add(oCk);

FormsAuthentication.RedirectFromLoginPage("alisveris_akaymaz",true);

Bu satırlarda öncelikle “Forum” uygulaması için bir FormsAuthenticationTicket nesnesi oluşturulmaktadır. Bu nesne, “Forum” uygulamasının güvenliğinden geçebilen bir kullanıcı adını içermektedir. Ardından RedirectFromLoginPage yordamı çağrılarak içinde bulunan “Alisveris” uygulaması için güvenlik cookie’sinin oluşturulması sağlanmıştır. Bu şekilde kullanıcı ilk olarak “Alisveris” uygulamasına girmişse hem “Alisveris” uygulaması için gerekli güvenlik cookie’si oluşturulmuş hem de “Forum” uygulamasına kolayca giriş yapabileceği bileti ona sağlanmıştır. Aynı şekilde kullanıcı girişi “Alisveris” üzerinde değil de “Forum” üzerinden olabilir düşüncesiyle “Aliveris” uygulamasının biletini de “Forum” uygulaması üzerinde hazırlamak gerekir.

FormsAuthenticationTicket bilet = new FormsAuthenticationTicket(1,
    "alisveris_akaymaz", DateTime.Now,
    DateTime.Now.AddYears(1), true, "");

HttpCookie oCk = new HttpCookie(".AlisverisBilgi");
oCk.Value = FormsAuthentication.Encrypt(bilet);
oCk.Expires = bilet.Expiration;
HttpContext.Current.Response.Cookies.Add(oCk);

FormsAuthentication.RedirectFromLoginPage("forum_akaymaz",true);

Oluşturulan cookie’ler aynı domain altındaki uygulamalar tarafından erişileceği sorun oluşturmayacaktır.
3) Aynı domain altındaki iki sub-domain arasında SSO
Üçüncü bir senaryo ise bu iki uygulamanın http://www.aliveris.com ve http://forum.alisveris.com şeklinde iki farklı domain altında çalıştığıyla ilgilidir. Bu durumda önceki senaryoda verilmiş olan kodlar çalışmayacaktır çünkü uygulamalara ait cookie’ler istemci tarafında farklı dosyalarda saklanıyor olacaktır. Her uygulama sadece kendi cookie’sini görüyor olur.
Böyle bir durumda sözkonusu cookie’leri domain seviyesinde oluşturmamız gerekir ki tüm alt domainler bu cookie’lere erişsin. Önceki kod satırlarımızı uygulamalar için aşağıdaki gibi düzenleyelim.

FormsAuthenticationTicket bilet = new FormsAuthenticationTicket(1,
    "forum_akaymaz", DateTime.Now,
    DateTime.Now.AddYears(1), true, "");

HttpCookie oCk = new HttpCookie(".ForumBilgi");
oCk.Value = FormsAuthentication.Encrypt(bilet);
oCk.Expires = bilet.Expiration;
oCk.Domain = ".alisveris.com";
HttpContext.Current.Response.Cookies.Add(oCk);

FormsAuthentication.RedirectFromLoginPage("alisveris_akaymaz",true);

“Forum” uygulaması için de aynı uyarlamayı yapalım.

FormsAuthenticationTicket bilet = new FormsAuthenticationTicket(1,
    "alisveris_akaymaz", DateTime.Now,
    DateTime.Now.AddYears(1), true, "");

HttpCookie oCk = new HttpCookie(".AlisverisBilgi");
oCk.Value = FormsAuthentication.Encrypt(bilet);
oCk.Expires = bilet.Expiration;
oCk.Domain = ".alisveris.com";
HttpContext.Current.Response.Cookies.Add(oCk);

FormsAuthentication.RedirectFromLoginPage("forum_akaymaz",true);

Satırlarda cookie’lerin domain değerini “.alisveris.com” olarak set etmemiz, bu cookie’lerin hem http://www.alisveris.com hem de http://forum.alisveris.com veya başka sub-domain’ler tarafından görülmesini sağlar. Bununla birlikte “Forum”a ait güvenlik cookie’sinin domain değerini “forum.alisveris.com” olarak set ederek bu cookie’nin diğer sub-domain’ler tarafından görülmesini de engelleyebiliriz.
Daha önce de ifade ettiğimiz gibi bu iki uygulamadaki anahtarların aynı olması gerekir.
4) .NET Framework’ün farklı sürümleri altında çalışan uygulamalar için SSO
Bu senaryoda .NET Framework’ün sürümleri authorization ticket için farklı şifreleme yöntemlerini kullandığı için önceki çözümler geçerli olmayacaktır. Örneğin ASP.NET 1.1 sürümü, 3DES (Triple Data Encryption Standard), ASP.NET 2.0 AES (Advanced Encryption Standard) encryption algoritmasını kullanır. Bu bilgiden dolayı ASP.NET 2.0’da çalışan bir uygulamanın ASP.NET 1.1’deki uygulamayla uyumlu çalışabilmesi için ASP.NET 2.0’ın cookie şifreleme algoritması 3DES olarak değiştirilmelidir. Bu amaçla ASP.NET 2.0 ile birlikte gelmiş bir özellikle olarak web.config dosyasında anahtarını aşağıdaki gibi düzenleyebiliriz.

decryption=”3DES” ifadesi, ASP.NET 2.0’ın eski şifreleme yöntemini kullanmasını sağlar.
5) Farklı domain altındaki uygulamalar için SSO
Örnek olarak kullandığımız “Alisveris” ve “Forum” uygulamalarının http://alisveris.com ve http://forum.com şeklinde farklı domainler altında olduğunu düşünelim. Bu durumda aynı cookie’yi veya her biri için ayrı oluşturulmuş cookie’leri paylaşamazlar. Bu senaryoda her uygulama diğerinden bağımsız kendi cookie’sini kullanır ve kullanıcının diğer uygulamada login olup olmadığı doğrulanır. Bunu yapmanın yolu da çeşitli yönlendirmeler yapmaktır. Bir uygulamadan diğerine geçileceği zaman kullanıcı daha önce belirlenmiş bir sayfaya gönderilir ve bu sayfada kullanıcının geldiği uygulamadan yetkilenip yetkilenmediği denetlenir. Bu sayfanın isminin sso.aspx olduğunu düşünelim.

  1. Kullanıcı, “Alisveris” uygulamasına bir request gönderir. Eğer kullanıcı tarafında “Alisveris” uygulamasına ait yetki cookie’si mevcutsa kullanıcı logon ettirilir ve sözkonusu request proses edilir.
  2. Eğer kullanıcıda yetki cookie’si bulunmuyorsa kullanıcı, “forum.com/sso.aspx” sayfasına yönlendirilir.
  3. “forum.com/sso.aspx” sayfası “Forum” uygulamasına ait yetki cookie’sinin olup olmadığını kontrol eder. Eğer yetki cookie’si mevcutse cookie’deki kullanıcı adını request’i gönderen sayfaya “SSOKullaniciAdi” parametresiyle query string olarak gönderir.
  4. Requestin gönderildiği sayfaya dönüldüğünde “SSOKullaniciAdi” parametresi kontrol edilir ve parametrenin değerine göre “Alisveris” tarafında da yetki cookie’si oluşturulur.

“Forum” uygulamasının sso.aspx sayfasını aşağıdaki gibi oluşturalım.

// Bu sayfayı çağıran URL adresini öğrenelim. Kullanıcıyı yeniden o adrese yönlendireceğiz
UriBuilder uri = new UriBuilder(Request.UrlReferrer);

HttpCookie oCk = HttpContext.Current.Request.Cookies[".ForumBilgi"];

// Cookie var ise
if (oCk != null)
{
    try
    {
        string ckDeger = HttpContext.Current.Server.UrlDecode(oCk.Value);
        FormsAuthenticationTicket bilet = FormsAuthentication.Decrypt(ckDeger);

        // Kullanıcı adını query string olarak gönderelim.
        uri.Query = uri.Query + "&SSOKullaniciAdi" + bilet.Name;
    }
    catch
    {

    }
}

//Sayfanın çağrıldığı yere geri dönelim.
Response.Redirect(uri.ToString());

“Alisveris” uygulamasında Global.asax içerisinde Application_BeginRequest yordamı içerisinde aşağıdaki gibi düzenleme yapalım.

// Kullanıcının login olup olmadığını kontrol edelim
HttpCookie ck = HttpContext.Current.Request.Cookies[".AlisverisBilgi"];

//Yetki cookie'si varsa
if (ck != null) //
{
    try
    {
        string ckDeger = HttpContext.Current.Server.UrlDecode(ck.Value);
        FormsAuthenticationTicket blt = FormsAuthentication.Decrypt(ckDeger);
        //Cookie'nin içeriği çözüldü. Kullanıcının requestini proses etmeye devam et
        return;
    }
    catch
    {
    }
}

// Eğer yetki cookie'si yoksa forum.com'a kullanıcının oraya login olup olmadığını soralım
UriBuilder uri = new UriBuilder(Request.UrlReferrer);

// Forum uygulamasında döngüye girmesin diye
if (uri.Host != "forum.com" || uri.Path != "/sso.aspx")
{
    Response.Redirect("http://forum.com/sso.aspx");
}
else
{
    //Bu nokta, forum.com'dan geri dönüldüğünde çalışacak noktadır.
    //forum.com'dan gelen bilgileri kontrol edelim
    if (Request.QueryString["SSOKullaniciAdi"] == null)
    {
        // forum.com da yetki cookie'sini sahip değil
        return; // Kullanıcı login ettirilmeden devam edilir.
    }
    else
    {

        // Kullanıcı forum.com'a login olmuştur. Bu durumda kullanıcıya ait kullanıcını adını alacağız
        string kullaniciAdi = (string)Request.QueryString["SSOKullaniciAdi"];

        // Alisveris uygulaması için yetki cookie'sini oluşturalım
        FormsAuthenticationTicket bilet = new FormsAuthenticationTicket(1,
            kullaniciAdi, DateTime.Now,
            DateTime.Now.AddYears(1), true, "");

        HttpCookie oCk = new HttpCookie(".AlisverisBilgi");
        oCk.Value = FormsAuthentication.Encrypt(bilet);
        oCk.Expires = bilet.Expiration;
        HttpContext.Current.Response.Cookies.Add(oCk);
    }
}

Bu kodların her iki uygulamada da o uygulamaya ait cookie adına göre uyarlanmış olarak mevcut olması gerekir. Böylece iki uygulama tarafından yetki kontrolü yapılmış olur. Bu senaryoda görüldüğü gibi herhangi bir cookie paylaşımı yapılmamaktadır ve uygulamalara ait encryption ve validation anahtarlarının aynı olma zorunluluğu olmadığı için uygulamalar farklı anahtar değerine sahip olması da sorun teşkil etmez.
Bu çözümde, kullanıcını adını diğer uygulamaya query string olarak gönderiyor olmamız bir güvenlik açığı olarak değerlendirilebilir. Bunun için iki koruma sağlanabilir. Birincisi ki bizim de uygulamadığımız gibi her zaman referrer sayfa yani requesti gönderen sayfanın kim olduğu kontrol edilir. Örneğimizde alisveris.com/sso.aspx veya forum.com/sso.aspx sayfaları dışında bir kaynaktan gelecek olan “SSOKullaniciAdi” parametresi kabul edilmemektedir. İkinci koruma olarak query string değeri uygulamalar arasındaki ortak bir anahtar üzerinden encrypt edilerek karşı tarafa gönderilebilir.
6) Mixed-mode authentication (Forms ve Windows) için SSO
Şu ana kadarki senaryolar hep Forms authentication üzerine kuruluydu. Fakat bazı durumlarda internet kullanıcılarını öncelikle Forms authentication ile içeri almak eğer işlem başarısız olursa intranet kullanıcısı olup olmadığını öğrenmek üzere kullanıcının domain üzerinde yetkilenip yetkilenmediği kontrol edilmek istenebilir. Bu durumda aşağıdaki parametreyi kullanarak kullanıcının Windows kullanıcı adı elde edilir.
Request.ServerVariables["LOGON_USER"]
Bu parametre, uygulama, Anonymous Access seçeneği disable olmadığı sürece boş değer döndürür. IIS üzerinde sözkonusu uygulama için bu seçeneği disable, Integrate Windows Authentication seçeneğini de enable ederek varsa LOGON_USER parametresinin intranet’e login olmuş o anki domain kullanıcısını göstermesini sağlayabiliriz. Fakat tüm internet kullanıcılarının domain üstünde tanımlanmış olması mantıklı bir durum olmadığı için internet kullanıcılarını Forms authentication ile intranet kullanıcılarını ise Windows authentication ile içeri alacak bir yöntem geliştirmeliyiz.
Bunu sağlamak için intranet kullanıcılarına özgü bir giriş sayfası yapmak daha doğru olacaktır. Bu giriş sayfası Integrate Windows Authentication seçeneği enable edilmiş olacak çalışacaktır. Bu sayfa altında kullanıcının domain user olup olmadığı kontrol edilir, yetki cookie’si yaratılır ve kullanıcı ana siteye yönlendirilir.
Aynı uygulamada hem Windows Authentication hem de Forms Authentication yöntemini kullanmak için şu şekilde bir yönlendirme yapılabilir.

  1. Sözkonusu web uygulaması için IIS üzerinde sadece “Anonymous access” seçeneği aktifleştirilir. Bunun haricinde başka bir authentication seçeneği set edilmez.
  2. ASP.NET tarafında uygulamanın yetkilendirme mekanizması web.config dosyasında “Forms Authentication” olarak set edilir ve login sayfası olarak örnek olarak “weblogin.aspx” sayfası seçilir.
  3. Uygulama içerisinde yine örnek olarak “winlogin.aspx” sayfası oluşturulur ve IIS üzerinde sadece bu sayfa için “Anonymous access” seçeneği kaldırılır “Integrated Windows authentication” seçeneği set edilir.
  4. weblogin.aspx sayfasının Page_Load() yordamında istemcinin intranet’ten gelip gelmediği kontrol edilir. Bunun için örneğin kullanıcının IP bilgisine bakılabilir. Eğer kullanıcının IP’si, kurumun IP aralığındaysa kullanıcı winlogin.aspx sayfasına yönlendirilir, değilse weblogin.aspx sayfasında bırakılır.
  5. Dışarıdan gelen kullanıcılar, varsayılan olarak weblogin.aspx sayfasına gelecektir. Eğer bu sayfadaki form üzerinden login olurlarsa istemciye bir FormsAuthentication ticket verilmiş olur.
  6. İçeriden gelen kullanıcılar otomatik olarak weblogin.aspx sayfası üzerinde winlogin.aspx sayfasına yönlendirilmiş olur. IIS, otomatik olarak kullanıcının bu sayfaya erişip erişemediğini kontrol eder eğer kullanıcı domainde değilse yani sayfayı otomatik olarak görme yetkisi yoksa ondan Windows kullanıcı adı ve şifre istenilir eğer varsa IIS, istemciye ait network identity bilgisini http üzerinden taşır. Bu bilgiye LOGON_USER parametresiyle erişilir. Bize düşen, bu bilgiyi alıp veritabanımızla karşılaştırmak ve ona göre FormsAuthentication ticket oluşturmaktır.

Bu yazı için Michael Morozov’ın ilgili yazısı referans alınmıştır.

.NET’te Single Sign-On (SSO)” üzerine 6 düşünce

  1. Hüseyin KÜÇÜK

    hocam ellerine sağlık tam araştırmaya başladığım konuydu, hazıra konmuş gibi oldum :)

    Cevapla
  2. Ali Kadir BAĞCIOĞLU

    Merhaba,
    IIS üzerinden Sayfa bazında Auth. ayarlamayı yapamadım. Acaba IIS den sayfa bazında Auth. nasıl atanabiliyor?
    takıldığım yer 3. madde;
    Uygulama içerisinde yine örnek olarak “winlogin.aspx” sayfası oluşturulur ve IIS üzerinde sadece bu sayfa için “Anonymous access” seçeneği kaldırılır “Integrated Windows authentication” seçeneği set edilir.

    Cevapla

Bir cevap yazın

E-posta hesabınız yayımlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir

Time limit is exhausted. Please reload CAPTCHA.