JWT 身份验证 ASP.NET Core MVC 应用程序

Posted

技术标签:

【中文标题】JWT 身份验证 ASP.NET Core MVC 应用程序【英文标题】:JWT Authentication ASP.NET Core MVC application 【发布时间】:2019-06-30 18:09:34 【问题描述】:

我已经看到许多关于如何将 JWT 身份验证与 Angular、React、Vue 等客户端一起使用的示例,但找不到任何将 JWT 身份验证与 ASP.NET Core(特别是 2.2)Web App Mvc 一起使用的示例。

有没有人有任何关于如何做到这一点的例子或建议?

谢谢,

【问题讨论】:

auth0.com/blog/… jasonwatmore.com/post/2018/08/14/… 另见c-sharpcorner.com/article/… 感谢您提供的所有答案和帮助,非常感谢。 【参考方案1】:

你可以使用这个基于nuget包JWT 3.0.3的类

using JWT;
using JWT.Algorithms;
using JWT.Serializers;
using Newtonsoft.Json;
using System;

namespace Common.Utils

  public class JwtToken
  
    private IJwtEncoder encoder;
    private IJwtDecoder decoder;

    /// <remarks>
    /// This requires a key value randomly generated and stored in your configuration settings. 
    /// Consider that it is a good practice use keys as at least long as the output digest bytes 
    /// length produced by the hashing algorithm used. Since we use an HMAC-SHA-512 algorithm, 
    /// then we can provide it a key at least 64 bytes long.
    /// <see cref="https://tools.ietf.org/html/rfc4868#page-7"/>
    /// </remarks>
    public string SecretKey  get; set;  

    public JwtToken()
    
        IJwtAlgorithm algorithm = new HMACSHA512Algorithm();
        IJsonSerializer serializer = new JsonNetSerializer();
        IDateTimeProvider datetimeProvider = new UtcDateTimeProvider();
        IJwtValidator validator = new JwtValidator(serializer, datetimeProvider);
        IBase64UrlEncoder urlEncoder = new JwtBase64UrlEncoder();

        encoder = new JwtEncoder(algorithm, serializer, urlEncoder);
        decoder = new JwtDecoder(serializer, validator, urlEncoder);
        SecretKey = "";
    

    public JwtToken(string secretKey) : this()
    
        SecretKey = secretKey;
    

    public bool IsTokenValid(string token)
    
        return !string.IsNullOrWhiteSpace(DecodeToken(token));
    

    public string GetToken(object payload)
    
        try
        
            return encoder.Encode(payload, SecretKey);
        
        catch (Exception)
        
            return encoder.Encode(new DataModel(payload), SecretKey);
        
    

    public string DecodeToken(string token)
    
        try
        
            if (string.IsNullOrWhiteSpace(token) || token == "null")
            
                return null;
            
            return decoder.Decode(token, SecretKey, true);
        
        catch (TokenExpiredException)
        
            return null;
        
        catch (SignatureVerificationException)
        
            return null;
        
    

    public T DecodeToken<T>(string token) where T : class
    
        try
        
            if (string.IsNullOrWhiteSpace(token))
            
                return null;
            
            return decoder.DecodeToObject<T>(token, SecretKey, true);
        
        catch (TokenExpiredException)
        
            return null;
        
        catch (SignatureVerificationException)
        
            return null;
        
        catch (Exception)
        
            var data = decoder.DecodeToObject<DataModel>(token, SecretKey, true).Data;
            return JsonConvert.DeserializeObject<T>(JsonConvert.SerializeObject(data));
        
    
  

  public class DataModel
  
    public DataModel(object data)
    
        Data = data;
    
    public object Data  get; set; 
  

然后在您的StartupConfigure 方法中设置jwt 中间件 检查每个请求的身份验证状态:

app.Use((context, next) =>
        

            // verify app access token if not another service call
            var appAccessToken = context.Request.Headers["Authorization"];
            if (appAccessToken.Count == 0)
            
                context.Items["User"] = null;
            
            else
            
                var token = appAccessToken.ToString().Replace("Bearer ", "");
                var jwtToken = new JwtToken(config.JwtTokenSecret); //you need a secret (with requirements specified above) in your configuration (db, appsettings.json)
                if (string.IsNullOrWhiteSpace(token) || !jwtToken.IsTokenValid(token))
                
                    context.Response.StatusCode = 401;
                    return Task.FromResult(0);
                

                dynamic user = jwtToken.DecodeToken<dynamic>(token);

                var cachedToken = cache.Get(user.Id);  //you need some cache for store your token after login success and so can check against
                if (cachedToken == null || cachedToken.ToString() != token)
                
                    context.Response.StatusCode = 401;
                    return Task.FromResult(0);
                

                context.Items["User"] = new Dictionary<string, string>() 
                         "FullName",user.Name?.ToString(),
                         "FirstName",user.FirstName?.ToString(),
                         "LastName",user.LastName?.ToString(),
                         "Role",user.Role?.ToString(),
                         "Email",user.Email?.ToString()
                    ;
            
            return next();
        );

