arm ror_从头开始的RoR JWT加密

arm ror

Authentication when working with CORS always presents an interesting challenge. In a normal (i.e. non-API) Rails app, we “log in” a user by storing their unique user ID in the session store. This means that authentication information is stored on the server-side, in the session hash. In other words, our server becomes stateful, keeping track of whether or not a user is "logged in", and who that user is.

与CORS一起使用时,身份验证始终是一个有趣的挑战。 在普通的(即非API)Rails应用程序中,我们通过将其唯一的用户ID存储在会话存储中来“登录”用户。 这意味着身份验证信息存储在服务器端的session哈希中。 换句话说,我们的服务器变为有状态 ,跟踪用户是否“登录”以及该用户是谁。

What happens in a Rails API, then, when the client is divorced from the server? Well, we’ll need to tell the client, i.e. our Ember app, to store some kind of unique identifier and send that unique identifier to the Rails API with every request. Rails can then use the unique identifier, or token, to identify the user making the request.

那么,当客户端与服务器分离时,在Rails API中会发生什么? 好吧,我们需要告诉客户端,即我们的Ember应用程序,以存储某种唯一标识符,并在每次请求时将该唯一标识符发送到Rails API。 然后,Rails可以使用唯一标识符或令牌来标识发出请求的用户。

This is the basic model of Ember Simple Auth. With the Ember Simple Auth add-on, we can authorize our user like this:

这是Ember Simple Auth的基本模型。 借助Ember Simple Auth插件,我们可以像这样授权用户:

  • User “logs in” in via the browser and Ember grabs the user’s email and password and sends them to Rails, requesting authentication.

    用户通过浏览器“登录”,Ember获取用户的电子邮件和密码,然后将其发送到Rails,以请求身份验证。
  • Rails look up the user. If the user can be authenticated, Rails sends that user’s unique token back to Ember.

    Rails查找用户。 如果用户可以通过身份验证,Rails会将该用户的唯一令牌发送回Ember。

  • Ember stores that token in the session store, and sends it back to Rails with every subsequent request.

    Ember将该令牌存储在会话存储中,并将其与每个后续请求一起发送回Rails。
  • Rails receive any such requests, uses the token to look up the current user, and return requested data back to Ember, the client.

    Rails接收任何此类请求,使用令牌查找当前用户,并将请求的数据返回给客户端Ember。
  • When someone “logs out”, Ember removes the token from the session store, and subsequent requests to rails do not include that token. Rails, therefore, is not able to find a “current user”.

    当某人“注销”时,Ember将从会话存储中删除该令牌,并且随后对rails的请求也不包括该令牌。 因此,Rails无法找到“当前用户”。

One downside of this approach, however, is that it necessitates generating a unique token for every user-created, persisting that token in the database, and querying the database using that token every time we want to authenticate a user.

但是,这种方法的缺点是,它需要为每个用户创建的令牌生成唯一的令牌,将该令牌持久存储在数据库中,并且每次我们要验证用户身份时都使用该令牌查询数据库。

Wouldn’t it be great if we could authenticate the user in a stateless manner, without storing tokens in the database? Wouldn’t it be even greater if we could do that via requests that are compact and self-contained?

如果我们能够以无状态方式对用户进行身份验证而不将令牌存储在数据库中,那不是很好吗? 如果我们可以通过紧凑且自包含的请求来做到这一点,这会更大吗?

Well, lucky for us, there’s JWT Authentication.

好吧,对我们来说很幸运,这里有JWT Authentication

什么是JWT身份验证? (What is JWT Authentication?)

Image for post
Source: https://miro.medium.com/max/3776/1*44waelPu4JvYALzkvoh8zw.png
资料来源: https : //miro.medium.com/max/3776/1*44waelPu4JvYALzkvoh8zw.png

JSON Web Token (JWT) Authentication is a compact, URL-safe means of representing claims to be transferred between two parties. The claims in a JWT are encoded as a JSON object that is used as the payload of a JSON Web Signature (JWS) structure or as the plaintext of a JSON Web Encryption (JWE) structure, enabling the claims to be digitally signed or integrity protected with a Message Authentication Code (MAC) and/or encrypted.

JSON Web令牌(JWT)身份验证是一种紧凑的,URL安全的方法,用于表示要在两方之间转移的声明。 JWT中的声明被编码为JSON对象,用作JSON Web签名(JWS)结构的有效负载或JSON Web加密(JWE)结构的纯文本,从而使声明可以进行数字签名或完整性保护带有消息验证码(MAC)和/或加密的消息。

In plain English, JWT allows us to authenticate requests between the client and the server by encrypting authentication information into a compact JSON object. Instead of, say, passing a user’s own unique token (which we would need to persist to the database), or (god forbid), sending a user’s raw email and password with every authentication request, JWT allows us to encrypt a user’s identifying information, store it in a token, inside a JSON object, and include that object in every request that requires authentication.

