Autentisering och behörighet i ASP.NET Core

Denna handledning visar hur du implementerar schemabaserad autentisering och auktorisation i ASP.NET Core. ASP.NET Core 3.0 är väldigt strikt när det gäller hur autentisering och behörighetshantering ska implementeras.

Schemabaserad autentisering och auktorisation är mycket smidig att använda, du kan använda inbyggda scheman och skapa dina egna autentiseringshanterare. Du kan också lägga till flera scheman och skydda olika delar av din webbplats med olika scheman. Du kan också använda flera scheman för en controller.

Autentisering är en process för att bekräfta en användares identitet, ett inloggningsformulär används vanligen på webbplatser för att matcha en användare och en session. Auktorisation är en process för att ge tillgång till resurser beroende på användarrättigheter.

Jag kommer att implementera cookie-baserad autentisering, grundläggande autentisering och rollbaserad auktorisation i denna handledning.

Konfigurera tjänster

Autentisering läggs till i metoden ConfigureServices i klassen StartUp. Mellanprogram för autentisering och behörigheter läggs till i metoden Configure i klassen StartUp.

public void ConfigureServices(IServiceCollection services)
{
    // Add the mvc framework
    services.AddRazorPages();

    // Add memory cache
    services.AddDistributedMemoryCache();

    // Add redis distributed cache
    if (configuration.GetSection("AppSettings")["RedisConnectionString"] != "")
    {
        services.AddDistributedRedisCache(options =>
        {
            options.Configuration = configuration.GetSection("AppSettings")["RedisConnectionString"];
            options.InstanceName = "Fotbollstabeller:";
        });
    }

    // Add the session service
    services.AddSession(options =>
    {
        // Set session options
        options.IdleTimeout = TimeSpan.FromMinutes(20d);
        options.Cookie.Name = ".Fotbollstabeller";
        options.Cookie.Path = "/";
        options.Cookie.HttpOnly = true;
        options.Cookie.SameSite = SameSiteMode.Lax;
        options.Cookie.SecurePolicy = CookieSecurePolicy.SameAsRequest;
    });

    // Create database options
    services.Configure<DatabaseOptions>(options => 
    {
        options.connection_string = configuration.GetSection("AppSettings")["ConnectionString"];
        options.sql_retry_count = 1;
    });

    // Create cache options
    services.Configure<CacheOptions>(options => 
    {
        options.expiration_in_minutes = 240d;
    });
            
    // Add Authentication
    services.AddAuthentication()
        .AddCookie("Administrator", options =>
        {
            options.ExpireTimeSpan = TimeSpan.FromDays(10);
            options.Cookie.MaxAge = TimeSpan.FromDays(10);
            options.Cookie.HttpOnly = true;
            options.Cookie.SameSite = SameSiteMode.Lax;
            options.Cookie.SecurePolicy = CookieSecurePolicy.SameAsRequest;
            options.Events.OnRedirectToLogin = (context) =>
            {
                context.Response.StatusCode = StatusCodes.Status401Unauthorized;
                context.Response.Redirect("/admin_login");
                return Task.CompletedTask;
            };
        })
        .AddCookie("Member", options => 
        {
            options.ExpireTimeSpan = TimeSpan.FromHours(4);
            options.Cookie.MaxAge = TimeSpan.FromHours(4);
            options.Cookie.HttpOnly = true;
            options.Cookie.SameSite = SameSiteMode.Lax;
            options.Cookie.SecurePolicy = CookieSecurePolicy.SameAsRequest;
            options.Events.OnRedirectToLogin = (context) =>
            {
                context.Response.StatusCode = StatusCodes.Status401Unauthorized;
                context.Response.Redirect("/member_login");
                return Task.CompletedTask;
            };
        })
        .AddScheme<AuthenticationSchemeOptions, BasicAuthenticationHandler>("ApiAuthentication", null);

    // Add clients
    services.AddHttpClient();

    // Add repositories
    services.AddSingleton<IDatabaseRepository, MsSqlRepository>();
    services.AddSingleton<IWebsiteSettingRepository, WebsiteSettingRepository>();
    services.AddSingleton<IAdministratorRepository, AdministratorRepository>();
    services.AddSingleton<IFinalRepository, FinalRepository>();
    services.AddSingleton<IGroupRepository, GroupRepository>();
    services.AddSingleton<IStaticPageRepository, StaticPageRepository>();
    services.AddSingleton<IXslTemplateRepository, XslTemplateRepository>();
    services.AddSingleton<ISitemapRepository, SitemapRepository>();
    services.AddSingleton<IXslProcessorRepository, XslProcessorRepository>();
    services.AddSingleton<ICommonServices, CommonServices>();

} // End of the ConfigureServices method

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    // Use redirection
    app.UseMiddleware<RedirectMiddleware>();

    // Use error handling
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }
    else
    {
        app.UseStatusCodePagesWithReExecute("/home/error/{0}");
    }

    // Use static files
    app.UseStaticFiles(new StaticFileOptions
    {
        OnPrepareResponse = ctx =>
        {
            // Cache static files for 30 days
            ctx.Context.Response.Headers.Append("Cache-Control", "public,max-age=25920000");
            ctx.Context.Response.Headers.Append("Expires", DateTime.UtcNow.AddDays(300).ToString("R", CultureInfo.InvariantCulture));
        }
    });

    // Use sessions
    app.UseSession();

    // For most apps, calls to UseAuthentication, UseAuthorization, and UseCors must 
    // appear between the calls to UseRouting and UseEndpoints to be effective.
    app.UseRouting();

    // Use authentication and authorization middlewares
    app.UseAuthentication();
    app.UseAuthorization();

    // Routing endpoints
    app.UseEndpoints(endpoints =>
    {
        endpoints.MapControllerRoute(
            "default",
            "{controller=home}/{action=index}/{id?}");
    });

} // End of the Configure method

