代码改变世界

ASP.NET Core 如何用 Cookie 来做身份验证

2019-10-11 19:14  音乐让我说  阅读(533)  评论(0编辑  收藏  举报

前言

本示例完全是基于 ASP.NET Core 3.0。本文核心是要理解 Claim, ClaimsIdentity, ClaimsPrincipal,读者如果有疑问,可以参考文章 理解ASP.NET Core验证模型(Claim, ClaimsIdentity, ClaimsPrincipal)不得不读的英文博文

代码

项目文件 csproj 的配置

<Project Sdk="Microsoft.NET.Sdk.Web">

  <PropertyGroup>
    <TargetFramework>netcoreapp3.0</TargetFramework>
  </PropertyGroup>
  <ItemGroup>
  </ItemGroup>
</Project>

 

Program.cs

注意: ASP.NET Core 3.0 的配置和 v2.2 稍微有一点不同。

public class Program
{
    public static void Main(string[] args)
    {
        CreateHostBuilder(args).Build().Run();
    }

    public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
            .ConfigureWebHostDefaults(webBuilder =>
            {
                webBuilder.UseStartup<Startup>();
            });
}

 

Startup

注意:不管是 ConfigureServices 方法,还是 Configure 方法,配置的顺序至关重要,有可能明明配置了 XX,运行时却总是无效。比如笔者实验时,把 app.UseAuthentication(); 写到了 app.UseRouting() 前面,结果导致运行时,标记在 Action 方法上面的 [Authorize] 总是无效,结果发现是注册的顺序搞错了,大家一定要注意这点。

 

public class Startup
{
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public IConfiguration Configuration { get; }

    // This method gets called by the runtime. Use this method to add services to the container.
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme) // Sets the default scheme to cookies
            .AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, options =>
            {
                options.AccessDeniedPath = "/account/denied";
                options.LoginPath = "/account/login";
            });

        services.AddControllersWithViews();

        // Example of how to customize a particular instance of cookie options and
        // is able to also use other services.
        // will override CookieAuthenticationOptions, such as LoginPath => "/account/hello"
        //services.AddSingleton<IConfigureOptions<CookieAuthenticationOptions>, ConfigureMyCookie>();
    }

    // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }
        else
        {
            app.UseDeveloperExceptionPage(); // Temp Open
            //app.UseExceptionHandler("/Home/Error");
        }
            
        app.UseStaticFiles();

        app.UseRouting();

        app.UseAuthentication(); // Remember to put it behind app.UseRouting()
        app.UseAuthorization(); // Remember to put it behind app.UseRouting()

        app.UseEndpoints(endpoints =>
        {
            endpoints.MapControllerRoute(
                name: "default",
                pattern: "{controller=Home}/{action=Index}/{id?}");
        });
    }
}

 

 HomeController

在需要授权才能访问的 Action 方法上标记  [Authorize]

public class HomeController : Controller
{
    private readonly ILogger<HomeController> _logger;

    public HomeController(ILogger<HomeController> logger)
    {
        _logger = logger;
    }

    public IActionResult Index()
    {
        return View();
    }

    [Authorize]
    public IActionResult MyClaims()
    {
        return View();
    }

    public IActionResult Privacy()
    {
        return View();
    }
}

 _Layout.cshtml

在这里面,可以通过 @User.Identity.IsAuthenticated 来判断用户是否已经进行了授权,如果已经授权,则显示 “Logout” 链接。

<li class="nav-item">
    <a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Index">Home</a>
</li>
<li class="nav-item">
    <a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="MyClaims">My Claims</a>
</li>
<li class="nav-item">
    <a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Privacy">Privacy_@(User.Identity.IsAuthenticated)</a>
</li>
@if (User.Identity.IsAuthenticated)
{
    <li class="nav-item">
        <a class="nav-link text-dark" asp-area="" asp-controller="Account" asp-action="Logout">Logout</a>
    </li>
}

AccountController

注意:由于之前我们在 Starup 中配置了 CookieAuthenticationOptions 类(Microsoft.AspNetCore.Authentication.Cookies. CookieAuthenticationOptions)的 options.LoginPath = "/account/login"; 这时候如果访问 /home/MyCliams 时,会自动跳转到 /account/login。

ApplicationUser

    public class ApplicationUser
    {
        public string Email { get; set; }
        public string FullName { get; set; }
    }

