首页 > 解决方案 > 使用 Ajax 重定向到授权控制器

问题描述

我有一个问题,需要澄清一下。我有一个带有登录操作的 AccountController,它处理登录,如果成功,它将用户重定向到我希望它受到 jwt 保护的主页。所以我的问题是,我知道我正确地重定向到我的主页/索引,但我缺少一些东西来验证提供的令牌。1. 我需要在 HomeController 类的顶部添加什么属性标签?2.我怎样才能通过'@Url.Action("Index","Home")'?

编辑:成功登录后,它说我已经成功登录,但是由于我要求令牌位于标头中,所以当我尝试使用 @Url.Action() 重定向时,它会发送错误请求,因为我不是确定如何用它填充标题。

这是 Startup.cs:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Chat.Controllers;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.HttpsPolicy;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.IdentityModel.Tokens;

namespace Chat
{
    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(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(options =>
            {
                options.TokenValidationParameters = new TokenValidationParameters
                {
                    ValidateIssuer = true,
                    ValidateAudience = true,
                    ValidateIssuerSigningKey = true,
                    ValidIssuer = "localhost",
                    ValidAudience = "localhost",
                    IssuerSigningKey = AccountController.SIGNING_KEY
                };
            });

            services.Configure<CookiePolicyOptions>(options =>
            {
                // This lambda determines whether user consent for non-essential cookies is needed for a given request.
                options.CheckConsentNeeded = context => true;
                options.MinimumSameSitePolicy = SameSiteMode.None;
            });


            services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
        }

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

            app.UseHttpsRedirection();
            app.UseStaticFiles();
            app.UseCookiePolicy();

            app.UseMvc(routes =>
            {
                routes.MapRoute(
                    name: "default",
                    template: "{controller=Account}/{action=Index}/{id?}");
            });
        }
    }
}

这是带有登录操作的 AccountController:

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Chat.Models;
using Chat.DatabaseAccessObject;
using Chat.Identity;
using Chat.DatabaseAccessObject.CommandObjects;
using System.Linq.Expressions;
using System.Net.Mime;
using System.Security.Claims;
using System.Text;
using Microsoft.AspNetCore.Authentication;
using Microsoft.IdentityModel.Tokens;

namespace Chat.Controllers
{
    public class AccountController : Controller
    {
        private const string SECRET_KEY = "CHATSECRETKEY";
        public static SymmetricSecurityKey SIGNING_KEY = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(SECRET_KEY));
        private ServerToStorageFacade serverToStorageFacade = new ServerToStorageFacade();
        private AuthenticateUser authenticateUser = new AuthenticateUser();

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

        [HttpPost]
        public async Task<IActionResult> Login([FromBody]LoginModel loginModel)
        {
            if (ModelState.IsValid)
            {
                var mapLoginModelToUser = new MapLoginModelToUser();
                var user = await mapLoginModelToUser.MapObject(loginModel);

                // If login user with those credentials does not exist
                if(user == null)
                {
                    return BadRequest();
                }

                else
                {
                    var result = await this.authenticateUser.Authenticate(user);

                    if(result.Result == Chat.Enums.AuthenticateResult.Success)
                    {
                        // SUCCESSFUL LOGIN
                        // Creating and storing cookies

                        var token = Json(new
                        {
                            data = this.GenerateToken(user.Email, user.PantherID),
                            redirectUrl = Url.Action("Index","Home"),
                            success = true
                        });
                        return Ok(token);
                    }
                    else
                    {
                        // Unsuccessful login
                        return Unauthorized();
                    }
                }
            }

            return BadRequest();
        }
       private string GenerateToken(string email, string pantherId)
        {
            var claimsData = new[] { new Claim(ClaimTypes.Email, email), new Claim(ClaimTypes.Actor, pantherId) };

            var signInCredentials = new SigningCredentials(SIGNING_KEY, SecurityAlgorithms.HmacSha256);
            var token = new JwtSecurityToken(
                issuer: "localhost",
                audience: "localhost",
                expires: DateTime.Now.AddDays(7),
                claims: claimsData,
                signingCredentials: signInCredentials
            );

            return new JwtSecurityTokenHandler().WriteToken(token);
        }

        [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
        public async Task<IActionResult> Error() => View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
    }

这是位于“/Views/Account/Index.cshtml”中的 Index.cshtml:

@{
    Layout = null;
    ViewData["Title"] = "Login";
}

@model Chat.Models.LoginModel

<!doctype html>
<html lang="en">
<head>
    <!-- Required meta tags -->
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">