最后你需要生成令牌并在之后返回它 身份验证:

    [AllowAnonymous]
    public IActionResult Login(string username, string password)
    
        User user = null; //you need some User class with the structure of the previous dictionary
        if (checkAuthenticationOK(username, password, out user)) //chackAuthenticationOk sets the user against db data after a succesfull authentication
        
           var token = new JwtToken(_config.JwtTokenSecret).GetToken(user);  //_config is an object to your configuration
           _cache.Set(user.id, token);  //store in the cache the token for checking in each request
           return Ok(token);
        

        return StatusCode(401, "User is not authorized");
    

【讨论】:

嗨。你能解释一下 _cache 吗?随意指向我现有的文章。 您好,就缓存而言,您可以在代码随附的 cmets 中阅读,您需要一些实现它的机制。随意使用您喜欢的缓存库。对于 Asp.Net Core,您可以通过实现 IMemoryCache 接口使用非常好的 Microsoft.Extensions.Caching.Memory:docs.microsoft.com/it-it/aspnet/core/performance/caching/…,对于 Asp.Net,您可以使用 System.Runtime.Caching/MemoryCache 对象:docs.microsoft.com/it-it/dotnet/api/… 我见过的所有这些示例都展示了如何生成令牌,并将令牌返回给客户端..它通常在授权标头中发送回..这对于会影响您的网络应用程序来说非常有用webapi .. 但是没有任何教程解释常规 mvc 应用程序如何使用该令牌?就像您通过javascript获取令牌并将其注入所有后续请求的标头一样? (皮塔饼)或者你把它当作饼干附上?还是.net 处理所有这些而我遗漏了一些东西?...我按照教程进行操作.. 身份工作正常,直到我添加了 jwt 东西.. 现在没有登录工作。标头中没有标记 如果令牌不在标头中,什么会重定向到 Login 方法?谢谢。【参考方案2】:

在启动时添加以下代码

public void ConfigureServices(IServiceCollection services)
    

        services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
            .AddJwtBearer(options =>
            
                options.TokenValidationParameters = new TokenValidationParameters
                
                    ValidateIssuer = true,
                    ValidateAudience = true,
                    ValidateLifetime = true,
                    ValidateIssuerSigningKey = true,
                    ValidIssuer = Configuration["Issuer"],
                    ValidAudience = Configuration["Audience"],
                    IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["SigningKey"]))
                ;
            );

    


 public void Configure(IApplicationBuilder app, IHostingEnvironment env,, ILoggerFactory loggerFactory)
  
   app.UseAuthentication();


AccountController 中的登录操作代码

[Route("api/[controller]")]
public class AccountController : Controller

[AllowAnonymous]
[HttpPost]
[Route("login")]
public IActionResult Login([FromBody]LoginViewModel loginViewModel)


    if (ModelState.IsValid)
    
        var user = _userService.Authenticate(loginViewModel);


        var claims = new[]
        
                new Claim(JwtRegisteredClaimNames.Sub, loginViewModel.Username),
                new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString())
                ;

        var token = new JwtSecurityToken
                    (
                        issuer: _configuration["Issuer"],
                        audience: _configuration["Audience"],
                        claims: claims,
                        expires: DateTime.UtcNow.AddDays(10),
                        notBefore: DateTime.UtcNow,
                        signingCredentials: new SigningCredentials(new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration["SigningKey"])),
                             SecurityAlgorithms.HmacSha256)
                    );

        return Ok(new
        
            access_token = new JwtSecurityTokenHandler().WriteToken(token),
            expires_in = (int)token.ValidTo.Subtract(DateTime.UtcNow).TotalSeconds,// TimeSpan.FromTicks( token.ValidTo.Ticks).TotalSeconds,
            sub = loginViewModel.Username,
            name = loginViewModel.Username,
            fullName = user.FullName,
            jobtitle = string.Empty,
            phone = string.Empty,
            email = user.EmailName,

        );
    



【讨论】:

