JWT 认证概念

Posted

技术标签:

【中文标题】JWT 认证概念【英文标题】:JWT authentication concept 【发布时间】:2014-10-13 02:14:34 【问题描述】:

我目前正在使用基于 JSON Web 令牌的身份验证在 Angular JS 应用程序和 Node.js 服务器(作为 API)之间进行交互。

但是我有一个我自己无法回答的问题:当您对 JWT 服务器端将用户作为有效负载进行编码时,您如何继续在客户端检索用户信息? 这是一个小例子来理解我的问题:

我是基本用户,我将凭据发送到 API 进行身份验证。作为交换,我收到了一个 JWT 令牌,但我没有任何关于用户的信息,因为只有服务器拥有能够解码 JWT 令牌的密钥。那么服务器是否需要向我发送例如用户的 id,以便我可以调用我的 api 用户/id 来检索有关经过身份验证的用户的信息?

【问题讨论】:

【参考方案1】:

您通过解码每个请求的令牌来检索用户信息。因此,在您的示例中,将令牌返回给客户端后,客户端向服务器发出请求,以使用存储在编码令牌中的数据获取用户的名字和姓氏,该数据与请求一起发送回服务器。发出此 GET 请求时,您可以将令牌作为参数发送。我将使用一个非 cookie 存储的示例。以下是它的下降方式:

    用户使用其密码和用户名登录 服务器编码一个 json Web 令牌有效负载,其中包含使用 secret_key 登录的用户的唯一标识符(即 user_id)。示例函数调用可能如下所示。

有效负载 = user_id: 35 user_token = JWT.encode(payload, "your_secret_key");

    将 user_token 返回给客户端并将所述令牌存储在隐藏的 html 标记或 localStorage 变量中。使用 Angular,我会将其存储在 localStorage 中。

    现在用户已登录且令牌位于客户端,您可以提交包含 user_token 作为参数的 GET 请求。请记住,此 user_token 有效负载包含 user_id。

    服务器获取参数,解码user_token,从payload中获取user_id。

    您使用 user_id 查询数据库并将数据(名字和姓氏)以纯 json 格式返回。

请务必记住,在您的示例中唯一需要编码的是唯一标识符 (user_id)。在每个请求中,您都会解码本身就是身份验证机制的令牌。

【讨论】:

