php 使用令牌实现 RESTful API 身份验证 (Yii/Yii2)

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/25327476/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-25 17:47:23  来源:igfitidea点击:

Implementing an RESTful API Authentication using tokens (Yii/Yii2)

phprestyiiaccess-tokenrestful-authentication

提问by Zabs

I am building an API in Yii 1.x which will be used with a mobile application. Part of the process involves a login (with a username and password) using the following JSON request below:-

我正在 Yii 1.x 中构建一个 API,它将与移动应用程序一起使用。该过程的一部分涉及使用以下 JSON 请求登录(使用用户名和密码):-

// Request sent with username & password

// 使用用户名和密码发送请求

{
"request" : {
    "model" : {
        "username" : "bobbysmith",
        "password" : "mystrongpassword"
    }
  }
}

// If successfully logged in return the following response

// 如果登录成功返回以下响应

{
"response": {
    "code": 200,
    "message": "OK",
    "model": {
        "timestamp": 1408109484,
        "token": "633uq4t0qdtd1mdllnv2h1vs32"
    }
 }
}

This token is quite important - once a user is logged in on the app I'd like them to have access to other pages that require them to be logged in. I want the mobile app to store this token & if the same 633uq4t0qdtd1mdllnv2h1vs32token is found within any subsequent requests it will accept this as being an authenticated request (for this user 'bobbysmith').

此令牌非常重要 - 一旦用户登录应用程序,我希望他们能够访问需要他们登录的其他页面。我希望移动应用程序存储此令牌,如果相同的633uq4t0qdtd1mdllnv2h1vs32令牌是在任何后续请求中发现,它将接受这是一个经过身份验证的请求(对于这个用户“bobbysmith”)。

I am a little unsure of how to best go about doing this, I have done some research and can oAuth has been mentioned a few times, along with Basic Authentication via HTTPS.

我有点不确定如何最好地做到这一点,我做了一些研究,并且可以多次提到 oAuth 以及通过 HTTPS 的基本身份验证。

So in a nutshell this...

所以简而言之,这...

  1. On mobile app homepage, user logs in correctly with their username & password & this sends a request to the API.
  2. This returns a successful response (shown above) with the current timestamp & the all important token.
  3. The same user goes to another app page/view where this token is a) required and b) if it matches up this authenticates that user (e.g so they can edit that account etc..)
  4. Once user clicks 'Logout' this token is then removed (and can longer access My Account etc..) - essentially a token based authentication system.
  1. 在移动应用主页上,用户使用他们的用户名和密码正确登录,这会向 API 发送请求。
  2. 这将返回一个带有当前时间戳和所有重要标记的成功响应(如上所示)。
  3. 同一用户转到另一个应用程序页面/视图,其中 a) 需要此令牌,并且 b) 如果匹配,则验证该用户(例如,他们可以编辑该帐户等)
  4. 一旦用户点击“退出”,这个令牌就会被删除(并且可以继续访问我的帐户等)——本质上是一个基于令牌的身份验证系统。

Can anyone possibly explain the best way to achieve this? Please let me know if what I have stated isn't 100% clear and i'll provide more information.

任何人都可以解释实现这一目标的最佳方法吗?如果我所说的不是 100% 清楚,请告诉我,我会提供更多信息。

While I am using PHP, a Yii 1.x solution is ideal as that is what the current API is built using.

当我使用 PHP 时,Yii 1.x 解决方案是理想的,因为它是当前 API 使用的构建方式。

In a nutshell, the app ensures that every request to server includes token in the payload or header so this token can be retrieved on everysubsequent post, once logged out this token is simply removed OR set to null/empty

简而言之,应用程序确保对服务器的每个请求都在有效负载或标头中包含令牌,因此可以在每个后续帖子中检索此令牌,一旦注销,此令牌将被简单地删除或设置为空/空

回答by lin

Information about handling security interfaces

有关处理安全接口的信息