谢谢!这在 API 项目中确实有效。但是如果纯 MVC html 项目呢?我的意思是,如果没有 JavaScript,如何将令牌插入到请求身份验证标头中?谢谢!【参考方案3】:

我假设您已经在服务器端实现了 JWT。要在客户端处理此问题,首先您必须将令牌添加到 Web 浏览器本地存储。添加到您的主布局 javascript(我们将其命名为 AuthService.js)

以下代码在单击登录按钮后将令牌添加到本地存储。 gettokenfromlocalstorage() 从本地存储中检索令牌。

<script>
var token = "";
function Loginclick() 
    var form = document.querySelector('form');
    var data = new FormData(form);
    var authsevice = new AuthService();
    authsevice.LogIn(data.get("username").toString(), data.get("password").toString());

function gettokenfromlocalstorage() 
    var authserv = new AuthService();
    var mytoken = authserv.getAuth();
    authserv.LogOut();

var AuthService = /** @class */ (function () 
    function AuthService() 
        this.authKey = "auth";
    
    AuthService.prototype.LogIn = function (username, password) 
        this.username = username;
        this.password = password;
        this.grant_type = "password";
        this.client_id = "MyClientId";
        var loginurl = "/api/Token/Auth";
        var xhr = new XMLHttpRequest();
        xhr.open("POST", loginurl, true);
        xhr.setRequestHeader('Content-Type', 'application/json');
        xhr.send(JSON.stringify(this));
        xhr.onreadystatechange = function () 
            console.log("onreadystatechange");
        ;
        xhr.onerror = function () 
            var aaa = this.responseText;
        ;
        xhr.onload = function () 
            var data = JSON.parse(this.responseText);
            var auth = new AuthService();
            auth.setAuth(data);
        ;
    ;
    AuthService.prototype.LogOut = function () 
        this.setAuth(null);
        return true;
    ;
    AuthService.prototype.setAuth = function (auth) 
        if (auth) 
            localStorage.setItem(this.authKey, JSON.stringify(auth));
        
        else 
            localStorage.removeItem(this.authKey);
        
        return true;
    ;
    AuthService.prototype.getAuth = function () 
        var i = localStorage.getItem(this.authKey);
        return i === null ? null : JSON.parse(i);
    ;
    AuthService.prototype.isLoggedIn = function () 
        return localStorage.getItem(this.authKey) !== null ? true : false;
    ;
    return AuthService;
());
var aa = new AuthService();
var gettoken = aa.getAuth();
if (gettoken !== null) 
    token = gettoken.token;

</script>

要将标记添加到每个锚标记的标题中,请在脚本下方添加 你的主要布局。

<script>
var links = $('a');
for (var i = 0; i < links.length; i++)   
    links[i].onclick = function check() 
        addheader(this.href);
        return false;
    


function addheader(object) 
  
    let xhr = new XMLHttpRequest();
    xhr.open("GET", object, true);
    xhr.setRequestHeader('Authorization', 'Bearer ' + token);
    xhr.send(null);

    xhr.onload = function () 
        window.history.pushState("/", "", xhr.responseURL);
        //mycontainer is a div for parialview content
        $("#mycontainer").html(xhr.responseText);
        window.onpopstate = function (e) 
            if (e.state) 
                $("html").html = e.state;
                document.title = e.state.pageTitle;
            
        ;
     
    ;


</script>

请记住,使用这种方法时,每个视图都必须作为局部视图加载。 如果您直接在 Web 浏览器栏中插入 url 地址,则此解决方案不起作用。我还没弄明白。这就是为什么使用单页应用程序而不是多页应用程序更好地管理令牌身份验证的原因。

【讨论】:

【参考方案4】:

您可以使用this boilerplate 了解如何使用 .Net Core 实现 JWT 标记化。在项目中,您可以找到 JWT、Swagger 和 EF 功能。

【讨论】:

OP 询问的是 MVC 项目,您的链接是针对 Web API 的

以上是关于JWT 身份验证 ASP.NET Core MVC 应用程序的主要内容,如果未能解决你的问题,请参考以下文章

同时对 Asp.Net core 2.0 MVC 应用程序 (cookie) 和 REST API (JWT) 进行身份验证

ASP.NET Core 中的 Jwt 令牌身份验证

ASP.NET Core JWT 身份验证受众属性

ASP.NET Core JWT 身份验证更改声明(子)

ASP .NET CORE 2.2 JWT 和声明网站的身份验证

ASP.NET Core SPA 中基于 JWT 的身份验证 - 前端验证