【问题标题】:Securing API EndPoints with JWT (JSON Web Token)使用 JWT(JSON Web 令牌)保护 API 端点
【发布时间】:2020-09-29 11:47:53
【问题描述】:

已编辑附加信息:错误仍然存​​在..

我正在使用 CakePHP BackEnd 和 Vue FrontEnd 开发一个应用程序,并且正在使用 JWT Security 保护我的 API 后端。为此,我使用了“ADmad/JwtAuth.Jwt”插件,但我相信您不需要知道这个插件就能回答这个问题。基本思想是,在登录/注册时,客户端会收到一个 JWT 令牌,将其存储在 cookie/会话中,然后在对 API 的后续请求中使用它。

插件在我的 AppController 中加载,我的 API 机密存储在 app.php..

        $this->loadComponent('Auth', [
            'storage' => 'Memory',
            'authenticate' => [
                'ADmad/JwtAuth.Jwt' => [
                    'parameter' => 'token',
                    'userModel' => 'Users',
                    'fields' => [
                        'email' => 'id'
                    ],

                    'queryDatasource' => true
                ]
            ],
            'checkAuthIn' => 'Controller.initialize',
        ]);    

登录时,通过form/facebook,或注册,生成JWT并发送给客户端。我将通过登录操作来说明这一点,Facebook 登录和注册也是类似的。这些是用户控制器上允许的操作,其余的受 JWT Auth 插件保护..

$this->Auth->allow(['login', 'register', 'facebooklogin', 'getfbcredentials']);  

在客户端 (Vue) 上,函数 loginUser 从表单中获取电子邮件/密码凭据并将它们发送到 API。在服务器端(Cake API),哈希密码通过数据库中的值进行检查,邮件/密码凭据以及到期日期通过服务器端环境变量的 api 密钥加密发送回。

在客户端,这些凭据被解密并与原始邮件/密码值进行比较,以确保没有外部攻击者篡改它们。只有这样才允许登录并将令牌存储在 cookie 中..

我不是 100% 确定将它们存储在 cookie 或会话存储中是否更好,但 httponly cookie 似乎相同,我选择了这个选项,因为我已经阅读了支持和反对这两者的论据.. https://dzone.com/articles/cookies-vs-tokens-the-definitive-guide

客户端 Vue 获取交互..

methods: {
    
    loginUser(){
        
        this.errors = "";
        
        // if one of the mandatory fields is empty 
        if(!this.email || !this.password){
            
            this.errors = "fill in all fields with an *";
            
        // make the API call     
        } else {
            
            // cannot use associative arrays in Javascript : does not exist!! 
            this.data[0] = this.email;
            this.data[1] = this.password;            

            fetch(this.$baseURL + '/users/login', {
              method: 'POST',
              mode: 'cors',
              headers: {
                'Content-Type': 'application/json',
              },
              body: JSON.stringify(this.data),
            })
            .then(response => response.json())
            .then(json_data => {
                
                // console.log(json_data);
                
                // wrong password
                if(json_data == 2){
                    
                    this.errors = "wrong password, please correct."      
                    this.password = ""; 
                    
                // email not found     
                } else if (json_data == 0){
                    
                    this.errors = "email not registered, please register.."    
                
                // succesful save     
                } else if (json_data[0] == 1) {
                            
                    // perform JWT token check, store it & use it in subsequent API calls.. 
                    var payload = this.parseJwt(json_data[2]);                  
                                
                    this.$session.set('isLogged', true); 
                    this.$session.set('user', json_data[1]);
                    this.$session.set('navigateto', 'userhub'); 
                    this.$emit('navEvent');

                    this.errors = "welcome!" 
                    this.email = this.password = "";   

                    // (XSFR attack vulnerability cookie, protected through CORS :: XSS no vulnerability with cookie) 
                    this.$cookies.set('jwtoken', json_data[2], {httpOnly: true});
                            
                }
                
            })
            .catch(error => {

                // console.log("error");
                this.errors = "no success.. please try again."  

            });                        
        }
    },

API端(Cake)交互,登录动作UsersController:

// loginaction needs to be the same, is defined in AppController 
public function login(){

    $data = $this->request->data; 
    $sendback = [];
    $hasher = new DefaultPasswordHasher();
    
    // no automatic view, only data returned
    $this->autoRender = false;        
    
    // mandatory check to prevent server timeout 
    if($data){
              
        $users = $this->Users->find('all');
        
        foreach($users as $user):

            if ($user['email'] === $data[0]){

                if ($hasher -> check($data[1], $user['password'])){

                    // get user id, this is the user given you implement a check for double entries 
                    // never a password in storage!!!! 
                    $user['password'] = "";
                    $user['resetcode'] = "";
                    
                    // JWT Encode the payload/ secret to generate token..
                    // secret from environment variables, not known to the attacker, needs to know this to form the token or steal the token.. 
                    $apisecret = Configure::read('apisecret');  
                    $payload = ['mail' => $data[0], 'password' => $data[1],'exp' =>  time() + 604800, 'id' => $user['id'], 'sub' => $user['id']];
                    
                    // uses salt value in app.php for encryption::encryption mechanism uses firebase library combined with salt value..  
                    $jwt = JWT::encode([$payload, $apisecret], Security::salt());
                    
                    $sendback[0] = 1;
                    $sendback[1] = $jwt;

                // wrong password 
                } else {

                    $sendback[0] = 2;

                }
                
                // break actually does the same as a $this->Users->find('first') with condition email.. 
                // break the loop whenever email is found, run password check 1 time.. 
                break;                    

            } else {

                $sendback[0] = 0;

            }

        endforeach;        
        
    }   
         
    return $this->response
    ->withType('application/json')
    ->withStringBody(json_encode($sendback));              
                
}

到目前为止一切顺利.. :-) 现在我想在对后端的后续 API 请求中使用这个 cookie。因此,我在获取请求的授权标头中设置了从我的 cookie 中检索到的 JWT。我的后端出现错误,指出“您无权访问该位置”。 (错误日志底部页面)..