Grundläggande autentisering

Jag har skapat en anpassad hanterare för grundläggande autentisering. Jag använder den här hanteraren i schemat ApiAuthentication.

public class BasicAuthenticationHandler : AuthenticationHandler<AuthenticationSchemeOptions>
{
    #region Variables

    private readonly IAdministratorRepository administrator_repository;

    #endregion

    #region Constructors

    public BasicAuthenticationHandler(IOptionsMonitor<AuthenticationSchemeOptions> options, ILoggerFactory logger,
        UrlEncoder encoder, ISystemClock clock, IAdministratorRepository administrator_repository)
        : base(options, logger, encoder, clock)
    {
        // Set instance variables
        this.administrator_repository = administrator_repository;

    } // End of the constructor

    #endregion

    #region Methods

    protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
    {
        // Make sure that there is an Authorization header
        if (Request.Headers.ContainsKey("Authorization") == false)
        {
            // Return failure
            return AuthenticateResult.Fail("No Authorization header");
        }

        // Get the authorization header
        string authHeader = Request.Headers["Authorization"];

        // Get tokens
        string authToken = authHeader.Substring("Basic ".Length).Trim();
        string decodedToken = Encoding.UTF8.GetString(Convert.FromBase64String(authToken));

        // Get the separator index
        Int32 seperatorIndex = decodedToken.IndexOf(":");

        // Get the username and password
        string username = decodedToken.Substring(0, seperatorIndex);
        string password = decodedToken.Substring(seperatorIndex + 1);

        // Get a api user, username must be unique
        Administrator api_user = await this.administrator_repository.GetApiUser(username, password);

        // Make sure that the username and password is correct
        if(api_user != null)
        {
            // Create claims
            ClaimsIdentity identity = new ClaimsIdentity(Scheme.Name);
            identity.AddClaim(new Claim("user", JsonConvert.SerializeObject(api_user)));
            ClaimsPrincipal principal = new ClaimsPrincipal(identity);
            AuthenticationTicket ticket = new AuthenticationTicket(principal, Scheme.Name);

            // Return success
            return AuthenticateResult.Success(ticket);
        }
        else
        {
            // Return failure
            return AuthenticateResult.Fail("Incorrect username or password");
        }

    } // End of the HandleAuthenticateAsync method

    #endregion

} // End of the class

Kräv autentisering och auktorisation

Lägg till ett auktoriseringsattribut till controllers och/eller metoder som kräver autentisering. Du kan ange ett eller flera scheman som ska användas. Standardschemat används om inget schema anges. Lägg till roller om du vill begränsa åtkomst till användare med vissa roller.

// One scheme
[Authorize(AuthenticationSchemes = "Administrator")]
public class admin_xsl_templatesController : Controller

// Multiple schemes
[Authorize(AuthenticationSchemes = "Administrator,Member")]
public class admin_xsl_templatesController : Controller

// Protect a method by only allowing some roles
[HttpGet]
[Authorize(Roles = "Administrator,Editor")]
public async Task<IActionResult> index()

// Api authentication
[Route("api/jobs/[action]")]
[Authorize(AuthenticationSchemes = "ApiAuthentication")]
public class JobsController : Controller

Inloggning och utloggning

Jag har ett inloggningsformulär på min webbplats som anropar metoden login. Jag lägger till anspråk och loggar in användaren med schemat Administrator. Spara inte för mycket information i anspråk avseende cookie-baserad autentisering, anspråk sparas i cookien. Spara en identifierare och använd denna identifierare för att hämta information om en användare.

[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> login(IFormCollection collection)
{
    // Get the data from the form
    string username = collection["txtUsername"];
    string password = collection["txtPassword"];

    // Get the administrator
    Administrator administrator = this.administrator_repository.GetOneByUsername(username);

    // Create response data
    ResponseData data = null;

    // Check if the user name exists and if the password is correct
    if (administrator != null && this.administrator_repository.ValidatePassword(administrator.id, password) == true)
    {
        // Create claims
        ClaimsIdentity identity = new ClaimsIdentity("Administrator");
        //identity.AddClaim(new Claim("administrator", JsonConvert.SerializeObject(administrator)));
        identity.AddClaim(new Claim(ClaimTypes.Name, administrator.admin_user_name));
        identity.AddClaim(new Claim(ClaimTypes.Role, administrator.admin_role));
        ClaimsPrincipal principal = new ClaimsPrincipal(identity);

        // Sign in the administrator
        await HttpContext.SignInAsync("Administrator", principal);

        // Add success data
        data = new ResponseData(true, username, $"Du har nu loggats in!");
    }
    else
    {
        // Add error data
        data = new ResponseData(false, username, $"Användarnamnet eller lösenordet är felaktigt!");
    }

    // Return the data
    return Json(data: data);

} // End of the post login method

[HttpGet]
public async Task<IActionResult> logout()
{
    // Sign out the administrator
    await HttpContext.SignOutAsync("Administrator");

    // Redirect the user to the login page
    return RedirectToAction("index", "admin_login");

} // End of the logout method

Erhåll användarinformation

Vi kan enkelt få information om en inloggad användare i våra metoder från HttpContext eftersom vi sparat denna information som anspråk.

// Get Json document
Claim claim = HttpContext.User.FindFirst("administrator");
Administrator user = JsonConvert.DeserializeObject<Administrator>(claim.Value);

// Get administrator from username
Administrator user = this.administrator_repository.GetOneByUsername(HttpContext.User.Identity.Name);

Lämna ett svar

E-postadressen publiceras inte. Obligatoriska fält är märkta *