thinkphp6+jwt 实现登录验证

本文介绍如何运用thinkphp6开发RESTful API接口,实现管理系统前后分离登录验证功能。

输出JSON格式封装

首先把系统输出数据的格式进行统一,创建common文件夹并且创建Output.php。

<?php 
namespace app\adminApi\common; 
use think\Response; 
trait Output{      
/**     
* 处理成功返回API数据    
* @param  mixed   $data 要返回的数据     
* @param  integer $code 返回的code     
* @param  mixed   $message 提示信息     
* @param  string  $type 返回数据格式     
* @param  array   $header 发送的Header信息     
* @return Response     
*/    
public function Success($data , string $message = '请求成功', int $code = 200, string $type = 'json',$header = []) :Response    {        
     $result = [            
         'code' => $code,            
         'message' => $message,            
         'time' => date('Y-m-d H:i:s',time()),            
         'data' => $data        
     ];        
     return  json($result,$code);             
}     
/**     
* 处理失败返回API数据   
* @param  mixed   $data 要返回的数据     
* @param  integer $code 返回的code     
* @param  mixed   $message 提示信息     
* @param  string  $type 返回数据格式     
* @param  array   $header 发送的Header信息     
* @return Response     
*/    
public function Error($data , string $message = '请求失败', int $code = 500, string $type = 'json',$header = []) :Response    {        
     $result = [            
         'code' => $code,            
         'message' => $message,            
         'time' => date('Y-m-d H:i:s',time()),            
         'data' => $data        
     ];        
     return  json($result,$code);           
}     
/**     
* @param int $code     
* @param string $message     
* @param array $data     
* @param array $header     
*/    
public function returnMsg($data = [], $message = '',$code = 500,$header = [])    {       
     $res = [            
        'code' => $code,            
        'message' => $message,            
        'time' => date('Y-m-d H:i:s',time()),            
        'data' => $data        
     ];        
     return json($res,$code);        
        
}
}

在app下BaseController.php文件引入封装方法

<?php
declare (strict_types = 1);

namespace app\adminApi;
use think\App;
use think\exception\ValidateException;
use think\Validate;
/** 
* 引用封装方法
*/
use app\adminApi\common\Output;
/** 
* 控制器基础类 
*/
abstract class BaseController{  
   use Output;  
   /**     
   * Request实例     
   * @var \think\Request     
   */    
   protected $request;    
   /**     
   * 应用实例     
   * @var \think\App     
   */    
   protected $app;    
   /**     
   * 是否批量验证     
   * @var bool     
   */    
   protected $batchValidate = false;    
   /**     
   * 控制器中间件     
   * @var array     
   */    
   protected $middleware = [];    
   /**     
   * 构造方法     
   * @access public     
   * @param  App  $app  应用对象     
   */    
   public function __construct(App $app)    {        
       $this->app     = $app;        
       $this->request = $this->app->request;        
       // 控制器初始化        
       $this->initialize();    
   }    
   // 初始化    
   protected function initialize()    {}    
   /**     
   * 验证数据     
   * @access protected     
   * @param  array        $data     数据     
   * @param  string|array $validate 验证器名或者验证规则数组     
   * @param  array        $message  提示信息     
   * @param  bool         $batch    是否批量验证     
   * @return array|string|true     
   * @throws ValidateException     
   */    
   protected function validate(array $data, $validate, array $message = [], bool $batch = false)    {        
       if (is_array($validate)) {            
           $v = new Validate();            
           $v->rule($validate);        
       } else {            
           if (strpos($validate, '.')) {                
              // 支持场景                
              list($validate, $scene) = explode('.', $validate);            
           }            
           $class = false !== strpos($validate, '\\') ? $validate : $this->app->parseClass('validate', $validate);            
           $v     = new $class();            
           if (!empty($scene)) {                
                $v->scene($scene);            
           }        
       }        
       $v->message($message);        
       // 是否批量验证        
       if ($batch || $this->batchValidate) {            
           $v->batch(true);        
       }        
       return $v->failException(true)->check($data);    
   }
}

