Archiv der Kategorie: Authentication

SimpleMembership mit ASP.NET MVC4 und EF


Wenn es mal wieder schnell gehen soll und man nicht viel Zeit in eine eigene Benutzerverwaltung investieren möchte, dann ist man beim SimpleMembership Provider für ASP.NET Projekte genau richtig.

Aber was macht bzw. ist der SimpleMembership Provider eigentlich?

Wenn man eine neue ASP.NET MVC4 Webseite für das Internet erstellt, dann ist hier bereits der SimpleMembership Provider “enthalten” und muss nur noch entsprechend konfiguriert werden. Dabei stellt der Provider bereits fertige Funktionen wie “WebSecurity.CreateUserAndAccount”, “WebSecurity.Login”, … zur Verfügung. Der Provider erstellt beim erstmaligen Ausführen eigene Tabellen in der angegebenen Datenbank und verwaltet damit die passenden Passwörter, Rollen, … Damit ist eine einfache Nutzerverwaltung innerhalb von ein paar Minuten konfiguriert und man hat Zugriff auf alle wichtigen Funktionen die man für eine einfache Benutzerverwaltung benötigt.

Man muss bei älteren Webprojekten zusätzlich aufpassen, denn es gibt einen “alten” MemberShip Provider aus ASP.NET 2.0 Zeiten welcher anders implementiert wird, hier muss man die Datenbanktabellen z.B. über ein extra Tool anlegen, außerdem wird kein OAuth unterstützt.

Einen sehr ausführlichen Artikel zum Thema SimpleMembership hat z.B. Jon Galloway geschrieben, dieser Artikel verschafft einen sehr guten Überblick über die Thematik.

Einbinden des SimpleMembership Providers in ein ASP.NET MVC Projekt mit EF

Im Folgenden will ich kurz erläutern wie man den bereits “fertigen” SimplemMembership Provider in einem MVC4 Projekt mit EF einrichtet und einige “unnötige” Abhängigkeiten wie einen zusätzlichen DB Context entfernt.

Dazu erstellen wir zuerst ein neues ASP.NET MVC4 Internet Projekt.

image

In dem neu erstellten ASP.NET MVC Projekt gibt es unter Filters die Datei “InitializeSimpleMembershipAttribute.cs” hier findet man den folgenden Aufruf:


 WebSecurity.InitializeDatabaseConnection("DefaultConnection", "UserProfile", "UserId", "UserName", autoCreateTables: true);

In diesen Aufruf tragen wir später unseren passenden Connectionstring und die Tabelle sowie die Spaltennamen für UserID und UserName ein.

image

Als nächstes legen wir erst einmal ein EF5 Model an in einer extra Klassenbibliothek an

image

Dafür wählen wir ein leeres Model

image

und erstellen eine neue Entität für das Model, den “Member” dieser wird später benötigt um unserem SimpleMembership Provider zu sagen wo der Username und die passende UserId stehen.

image

Da ich in meinen Projekten nie auf den eingebauten Connectionstring zurückgreife, sondern diesen lieber selbst “zusammenbaue” fügen wir unserer Klassenbibliothek in der sich das EF Model befindet noch eine weitere Datei “MemberModelContainer.cs” hinzu die unseren Context um einen weiteren Konstruktor erweitert. Diesem Konstruktor können wir direkt einen ConnectionString übergeben, den wir selbst zusammengebaut haben.

image

public partial class MemberModelContainer
{
    public MemberModelContainer(string connectionString) : base(connectionString)
    {
    }
}

Jetzt fügen wir unserer Webseite einen Verweis auf unser neues Projekt “Member.EF.Model” hinzu und erweitern die Web.config unter den appSettings um die folgenden Werte:


Damit wir jetzt überall auch auf unser Model zugreifen können, erstellen wir eine weitere Helper Klasse “CurrentHttpContext” die uns für den jeweiligen Request das aktuelle EF Model zur Verfügung stellt, mit den angegebenen Verbindungsdaten aus der Web.config.

public class CurrentHttpContext
{
    #region Properties
    private static string m_httpContextName = "EFDataModelHttpContextName";
    #endregion

