使用JWT(JSON网络令牌)保护API端点

时间:2020-09-29 11:47:53

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

使用其他信息进行了编辑:错误仍然存​​在。

我正在使用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',
        ]);    

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

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

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

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

我不确定100%是否最好将它们存储在cookie或sessionstorage中,但是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令牌时我做错了什么?我认为我的授权标头中有错误,但不确定。有人可以帮助我吗?

1 个答案:

答案 0 :(得分:0)

从Packege Documentation和一个简单但很好的解释tutorial中,我建议您添加

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

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

另外,当您创建令牌时,在这样的有效载荷中添加用户ID

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

也请您按照上面的教程链接进行操作,以更好地理解

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

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

  • 对于调试,Auth允许将该方法绑定到/ holidays请求并通过以下方法检查标头

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

  • 在控制器方法中打印$ header,以查看CakePHP是否收到授权头。