fetch(this.$baseURL + '/holidays', {
      method: 'POST',
      mode: 'cors',
      headers: {
        'Content-Type': 'application/json',
        'Authorization': 'Bearer ' + this.$cookies.get("jwtoken"),
      },
      body: JSON.stringify(this.data),
    })
    .then(response => response.json())
    .then(json_data => {
        
        // console.log(json_data);
        
    })
    .catch(error => {

        this.errors = "no success.. please try again."

    });

错误日志

2020-09-29 11:44:14 Error: [Cake\Http\Exception\UnauthorizedException] You are not authorized to access that location. (C:\wampserver\www\wamp_projects\holidays_backend\vendor\admad\cakephp-jwt-auth\src\Auth\JwtAuthenticate.php:264)
#0 C:\wampserver\www\wamp_projects\holidays_backend\vendor\cakephp\cakephp\src\Controller\Component\AuthComponent.php(387): ADmad\JwtAuth\Auth\JwtAuthenticate->unauthenticated(Object(Cake\Http\ServerRequest), Object(Cake\Http\Response))
#1 C:\wampserver\www\wamp_projects\holidays_backend\vendor\cakephp\cakephp\src\Controller\Component\AuthComponent.php(315): Cake\Controller\Component\AuthComponent->_unauthenticated(Object(App\Controller\UsersController))
#2 C:\wampserver\www\wamp_projects\holidays_backend\vendor\cakephp\cakephp\src\Event\EventManager.php(352): Cake\Controller\Component\AuthComponent->authCheck(Object(Cake\Event\Event))
#3 C:\wampserver\www\wamp_projects\holidays_backend\vendor\cakephp\cakephp\src\Event\EventManager.php(329): Cake\Event\EventManager->_callListener(Array, Object(Cake\Event\Event))
#4 C:\wampserver\www\wamp_projects\holidays_backend\vendor\cakephp\cakephp\src\Event\EventDispatcherTrait.php(113): Cake\Event\EventManager->dispatch(Object(Cake\Event\Event))
#5 C:\wampserver\www\wamp_projects\holidays_backend\vendor\cakephp\cakephp\src\Controller\Controller.php(676): Cake\Controller\Controller->dispatchEvent('Controller.init...')
#6 C:\wampserver\www\wamp_projects\holidays_backend\vendor\cakephp\cakephp\src\Http\ActionDispatcher.php(115): Cake\Controller\Controller->startupProcess()
#7 C:\wampserver\www\wamp_projects\holidays_backend\vendor\cakephp\cakephp\src\Http\ActionDispatcher.php(94): Cake\Http\ActionDispatcher->_invoke(Object(App\Controller\UsersController))
#8 C:\wampserver\www\wamp_projects\holidays_backend\vendor\cakephp\cakephp\src\Http\BaseApplication.php(234): Cake\Http\ActionDispatcher->dispatch(Object(Cake\Http\ServerRequest), Object(Cake\Http\Response))
#9 C:\wampserver\www\wamp_projects\holidays_backend\vendor\cakephp\cakephp\src\Http\Runner.php(65): Cake\Http\BaseApplication->__invoke(Object(Cake\Http\ServerRequest), Object(Cake\Http\Response), Object(Cake\Http\Runner))
#10 C:\wampserver\www\wamp_projects\holidays_backend\vendor\cakephp\cakephp\src\Routing\Middleware\RoutingMiddleware.php(162): Cake\Http\Runner->__invoke(Object(Cake\Http\ServerRequest), Object(Cake\Http\Response))
#11 C:\wampserver\www\wamp_projects\holidays_backend\vendor\cakephp\cakephp\src\Http\Runner.php(65): Cake\Routing\Middleware\RoutingMiddleware->__invoke(Object(Cake\Http\ServerRequest), Object(Cake\Http\Response), Object(Cake\Http\Runner))
#12 C:\wampserver\www\wamp_projects\holidays_backend\vendor\cakephp\cakephp\src\Routing\Middleware\AssetMiddleware.php(88): Cake\Http\Runner->__invoke(Object(Cake\Http\ServerRequest), Object(Cake\Http\Response))
#13 C:\wampserver\www\wamp_projects\holidays_backend\vendor\cakephp\cakephp\src\Http\Runner.php(65): Cake\Routing\Middleware\AssetMiddleware->__invoke(Object(Cake\Http\ServerRequest), Object(Cake\Http\Response), Object(Cake\Http\Runner))
#14 C:\wampserver\www\wamp_projects\holidays_backend\vendor\cakephp\cakephp\src\Error\Middleware\ErrorHandlerMiddleware.php(96): Cake\Http\Runner->__invoke(Object(Cake\Http\ServerRequest), Object(Cake\Http\Response))
#15 C:\wampserver\www\wamp_projects\holidays_backend\vendor\cakephp\cakephp\src\Http\Runner.php(65): Cake\Error\Middleware\ErrorHandlerMiddleware->__invoke(Object(Cake\Http\ServerRequest), Object(Cake\Http\Response), Object(Cake\Http\Runner))
#16 C:\wampserver\www\wamp_projects\holidays_backend\vendor\cakephp\debug_kit\src\Middleware\DebugKitMiddleware.php(53): Cake\Http\Runner->__invoke(Object(Cake\Http\ServerRequest), Object(Cake\Http\Response))
#17 C:\wampserver\www\wamp_projects\holidays_backend\vendor\cakephp\cakephp\src\Http\Runner.php(65): DebugKit\Middleware\DebugKitMiddleware->__invoke(Object(Cake\Http\ServerRequest), Object(Cake\Http\Response), Object(Cake\Http\Runner))
#18 C:\wampserver\www\wamp_projects\holidays_backend\vendor\cakephp\cakephp\src\Http\Runner.php(51): Cake\Http\Runner->__invoke(Object(Cake\Http\ServerRequest), Object(Cake\Http\Response))
#19 C:\wampserver\www\wamp_projects\holidays_backend\vendor\cakephp\cakephp\src\Http\Server.php(97): Cake\Http\Runner->run(Object(Cake\Http\MiddlewareQueue), Object(Cake\Http\ServerRequest), Object(Cake\Http\Response))
#20 C:\wampserver\www\wamp_projects\holidays_backend\webroot\index.php(40): Cake\Http\Server->run()
#21 {main}
Request URL: /users/retrievepicture
Referer URL: http://localhost:8080/