    <!-- Bootstrap CSS -->
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous">
    <link href="/css/signin.css" rel="stylesheet">
    <script src="~/lib/jquery/dist/jquery.js"></script>
    <script src="~/lib/bootstrap/dist/js/bootstrap.js"></script>
    <script src="~/js/Login.js"></script>
</head>
<body class="text-center">
    <form id="formSubmit" method="post" class="form-signin">
        <img class="mb-4" src="~/images/Chat-Curved.png" alt="" width="150" height="150">
        <h1 class="h3 mb-3 font-weight-normal">Please sign in</h1>
        <label for="inputEmail" class="sr-only">Email address</label>
        @Html.TextBoxFor(m => m.inputEmail, new { @class = "form-control", @type="email", @placeholder = "Email address", @required = "required", @autofocus = "" })
        <label for="inputPassword" class="sr-only">Password</label>
        @Html.PasswordFor(m => m.inputPassword, new { @class = "form-control", @type="password", @placeholder = "Password", @required = "required"})
        <div class="checkbox mb-3">
            <label>
                @Html.CheckBoxFor(m => m.rememberMe)  Remeber me
            </label>
        </div>
        <button id="btnLogin" class="btn btn-lg btn-primary btn-block" type="submit">Sign in</button>
        <p class="mt-5 mb-3 text-muted">&copy; 2017-2018</p>
    </form>

</body>
</html>

这是带有索引操作的 HomeController:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;

namespace Chat.Controllers
{
    public class HomeController : Controller
    {
        public IActionResult Index()
        {
            return View();
        }
    }
}

这是 Index.cshtml 引用的 Login.js:

$(document).ready(function () {
    $("#formSubmit").submit(function (event) {
        event.preventDefault();
        var email = $("#inputEmail").val();
        var password = $("#inputPassword").val();
        var remember = $("#rememberMe").val();
        var loginModel = {
            inputEmail: email,
            inputPassword: password,
            rememberMe: remember
        };
        $.ajax({
            type: 'POST',
            url: 'Account/Login',
            data: JSON.stringify(loginModel),
            contentType: 'application/json; charset=utf-8;',
            success: function (response) {
                var token = response.value.data;
                localStorage.setItem("token", token);
                window.location.href = response.value.redirectUrl;
            }
        });
    });
});

标签: c#ajaxmodel-view-controllerjwt

解决方案


如果要授权控制器,则必须使用中间件(ActionFilterAttribute),它将检测用户的 http 请求并通过解码用户的令牌来验证它们。您可以过滤所有 http 方法(GET、POST、PUT、DELETE...等),并且可以为特定的 http 方法实现自己的授权逻辑。

AuthorizationRequiredAttribute.cs

注意:这里所有代码都与您的问题无关。但希望你能理解。

[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)]
public class AuthorizationRequiredAttribute : ActionFilterAttribute
{
    private readonly IAccessTokenServices _accessTokenServices;
    private readonly IPermissionServices _permissionServices;
    private readonly IAuditLogServices _auditLogServices;
    private IConfiguration _config;
    public AuthorizationRequiredAttribute(IAccessTokenServices accessTokenServices, IPermissionServices permissionServices,
        IAuditLogServices auditLogServices,IConfiguration config)
    {
        _accessTokenServices = accessTokenServices;
        _config = config;
        _permissionServices = permissionServices;
        _auditLogServices = auditLogServices;
    }
    public override void OnActionExecuting(ActionExecutingContext context)
    {
        try
        {
            if (context.HttpContext.Request.Headers.ContainsKey(Constants.HttpHeaders.Token))
            {
                var handler = new JwtSecurityTokenHandler();
                var token = handler.ReadToken(context.HttpContext.Request.Headers[Constants.HttpHeaders.Token])
                    as JwtSecurityToken;
                var expireDate = Convert.ToDateTime(token.Claims.First(claim => claim.Type == Constants.JwtClaims.ExpiresOn).Value);
                if (context.HttpContext.Request.Method == WebRequestMethods.Http.Get)
                {
                    if (expireDate < DateTime.Now)
                    {
                        context.Result = new UnauthorizedResult();
                    }
                }
                else
                {

                    var accessToken = _accessTokenServices
                        .Details(x => x.Token == context.HttpContext.Request.Headers[Constants.HttpHeaders.Token]);
                    if (accessToken != null)
                    {
                        if (accessToken.ExpiresOn < DateTime.Now)
                        {
                            _accessTokenServices.Delete(accessToken);
                            context.Result = new UnauthorizedResult();
                        }
                        else
                        {
                            var userId = Convert.ToInt32(token.Claims.First(claim => claim.Type == Constants.JwtClaims.UserId).Value);
                            var userTypeId = Convert.ToInt32(token.Claims.First(claim => claim.Type == Constants.JwtClaims.UserTypeId).Value);
                            if (accessToken == null)
                            {
                                context.Result = new UnauthorizedResult();
                            }
                            else if (!_permissionServices.IsPermissionExist(context.HttpContext.Request.Path.ToString(), userTypeId))
                            {
                                context.Result = new StatusCodeResult((int)HttpStatusCode.NotAcceptable);
                            }
                            else
                            {

                                _auditLogServices.Save(context.HttpContext.Request.Path.ToString(), userId);
                                accessToken.ExpiresOn = DateTime.Now.AddMinutes(Convert.ToInt16(_config["Jwt:ExpiresOn"]));
                                _accessTokenServices.UpdateExpireTime(accessToken);

                            }
                        }
                    }
                    else
                    {
                        context.Result = new UnauthorizedResult();
                    }
                }
            }
            else
            {
                context.Result = new NotFoundResult();
            }
        }
        catch (Exception ex)
        {
            context.Result = new BadRequestResult();
        }
        base.OnActionExecuting(context);
    }
}

}

家庭控制器.cs

现在您可以使用 AuthorizationRequiredAttribute 作为 api/controller 过滤器服务。我已经修改了您的控制器并查看 Message 方法

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;

namespace Chat.Controllers
{
    [Route("api/home")]
    public class HomeController : Controller
    {

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

        [HttpGet("message"), ServiceFilter(typeof(AuthorizationRequiredAttribute))]
        public IActionResult Message()
        {
            return Ok("Hello World!");
        }
    }
}

推荐阅读