用简单的英语来说,JWT通过将身份验证信息加密到紧凑的JSON对象中,使我们能够在客户端和服务器之间对请求进行身份验证。 JWT允许我们对用户的身份信息进行加密,而不是传递用户自己的唯一令牌(我们需要将其持久化到数据库中)或(禁止上帝)在每个身份验证请求中发送用户的原始电子邮件和密码。 ,将其存储在JSON对象内的令牌中,并将该对象包含在每个需要身份验证的请求中。

Let’s take a closer look at how that cycle might work, using the example of an Ember app + Rails API.

让我们使用Ember应用程序+ Rails API的示例来仔细研究该循环的工作方式。

  • The user fills out the login form via the Ember app and hits “login!”

    用户通过Ember应用程序填写登录表单,然后点击“登录!”
  • Ember POSTs user's email and password to the Rails API.

    Ember POST的用户的电子邮件和密码到Rails API。

  • Rails receive the POST request and queries the database for the right user. If the user can be authenticated...

    Rails接收POST请求,并在数据库中查询合适的用户。 如果用户可以通过身份验证...

  • We’ll use JWT to encrypt that user’s unique ID into a compact and secure JSON Web Token.

    我们将使用JWT将用户的唯一ID加密为紧凑且安全的JSON Web令牌。
  • This token is then included in the response that Rails sends back to Ember.

    然后,该令牌包含在Rails发送回Ember的响应中。
  • Ember stores the encrypted JWT token in local storage, retrieving it and sending it back to Rails, as the HTTP Authentication header in any authenticated requests.

    Ember将加密的JWT令牌存储在本地存储中,将其检索并将其作为任何经过身份验证的请求中的HTTP Authentication标头发送回 Rails。

So, what’s so great about this system?

那么,这个系统有什么优点呢?

Well, for one thing, we are not storing a unique user token in our database. Instead, we are encrypting the user’s unique identifying info in our token, and decrypting it on the Rails side when we need to identify the “current user”. Secondly, our server is not responsible for keeping track of the current user, as is the case when we use Rails’ session object.

好吧,一方面,我们没有在数据库中存储唯一的用户令牌。 相反,我们在令牌中加密用户的唯一标识信息,并在需要标识“当前用户”时在Rails端对其解密。 其次,我们的服务器不负责跟踪当前用户,就像我们使用Rails的session对象一样。

If you’re not yet convinced of how great this is, check out the jwt.io documentation. It offers some very clear and concise info.

如果您还不确定这有多出色,请查看jwt.io文档 。 它提供了一些非常清晰简洁的信息。

JWT加密:它如何工作? (JWT Encryption: How Does it Work?)

Image for post
Photo by Markus Spiske on Unsplash
Markus SpiskeUnsplash拍摄的照片

JWT tokens are encrypted in three parts:

JWT令牌分为三个部分进行加密:

  1. The header: the meta-data describing the encryption algorithm and type of token

    标头:描述加密算法和令牌类型的元数据
  2. The payload: the actual data concerning the user (id, email, etc.)

    有效负载:与用户有关的实际数据(ID,电子邮件等)
  3. The signature: a special combo of header info + payload to ensure that the sender of the token is really you!

    签名:标头信息+有效负载的特殊组合,以确保令牌的发送者确实是您!

Let’s take a look at an example, using the JWT Ruby library to encode our very own token!

让我们看一个使用JWT Ruby库编码我们自己的令牌的示例!

Given this information:

鉴于此信息:

  • {user_id: 1}

    {user_id: 1}

  • hmac secret: $39asdulawk3j489us39vm9370dmsZ

    hmac机密: $39asdulawk3j489us39vm9370dmsZ

  • encryption algorithm: HS256

    加密算法: HS256

We can encrypt our token in the following way:

我们可以通过以下方式加密令牌:

And it will return our three-part JWT:

它将返回我们的三部分JWT:

QyJ0asdfjos.ald925lIn0.eyJ0ZXN0Ijas39uZGF0YSJ9.

Similarly, to decode our token, we can use the following JWT Ruby code, where token is set equal to the above JWT, and hmac is set equal to the hmac secret we used to encrypt that token:

类似地,要解码令牌,我们可以使用以下JWT Ruby代码,其中将token设置为与上述JWT相等,并且将hmac设置为等于用于加密该令牌的hmac机密:

As we’ve seen here, it’s not too complicated to implement JWT in Ruby. So, let’s get started on implementing it in Rails.

正如我们在这里看到的那样,在Ruby中实现JWT并不太复杂。 因此,让我们开始在Rails中实现它。

构建一个JWT Auth库 (Building a JWT Auth Library)