LoginViewModel

    public class LoginViewModel
    {
        public string UserName { get; set; }

        public string Password { get; set; }
    }

 

 

 

    public class AccountController : Controller
    {
        [HttpGet]
        public IActionResult Login(string returnUrl = null)
        {
            ViewData["ReturnUrl"] = returnUrl;
            return View();
        }

        private async Task<ApplicationUser> AuthenticateUser(string email, string password)
        {
            // For demonstration purposes, authenticate a user
            // with a static email address. Ignore the password.
            // Assume that checking the database takes 500ms

            await Task.Delay(500);

            //if (email == "maria.rodriguez@contoso.com")
            //{
                return new ApplicationUser()
                {
                    Email = "maria.rodriguez@contoso.com",
                    FullName = "Maria Rodriguez"
                };
            //}
            //else
            //{
            //    return null;
            //}
        }

        [HttpPost]
        public async Task<IActionResult> Login(LoginViewModel loginViewModel, string returnUrl = null)
        {
            if (!ModelState.IsValid)
            {
                return Content("validation fail");
            }
            var user = await AuthenticateUser(loginViewModel.UserName, loginViewModel.Password);
            if (user == null)
            {
                ModelState.AddModelError(string.Empty, "Invalid login attempt.");
                return View();
            }
            ViewData["ReturnUrl"] = returnUrl;

            var claims = new List<Claim>
                {
                    new Claim(ClaimTypes.Name, user.Email),
                    new Claim("FullName", user.FullName),
                    new Claim(ClaimTypes.Role, "Administrator"),
                };

            var claimsIdentity = new ClaimsIdentity(
                claims, CookieAuthenticationDefaults.AuthenticationScheme);

            var authProperties = new AuthenticationProperties
            {
                //AllowRefresh = <bool>,
                // Refreshing the authentication session should be allowed.

                //ExpiresUtc = DateTimeOffset.UtcNow.AddMinutes(10),
                // The time at which the authentication ticket expires. A 
                // value set here overrides the ExpireTimeSpan option of 
                // CookieAuthenticationOptions set with AddCookie.

                //IsPersistent = true,
                // Whether the authentication session is persisted across 
                // multiple requests. When used with cookies, controls
                // whether the cookie's lifetime is absolute (matching the
                // lifetime of the authentication ticket) or session-based.

                //IssuedUtc = <DateTimeOffset>,
                // The time at which the authentication ticket was issued.

                //RedirectUri = <string>
                // The full path or absolute URI to be used as an http 
                // redirect response value.
            };

            await HttpContext.SignInAsync(
                CookieAuthenticationDefaults.AuthenticationScheme,
                new ClaimsPrincipal(claimsIdentity),
                authProperties);

            if (Url.IsLocalUrl(returnUrl))
            {
                return Redirect(returnUrl);
            }
            else
            {
                return Redirect("/");
            }
        }

        public IActionResult AccessDenied(string returnUrl = null)
        {
            return View();
        }

        public async Task<IActionResult> Logout()
        {
            #region snippet1
            await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
            #endregion

            return Redirect("/");
        }
    }

 

上面的代码即包含“登录”方法,又包含登出方法。

await HttpContext.SignInAsync(
    CookieAuthenticationDefaults.AuthenticationScheme,
    new ClaimsPrincipal(claimsIdentity),
    authProperties);

关于 Claim, ClaimsIdentity, ClaimsPrincipal,读者如果有疑问,可以参考文章 理解ASP.NET Core验证模型(Claim, ClaimsIdentity, ClaimsPrincipal)不得不读的英文博文

await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);

 

 MyClaims.cshtml

 

@using Microsoft.AspNetCore.Authentication

<h2>HttpContext.User.Claims</h2>

<dl>
    @foreach (var claim in User.Claims)
    {
        <dt>@claim.Type</dt>
        <dd>@claim.Value</dd>
    }
</dl>

<h2>AuthenticationProperties</h2>

<dl>
    @{
        var taskAuth = await Context.AuthenticateAsync();
    }
    @if(taskAuth != null && taskAuth.Properties != null && taskAuth.Properties.Items != null)
    {
        @foreach (var prop in taskAuth.Properties.Items)
        {
            <dt>@prop.Key</dt>
            <dd>@prop.Value</dd>
        }
    }
    else
    {
        <dt>no data.</dt>
    }
</dl>

 

 运行截图

1. 没有登录的情况下

 

 

 

2. 登录界面

3. 登录成功

 

 

谢谢浏览!