感谢您的回答,但如果您这样做,您如何使用信息客户端。如果你是“John Doe”用户并且你有用户名 jdoe 和密码 pwd,我将通过一个例子再次解释,你提交 jdoe 和 pwd 并收到一个令牌。但是,您如何检索存储在数据库中的用户信息,例如名字和姓氏,以便在应用程序中显示? 感谢 Micah 的编辑,它更清晰了。我后来想了想,你的回答证实了这一点。非常感谢。晚上好。 有效载荷的“子”(主题)键是否由 JWT 规范定义,用于识别此令牌声明的主题,即通常足够用于输入用户 ID,而不是任意的 'user_id' 键。是这样,还是我误解了? (tools.ietf.org/html/rfc7519#section-4.1.2)【参考方案2】:

已接受答案中的策略有效,但它忽略了 客户端可以看到 JWT 的有效负载这一事实。在The Anatomy of a JSON Web Token 中有很好的解释。

一个 JWT 有 3 个部分。前两个 headerpayload 是 base64 编码的。客户端可以轻松解码它们。有效负载有关于用户的声明,客户端可以使用这些数据(用户 ID、名称、角色、令牌过期)而无需向服务器发出另一个请求。

JWT 的第三部分是signature。它是headerpayload 中的hash,是只有服务器知道的秘密。服务器将对每个请求验证令牌和用户的权限。

客户端永远不知道秘密,它只是有一个声称是给定用户的令牌。

【讨论】:

客户怎么不知道秘密?它需要秘密来准确地组成令牌...... 或者您是否期望服务器是唯一将令牌有效负载和标头一起分配的来源?您不应该让客户端编码自己的有效负载吗? @BenNelson 确切地说,服务器是唯一应该生成令牌的东西。如果您让客户端生成令牌,您就会面临来自恶意用户的攻击,因为他们将能够生成带有任何类型有效负载的令牌。 如果我捕获了某人的令牌,那么我可以冒充。有什么机制可以防止这种情况发生? @variable 我认为没有,如果您可以捕获某人的 cookie,也会发生同样的事情。您应该始终使用 HTTPS 并在 JWT 上设置适当的到期日期。但是,如果有人可以访问用户的浏览器,他们就可以使用 cookie/JWT 为所欲为。【参考方案3】:

您在客户端上有有效负载,如果您需要的数据在有效负载中,您可以轻松地在有效负载上执行Base64 Decode 以找到它!

要理解这一点,请按以下步骤操作:

    客户端发送用户名:用户,密码:传递给服务器。

    服务器启动鉴权业务,发现用户名和密码有效。

    服务器必须将这些信息返回给客户端。这是JWT 有一些规则的地方。服务器必须将token 返回给客户端。令牌包含三个部分 Header.PayLoad.Signature 。现在忘记签名,这是造成混乱的部分。

第一部分是Header。比如:

"typ":"JWT","alg":"HS256"

Base64 Decode 之后将是eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9。请考虑这只是一个解码,根本没有加密!要查看此内容,您可以转到https://www.base64decode.org/ 并进行测试。

header 之后,服务器需要向用户发送有效载荷。服务器可能会决定发送以下json(我说决定,因为这里没有标准要求,您可以发送或多或少的数据作为有效负载,例如,您还可以设置用户权限例如admin:true,或者用户优先和姓氏,但请记住,JWT 大小必须很小,因为它将在每次请求时发送到服务器)

"username":"user","id":3,"iat":1465032622,"exp":1465050622

再次根据JWT,服务器在这里需要Base64 Decode(同样根本不需要加密)。上面的 json 将是eyJ1c2VybmFtZSI6IjEiLCJpZCI6MywiaWF0IjoxNDY1MDMyNjIyLCJleHAiOjE0NjUwNTA2MjJ9

到目前为止,服务器创建了HeaderPayload。现在是时候做签名了!这很容易:

var encodedString=base64UrlEncode(header) + "." + base64UrlEncode(payload);
//As our example base64UrlEncode(header) is eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9
//and the base64UrlEncode(payload) is eyJ1c2VybmFtZSI6IjEiLCJpZCI6MywiaWF0IjoxNDY1MDMyNjIyLCJleHAiOjE0NjUwNTA2MjJ9

 var signature=HMACSHA256(encodedString, 'a secret string which is kept at server');

签名是用你没有的密钥制作的!你也不需要它。所有令牌数据都在有效负载中,可以通过解码访问(再次没有解密!)。

此签名用于服务器,当您将令牌发送回服务器时,服务器会检查签名是否正确以确保他可以信任令牌数据。

总结一下看看下面的令牌

//Header
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.
//PayLoad
eyJ1c2VybmFtZSI6IjEiLCJpZCI6MywiaWF0IjoxNDY1MDMyNjIyLCJleHAiOjE0NjUwNTA2MjJ9.
//Signature
0K8TL1YS0XKnEIfI3lYs-bu2vbWHSNZsVJkN1mXtgWg

标头和有效负载是Base64 Decoded,您可以在客户端对其进行编码。但是你不能用签名做任何事情。

签名仅供服务器使用。客户端使用他的令牌发送每个请求,服务器必须确保客户端没有更改令牌有效负载的任何部分(例如更改用户 ID)。这就是签名字符串变得重要的地方,服务器用它的密钥重新检查签名 每个请求

注意:

你还想知道为什么 JWT 使用编码和解码吗?!为了使洞令牌 URL 安全!

【讨论】:

如果我捕获了某人的令牌,那么我可以冒充。有什么机制可以防止这种情况发生? 我认为JWT中没有机制。它就像会话 ID。如果您有某个会话 ID,则可以模拟。【参考方案4】:

JWT(JSON Web 令牌)在 Web 开发中变得越来越流行。它是 一种开放标准,允许在各方之间以安全和紧凑的方式将数据作为 JSON 对象传输。各方之间使用 JWT 传输的数据经过数字签名,因此可以轻松验证和信任。

ASP.NET Core 中的 JWT

第一步是在我们的项目中配置基于 JWT 的身份验证。我们可以添加在每个授权请求中触发的自定义 jwt auth 中间件。

Startup.cs

    services.AddMvc(options => options.EnableEndpointRouting = false);
    var tokenValidationParams = new TokenValidationParameters
            
                ValidateIssuerSigningKey = true,
                IssuerSigningKey = new SymmetricSecurityKey("Jwt_Key"),
                ValidateIssuer = true,
                ValidateAudience = true,
                ValidateLifetime = true,
                RequireExpirationTime = false,
                ValidIssuer = "Jwt_Issuer",
                ValidAudience = "Jwt_Audience",
                ClockSkew = TimeSpan.Zero
            ;

            services.AddSingleton(tokenValidationParams);

            services.AddAuthentication(options => 
                options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
                options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
                options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
            )
            .AddJwtBearer(jwt => 
                jwt.SaveToken = true;
                jwt.TokenValidationParameters = tokenValidationParams;
            );

            services.AddMvc();
            
            
            
            // 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();
                        
                        // custom jwt auth middleware
                        **app.UseMiddleware<JwtMiddleware>();**
                        app.UseAuthentication();
                        app.UseMvc();
                        app.Run(async (context) =>
                        
                            await context.Response.WriteAsync("Welcome to DATA API");
                        );
            
                    

        

生成 JWT

GenerateJSONWebToken(User userInfo)
                
                    var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("Jwt_Key"));
                    var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256);
    
                    var claims = new[] 
                        new Claim(JwtRegisteredClaimNames.Sub, userInfo.UserID),
                        new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString())
                    ;
        
                    var token = new JwtSecurityToken("Jwt_Issuer","Jwt:Audience",
                        claims,
                        expires: DateTime.Now.AddHours(24),
                        signingCredentials: credentials);
        
                    return new JwtSecurityTokenHandler().WriteToken(token);
                
    