Focus a solution which provides all the good (RESTful) auth stuff at once, which probably will be:

专注于一次提供所有好的(RESTful)身份验证内容的解决方案,这可能是:

  • SSL(most IMPORTANT, else "HTTP-Auth" would be sence less, everyone would be able to read out your Request Header / Body Man-in-the-middle-attack)
  • oAuth(or better oAuth2!)
  • HTTP-Auth
  • Tokens(including a limited lifetime, refresh and maybe include IP/DeviceUID check logic-> if it's mobile!)
  • Salt generated passwords
  • Custom HTTP-Headers for ID/ENV/CLIENTchecks or what ever.
  • Encrypted body data for Request and Response to prevent data manipulations
  • SSL(最重要的是,否则“HTTP-Auth”将没有意义,每个人都可以读出您的请求标头/正文中间人攻击
  • oAuth(或更好的 oAuth2!)
  • HTTP认证
  • 令牌(包括有限的生命周期、刷新,可能包括 IP/DeviceUID 检查逻辑 -> 如果它是移动的!)
  • 盐生成密码
  • 用于ID/ENV/CLIENT检查或其他任何内容的自定义 HTTP 标头。
  • 用于请求和响应的加密正文数据以防止数据操作

Hint:Personal user data should be allways encrypted!

提示:个人用户数据应始终加密!



Maybe a solution

也许是一个解决方案

Above you can see the standard information about security interfaces. To ensure lasting security you can try it like in the next part. I'am not sure about your AppSidePersitence. Maybe its sqlLite or something like that. That's why I don't indicate a code-based DB-Schema, like I did it to Yii. You will need a storage/persistence inside your Yii application (backend) and also inside your app (client) to store times and tokens.

您可以在上方看到有关安全接口的标准信息。为了确保持久的安全性,您可以像下一部分一样尝试。我不确定您的 AppSidePersitence。也许它的 sqlLite 或类似的东西。这就是为什么我不像我对 Yii 那样指出基于代码的 DB-Schema。您将需要在 Yii 应用程序(后端)和应用程序(客户端)中使用存储/持久性来存储时间和令牌。

Your YiiDBModel

你的 YiiDBModel

enter image description here

在此处输入图片说明

-- -----------------------------------------------------
-- Table `mydb`.`user`
-- -----------------------------------------------------
CREATE TABLE IF NOT EXISTS `mydb`.`user` (
  `id` INT NOT NULL,
  `username` VARCHAR(255) NOT NULL,
  `password` VARCHAR(255) NOT NULL,
  `lastLogin` DATETIME NULL,
  `modified` DATETIME NULL,
  `created` DATETIME NULL,
  PRIMARY KEY (`id`))
ENGINE = InnoDB;

----------------------------------------------------
-- Table `mydb`.`authToken`
-- -----------------------------------------------------
CREATE TABLE IF NOT EXISTS `mydb`.`authToken` (
  `id` INT NOT NULL,
  `userId` INT NOT NULL,
  `token` VARCHAR(255) NOT NULL,
  `created` DATETIME NOT NULL,
  PRIMARY KEY (`id`, `userId`),
  INDEX `fk_authToken_user_idx` (`userId` ASC),
  UNIQUE INDEX `token_UNIQUE` (`token` ASC),
  CONSTRAINT `fk_authToken_user`
    FOREIGN KEY (`userId`)
    REFERENCES `mydb`.`user` (`id`)
    ON DELETE NO ACTION
    ON UPDATE NO ACTION)
ENGINE = InnoDB;

Your AppPersitenceModel

你的 AppPersitenceModel

enter image description here

在此处输入图片说明

Handling your token right and ensure login security

正确处理您的令牌并确保登录安全

  1. Any token you generate on Yii-Application side will be stored with a "created"-Datetime in your YiiApp-Database (see table authToken). Each user has only one "token", which will generated after a correct "login"-Request. In that way, you don't need to check tokens while "login". Defined by the schema, "token" is unique! I think there is no need to handle historical data (old tokens which are expired).
  2. Once the user-login was validated as "success" by Yii you generate a new user-token with current timestamp, which will be stored in your YiiApp-DB. In your YiiApp you need to configured a "expire time", which will be added to the current timestamp, for example, if you like to use "timestamps": Current timestamp is: 1408109484and your expire time is set to 3600(which is 3600 sec = 1h). So ... your expire datetime which will be send via API is (1408109484+3600). Btw. Hint: You don't need to send attributes like "code": 200. Response-Codes are included in your Requests/Response-Header-Data.

    ** 200 OK Response-Example, after user-login was successful, holds the calculated "expired"-date:**

     {
        "error": null,
        "content": {
            "expires": 1408109484,
            "token": "633uq4t0qdtd1mdllnv2h1vs32"
        }
    }
    
  3. Important: Every requests which you want to be secured, needs to be send with your generated User-"token". Which will probably stored in your deviceStorage. You can handle your "login-states"- really RESTful if you using HTTP-Response-Codesright, for example, 200 OK (if all is fine) or 401 (not authorized, user is not loged in or session is expired). You need to validate your User-Request's on Yii side. Read out the token from incoming requests, validate it due to given tokens in database and compare "created"-DB with the current incoming Request-Time (HTTP-Requests).

    ** Request-Example, default schema on any security requests:**

    {
        "token": "633uq4t0qdtd1mdllnv2h1vs32"
        "content": {
            "someDataYouNeed" : null
        }
    }
    

    ** 401 Unauthorized Response-Example, token expired :**

    {
        "error": 1, // errorCode 1: token is expired
        "content": {
            "someDataYouNeed" : null
        }
    }
    

    ** 401 Unauthorized Response-Example, user is not logged in (no token exists in YiiDB):**

    {
        "error": 2, // errorCode 2: user is not logged in
        "content": {
            "someDataYouNeed" : null
        }
    }
    
  4. Keep a User-Session alive?That's pretty easy. Just update "created"-Date in authToken-Table to the current request time. Do that every time, a valid request was send by the user. In that way, the session will not expire, if the user is still active. Ensure, your DB-Token is not expired, before updating expires-Date field in DB. If no request be send while the session expires, the keep alive won't be possible anymore.

    Sorry, but adding PHP-Codes would be too much.

  1. 您在 Yii-Application 端生成的任何令牌都将与 YiiApp-Database 中的“created”-Datetime 一起存储(参见表 authToken)。每个用户只有一个“令牌”,它将在正确的“登录”请求后生成。这样,您就无需在“登录”时检查令牌。由模式定义,“令牌”是唯一的!我认为没有必要处理历史数据(过期的旧令牌)。
  2. 一旦用户登录被 Yii 验证为“成功”,您将生成一个具有当前时间戳的新用户令牌,该令牌将存储在您的 YiiApp-DB 中。在您的 YiiApp 中,您需要配置一个“过期时间”,它将添加到当前时间戳中,例如,如果您喜欢使用“时间戳”:当前时间戳为:1408109484并且您的过期时间设置为3600(即 3600 秒= 1 小时)。所以...您将通过 API 发送的到期日期时间是(1408109484+3600). 顺便提一句。提示:您不需要发送像"code": 200. Response-Codes 包含在您的 Requests/Response-Header-Data 中。

    ** 200 OK Response-Example,用户登录成功后,保存计算出的“过期”日期:**

     {
        "error": null,
        "content": {
            "expires": 1408109484,
            "token": "633uq4t0qdtd1mdllnv2h1vs32"
        }
    }
    
  3. 重要提示:您想要保护的每个请求都需要与您生成的用户“令牌”一起发送。这可能会存储在您的 deviceStorage 中。你可以处理你的“登录状态”——如果你正确使用HTTP-Response-Codes,真的是 RESTful ,例如,200 OK(如果一切正常)或 401(未授权,用户未登录或会话已过期)。您需要在 Yii 端验证您的用户请求。从传入请求中读取令牌,根据数据库中给定的令牌对其进行验证,并将“创建的”-DB 与当前传入的请求时间(HTTP 请求)进行比较。

    ** 请求示例,任何安全请求的默认架构:**

    {
        "token": "633uq4t0qdtd1mdllnv2h1vs32"
        "content": {
            "someDataYouNeed" : null
        }
    }
    

    ** 401 未经授权的响应示例,令牌已过期:**

    {
        "error": 1, // errorCode 1: token is expired
        "content": {
            "someDataYouNeed" : null
        }
    }
    

    ** 401 Unauthorized Response-Example, 用户未登录(YiiDB 中不存在令牌):**

    {
        "error": 2, // errorCode 2: user is not logged in
        "content": {
            "someDataYouNeed" : null
        }
    }
    
  4. 保持用户会话活着?这很容易。只需将 -Table 中的“created”-Date 更新authToken为当前请求时间。每次都这样做,用户发送了一个有效的请求。这样,如果用户仍处于活动状态,会话就不会过期。在更新expiresDB 中的 -Date 字段之前,请确保您的 DB-Token 未过期。如果在会话到期时没有发送请求,则保持活动将不再可能。

    抱歉,但是添加 PHP 代码太多了。

回答by Razvan Grigore

By this time probably you switched to Yii2, and for future reference, the cleanest solution would be to use included classes for RESTful APIs, or one can implement them in any framework.

到这个时候你可能已经切换到 Yii2,为了将来参考,最干净的解决方案是使用包含类的 RESTful API,或者可以在任何框架中实现它们。

Source: HttpBearerAuth.php

来源:HttpBearerAuth.php

The advantages are explained fully in this article, but to summarise, it's better to use your solution with request headers, since GET parameters might be saved in logs and Basic Auth password can be easily intercepted if you don't use SSL (you should!)

优点在本文中得到了充分的解释,但总而言之,最好将您的解决方案与请求标头一起使用,因为 GET 参数可能会保存在日志中,并且如果您不使用 SSL,则可以轻松拦截基本身份验证密码(您应该! )

回答by Mike Miller

If you are building for a native mobile app then sensible thing would be to rely on the security of the native memory (eg the iOS keychain) and not a cookie based solution. Otherwise how you have described seems fine. As long as your payload is being sent over SSL it doesnt really matter if the token is in the PUT or the POST. Your token management (ie expiration times) are business decisions you have to make. Back end I would do as you describe and hold the token in your database and delete it when it has become defunct for whatever reasons and return a message to your client app to put it back into logged out mode/re-request credentials.

如果您正在构建本机移动应用程序,那么明智的做法是依赖本机内存(例如 iOS 钥匙串)的安全性,而不是基于 cookie 的解决方案。否则你描述的方式似乎很好。只要您的有效负载是通过 SSL 发送的,令牌是在 PUT 中还是在 POST 中并不重要。您的令牌管理(即到期时间)是您必须做出的业务决策。后端我会按照您的描述执行并将令牌保存在您的数据库中,并在它因任何原因失效时将其删除,并向您的客户端应用程序返回一条消息以将其恢复为注销模式/重新请求凭据。

EDIT: Check out this awesome tut from the prolific Phil Sturgeon. He also has a great CI library for building RESTful API's in CI which might be worth looking at.

编辑:看看这个来自多产的 Phil Sturgeon 的精彩表演。他还有一个很棒的 CI 库,用于在 CI 中构建 RESTful API,这可能值得一看。

http://philsturgeon.uk/blog/2013/07/building-a-decent-api

http://philsturgeon.uk/blog/2013/07/building-a-decent-api

http://code.tutsplus.com/tutorials/working-with-restful-services-in-codeigniter--net-8814

http://code.tutsplus.com/tutorials/working-with-restful-services-in-codeigniter--net-8814