    ///
    /// Gibt den aktuellen DataModelContainer für diesen Request zurück.
    ///
    public static MemberModelContainer GetDataModel()
    {
         //Den aktuellen dataContext nur für den aktuellen Request speichern
        if (HttpContext.Current.Items[m_httpContextName] == null)
        {
            //Connectionstring wird anhand des ModelNamens aus der web.config geladen
            MemberModelContainer efModelContainer = new MemberModelContainer(GetConnectionString("MemberModel"));
            HttpContext.Current.Items.Add(m_httpContextName, efModelContainer);
        }

        return (MemberModelContainer)HttpContext.Current.Items[m_httpContextName];
    }

    ///
    /// Gibt den Connectionstring zur DB zurück für das EntityFramework
    ///
    public static string GetConnectionString(string modelName)
    {
        //Initialize the EntityConnectionStringBuilder.
        EntityConnectionStringBuilder entityBuilder = new EntityConnectionStringBuilder();
        //Providernamen setzten
        entityBuilder.Provider = "System.Data.SqlClient";
        //Connectionstring setzten
        entityBuilder.ProviderConnectionString = GetSqlBuilder().ToString();
        //Metadaten setzten.
        entityBuilder.Metadata = string.Format(@"res://*/{0}.csdl|res://*/{0}.ssdl|res://*/{0}.msl", modelName);
        return entityBuilder.ConnectionString;
    }

    public static SqlConnectionStringBuilder GetSqlBuilder()
    {
        SqlConnectionStringBuilder sqlBuilder = new SqlConnectionStringBuilder();
        //Die Zugangsdaten aus der Web.Config laden, nicht den Connectionstring verwenden.
        sqlBuilder.DataSource = ConfigurationManager.AppSettings["DBDataSource"]; ;
        sqlBuilder.InitialCatalog = ConfigurationManager.AppSettings["DBCatalog"]; ;
        sqlBuilder.UserID = ConfigurationManager.AppSettings["DBUser"];
        sqlBuilder.Password = ConfigurationManager.AppSettings["DBPassword"];
        //Wichtige Option damit mehrere Abfragen geöffnet werden können
        //um Subabfragen ausführen zu können von Untergordneten Projekten
        sqlBuilder.MultipleActiveResultSets = true;
        sqlBuilder.IntegratedSecurity = false;
        //Dient zur Identifizierung der DB Connection/Application wenn man z.B: Fehler auf dem DB Server sucht.
        sqlBuilder.ApplicationName = "MemberManagement";

        return sqlBuilder;
    }
}

Jetzt können wir überall auf unsere eigenen Modeldaten zugreifen. Als nächstes erstellen wir die passende Datenbank aus unseren Modeldaten. Um unsere DB zu erstellen, fügen wir einfach in der Global.asax unter “Application_Start” folgendes hinzu:

MemberModelContainer container = new MemberModelContainer(CurrentHttpContext.GetConnectionString("MemberModel"));
container.Database.CreateIfNotExists();

(Privat nutze ich diese Funktion nur wenn ich entwickle, im Livebetrieb erstelle ich mir das passende SQL Script über das edmx Model und erstelle darüber meine Datenbank einmalig.)

Jetzt konfigurieren wir noch die Standardeinstellungen des SimpleMembership Providers und entfernen Code der nicht benötigt wird.

Als erstes bearbeiten wir die Datei “InitializeSimpleMembershipAttribute.cs”, hier wird einiges in die Global.asax ausgelagert, dazu gehört auch der Funktionsaufruf “LazyInitializer.EnsureInitialized”, denn diese Zeile muss nur einmal ausgeführt werden und dies lässt sich in der Global.asax im Application_Start am besten umsetzten.

Damit sieht die “InitializeSimpleMembershipAttribute.cs” am Ende folgendermaßen aus:

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public sealed class InitializeSimpleMembershipAttribute : ActionFilterAttribute
{
    public class SimpleMembershipInitializer
    {
        public SimpleMembershipInitializer()
        {
            try
            {
                  WebSecurity.InitializeDatabaseConnection(CurrentHttpContext.GetSqlBuilder().ConnectionString, "System.Data.SqlClient", "MemberSet", "Id", "Username", autoCreateTables: true);
            }
            catch (Exception ex)
            {
                throw new InvalidOperationException("The ASP.NET Simple Membership database could not be initialized. For more information, please see http://go.microsoft.com/fwlink/?LinkId=256588", ex);
            }
        }
    }
}