在route路由文件配置RESTFul请求的资源路由

<?php
use think\facade\Route;
Route::resource('blogs', 'Blogs')->only(['index', 'read']);

Blogs示例控制器

<?php
namespace app\adminApi\controller;
use app\adminApi\BaseController;

class Blogs extends BaseController{    

/*** @OA\GET(
*   tags={"博客管理"},
*     summary="博客列表",
*     path="/api/admin.php/blogs",
*    @OA\Response(
*         response="200",
*         description="状态码、提示消息",
*   )
*)
*/    
public function index()    {               
    return self::Success([]);    
}    
/**     
* @OA\Get(path="/api/admin.php/blogs/{id}",     
*   tags={"博客管理"},     
*   summary="博客文章",     
*   @OA\Parameter(name="id", in="path", description="文章id", @OA\Schema(type="number"),required=true),     
*   @OA\Response(response="200", description="The User")     
* )     
*/    
public function read($id)    {               
  return self::Error();    
}}

用swagger测试http://域名/api/admin.php/blogs,swagger使用方法请查看(0到1实现thinkphp6+swagger-php3.0配置管理接口文档

自定义异常处理

thinkphp6的异常是HTML格式输出,既然我们开发RESTful API接口,也希望异常按照统一规格以json输出。(thinkphp6官方异常处理文档

应用文件夹下创建ExceptionHandle.php并且重写render()方法。

<?php
namespace app\adminApi;
use think\db\exception\DataNotFoundException;
use think\db\exception\ModelNotFoundException;
use think\exception\Handle;
use think\exception\HttpException;
use think\exception\HttpResponseException;
use think\exception\ValidateException;
use think\Response;use Throwable;
/** 
* 应用异常处理类 
*/
class ExceptionHandle extends Handle{    
    private $code;    
    private $message;    
    private $statuscode;    
    /**     
    * @param \think\Request $request     
    * @param Throwable $e     
    * @return Response     
    */    
    public function render($request, Throwable $e): Response    {        
       $this->statuscode = $this->code = 500;        
       if($e instanceof BaseException){//是否异常实例            
          $this->code = $e->code;            
          $this->message = $e->message;        
       }else{            
          if (config('app_debug')){                
             return parent::render($request, $e); // TODO: Change the autogenerated stub            
          }            
          $this->code = 500;            
          $this->message = $e->getMessage() ?: '服务器内部错误';        
       }        
       // Http异常        
       if ($e instanceof \think\exception\HttpException)        {            
          $this->statuscode = $this->code = $e->getStatusCode();        
       }        
       $data = [            
          'code' => $this->statuscode,            
          'message' => $this->message,            
          'time' => time(),            
          'data' => null,            
          'request_url' => $request->url()        
       ];        
       return json($data,$this->statuscode);    
   }
}

在对应的模块文件夹创建provider.php文件

<?php
use app\adminApi\ExceptionHandle;
use app\adminApi\Request;
// 容器Provider定义文件
return [    
   'think\Request'          => Request::class,    
   'think\exception\Handle'  => '\\app\\adminApi\\ExceptionHandle'
];

用swagger测试http://域名/api/admin.php/blogs1,可以看到异常已经可以按json格式输出。

权限校验处理

权限校验我们使用thinkphp6的中间件来处理,在应用middleware文件夹新建Auth.php文件。

<?php
declare (strict_types = 1);
namespace app\adminApi\middleware;
use app\adminApi\common\Output;
class Auth{    
  use Output;    
  /**     
  * 处理请求     
  *     
  * @param \think\Request $request     
  * @param \Closure       $next     
  * @return Response     
  */    
  public function handle($request, \Closure $next)    {        
     return self::returnMsg(null,'请先登陆!',404);
  }
}

继续使用blogs.php作为示例,引入中间件。

<?php
namespace app\adminApi\controller;
use app\adminApi\BaseController;
//引入权限中间件
use app\adminApi\middleware\Auth;

class Blogs extends BaseController{   

protected $middleware = [Auth::class]; 

/*** @OA\GET(
*   tags={"博客管理"},
*     summary="博客列表",
*     path="/api/admin.php/blogs",
*    @OA\Response(
*         response="200",
*         description="状态码、提示消息",
*   )
*)
*/    
public function index()    {               
    return self::Success([]);    
}    
/**     
* @OA\Get(path="/api/admin.php/blogs/{id}",     
*   tags={"博客管理"},     
*   summary="博客文章",     
*   @OA\Parameter(name="id", in="path", description="文章id", @OA\Schema(type="number"),required=true),     
*   @OA\Response(response="200", description="The User")     
* )     
*/    
public function read($id)    {               
  return self::Error();    
}}

用swagger测试http://域名/api/admin.php/blogs

把protected $middleware = [Auth::class]; 修改为protected $middleware = [Auth::class => ['except' => ['index']]];,可配置该控制器不作权限校验,修改后

访问read则带有校验,

使用jwt实现信息加密

安装jwt扩展

composer require firebase/php-jwt

在应用services文件夹新建JwtService.php文件,封装加密跟解密方法

<?php 
namespace app\adminApi\services; 
use Firebase\JWT\JWT;
use Firebase\JWT\Key; 
class JwtService{          
   private $key = "md5";    
   private $iss = "https://域名";    
   private $aud = "XXX";    
   private $expTime = 36000;    
   // jwt生成    
   public function setJwtToken($uid)    {        
      $payload = array(            
         "iss" => $this->iss,            
         "aud" => $this->aud,            
         "iat"=>time(),      //签发时间            
         "nbf"=>time(),      //在什么时候jwt开始生效            
         "exp"=> time()+$this->expTime,  //token 过期时间      
         'uid' => $uid,          //前端页面所传uid        
      );         
      $jwt = JWT::encode($payload, md5($this->key), 'HS256');        
      return $jwt;    
   }    
   // jwt解密    
   public function decryptJwtToken($token)    {      
      $decoded = JWT::decode($token, new Key(md5($this->key), 'HS256'));        
      return $decoded;    
   }
}

在中间件Auth.php进行权限校验

<?php
declare (strict_types = 1);
namespace app\adminApi\middleware;
use think\facade\Session;
use app\adminApi\common\Output;
use app\adminApi\services\JwtService;
use Firebase\JWT\ExpiredException;
class Auth{    
   use Output;    
   /**     
   * 处理请求
   * @param \think\Request $request     
   * @param \Closure       $next     
   * @return Response     
   */    
   public function handle($request, \Closure $next)    {  
      //获取请求的时候头部携带着这个Authorization
      $token = $request->header('Authorization');               
      $tokenJwt = new JwtService();        
      if (!$token) return self::returnMsg(null, '请先登陆!', 401); 
      //判断token是否为空               
      $token = substr($token,7);               
      try {            
         $jwtAuth = $tokenJwt->decryptJwtToken($token);            
         $authInfo = json_decode(json_encode($jwtAuth), true);            
         if (!$authInfo['uid']) {                
             return self::returnMsg(null, '用户不存在', 401);            
         }
         return $next($request);        
      } catch (ExpiredException $e) {            
         return self::returnMsg(null, 'token过期', 401);        
      } catch (\Exception $e) {            
         return self::returnMsg(null, $e->getMessage(), 401);        
      }    
  }
}

在Login控制器写登录方法

<?php
namespace app\adminApi\controller;
use think\facade\Db;
use think\facade\Request;
use app\adminApi\CommonController;
use think\exception\ValidateException;
use app\adminApi\validate\Admin;
use app\adminApi\services\JwtService;
class Login extends CommonController {     
   /**  @OA\Post(    
   *     path="/api/admin.php/login/checklogin",    
   *     tags={"用户相关"},    
   *     summary="用户登录",    
   *     @OA\RequestBody(required=true,description="body",content={    
   *       @OA\MediaType(mediaType="application/json",    
   *           @OA\Schema(    
   *               @OA\Property(    
   *                   property="admin_user",    
   *                   type="string",    
   *                   description="账号"    
   *               ),    
   *               @OA\Property(    
   *                   property="admin_pwd",    
   *                   type="string",    
   *                   description="密码"    
   *               ),    
   *           )    
   *       )    
   *   }),    
   *     @OA\Response(        
   *         response="200",        
   *         description="状态码、提示消息"        
   *     )    
   * )    
   */    
   public function checklogin(){        
      $loginData=Request::param();    
      $tokenJwt = new JwtService();      
      try {            
         validate(Admin::class)->check($loginData);           
         $post_pwd = $loginData['admin_pwd'];            
         $where['admin_user'] = $loginData['admin_user'];            
         $admin = Db::name('admin')->where($where)->field('uid,admin_pwd')->find();            
          
         if ($admin['admin_pwd'] == md5($password)) {                
            $uid = $admin['uid'];                
            $admin_user['uid'] = $uid;                
            $admin_user['username'] = $where['admin_user'];   
                           
            $addData['login_time'] = time();                
            return self::Success($tokenJwt->setJwtToken($uid),'登录成功');            
         }else{                
            return self::Error(null,'登录失败',400);            
         }        
     } catch (ValidateException $e) {            
       // 验证失败 输出错误信息          
       return self::Error(null,$e->getError(),400);                  
     }           
}

用swagger测试 http://域名/api/admin.php/login/checklogin

带上token请求Blog示例,请求成功

最后,关于如何处理Authorization过期的问题,有两种解决办法,第一种就是,将Authorization的时间设置长一些,这样Authorization就不会过期,但是这样就有一个弊端,一旦客户端拿到了这个Authorization就相当于有了密钥,主动权也就掌握在了用户的手上。所以不推荐这种方案。第二种就是,后端处理,当Authorization过期的时候重新获取Authorization,将新的token传给前端,前端在将新的Authorization存储起来,替换掉原来的Authorization,下一次请求的时候就携带着新的Authorization请求。

至于,后端整个登录校验功能已经完成。

文章来源:thinkphp6+jwt 实现登录验证 | 猿小莫的博客

  • 1
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Layui是一个前端UI框架,它能够帮助我们更快速、更简单地构建美观、实用的Web界面。而ThinkPHP是一个PHP开发框架,它能够帮助我们更高效、更简洁地开发PHP应用程序。 要实现Layui和ThinkPHP6的后台登录功能,可以按照以下几个步骤进行: 1. 配置Layui的登录页面:在HTML中引入Layui的相关资源文件,包括CSS和JS,并设置一个表单,用于用户输入账号和密码等信息。 2. 创建登录接口:在ThinkPHP6的项目中创建一个用于登录验证的接口,通常是一个处理登录请求的方法。在这个方法中,需要获取用户输入的账号和密码,并与数据库中存储的账号密码进行比对。 3. 使用验证码:为了增加登录安全性,可以在登录页面中添加验证码功能。可以使用Layui的扩展组件laycode,结合后台开发语言生成验证码的图片。在提交登录请求时,需要对用户输入的验证码和生成的验证码进行比对。 4. 实现验证码刷新:当用户觉得当前验证码难以辨认时,可以提供一个刷新按钮,用户可以点击该按钮刷新验证码。刷新验证码实际上是重新生成一张验证码图片,并替换到当前验证码图片的地方。 以上就是在Layui和ThinkPHP6中实现后台登录以及验证码刷新的大致步骤。当然,具体实现还需要结合具体的代码和业务逻辑进行调整。希望这些信息能够对你有所帮助!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值