我在使用 JSON Web Token 时做错了什么?我认为我的授权标头中有错误,但不确定.. 谁能帮助我?

【问题讨论】:

    标签: vue.js cakephp cakephp-3.x jwt-auth


    【解决方案1】:

    来自Packege Documentation和一个简单但很好的解释tutorial我建议你添加

    'storage' => 'Memory',
    'checkAuthIn' => 'Controller.initialize',
    

    在你的 $this->loadComponent('Auth',...)

    另外,当您创建令牌时,像这样在有效负载中添加 user-id、sub

    payload = ['id' => $user['id'],'sub' => $user['id'] ,'mail' => $data[0], 'password' => $data[1],'exp' =>  time() + 3600];
    

    还请您按照上面的教程链接更好地理解。

    编辑:如果这个问题仍然存在,请

    • 检查 Vue 是否在 API 请求中发送 Token

    • 对于调试,Auth 允许绑定 /holidays 请求的方法并检查标头

      $header = $request->getHeaderLine('Authorization');

    • 在控制器方法中打印 $header 以查看 CakePHP 是否接收到 Authorization Header。

    【讨论】:

    • 您好 Kaiser Ahmed Tushar,感谢您的回复。我已经实施了您的建议,但仍然收到相同的错误消息,但没有更多详细信息。还有其他可能导致这种情况的原因吗?已在问题中编辑..
    • 您好,在您对问题进行编辑后,我还在最后的答案中添加了一些注释。请让我知道输出。同时提供 JWT 令牌。
    • 它仍然不起作用..我决定解决插件并自己编写一些函数来编码/解码令牌。此解决方案工作正常。如果你愿意,我可以与你分享代码..
    • 如果解决方案有效,那么我们无需担心。编码愉快。
    猜你喜欢
    • 2014-06-30
    • 2017-05-13
    • 2018-12-26
    • 2020-11-09
    • 2019-12-15
    • 2016-08-01
    • 2016-09-19
    • 2016-05-19
    • 2016-02-07
    相关资源
    最近更新 更多