Image for post
Photo by Jason D on Unsplash
杰森DUnsplash上的 照片

Setup:This post assumes that you have a Rails 5 API app up and running and that the app has a User model. We’re using the bcrypt gem to hash and store user passwords, so your User model should include the has_secure_password method.

设置:本文假定您已启动并运行Rails 5 API应用,并且该应用具有用户模型。 我们正在使用bcrypt gem来哈希和存储用户密码,因此您的User模型应包含has_secure_password方法。

Building a JWT Auth Library:Since we’ll need to regularly encode and decode JWTs, it makes sense to write a plain old Ruby class (PORO), that will wrap up some of that functionality.

构建JWT Auth库:由于我们需要定期对JWT进行编码和解码,因此编写一个普通的旧Ruby类(PORO)很有意义,它将封装一些功能。

We’ll put this class, which we’ll call Auth, inside our lib/ directory.

我们将把这个类(我们称为Auth)放在我们的lib/目录中。

  • Add the JWT gem to your Gemfile and bundle install:

    将JWT gem添加到您的Gemfile并捆绑安装:
# Gemfilegem 'jwt'
  • Create a file lib/auth.rb

    创建一个文件lib/auth.rb

  • Add your lib/ directory to the Rails autoload path:

    将您的lib/目录添加到Rails自动加载路径:

# config/application.rb
config.autoload_paths << Rails.root.join('lib')
  • Define your Auth class to have a method, .issue, which will be responsible for generating a JWT from a given user's information, and a .decode method, which will decode a given JWT:

    将您的Auth类定义为具有方法.issue.decode方法,该方法将根据给定的用户信息生成JWT,该方法将对给定的JWT进行解码:

We’ll start with our .issue method.

我们将从.issue方法开始。

生成JWT: Auth.issue (Generating a JWT: Auth.issue)

This method will simply wrap the JWT.encode method that the JWT gem makes available to us. This method takes in three arguments:

该方法将简单包装JWT gem提供给我们的JWT.encode方法。 此方法接受三个参数:

  • The data, in the form of a hash, that you will encode in the JWT

    您将在JWT中编码为哈希形式的数据
  • The key to your hashing algorithm

    您的哈希算法的关键
  • The type of hashing algorithm

    哈希算法的类型

For example:

例如:

payload = {name: "sophie"}
secret_key = "masd82348$asldfja"
algorithm = "HS256"JWT.encode(payload, secret_key, algorithm)
=> "esdiva23euihrusdfcnkjz2snciusdhuihr7480y2qikjh8"

Tada!

多田

How, though, will we generate a super-secret key?

但是,我们将如何生成超级秘密密钥?

Generating a Hashing Key

生成哈希密钥

We’ll use the Digest module that is native to Ruby to generate our secret key. We’ll generate our key in the command line, in the Pry console, and add it to our environment as an environment variable. We’ll use Figaro to make sure that the environment variable doesn’t get pushed up to GitHub.

我们将使用Ruby固有的Digest模块来生成我们的密钥。 我们将在Pry控制台的命令行中生成密钥,并将其作为环境变量添加到我们的环境中。 我们将使用Figaro来确保环境变量不会被推送到GitHub。

$ pry 
pry > Digest::SHA256.digest('beyonce')
=> "\x85\x11\xFA\xEF\xF2A\x11\xC7\x90\x9C!{\xDC\x11W\xFB\x93\xE5\xA3\xCD\xE3\xC2\x9E#7\xC4\xCDa\xCF\xC9/\xEA"

We’ll add it to our application.yml like this:

我们将其添加到我们的application.yml如下所示:

# config/application.yml
AUTH_SECRET: \x85\x11\xFA\xEF\xF2A\x11\xC7\x90\x9C!{\xDC\x11W\xFB\x93\xE5\xA3\xCD\xE3\xC2\x9E#7\xC4\xCDa\xCF\xC9/\xEA

Okay, now we’re ready to finish up that Auth.issue method.

好的,现在我们准备完成Auth.issue方法。

Note: You can also generate a secret in Rails by running rake secret in the command line in the directory of your app.

注意:您还可以通过 在应用目录的命令行中 运行 rake secret secret在Rails中生成密钥

Auth.issue

Auth.issue

Notice that the hashing algorithm is stored in a class constant, ALGORITHM. This is because our .decode method will also need to access that information. Similarly, we've wrapped our accessing of the ENV["AUTH_SECRET"] in a method call, because we'll also need that data for our .decode method

注意,哈希算法存储在类常量 ALGORITHM 这是因为我们的 .decode 方法也将需要访问该信息。 同样,我们将对 ENV["AUTH_SECRET"] 访问包装 在一个方法调用中,因为对于 .decode 方法 ,我们还将需要该数据。

While we’re here, let’s define that decode method.

在这里,让我们定义该解码方法。

解码JWT: Auth.decode (Decoding a JWT: Auth.decode)

Our Auth.decode method will simply wrap the JWT.decode method that the jwt gem makes available to us. This method takes in three arguments:

我们的Auth.decode方法将简单包装jwt gem提供给我们的JWT.decode方法。 此方法接受三个参数:

  • The JWT that we want to decode,

    我们要解码的JWT,
  • The hashing algorithm’s secret key

    哈希算法的密钥
  • The type of hashing algorithm

    哈希算法的类型

Okay, now we’re ready to use our Auth library in our controllers.

好的,现在我们可以在控制器中使用Auth库了。

在会话控制器中授权用户 (Authorizing a User in the Sessions Controller)

First, we’ll need to define a route that maps to Sessions#create

首先,我们需要定义一个映射到Sessions#create的路由

# config/routes.rb...
post '/login', to: "sessions#create"

This is the route to which our client-side application needs to POST the user's email and password, when the user fills out the login form.

当用户填写登录表单时,这是客户端应用程序需要POST用户的电子邮件和密码的方法。

Let’s build out the create action of our Sessions Controller.

让我们构建Sessions Controller的create动作。

Here, we use the data in the auth_params to authorize our user via bcrypt. If the user can be authenticated, we generating our JWT, using the Auth library we just defined. Here's the line where the magic happens:

在这里,我们使用auth_params的数据通过bcrypt授权我们的用户。 如果用户可以通过身份验证,我们将使用刚刚定义的Auth库生成JWT。 这是魔术发生的那一行:

jwt = Auth.issue({user: user.id})

Note that we are encrypting the user’s ID, not an email and password. This is because the user’s ID is a unique identifier without being sensitive information (like a password).

请注意,我们是在加密用户的ID,而不是电子邮件和密码。 这是因为用户的ID是唯一标识符,而不是敏感信息(例如密码)。

Note: my auth_params are grabbing data that is nested under the auth key. This is because my client-side application, an Ember app, is structuring data in the authorization request in that manner. You may structure the data in your requests differently, and therefore need to define slightly different strong params.

注意:我的 auth_params 正在抓取嵌套在 auth 下的数据 这是因为我的客户端应用程序Ember应用程序正在以这种方式构造授权请求中的数据。 您可能会以不同的方式构造请求中的数据,因此需要定义略有不同的强参数。

Then, we are sending the JWT back to the client-side app, as JSON, where it will be stored in localStorage.

然后,我们将JWT作为JSON发送回客户端应用程序,并将其存储在localStorage

Phew!

定义current_user方法 (Defining the current_user Method)

Okay, so, if our client-side app is set up properly, all subsequent requests to our API will include the following header:

好的,因此,如果正确设置了我们的客户端应用程序,则对API的所有后续请求都将包含以下标头:

"HTTP_AUTHORIZATION" => "Bearer <super encoded JWT>"

So, our current_user method, which we'll define in the Application Controller, will need to decode the JWT. Let's do it.

因此,我们将在应用程序控制器中定义的current_user方法将需要解码JWT。 我们开始做吧。

Some of this code should look pretty familiar: the current_user method is in charge of retrieving the current user, the logged_in? method returns true or false, depending on the presence of a current user, and the authenticate method acts as a gatekeeper, returning an error to the client-side application if there is no logged-in user.

其中一些代码应该看起来很熟悉: current_user方法负责检索当前用户, logged_in? 方法将返回true或false,具体取决于当前用户的存在,并且authenticate方法充当网守,如果没有登录用户,则将错误返回到客户端应用程序。

Let’s dive into our current_user method.

让我们深入研究current_user方法。

  • First, we check to see if our request object has a header, or a key, of ["HTTP_AUTHORIZATION"] and if the value of that header includes a realm designation of Bearer. If so...

    首先,我们检查request对象是否具有[“ HTTP_AUTHORIZATION”]的标头或键, 以及该标头的值是否包含Bearer的领域名称。 如果是这样的话...

  • Grab the token out of the value of that header, which would be something like "Bearer <encoded JWT>"

    从该标头的值中获取令牌,这类似于"Bearer <encoded JWT>"

  • Then, in our #auth method, we use our Auth library to decode the token:

    然后,在我们的#auth方法中,我们使用Auth库对令牌进行解码:

Auth.decode(token)
  • Finally, we use the decoded token, which is now in the below format and contains a user’s unique ID

    最后,我们使用解码后的令牌,该令牌现在采用以下格式,并包含用户的唯一ID
{user: 1}

to find and authorize the current user.

查找并授权当前用户。

And that’s it!

就是这样!

翻译自: https://medium.com/devops-dudes/ror-jwt-encryption-from-scratch-49448bae16f9

arm ror

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值