一,安装第三方库
1,官方代码站:
https://github.com/lcobucci/jwt
文档地址:
https://lcobucci-jwt.readthedocs.io/en/latest/
2,用composer安装
liuhongdi@lhdpc:/data/laravel/dignews$ composer require lcobucci/jwt
3,安装后查看库的版本:
liuhongdi@lhdpc:/data/laravel/dignews$ composer show lcobucci/jwt
name : lcobucci/jwt
descrip. : A simple library to work with JSON Web Token and JSON Web Signature
keywords : JWS, jwt
versions : * 4.0.4
type : library
...
二,php代码
1,封装类:App\extend\jwt\JwtUtil.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
|
<?php namespace App\extend\jwt; use Lcobucci\JWT\Configuration; use Lcobucci\JWT\Signer\Hmac\Sha256; use Lcobucci\JWT\Signer\Key\InMemory; /** * 单例模式 * Class JwtUtil * @package App\extend\jwt */ class JwtUtil { private $config ; private $key = "Ge1KCTRhdVsmUUZY0GrwgEvLubPvLOCM" ; private $iss = "liuhongdi.com" ; //颁发者(iss声明) private $aud = "liuhongdiauth.com" ; //访问群体(aud声明) private $jti = "5t6y9400453" ; //id(jti声明) private $expTime = 1; //令牌有效时间,单位小时 private static $instance ; // 单例模式JwtAuth句柄 // 获取JwtAuth的句柄 public static function getInstance() { if ( is_null (self:: $instance )) { self:: $instance = new self(); } return self:: $instance ; } /** * 构造 */ public function __construct() { self::init(); } /** * 初始化 */ private function init() { $config = Configuration::forSymmetricSigner( new Sha256(), InMemory::base64Encoded( $this ->key) ); $this ->config = $config ; } /** * 创建JWT * @param array $arrClaim * @return string * @throws \Exception * @author 老刘 */ public function createToken( array $arrClaim ) { $config = $this ->config; assert( $config instanceof Configuration); if ( is_array ( $arrClaim ) && count ( array_filter ( array_keys ( $arrClaim ), 'is_string' ))>0) { //是关联数组 } else { //不是关联数组 throw new \Exception( "claim参数必须为关联数组" ); } $now = new \DateTimeImmutable(); $token = $config ->builder() // 配置颁发者(iss声明) ->issuedBy( $this ->iss) // 配置访问群体(aud声明) ->permittedFor( $this ->aud) // 配置id(jti声明) ->identifiedBy( $this ->jti) // 配置令牌发出的时间(iat声明) ->issuedAt( $now ) // 配置令牌的过期时间(exp claim) ->expiresAt( $now ->modify( "+{$this->expTime} hour" )); //claim foreach ( $arrClaim as $k => $item ) { $token = $token ->withClaim( $k , $item ); } // 生成新令牌 $token = $token ->getToken( $config ->signer(), $config ->signingKey()); return $token ->toString(); } /** * 解析token * @param string $jwt * @return mixed * @author 老刘 */ public function parseToken(string $jwt ) { $config = $this ->config; $token = $config ->parser()->parse( $jwt ); return $token ->claims(); } /** * 验证令牌 * @param $jwt * @return mixed * @throws \Exception * @author 老刘 */ public function validatorToken( $jwt ) { $config = $this ->config; $token = $config ->parser()->parse( $jwt ); $claims = $token ->claims(); $jti = (string) $claims ->get( 'jti' ); $iss = (string) $claims ->get( 'iss' ); $aud = $claims ->get( 'aud' ); $exp = $claims ->get( 'exp' ); $now = new \DateTimeImmutable(); // 是否过期 if ( $exp < $now ) { throw new \Exception( "身份已过期" ); } //验证jwt id是否匹配 $validate_jwt_id = new \Lcobucci\JWT\Validation\Constraint\IdentifiedBy( $jti ); // 验证签发人url是否正确 $validate_issued = new \Lcobucci\JWT\Validation\Constraint\IssuedBy( $iss ); // 验证客户端url是否匹配 $validate_aud = new \Lcobucci\JWT\Validation\Constraint\PermittedFor( $aud [0]); $config ->setValidationConstraints( $validate_jwt_id , $validate_issued , $validate_aud ); $constraints = $config ->validationConstraints(); //验证 if (! $config ->validator()->validate( $token , ... $constraints )) { throw new \Exception( "非法的请求" ); } return $claims ; } } |
2,创建controller:
liuhongdi@lhdpc:/data/laravel/dignews$ php artisan make:controller LoginController
INFO Controller [app/Http/Controllers/LoginController.php] created successfully.
代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
|
<?php namespace App\Http\Controllers; use Illuminate\Http\Request; use App\extend\result\Result; use App\Models\Staff as StaffModel; use App\business\StaffLogin; use Illuminate\Support\Facades\Redis; use App\extend\jwt\JwtUtil; class LoginController extends Controller { /** * 得到用户信息 */ public function info(Request $request ) { //得到数据库的用户id $staffId = $request ->auth; $staff = new StaffModel(); $rows = $staff ->getOneByStaffId( $staffId ); if ( is_null ( $rows )) { return Result::ErrorCode(100, '用户不存在' ); } unset( $rows [ 'password' ]); //添加头像 $host = "http://192.168.219.6/imgstaffhead" ; $rand = rand(1000,9999); $rows [ 'head' ] = $host . "/" . $staffId . ".jpg?rand=" . $rand ; //返回 return Result::Success( $rows ); } //得到jwt信息,返回给线下 function getJwtInfo( $userId , $nickName ) { //验证成功,生成jwt返回 //$jUtil = new JwtUtil(); //$token = $jUtil->createJwt($userId); $token = JwtUtil::getInstance()->createToken([ 'uid' => $userId ]); $res = [ "tokenString" => $token ]; $host = "http://192.168.219.6/imgstaffhead" ; $rand = rand(1000,9999); $res [ 'head' ] = $host . "/" . $userId . ".jpg?rand=" . $rand ; $res [ 'nickname' ] = $nickName ; return $res ; } /** * 登录 * */ public function login(Request $request ) { $username = $request ->post( 'username' ); $password = $request ->post( 'password' ); $uniqid = $request ->post( 'uniqid' ); $captcode = $request ->post( 'captcode' ); //检查验证码: $value = Redis::get( $uniqid ); if ( strtolower ( $captcode ) !== strtolower ( $value )) { return Result::ErrorCode(400, "输入的验证码不正确" ); } //用username读取用户信息 $staff = new StaffModel(); $rows = $staff ->getOneByUsername( $username ); if ( $rows == null) { return Result::ErrorCode(422, '用户名出现错误' ); } else { if ( $rows [ 'is_active' ] == 0) { return Result::ErrorCode(422, '用户未激活' ); } else { //检查密码是否正确 if (password_verify( $password , $rows [ 'password' ])) { $res = $this ->getJwtInfo( $rows [ 'staff_id' ], $rows [ 'nickname' ]); //登录成功,写日志 $loginLog = new StaffLogin(); $loginLog ->log( $rows [ 'staff_id' ]); return Result::Success( $res ); } else { //报密码错误 return Result::ErrorCode(422, "用户名密码错误" ); } } } } } |
3,创建middleware
liuhongdi@lhdpc:/data/laravel/dignews$ php artisan make:middleware JwtAuth
INFO Middleware [app/Http/Middleware/JwtAuth.php] created successfully.
代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
|
<?php namespace App\Http\Middleware; use Closure; use Illuminate\Http\Request; use Symfony\Component\HttpFoundation\Response; use App\extend\jwt\JwtUtil; class JwtAuth { /** * Handle an incoming request. * * @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next */ public function handle(Request $request , Closure $next ): Response { //得到jwt的token $auth = $request ->header( 'authorization' ); if ( $auth == null) { return $next ( $request ); } $token = str_replace ( "Bearer " , "" , $auth ); if ( count ( explode ( '.' , $token )) <> 3) { throw new \Exception( '非法身份信息,请重新登录' , -1); } //检验token $objJwtAuth = JwtUtil::getInstance(); $claims = $objJwtAuth ->validatorToken( $token ); //得到用户id $uid = $claims ->get( 'uid' ); //添加到request $request ->merge([ 'auth' => $uid ]); return $next ( $request ); } } |
三,vue代码:
api/axios.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
|
import axios from 'axios' import { ElMessage } from "element-plus" ; import router from "../route/router.js" import { getUser } from '@/utils/user' import { showLoading, hideLoading } from '@/utils/loading' let config = { timeout:10000, }; const _axios = axios.create(config); //request interceptor _axios.interceptors.request.use( function (config) { showLoading(); //在发送请求前,增加token字串到header let userInfo = getUser().data; if ( 'tokenString' in userInfo && userInfo[ "tokenString" ] != "" ){ config.headers.Authorization = "Bearer " +userInfo[ "tokenString" ]; } return config; }, function (error) { // Do something with request error return Promise.reject(error); } ); //response interceptor _axios.interceptors.response.use( function (response) { hideLoading(); let code = response.data.code; if (code == '401' ) { //现在跳转去登录 let redi = {path: router.currentRoute.value.fullPath}; let redistr = JSON.stringify(redi); if (router.currentRoute.value.meta.redirectstr) { redistr = router.currentRoute.value.meta.redirectstr; } //打印出参数 if (router.currentRoute.value.meta.actstr) { let actstr = router.currentRoute.value.meta.actstr; router.replace({ path: '/login/login' ,query: { redirect: redistr,act:actstr } }); } else { router.replace({ path: '/login/login' ,query: { redirect: redistr } }); } } return response; }, function (error) { hideLoading(); // Do something with response error if (error.response.status) { //alert(error.response.status); switch (error.response.status) { // 401: 未登录 // 未登录则跳转登录页面,并携带当前页面的路径 // 在登录成功后返回当前页面,这一步需要在登录页操作。 case 401: router.replace({ path: '/login/login' }); break ; // 403 token过期 // 登录过期对用户进行提示 // 清除本地token和清空vuex中token对象 // 跳转登录页面 case 403: ElMessage.error( "登录过期,请重新登录" ); // 清除token localStorage.removeItem( 'token' ) // 跳转登录页面,并将要浏览的页面fullPath传过去,登录成功后跳转需要访问的页面 setTimeout(() => { router.push({ path: '/login/login' }); }, 1000) break ; // 404请求不存在 case 404: ElMessage.error( "网络请求不存在,404错误" ); break ; // 500服务端错误 case 500: ElMessage.error( "服务端有故障,500错误" ); break ; // 其他错误,直接抛出错误提示 default : var tip = "" ; if ( typeof (error.response.data.message) == "undefined" ) { tip = error.toString(); } else { tip = error.response.data.message; } ElMessage.error(tip); break ; } return Promise.reject(error.response) } } ); /** * get方法,对应get请求 * @param {String} url [请求的url地址] * @param {Object} params [请求时携带的参数] */ export function get (url, params) { return new Promise((resolve, reject) => { _axios.get(url, { params: params, }).then(res => { resolve(res.data) }). catch (err => { reject(err.data) }) }) } //postForm export function post (url, params) { return new Promise((resolve, reject) => { _axios.post(url, params,{headers: { 'Content-Type' : 'application/x-www-form-urlencoded' , }}) .then(res => { resolve(res.data) }) . catch (err => { reject(err.data) }) }) } |
说明:刘宏缔的架构森林—专注it技术的博客,
网站:https://blog.imgtouch.com
原文: https://blog.imgtouch.com/index.php/2023/10/31/laravel-yong-hu-ren-zheng-zhi-lcobucci-jwt-10-27/
代码: https://github.com/liuhongdi/ 或 https://gitee.com/liuhongdi
说明:作者:刘宏缔 邮箱: 371125307@qq.com
四,测试效果:
![](https://img2023.cnblogs.com/blog/1938691/202311/1938691-20231101074801221-646533346.png)
五,查看laravel框架的版本:
liuhongdi@lhdpc:/data/laravel/dignews$ php artisan --version
Laravel Framework 10.27.0