Wichtig ist in der Funktion “SimpleMembershipInitializer” der Aufruf für

WebSecurity.InitializeDatabaseConnection(CurrentHttpContext.GetSqlBuilder().ConnectionString, „System.Data.SqlClient“, „MemberSet“, „Id“, „Username“, autoCreateTables: true);” Hier wird unser Connectionstring angegeben, sowie der Name der Tabelle in der die Userdaten gespeichert werden und die Spalte mit der “UserId” sowie mit dem “Benutzernamen”. Diese Funktion muss entsprechend eurer eigenen Modeldaten angepasst werden. Außerdem erstellt, diese Funktion auch die passenden DB Tabellen die der SimpleMembership Provider benötigt.

Aufgrund dieser Informationen, ist der Provider dann in der Lage alle wichtigen Funktionen wie Login, Logout, Register, … uns in einem einfachen Aufruf zur Verfügung zu stellen.

Über die Klasse “WebSecurity” haben wir Zugriff auf alle wichtigen Account Funktionen und über die Klasse “Roles” haben wir Zugriff auf die Rollenverwaltung für die User, die der SimpleMembership Provider ebenfalls enthält.

Die angepasste Global.asax sieht dann folgendermaßen aus:

public class MvcApplication : System.Web.HttpApplication
{
    private static InitializeSimpleMembershipAttribute.SimpleMembershipInitializer _initializer;
    private static object _initializerLock = new object();
    private static bool _isInitialized;

    protected void Application_Start()
    {
        AreaRegistration.RegisterAllAreas();

        WebApiConfig.Register(GlobalConfiguration.Configuration);
        FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
        RouteConfig.RegisterRoutes(RouteTable.Routes);
        BundleConfig.RegisterBundles(BundleTable.Bundles);
        AuthConfig.RegisterAuth();

        MemberModelContainer container = new MemberModelContainer(CurrentHttpContext.GetConnectionString("MemberModel"));
        container.Database.CreateIfNotExists();

        LazyInitializer.EnsureInitialized(ref _initializer, ref _isInitialized, ref _initializerLock);

    }
}

Außerdem kann der folgende Code aus der “AccountModel.cs” entfernt werden, da wir unser eigenes Model und eigenen DB Context verwenden:

