后台管理之认证
前边我们把验证码做好了,这章我介绍下登录的认证,我介绍的认证是基于jwt的
首先在composer.json文件中加入
"require": {
"tymon/jwt-auth": "^1.0.0-rc.1"
},
然后执行命令
composer updsate
然后打开/bootstrap/app.php文件加入
$app->register(Tymon\JWTAuth\Providers\LumenServiceProvider::class);
然后生成秘钥
php artisan jwt:secret
打开.evn文件,你能看到新出现了秘钥
JWT_SECRET=QjQJCJhSR6OslcctlI1E0dtGOESSx5fJ1fYShzvLyRkFymFjciqzavaXpNtthdJ1
然后打开管理员模型/app/Models/Admin.php文件,让Admin类实现三个接口。一下代码是新增的,因为我们模型中已经有其他方法了,为了简洁,我贴出来了新增的代码。
AuthenticatableContract, AuthorizableContract, JWTSubject
getKey();
}
/**
* Return a key value array, containing any custom claims to be added to the JWT.
*
* @return array
*/
public function getJWTCustomClaims()
{
return [];
}
}
然后把/vendor/laravel/lumne-framework/目录下的config目录复制到项目目录下
打开auth.php文件,要修改几个地方
// 修改前
'defaults' => [
'guard' => env('AUTH_GUARD', 'api'),
],
// 替换成下边样子。这个认证是对应后台管理的,我就使用admin了
'defaults' => [
'guard' => env('AUTH_GUARD', 'admin'),
],
//替换前
'guards' => [
'api' => ['driver' => 'api'],
],
//替换后台。这里的admin要对应到前边guard的admin
'guards' => [
'admin' => [
'driver' => 'jwt', // 使用jwt
'provider' => 'admins'
],
],
//修改前
'providers' => [
//
],
// 修改后。
'providers' => [
'admins' => [
'driver' => 'eloquent',
'model' => \App\Models\Admin::class, //管理员模型
],
],
最终代码
[
'guard' => env('AUTH_GUARD', 'admin'),
],
/*
|--------------------------------------------------------------------------
| Authentication Guards
|--------------------------------------------------------------------------
|
| Next, you may define every authentication guard for your application.
| Of course, a great default configuration has been defined for you
| here which uses session storage and the Eloquent user provider.
|
| All authentication drivers have a user provider. This defines how the
| users are actually retrieved out of your database or other storage
| mechanisms used by this application to persist your user's data.
|
| Supported: "token"
|
*/
'guards' => [
'admin' => [
'driver' => 'jwt',
'provider' => 'admins'
],
],
/*
|--------------------------------------------------------------------------
| User Providers
|--------------------------------------------------------------------------
|
| All authentication drivers have a user provider. This defines how the
| users are actually retrieved out of your database or other storage
| mechanisms used by this application to persist your user's data.
|
| If you have multiple user tables or models you may configure multiple
| sources which represent each model / table. These sources may then
| be assigned to any extra authentication guards you have defined.
|
| Supported: "database", "eloquent"
|
*/
'providers' => [
'admins' => [
'driver' => 'eloquent',
'model' => \App\Models\Admin::class,
],
],
/*
|--------------------------------------------------------------------------
| Resetting Passwords
|--------------------------------------------------------------------------
|
| Here you may set the options for resetting passwords including the view
| that is your password reset e-mail. You may also set the name of the
| table that maintains all of the reset tokens for your application.
|
| You may specify multiple password reset configurations if you have more
| than one user table or model in the application and you want to have
| separate password reset settings based on the specific user types.
|
| The expire time is the number of minutes that the reset token should be
| considered valid. This security feature keeps tokens short-lived so
| they have less time to be guessed. You may change this as needed.
|
*/
'passwords' => [
//
],
];
配置结束,接下来开始老流程,路由、模型、控制器
路由
$router->post('/admin/login', 'AdminAuthController@login');
$router->post('/admin/logout', 'AdminAuthController@logout');
打开 Admin模型
然后建立一个AdminAuthController.php文件,这个文件我们建立在Controller目录下
jwt = $jwt;
}
public function login(Request $request)
{
// 验证提交的数据
$this->validate($request, [
'user_name' => 'required|string|max:20|min:2',
'password' => 'required|string|max:16|min:6',
'vercode' => 'required|max:4|min:4',
]);
// 接收提交的数据
$userName = $request->input('user_name');
$password = $request->input('password');
$vercode = $request->input('vercode');
// 取出服务器端保存的验证码
$captchaCode = $_COOKIE['captcha'] ?? '';
// 验证验证码是否一致,不一致直接返回错误
if (app('captcha')->check($vercode, $captchaCode) === false) {
$this->response->setMsg(400, '验证码错误');
return $this->response->responseJSON();
}
// 验证码使用完后,消除cookie,为了安全
setcookie('captcha',$vercode,time() - 10);
$admin = Admin::where('user_name', $userName)->first();
// 如果用户名不存在,直接返回错误
if (empty($admin->id)) {
$this->response->setMsg(400, '账号或者密码错误');
return $this->response->responseJSON();
}
// 检查密码是否一致,不一致的话,返回错误
if (!password_verify($password, $admin->password)) {
$this->response->setMsg(400, '账号或者密码错误');
return $this->response->responseJSON();
}
// 账号未启用,不能登录
if ($admin->state != 1) {
$this->response->setMsg(-1, '错误!请联系管理员');
return $this->response->responseJSON();
}
$token = $this->jwt->fromUser($admin); // 获取token
$data = $this->respondWithToken($token); // 组装相关信息
$this->response->setData($data);
return $this->response->responseJSON();
}
public function logout(Request $request)
{
}
protected function respondWithToken($token)
{
return [
'access_token' => $token,
'token_type' => 'bearer',
'expires_in' => $this->jwt->factory()->getTTL(),
];
}
}
我们用postman请求下接口看看通不通
接口通了,但是返回user_name字段是必填的,这个也正确,因为我们没有提交user_name信息,我们把信息补全
我得请求验证码接口,拿到验证码
我把data中的那一长串字符复制出来,然后找个页面
src后边的值就是我复制出来的,然后看看验证码
我们回到postman,修改下参数
access_token的值就是生成的token,还有过期时间expires_in,值是60分钟,如何修改这个值呢?在config目录下新建一个文件jwt.php,
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
return [
/*
|--------------------------------------------------------------------------
| JWT Authentication Secret
|--------------------------------------------------------------------------
|
| Don't forget to set this in your .env file, as it will be used to sign
| your tokens. A helper command is provided for this:
| `php artisan jwt:secret`
|
| Note: This will be used for Symmetric algorithms only (HMAC),
| since RSA and ECDSA use a private/public key combo (See below).
|
*/
'secret' => env('JWT_SECRET'),
/*
|--------------------------------------------------------------------------
| JWT Authentication Keys
|--------------------------------------------------------------------------
|
| The algorithm you are using, will determine whether your tokens are
| signed with a random string (defined in `JWT_SECRET`) or using the
| following public & private keys.
|
| Symmetric Algorithms:
| HS256, HS384 & HS512 will use `JWT_SECRET`.
|
| Asymmetric Algorithms:
| RS256, RS384 & RS512 / ES256, ES384 & ES512 will use the keys below.
|
*/
'keys' => [
/*
|--------------------------------------------------------------------------
| Public Key
|--------------------------------------------------------------------------
|
| A path or resource to your public key.
|
| E.g. 'file://path/to/public/key'
|
*/
'public' => env('JWT_PUBLIC_KEY'),
/*
|--------------------------------------------------------------------------
| Private Key
|--------------------------------------------------------------------------
|
| A path or resource to your private key.
|
| E.g. 'file://path/to/private/key'
|
*/
'private' => env('JWT_PRIVATE_KEY'),
/*
|--------------------------------------------------------------------------
| Passphrase
|--------------------------------------------------------------------------
|
| The passphrase for your private key. Can be null if none set.
|
*/
'passphrase' => env('JWT_PASSPHRASE'),
],
/*
|--------------------------------------------------------------------------
| JWT time to live
|--------------------------------------------------------------------------
|
| Specify the length of time (in minutes) that the token will be valid for.
| Defaults to 1 hour.
|
| You can also set this to null, to yield a never expiring token.
| Some people may want this behaviour for e.g. a mobile app.
| This is not particularly recommended, so make sure you have appropriate
| systems in place to revoke the token if necessary.
|
*/
'ttl' => env('JWT_TTL', 7200),
/*
|--------------------------------------------------------------------------
| Refresh time to live
|--------------------------------------------------------------------------
|
| Specify the length of time (in minutes) that the token can be refreshed
| within. I.E. The user can refresh their token within a 2 week window of
| the original token being created until they must re-authenticate.
| Defaults to 2 weeks.
|
| You can also set this to null, to yield an infinite refresh time.
| Some may want this instead of never expiring tokens for e.g. a mobile app.
| This is not particularly recommended, so make sure you have appropriate
| systems in place to revoke the token if necessary.
|
*/
// 'refresh_ttl' => env('JWT_REFRESH_TTL', 20160),
'refresh_ttl' => env('JWT_REFRESH_TTL', 20160),
/*
|--------------------------------------------------------------------------
| JWT hashing algorithm
|--------------------------------------------------------------------------
|
| Specify the hashing algorithm that will be used to sign the token.
|
| See here: https://github.com/namshi/jose/tree/master/src/Namshi/JOSE/Signer/OpenSSL
| for possible values.
|
*/
'algo' => env('JWT_ALGO', 'HS256'),
/*
|--------------------------------------------------------------------------
| Required Claims
|--------------------------------------------------------------------------
|
| Specify the required claims that must exist in any token.
| A TokenInvalidException will be thrown if any of these claims are not
| present in the payload.
|
*/
'required_claims' => [
'iss',
'iat',
'exp',
'nbf',
'sub',
'jti',
],
/*
|--------------------------------------------------------------------------
| Persistent Claims
|--------------------------------------------------------------------------
|
| Specify the claim keys to be persisted when refreshing a token.
| `sub` and `iat` will automatically be persisted, in
| addition to the these claims.
|
| Note: If a claim does not exist then it will be ignored.
|
*/
'persistent_claims' => [
// 'foo',
// 'bar',
],
/*
|--------------------------------------------------------------------------
| Lock Subject
|--------------------------------------------------------------------------
|
| This will determine whether a `prv` claim is automatically added to
| the token. The purpose of this is to ensure that if you have multiple
| authentication models e.g. `App\User` & `App\OtherPerson`, then we
| should prevent one authentication request from impersonating another,
| if 2 tokens happen to have the same id across the 2 different models.
|
| Under specific circumstances, you may want to disable this behaviour
| e.g. if you only have one authentication model, then you would save
| a little on token size.
|
*/
'lock_subject' => true,
/*
|--------------------------------------------------------------------------
| Leeway
|--------------------------------------------------------------------------
|
| This property gives the jwt timestamp claims some "leeway".
| Meaning that if you have any unavoidable slight clock skew on
| any of your servers then this will afford you some level of cushioning.
|
| This applies to the claims `iat`, `nbf` and `exp`.
|
| Specify in seconds - only if you know you need it.
|
*/
'leeway' => env('JWT_LEEWAY', 0),
/*
|--------------------------------------------------------------------------
| Blacklist Enabled
|--------------------------------------------------------------------------
|
| In order to invalidate tokens, you must have the blacklist enabled.
| If you do not want or need this functionality, then set this to false.
|
*/
'blacklist_enabled' => env('JWT_BLACKLIST_ENABLED', true),
/*
| -------------------------------------------------------------------------
| Blacklist Grace Period
| -------------------------------------------------------------------------
|
| When multiple concurrent requests are made with the same JWT,
| it is possible that some of them fail, due to token regeneration
| on every request.
|
| Set grace period in seconds to prevent parallel request failure.
|
*/
'blacklist_grace_period' => env('JWT_BLACKLIST_GRACE_PERIOD', 0),
/*
|--------------------------------------------------------------------------
| Cookies encryption
|--------------------------------------------------------------------------
|
| By default Laravel encrypt cookies for security reason.
| If you decide to not decrypt cookies, you will have to configure Laravel
| to not encrypt your cookie token by adding its name into the $except
| array available in the middleware "EncryptCookies" provided by Laravel.
| see https://laravel.com/docs/master/responses#cookies-and-encryption
| for details.
|
| Set it to true if you want to decrypt cookies.
|
*/
'decrypt_cookies' => false,
/*
|--------------------------------------------------------------------------
| Providers
|--------------------------------------------------------------------------
|
| Specify the various providers used throughout the package.
|
*/
'providers' => [
/*
|--------------------------------------------------------------------------
| JWT Provider
|--------------------------------------------------------------------------
|
| Specify the provider that is used to create and decode the tokens.
|
*/
'jwt' => Tymon\JWTAuth\Providers\JWT\Lcobucci::class,
/*
|--------------------------------------------------------------------------
| Authentication Provider
|--------------------------------------------------------------------------
|
| Specify the provider that is used to authenticate users.
|
*/
'auth' => Tymon\JWTAuth\Providers\Auth\Illuminate::class,
/*
|--------------------------------------------------------------------------
| Storage Provider
|--------------------------------------------------------------------------
|
| Specify the provider that is used to store tokens in the blacklist.
|
*/
'storage' => Tymon\JWTAuth\Providers\Storage\Illuminate::class,
],
];
找到ttl那一项,我这里改为7200分钟,在用postman登录一下看看结果
{
"code": 200,
"msg": "操作成功",
"data": {
"access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwOlwvXC9jb2NvY21zLmNvbVwvYWRtaW5cL2xvZ2luIiwiaWF0IjoxNTc5MjMzMzU3LCJleHAiOjE1Nzk2NjUzNTcsIm5iZiI6MTU3OTIzMzM1NywianRpIjoieFQ0OG1rc29hVXpxZlR3dyIsInN1YiI6MiwicHJ2IjoiZGY4ODNkYjk3YmQwNWVmOGZmODUwODJkNjg2YzQ1ZTgzMmU1OTNhOSJ9.RgTZ3LTecUv3x80OIC4gB3IwB-S0qDu5bANyJ35_djo",
"token_type": "bearer",
"expires_in": 7200
}
}
我们来看看这个access_token怎么用,回到我们的/routes/web.php中,我们在$router->group()中添加中间件:'middleware' => 'auth.admin',如下
这个就是对该分组下的所有路由都进行认证,所以$router->get('/login', 'AuthController@login'); /这行代码要放到分组的外边
我们找到些中间件的地方,打开/app/Http/Middleware/目录,新建AdminAuthenticate.php文件
auth = $auth;
}
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @param string|null $guard
* @return mixed
*/
public function handle($request, Closure $next, $guard = 'admin')
{
$this->auth->shouldUse($guard);
if ($this->auth->guard($guard)->guest()) {
$arrayResult['code'] = 401;
$arrayResult['message'] = '认证错误';
$arrayResult['data'] = [];
return response()->json($arrayResult, 401)->setEncodingOptions(JSON_UNESCAPED_UNICODE);
}
return $next($request);
}
}
注意:handle()方法中的参数$guatd是admin,不能是其他的,这里的admin对应路由中的'middleware' => 'auth.admin',也对应/config/auth.php文件中的admin。
还没有完,打开/bootstrap/app.php文件,加入一下代码
// 此处的auth.admin对应路由中的auth.admin,不然不会生效
$app->routeMiddleware([
'auth.admin' => App\Http\Middleware\AdminAuthenticate::class,
]);
我们再刷新后台管理的页面
认证生效了,如何能访问呢?前边我们生成的access_token你还记得吗?我们使用这个token,使用简单的方式请求一下,在/admin/index后边加上?token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwOlwvXC9jb2NvY21zLmNvbVwvYWRtaW5cL2xvZ2luIiwiaWF0IjoxNTc5MjMzMzU3LCJleHAiOjE1Nzk2NjUzNTcsIm5iZiI6MTU3OTIzMzM1NywianRpIjoieFQ0OG1rc29hVXpxZlR3dyIsInN1YiI6MiwicHJ2IjoiZGY4ODNkYjk3YmQwNWVmOGZmODUwODJkNjg2YzQ1ZTgzMmU1OTNhOSJ9.RgTZ3LTecUv3x80OIC4gB3IwB-S0qDu5bANyJ35_djo,
即/admin/index?token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwOlwvXC9jb2NvY21zLmNvbVwvYWRtaW5cL2xvZ2luIiwiaWF0IjoxNTc5MjMzMzU3LCJleHAiOjE1Nzk2NjUzNTcsIm5iZiI6MTU3OTIzMzM1NywianRpIjoieFQ0OG1rc29hVXpxZlR3dyIsInN1YiI6MiwicHJ2IjoiZGY4ODNkYjk3YmQwNWVmOGZmODUwODJkNjg2YzQ1ZTgzMmU1OTNhOSJ9.RgTZ3LTecUv3x80OIC4gB3IwB-S0qDu5bANyJ35_djo
加上token进来了,但是管理员列表出现错误了,这个正常,因为管理员列表的url后边没有跟token,url后没有跟token的,都会出现认证错误。在前后端分离的情况下,token可以放到header中,但是,url直接请求只能跟在url后边。
JWT是服务于接口的无状态的请求的,但是我们使用的是x-admin后台管理框架,这个框架是基于iframe实现的。以上的做法,完全适用于前后端完全分离的情况,下一节我介绍如何实现登录认证,还是基于JWT方式,不过我们要换一种方法实现。
完。