此方法返回 JWT Totken like

令牌:“eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJKa WduZXNoIFRyaXZlZGkiLCJlbWFpbCI6InRlc3QuYnRlc3RAZ21haWwuY29tIiwiRG F0ZU9mSm9pbmciOiIwMDAxLTAxLTAxIiwianRpIjoiYzJkNTZjNzQtZTc3Yy00ZmU xLTgyYzAtMzlhYjhmNzFmYzUzIiwiZXhwIjoxNTMyMzU2NjY5LCJpc3MiOiJUZXN0 LmNvbSIsImF1ZCI6IlRlc3QuY29tIn0.8hwQ3H9V8mdNYrFZSjbCpWSyR1CNyDYHc Gf6GqqCGnY"

调用授权方法

 [Authorize]
   public ActionResult<IEnumerable<string>> Get()
   
    return new string[]  "value1", "value2", "value3", "value4",
    "value5" ;
   
    

验证 Jwt 中间件类中的 Token

JwtMiddleware
    
        private readonly RequestDelegate _next;
        private readonly TokenValidationParameters _tokenValidationParams;
        public JwtMiddleware(RequestDelegate next, TokenValidationParameters 
        tokenValidationParams)
        
            _next = next;
            _tokenValidationParams = tokenValidationParams;
        

        public async Task Invoke(HttpContext context)
        
            var token = context.Request.Headers["Authorization"].FirstOrDefault()?.Split(" ").Last();

            var jwtTokenHandler = new JwtSecurityTokenHandler();
            // Validation 1 - Validation JWT token format
            var tokenInVerification = jwtTokenHandler.ValidateToken(token, _tokenValidationParams, out var validatedToken);

            if (validatedToken is JwtSecurityToken jwtSecurityToken)
            
                var result = jwtSecurityToken.Header.Alg.Equals(SecurityAlgorithms.HmacSha256, StringComparison.InvariantCultureIgnoreCase);

                if (result == false)
                
                    Error Invalid = new Error()
                    
                        Success = false,
                        Errors = "Token is Invalid"
                    ;

                    context.Items["Error"] = Invalid;
                
            

            await _next(context);
        
    

授权属性

public void OnAuthorization(AuthorizationFilterContext context)
 
    var Error= (UserModel)context.HttpContext.Items["Error"];
    if (AuthResult != null)
    
        // not logged in
        context.Result = new JsonResult(new  message = "Unauthorized Access" )  
     StatusCode = StatusCodes.Status401Unauthorized ;
    
 

我希望这对你有用。

【讨论】:

请不要只发布代码作为答案,还要解释您的代码的作用以及它如何解决问题的问题。带有解释的答案通常更有帮助、质量更好,并且更有可能吸引投票

以上是关于JWT 认证概念的主要内容,如果未能解决你的问题,请参考以下文章

JWT在线解码网址和结构介绍

JSON Web Token

JWT 身份认证优缺点分析

HTTP认证:Oauth2, JWT, BASIC

内嵌jwt工具类)

SpringSecurity整合JWT