public class UsersContext : DbContext
 {
        public UsersContext()
            : base("DefaultConnection")
        {
        }

        public DbSet UserProfiles { get; set; }
  }

    [Table("UserProfile")]
    public class UserProfile
    {
        [Key]
        [DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
        public int UserId { get; set; }
        public string UserName { get; set; }
    }

In der “AccountController.cs” muss noch die Funktion “ExternalLoginConfirmation” auf unser Model angepasst werden

[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public ActionResult ExternalLoginConfirmation(RegisterExternalLoginModel model, string returnUrl)
{
    string provider = null;
    string providerUserId = null;

    if (User.Identity.IsAuthenticated || !OAuthWebSecurity.TryDeserializeProviderUserId(model.ExternalLoginData, out provider, out providerUserId))
    {
        return RedirectToAction("Manage");
    }

    if (ModelState.IsValid)
    {
        // Insert a new user into the database
        using (MemberModelContainer db = CurrentHttpContext.GetDataModel())
        {
            var user = db.MemberSet.FirstOrDefault(u => u.Username.ToLower() == model.UserName.ToLower());
            // Check if user already exists
            if (user == null)
            {
                // Insert name into the profile table
                db.MemberSet.Add(new global::Member.EF.Model.Member() { Username  = model.UserName, Vorname = "", Nachname = ""});
                db.SaveChanges();

                OAuthWebSecurity.CreateOrUpdateAccount(provider, providerUserId, model.UserName);
                OAuthWebSecurity.Login(provider, providerUserId, createPersistentCookie: false);

                return RedirectToLocal(returnUrl);
            }
            else
            {
                ModelState.AddModelError("UserName", "User name already exists. Please enter a different user name.");
            }
        }
    }

    ViewBag.ProviderDisplayName = OAuthWebSecurity.GetOAuthClientData(provider).DisplayName;
    ViewBag.ReturnUrl = returnUrl;
    return View(model);
}

Und die Registerfunktion muss ebenfalls angepasst werden, denn hier muss der Vorname und Nachname mit in der DB eingetragen werden, sonst kommt es zu einem Fehler beim Anlegen des Users.

[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public ActionResult Register(RegisterModel model)
{
    if (ModelState.IsValid)
    {
        // Attempt to register the user
        try
        {
            //TODO  RegisterModel und View um Vorname und Nachname erweitern, dann kann dies hier mit ausgfüllt werden.
            WebSecurity.CreateUserAndAccount(model.UserName, model.Password, new { Vorname = "TestVorname", Nachname="TestNachname" });
            WebSecurity.Login(model.UserName, model.Password);
            return RedirectToAction("Index", "Home");
        }
        catch (MembershipCreateUserException e)
        {
            ModelState.AddModelError("", ErrorCodeToString(e.StatusCode));
        }
    }

    // If we got this far, something failed, redisplay form
    return View(model);
}

Außerdem kann das Attribut “[InitializeSimpleMembership]” über der AccountController Klasse entfernt werden, denn die Initialisierung des SimpleMembership Providers, findet jetzt in der Global.asax statt.

Wenn wir jetzt unsere Webseite entsprechend ausführen und einen neuen User Registrieren, sieht unsere Datenbank dann folgendermaßen aus:

image

Die Tabelle “dbo.MemberSet” wurde von unserem EF Model angelegt und die restlichen Tabellen vom SimpleMembership Provider.

Dipendency Injection und TDD mit dem SimpleMembership Provider

Da der SimpleMembership Provider nicht über einen Konstruktor initialisiert werden kann, ist es nicht möglich diesen per DI zu initialisieren. Da ich versuche in meinen aktuellen Projekten auf DI zu setzten, überlege ich aktuell ob ich wirklich auf den SimpleMembership Provider setzte oder einfach ein eigenes UserRepository erstelle, wer hier bereits entsprechende Erfahrungen gemacht hat, würde ich mich über einen Kommentar freuen.

Um den SimpleMembership Provider zu testen, habe ich nur den folgenden Artikel gefunden, der angibt wie man den SimpleMembership Provider zumindest “testen” kann.

Auch den folgenden Artikel darüber ob man auf den SimpleMembership Provider setzten sollte fand ich zumindest sehr informativ.

Das fertige Projekt kann wie immer über meinen Codeplex Account angeschaut werden unter:

MVC –> SimpleMembershipProvider

MVC 4 Web API Custom Authorize Attribut


Wie bereits in meinem letzten Artikel erwähnt, benötigt man für Web API ein eigenes Authorize Attribut aus einem anderen .NET Namespace. Das hier verwendete Authorize Attribut kommt aus dem Namespace “System.Web.Http.AuthorizeAttribute”.

Auch hier bietet Microsoft wieder eine Standardlösung an, die nur prüft ob der User eingeloggt ist, dafür muss wie bisher gewohnt im Web API Controller das Authorize Attribut aus dem oben genannten Namespace verwendet werden.

Gibt man mit seinen Web API Aufrufen aber Nutzerspezifische Daten zurück, die nicht jeder eingeloggte User abrufen darf, wird hier wieder ein eigenes Authorize Attribut benötigt. Hier handelt es sich um den gleichen Aufbau wie beim normalen Authorize Attribut. Man kann wieder auf den Controler, Action, Id und sonstige Routing Informationen zugreifen und dann entsprechend im Custom Authorize Attribut darauf reagieren. Damit man weiß welcher User eingeloggt ist muss auch hier wieder das “FormsAuthentication.SetAuthCookie” beim normalen Seitenlogin benutzt werden.

Ich möchte zusätzlich noch darauf hinweisen, das ich für meine Web API Aufrufe nicht die Standardroute verwende. Damit wir die Web API auch so nutzen können wie wir es bisher von den MVC Controllern gewohnt sind, müssen wir noch eine weitere Route zu unserer WebApi hinzufügen. Denn mit der Standardroute für API aufrufe sind nur GET/POST/DELETE Abfragen möglich. Aber meist hat man mehr wie nur diese “paar” Abfragen die man für einen View benötigt, daher habe ich noch eine neue Route vor der “DefaultApi” Route hinzugefügt.

public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        //Registrieren unseres Filters für die Modelvalidation für AJAX Requests
        config.Filters.Add(new ValidateAttribute());

        //Alternatives Routing, hier ist es jetzt wieder möglich gezielte Actionen und Controller abzufragen
        //In der DefaultApi kann man nur Get/Post/Delete abfragen und ist damit sehr begrenzt was die Abfragen angeht.
        config.Routes.MapHttpRoute(
            name: "AlternateWebApi",
            routeTemplate: "api/{controller}/{action}/{id}",
            defaults: new { id = RouteParameter.Optional }
        );

        //Standardroute  - hier lässt sich nur GET/POST/DELETE abfragen.
        config.Routes.MapHttpRoute(
            name: "DefaultApi",
            routeTemplate: "api/{controller}/{id}",
            defaults: new { id = RouteParameter.Optional }
        );
    }
}

Unser Benutzerdefiniertes Web API Authorize Attribut sieht dann folgendermaßen aus und hier darauf achten das man das richtige Authorize Attribut aus dem richtigen Namespace einbindet!

using AuthorizeAttribute = System.Web.Http.AuthorizeAttribute;

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class CustomWebApiAuthorizeAttribute : AuthorizeAttribute
{
    #region Member
    private HttpActionContext CurrentContext { get; set; }
    #endregion

    /// <summary>
    /// Prüfen ob der User auch Autorisiert ist, auf die passenden API Aufrufe zuzugreifen
    /// </summary>
    public override void OnAuthorization(HttpActionContext actionContext)
    {
        //Wenn der User nicht eingeloggt ist, dann wird er hier schon mit einem 401 abgewiesen
        base.OnAuthorization(actionContext);

        //Der Filtercontext wird benötigt um auf die RequestDaten zuzugreifen, z.b. auf die UserId die zugegriffen
        CurrentContext = actionContext;
       

        //Wenn kein User eingeloggt ist, dann hat er auch keinen Zugriff.
        if (!HttpContext.Current.User.Identity.IsAuthenticated)
        {
            //Wenn der User nicht eingeloggt ist, auf die Loginseite verweisen
            HandleUnauthorizedRequest(actionContext);
        }
        else
        {
            //Prüfen der passenden Rechte für die einzelnen Actions/Methoden
            //Wenn der User nicht für den View/Action authentifiziert ist, dann auf die Loginseite verweisen.
            if (!CheckRights())
            {
                HandleUnauthorizedRequest(actionContext);
            }
        }

        //Wenn alles i.o. ist "nichts" unternehmen und einfach beim Aufbau der Seite weitermachen.
    }

    /// <summary>
    /// Den unautorisierten Eingriff, "abwehren"
    /// </summary>
    /// <param name="actionContext"></param>
    protected override void HandleUnauthorizedRequest(System.Web.Http.Controllers.HttpActionContext actionContext)
    {
        var challengeMessage = new System.Net.Http.HttpResponseMessage(System.Net.HttpStatusCode.Unauthorized);
        throw new HttpResponseException(challengeMessage);
    }

    #region Private Functions
    /// <summary>
    /// Prüfen um welchen CurrentController es sich handelt und die passende Sicherheitsprüfung vornehmen
    /// Hier werden Nur API Controller überprüft
    /// </summary>
    /// <returns>TRUE->Darf zugreifen | FALSE->Darf nicht zugreifen</returns>
    private bool CheckRights()
    {
        //Setzen des aktuellen Actionnamen, der aufgerufen wird.
        string actionName = CurrentContext.ActionDescriptor.ActionName;
        //Laden des Controllernamens über den der API Aufruf kam
        string controllerName = CurrentContext.ControllerContext.ControllerDescriptor.ControllerName;
        //Auslesen der ID, wenn eine in der URL mit übergeben wurde.
        string id = CurrentContext.ControllerContext.RouteData.Values.ContainsKey("id") ?
                    CurrentContext.ControllerContext.RouteData.Values["id"].ToString() : null;

        //Den aktuellen User ermitteln der den API Aufruf gestartet hat.
        string username = HttpContext.Current.User.Identity.Name;

        //TODO Prüfung vornehmen ob der Aktuelle User auf die ermittelten Resourcen zugreifen darf.

        return true;
    }
    #endregion
}

Das Attribut kann dann wieder problemlos über dem passenden ApiController verwendet werden

[CustomWebApiAuthorize]
public class WorktimeApiController : ApiController
{
    /// <summary>
    /// Filtert unsere Arbeitszeiteinträge nach den letzten Einträgen die vorgenommen wurde.
    /// </summary>
    /// <param name="id">Die zugehörige UserId zu der die Einträge gefiltert werden sollen</param>
    /// <param name="value">Unsere Modeldaten nach denen gefiltert werden soll</param>
    public HttpResponseMessage ByEntryFilter(long id, WorktimeEntryFilterModel value)
    {
        if (value != null)
        {
            WorktimeListModel worktime = new WorktimeListModel(id, value);
            //Den passen Listentyp speichern, wenn der jeweilige Filter ausgewählt wurde.
            worktime.SaveDefaultListType(id, EWorktimeListTypes.ByEntries);
            worktime.FilterWorktimeList();

            //Wird automatisch in JSON Objekt umgewandelt und gibt den StatusCode 200 für OK zurück!
            HttpResponseMessage response = Request.CreateResponse<WorktimeListModel>(HttpStatusCode.OK, worktime);
            return response;
        }

        //Statuscode 400 Zurückgeben
        throw new HttpResponseException(HttpStatusCode.BadRequest);
    }

...

ASP.NET MVC 4 Custom Authorize Attribute


Wie bereits in einem älteren Artikel von mir beschrieben gibt es hier noch einmal eine kleine Auffrischung der Materie inklusive eines Beispiels auf Codeplex.

Wozu ist das Authorize Attribut eigentlich da? Das Authorize Attribut kümmert sich darum, das nur autorisierte (eingeloggte) User den “Internen” Bereich einer Webseite anschauen können, dabei kann es sich z.B. um den Administratorbereich handeln.

Microsoft stellt von Haus aus bereits ein “Authorize” Attribut zur Verfügung was aber standardmäßig nur prüft ob der User auch eingeloggt ist. Wichtig ist hier das beim Login “FormsAuthentication.SetAuthCookie” verwendet wird, wenn der Login erfolgreich war. Denn damit weiß dann auch das Standard “Authorize” Attribut ob der User auf die jeweilige Aktion/Controller zugreifen darf.

Wenn man das “Authorize” Attribut direkt auf den ganzen Controller anwendet, kann man einfach mit einem “AllowAnonymous” Attribut über der jeweiligen Action diese Action wieder “freigeben” und auch ein User der nicht eingeloggt ist kann dann auf diese Action zugreifen.

Im Folgenden Beispiel sieht man ein Beispiel mit dem Standard “Authorize” Attribut, welches auf den gesamten Controller angewendet wird. Damit sich dann ein User auch noch einloggen kann, muss die Login Funktion mit einem “AllowAnonymous” Attribut bestückt werden. Außerdem sieht man bei der Loginmethode die Verwendung von “FormsAuthentication.SetAuthCookie”.

[Authorize]
public class AccountController : Controller
{
    [AllowAnonymous]
    public ActionResult Login(string returnUrl)
    {
        ViewBag.ReturnUrl = returnUrl;
        return View();
    }

    [AllowAnonymous]
    [HttpPost]
    public ActionResult Login(LoginModel model, string returnUrl)
    {
        if (ModelState.IsValid)
        {
            //ACHTUNG HIER müsste vorher nat. ein DB Abgleich mit den Usern aus der DB stattfinden
            //Aktuell setzten wir hier jeden User als Authorisiert, wenn er einen beliebigen Usernamen und PW angibt.
            FormsAuthentication.SetAuthCookie(model.UserName, model.RememberMe);
            
            if (Url.IsLocalUrl(returnUrl))
            {
                return Redirect(returnUrl);
            }
            else
            {
                return RedirectToAction("Index", "Home");
            }

        }

        // If we got this far, something failed, redisplay form
        return View(model);
    }

    public ActionResult LogOff()
    {
        FormsAuthentication.SignOut();

        return RedirectToAction("Index", "Home");
    }

Dann gibt es noch die Möglichkeit ein eigenes Authorize Attribut zu erstellen in dem man von “AuthorizeAttribute” Attribut ableitet (System.Web.Mvc.AuthorizeAttribute).

Ein eigenes Authorize Attribut ist z.B. notwendig wenn man noch zusätzliche Rechte bei einigen Actions oder ganzen Controllern für einzelne User überprüfen muss. Eine Seite die einen eigenen Admin Bereich hat, benötigt eine Extra Prüfung der Rechte für den Admin Bereich, denn hier hat nicht jeder Autorisierte Nutzer auch Zugriff, sondern nur spezielle Nutzer denen das Recht über die eigene Rechteverwaltung zugewiesen wurde. Dafür gibt es dann das eigene Authorize Attribut.

Bei der hier gezeigten Variante setzt Ihr nur das Authorize Attribut auf den jeweiligen Controller oder Action und eine Prüfung welcher User bzw. welche Rolle auf diesen Controller Zugriff hat, wird alles im Authorize Attribut selbst erledigt. Denn dort können wir auch sehen welche Action, Controller, Id oder Custom Route Values gerade aufgerufen wurde und entsprechend eure Rechte prüfen.

Ich finde der Vorteil an dieser Umsetzung liegt vor allem darin das ich eine zentrale Stelle habe an der ich meine Rechte für Alle Controller und Actions verwalten und setzten kann. Ich bin mit dieser Variante bisher sehr gut zurecht gekommen.

using AuthorizeAttribute = System.Web.Mvc.AuthorizeAttribute;

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class CustomAuthorizeAttribute : AuthorizeAttribute
{
    #region Member
    private AuthorizationContext CurrentContext { get; set; }
    #endregion

    #region Konstruktor
    /// <summary>
    /// Es muss nichts übergeben werden, da controller und action direkt hier ausgelesen werden können
    /// </summary>
    public CustomAuthorizeAttribute()
    {
    }
    #endregion

    #region Public Functions
    /// <summary>
    /// Funktion die ausgeführt wird, wenn das Attribut "genutzt" wird, das Authorisation Attribut wird immer als erstes Attribut ausgeführt.
    /// </summary>
    public override void OnAuthorization(AuthorizationContext filterContext)
    {
        base.OnAuthorization(filterContext);
        //Der Filtercontext wird benötigt um auf die RequestDaten zuzugreifen, 
        //z.b. auf die Id, Action oder den Controller auf den zugegriffen werden soll.
        CurrentContext = filterContext;
      
        //Wenn kein User eingeloggt ist, dann hat er auch keinen Zugriff.
        if (!HttpContext.Current.User.Identity.IsAuthenticated)
        {
            //Wenn der User nicht eingeloggt ist, auf die Loginseite verweisen
            filterContext.Result = new HttpUnauthorizedResult();
        }
        else
        {
            //Prüfen der passenden Rechte für die einzelnen Actions/Methoden 
            if (!CheckRights())
            {
                //Auf die Startseite verweisen, wenn der User keinen Zugriff auf den Kontent hat, den er angefordert hat.
                filterContext.Result = new HttpUnauthorizedResult();
                //Oder auf eine beliebige Andere Webseite verweisen, wenn man nicht unbefingt die Startseite anzeigen möchte.
                //filterContext.Result = new RedirectResult("~/Home/Index");
            }
        }

        //Wenn alles i.o. ist "nichts" unternehmen und einfach beim Aufbau der Seite weitermachen.
    }
    #endregion

    #region Private Functions
    protected void HandleUnauthorizedRequest(System.Web.Http.Controllers.HttpActionContext actionContext)
    {
        var challengeMessage = new System.Net.Http.HttpResponseMessage(System.Net.HttpStatusCode.Unauthorized);
        throw new HttpResponseException(challengeMessage);
    }

    /// <summary>
    /// Prüfen um welchen Controller es sich handelt und die passende Sicherheitsprüfung vornehmen
    /// </summary>
    /// <returns>TRUE->Darf zugreifen | FALSE->Darf nicht zugreifen</returns>
    private bool CheckRights()
    {
        //Wir können hier recht einfach ermitteln um welchen Controller oder View es sich handelt den wir gerade 
        //überprüfen wollen - wenn wir erst einmal von der Standardroute ausgehen.
        string controllerName = CurrentContext.RouteData.GetRequiredString("controller");
        string actionName = CurrentContext.RouteData.GetRequiredString("action");
        string id = CurrentContext.RouteData.Values.ContainsKey("id") ? CurrentContext.RouteData.Values["id"].ToString() : null;

        //Den aktuell eingeloggten User ermitteln für den die Rechte geprüft werden sollen 
        //Der wert der im Username steht legen wir beim Login fest
        //bei "FormsAuthentication.SetAuthCookie(model.UserName, model.RememberMe)" 
        //wird hier der Username oder auch die UserId angezeigt.
        string userName = HttpContext.Current.User.Identity.Name;
        

        //Prüfen welcher Controller das Authorize Attribut aufgerufen hat und dann schauen welche Action aufgerufen wurde.
        switch (controllerName)
        {
            case "Home":
                //Rechteprüfung für den jewiligen Controller und evtl. auch auf die 
                //jeweilige Action bezogen durchführen
                return CheckHomeRights();
                break;

            case "Person":
                break;

        }

        return false;
    }

    /// <summary>
    /// Prüft alle Rechte für den HomeController und den enthaltenen Actions
    /// </summary>
    /// <returns></returns>
    private  bool CheckHomeRights()
    {
        return false;
    }
    #endregion
}

Aufruf des Custom Authorize Attributes z.B. für einen kompletten Controller

 [CustomAuthorize]
 public class HomeController : Controller
 {
     public ActionResult Index()
     {
         ViewBag.Message = "Modify this ...";

         return View();
     }
...

}

Wenn man jedoch einen WebAPI Controller überprüfen möchte ist dafür wieder ein anderes Custom Authorize Attribut zuständig, das findet Ihr dann in meinem nächsten Blogeintrag.

Codeplex:

Dann unter “Source Code” –> “Browse” –> “Testprojekte” –> “Mvc4WebApiKoTb” hier findet Ihr dann die Wichtigsten Dateien im Webprojekt unter “Views/Home”, “Helpers/Authentication” und “Views/Account”. Das Projekt sollte auch so lauffähig sein.

ASP.NET MVC 3 Nutzer Authentication mit Attributen


Bei einer Webseite mit einer Nutzerverwaltung, ist mit das wichtigste das man nicht einfach die Daten anderer User betrachten darf oder sich Zugriff zum Adminbereich verschafft, nur weil man etwas mit der URL “umherspielt”.

Dafür gibt es zum einen das Attribut “Authorize” was direkt mitgeliefert wird und prinzipiell prüft, ob überhaupt ein User eingeloggt ist und wenn nicht wird auf die Loginseite verwiesen.

Dann besteht aber noch die Möglichkeit ein Custom Attribut zu erstellen, in dem man alle wichtigen Daten für den aktuellen Controller und den Aufgerufenen View überprüfen kann.

Zum einen haben mir hier die folgenden Links sehr weitergeholfen:

http://msdn.microsoft.com/en-us/library/gg416513%28VS.98%29.aspx

http://msdn.microsoft.com/en-us/library/dd381609%28v=VS.98%29.aspx

http://geekswithblogs.net/brians/archive/2010/07/08/implementing-a-custom-asp.net-mvc-authorization-filter.aspx

Dann noch ein kleines Beispiel wie so eine Umsetzung aussehen könnte. Als erstes benötigen wird eine neue Klasse die vom AuthorizeAttribute ableitet und wir können hier eigene Werte im Konstruktor übergeben, die wir evtl. später benötigen. Dann muss man noch OnAuthorization überschreiben und kann hier seine gewünschte Funktionalität einbinden.

image

Mit der Hilfe von filterContext.Result und mit HttpUnauthorizedResult() wird auf die Loginseite verwiesen, kann auch in einem der oberen Links nachgelesen werden.

image

oder mit new RedirectResult(“BeliebigeURL”) kann auch direkt auf eine URL verwiesen werden.image

Das Attribut kann dann in jedem beliebigen Controller eingesetzt werden und wird vor allen anderen Attributen ausgeführt.

image