Недавно я работал над проектом компании, используя интерфейсVue.js, серверная часть используетLaravelСоздайте службу API, пакет проверки подлинности пользователя изначально предназначался для использованияLaravel PassportДа, но это было немного хлопотно, поэтому я использовалjwt-auth.
Установить
jwt-authПоследняя версия1.0.0 rc.1версия, уже поддерживаетсяLaravel 5.5. если вы используетеLaravel 5.5версию, вы можете использовать следующую команду для установки. Согласно разделу комментариев@tradzeroбратан совет если тыLaravel 5.5В следующих версиях также рекомендуется использовать последнюю версию: Версии до RC.1 имеют проблемы с безопасностью при многопользовательской аутентификации с помощью токена.
$ composer require tymon/jwt-auth 1.0.0-rc.1
настроить
### Добавить поставщика услуг
Добавьте следующую строку вconfig/app.php
документproviders
В массиве:
app.php
'providers' => [
...
Tymon\JWTAuth\Providers\LaravelServiceProvider::class,
]
опубликовать профиль
Запустите следующую команду в вашей оболочке, чтобы выдатьjwt-authфайл конфигурации:
shell
$ php artisan vendor:publish --provider="Tymon\JWTAuth\Providers\LaravelServiceProvider"
Эта команда будетconfig
создать каталогjwt.php
Файл конфигурации, где вы можете настроить конфигурацию.
сгенерировать ключ
jwt-authзаранее определилArtisanКоманда удобна вам для генерации Secret, вам нужно толькоshell
Запустите следующую команду в:
shell
$ php artisan jwt:secret
Эта команда будет в вашем.env
Добавить новую строку в файлJWT_SECRET=secret
.
Настроить защиту авторизации
существуетconfig/auth.php
файл, нужно поставитьguards/driver
обновить доjwt
:
auth.php
'defaults' => [
'guard' => 'api',
'passwords' => 'users',
],
...
'guards' => [
'api' => [
'driver' => 'jwt',
'provider' => 'users',
],
],
только при использованииLaravel 5.2Его можно использовать только в случае вышеуказанной версии.
Изменить модель
При необходимости используйтеjwt-authВ качестве аутентификации пользователя нам необходимо аутентифицировать нашUser
Модель вносит небольшие изменения, реализует интерфейс, а измененныйUser
Модель выглядит следующим образом:
User.php
<?php
namespace App;
use Tymon\JWTAuth\Contracts\JWTSubject;
use Illuminate\Notifications\Notifiable;
use Illuminate\Foundation\Auth\User as Authenticatable;
class User extends Authenticatable implements JWTSubject
{
use Notifiable;
// Rest omitted for brevity
/**
* Get the identifier that will be stored in the subject claim of the JWT.
*
* @return mixed
*/
public function getJWTIdentifier()
{
return $this->getKey();
}
/**
* Return a key value array, containing any custom claims to be added to the JWT.
*
* @return array
*/
public function getJWTCustomClaims()
{
return [];
}
}
Подробное объяснение элементов конфигурации
jwt.php
<?php
return [
/*
|--------------------------------------------------------------------------
| JWT Authentication Secret
|--------------------------------------------------------------------------
|
| 用于加密生成 token 的 secret
|
*/
'secret' => env('JWT_SECRET'),
/*
|--------------------------------------------------------------------------
| JWT Authentication Keys
|--------------------------------------------------------------------------
|
| 如果你在 .env 文件中定义了 JWT_SECRET 的随机字符串
| 那么 jwt 将会使用 对称算法 来生成 token
| 如果你没有定有,那么jwt 将会使用如下配置的公钥和私钥来生成 token
|
*/
'keys' => [
/*
|--------------------------------------------------------------------------
| Public Key
|--------------------------------------------------------------------------
|
| 公钥
|
*/
'public' => env('JWT_PUBLIC_KEY'),
/*
|--------------------------------------------------------------------------
| Private Key
|--------------------------------------------------------------------------
|
| 私钥
|
*/
'private' => env('JWT_PRIVATE_KEY'),
/*
|--------------------------------------------------------------------------
| Passphrase
|--------------------------------------------------------------------------
|
| 私钥的密码。 如果没有设置,可以为 null。
|
*/
'passphrase' => env('JWT_PASSPHRASE'),
],
/*
|--------------------------------------------------------------------------
| JWT time to live
|--------------------------------------------------------------------------
|
| 指定 access_token 有效的时间长度(以分钟为单位),默认为1小时,您也可以将其设置为空,以产生永不过期的标记
|
*/
'ttl' => env('JWT_TTL', 60),
/*
|--------------------------------------------------------------------------
| Refresh time to live
|--------------------------------------------------------------------------
|
| 指定 access_token 可刷新的时间长度(以分钟为单位)。默认的时间为 2 周。
| 大概意思就是如果用户有一个 access_token,那么他可以带着他的 access_token
| 过来领取新的 access_token,直到 2 周的时间后,他便无法继续刷新了,需要重新登录。
|
*/
'refresh_ttl' => env('JWT_REFRESH_TTL', 20160),
/*
|--------------------------------------------------------------------------
| JWT hashing algorithm
|--------------------------------------------------------------------------
|
| 指定将用于对令牌进行签名的散列算法。
|
*/
'algo' => env('JWT_ALGO', 'HS256'),
/*
|--------------------------------------------------------------------------
| Required Claims
|--------------------------------------------------------------------------
|
| 指定必须存在于任何令牌中的声明。
|
|
*/
'required_claims' => [
'iss',
'iat',
'exp',
'nbf',
'sub',
'jti',
],
/*
|--------------------------------------------------------------------------
| Persistent Claims
|--------------------------------------------------------------------------
|
| 指定在刷新令牌时要保留的声明密钥。
|
*/
'persistent_claims' => [
// 'foo',
// 'bar',
],
/*
|--------------------------------------------------------------------------
| Blacklist Enabled
|--------------------------------------------------------------------------
|
| 为了使令牌无效,您必须启用黑名单。
| 如果您不想或不需要此功能,请将其设置为 false。
|
*/
'blacklist_enabled' => env('JWT_BLACKLIST_ENABLED', true),
/*
| -------------------------------------------------------------------------
| Blacklist Grace Period
| -------------------------------------------------------------------------
|
| 当多个并发请求使用相同的JWT进行时,
| 由于 access_token 的刷新 ,其中一些可能会失败
| 以秒为单位设置请求时间以防止并发的请求失败。
|
*/
'blacklist_grace_period' => env('JWT_BLACKLIST_GRACE_PERIOD', 0),
/*
|--------------------------------------------------------------------------
| Providers
|--------------------------------------------------------------------------
|
| 指定整个包中使用的各种提供程序。
|
*/
'providers' => [
/*
|--------------------------------------------------------------------------
| JWT Provider
|--------------------------------------------------------------------------
|
| 指定用于创建和解码令牌的提供程序。
|
*/
'jwt' => Tymon\JWTAuth\Providers\JWT\Namshi::class,
/*
|--------------------------------------------------------------------------
| Authentication Provider
|--------------------------------------------------------------------------
|
| 指定用于对用户进行身份验证的提供程序。
|
*/
'auth' => Tymon\JWTAuth\Providers\Auth\Illuminate::class,
/*
|--------------------------------------------------------------------------
| Storage Provider
|--------------------------------------------------------------------------
|
| 指定用于在黑名单中存储标记的提供程序。
|
*/
'storage' => Tymon\JWTAuth\Providers\Storage\Illuminate::class,
],
];
Пользовательское промежуточное ПО для аутентификации
Во-первых, позвольте мне объяснить эффект, которого я хочу достичь.Я надеюсь, что пользователь предоставит учетную запись и пароль для входа в систему. Если вход в систему будет успешным, я выпущу внешний интерфейс сaccess _token, установлен наheader
для запроса маршрутов, требующих аутентификации пользователя.
В то же время я надеюсь, что если срок действия токена пользователя истечет, я смогу временно передать этот запрос и обновить токен пользователя в этом запросе.access _token, и, наконец, поместите новый в заголовок ответаaccess _tokenВернитесь на переднюю часть, чтобы вы могли обновиться без болиaccess _token, пользователь может получить очень хороший опыт, поэтому начните писать код.
Выполните следующую команду, чтобы создать новое промежуточное ПО:
php artisan make:middleware RefreshToken
Код промежуточного ПО выглядит следующим образом:
RefreshToken.php
<?php
namespace App\Http\Middleware;
use Auth;
use Closure;
use Tymon\JWTAuth\Exceptions\JWTException;
use Tymon\JWTAuth\Http\Middleware\BaseMiddleware;
use Tymon\JWTAuth\Exceptions\TokenExpiredException;
use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException;
// 注意,我们要继承的是 jwt 的 BaseMiddleware
class RefreshToken extends BaseMiddleware
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
*
* @throws \Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException
*
* @return mixed
*/
public function handle($request, Closure $next)
{
// 检查此次请求中是否带有 token,如果没有则抛出异常。
$this->checkForToken($request);
// 使用 try 包裹,以捕捉 token 过期所抛出的 TokenExpiredException 异常
try {
// 检测用户的登录状态,如果正常则通过
if ($this->auth->parseToken()->authenticate()) {
return $next($request);
}
throw new UnauthorizedHttpException('jwt-auth', '未登录');
} catch (TokenExpiredException $exception) {
// 此处捕获到了 token 过期所抛出的 TokenExpiredException 异常,我们在这里需要做的是刷新该用户的 token 并将它添加到响应头中
try {
// 刷新用户的 token
$token = $this->auth->refresh();
// 使用一次性登录以保证此次请求的成功
Auth::guard('api')->onceUsingId($this->auth->manager()->getPayloadFactory()->buildClaimsCollection()->toPlainArray()['sub']);
} catch (JWTException $exception) {
// 如果捕获到此异常,即代表 refresh 也过期了,用户无法刷新令牌,需要重新登录。
throw new UnauthorizedHttpException('jwt-auth', $exception->getMessage());
}
}
// 在响应头中返回新的 token
return $this->setAuthenticationHeader($next($request), $token);
}
}
Настройка перехватчиков Axios
Мой предпочтительный набор HTTP-запросов:axios. Чтобы добиться эффекта безболезненного обновления токена, нам нужноaxiosОпределите перехватчик для получения нашего обновленного токена, код выглядит следующим образом:
app.js
import Vue from 'vue'
import router from './router'
import store from './store'
import iView from 'iview'
import 'iview/dist/styles/iview.css'
Vue.use(iView)
new Vue({
el: '#app',
router,
store,
created() {
// 自定义的 axios 响应拦截器
this.$axios.interceptors.response.use((response) => {
// 判断一下响应中是否有 token,如果有就直接使用此 token 替换掉本地的 token。你可以根据你的业务需求自己编写更新 token 的逻辑
var token = response.headers.authorization
if (token) {
// 如果 header 中存在 token,那么触发 refreshToken 方法,替换本地的 token
this.$store.dispatch('refreshToken', token)
}
return response
}, (error) => {
switch (error.response.status) {
// 如果响应中的 http code 为 401,那么则此用户可能 token 失效了之类的,我会触发 logout 方法,清除本地的数据并将用户重定向至登录页面
case 401:
return this.$store.dispatch('logout')
break
// 如果响应中的 http code 为 400,那么就弹出一条错误提示给用户
case 400:
return this.$Message.error(error.response.data.error)
break
}
return Promise.reject(error)
})
}
})
VuexКод внутри выглядит следующим образом:
store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
import axios from 'axios'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
name: null,
avatar: null,
mobile: null,
token: null,
remark: null,
auth: false,
},
mutations: {
// 用户登录成功,存储 token 并设置 header 头
logined(state, token) {
state.auth = true
state.token = token
localStorage.token = token
},
// 用户刷新 token 成功,使用新的 token 替换掉本地的token
refreshToken(state, token) {
state.token = token
localStorage.token = token
axios.defaults.headers.common['Authorization'] = state.token
},
// 登录成功后拉取用户的信息存储到本地
profile(state, data) {
state.name = data.name
state.mobile = data.mobile
state.avatar = data.avatar
state.remark = data.remark
},
// 用户登出,清除本地数据
logout(state){
state.name = null
state.mobile = null
state.avatar = null
state.remark = null
state.auth = false
state.token = null
localStorage.removeItem('token')
}
},
actions: {
// 登录成功后保存用户信息
logined({dispatch,commit}, token) {
return new Promise(function (resolve, reject) {
commit('logined', token)
axios.defaults.headers.common['Authorization'] = token
dispatch('profile').then(() => {
resolve()
}).catch(() => {
reject()
})
})
},
// 登录成功后使用 token 拉取用户的信息
profile({commit}) {
return new Promise(function (resolve, reject) {
axios.get('profile', {}).then(respond => {
if (respond.status == 200) {
commit('profile', respond.data)
resolve()
} else {
reject()
}
})
})
},
// 用户登出,清除本地数据并重定向至登录页面
logout({commit}) {
return new Promise(function (resolve, reject) {
commit('logout')
axios.post('auth/logout', {}).then(respond => {
Vue.$router.push({name:'login'})
})
})
},
// 将刷新的 token 保存至本地
refreshToken({commit},token) {
return new Promise(function (resolve, reject) {
commit('refreshToken', token)
})
},
}
})
Обновите обработчик для обработки исключений
Поскольку мы строимapi
сервис, поэтому нам нужно обновитьapp/Exceptions/Handler.php
серединаrender
метод для пользовательской обработки некоторых исключений.
Handler.php
<?php
namespace App\Exceptions;
use Exception;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
use Illuminate\Validation\ValidationException;
use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException;
class Handler extends ExceptionHandler
{
...
/**
* Render an exception into an HTTP response.
*
* @param \Illuminate\Http\Request $request
* @param \Exception $exception
* @return \Illuminate\Http\Response
*/
public function render($request, Exception $exception)
{
// 参数验证错误的异常,我们需要返回 400 的 http code 和一句错误信息
if ($exception instanceof ValidationException) {
return response(['error' => array_first(array_collapse($exception->errors()))], 400);
}
// 用户认证的异常,我们需要返回 401 的 http code 和错误信息
if ($exception instanceof UnauthorizedHttpException) {
return response($exception->getMessage(), 401);
}
return parent::render($request, $exception);
}
}
После обновления этого метода исключение, сгенерированное в нашем пользовательском промежуточном программном обеспечении выше, и исключение, сгенерированное нашей ошибкой проверки параметра ниже, будут преобразованы в указанный формат и сгенерированы.
использовать
Теперь мы можем в нашемroutes/api.php
Добавьте несколько новых маршрутов в файл маршрутизации, чтобы протестировать его:
api.php
Route::prefix('auth')->group(function($router) {
$router->post('login', 'AuthController@login');
$router->post('logout', 'AuthController@logout');
});
Route::middleware('refresh.token')->group(function($router) {
$router->get('profile','UserController@profile');
});
В твоемshel
l Выполните следующую команду, чтобы добавить новый контроллер:
$ php artisan make:controller AuthController
Откройте этот контроллер и напишите следующее
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use App\Transformers\UserTransformer;
class AuthController extends Controller
{
/**
* Get a JWT token via given credentials.
*
* @param \Illuminate\Http\Request $request
*
* @return \Illuminate\Http\JsonResponse
*/
public function login(Request $request)
{
// 验证规则,由于业务需求,这里我更改了一下登录的用户名,使用手机号码登录
$rules = [
'mobile' => [
'required',
'exists:users',
],
'password' => 'required|string|min:6|max:20',
];
// 验证参数,如果验证失败,则会抛出 ValidationException 的异常
$params = $this->validate($request, $rules);
// 使用 Auth 登录用户,如果登录成功,则返回 201 的 code 和 token,如果登录失败则返回
return ($token = Auth::guard('api')->attempt($params))
? response(['token' => 'bearer ' . $token], 201)
: response(['error' => '账号或密码错误'], 400);
}
/**
* 处理用户登出逻辑
*
* @return \Illuminate\Http\JsonResponse
*/
public function logout()
{
Auth::guard('api')->logout();
return response(['message' => '退出成功']);
}
}
затем мы входимtinker:
$ php artisan tinker
Выполните следующую команду, чтобы создать тестового пользователя, имя пользователя здесь — это номер мобильного телефона, вы можете самостоятельно заменить его адресом электронной почты. Не забудьте установить пространство имен:
>>> namespace App\Models;
>>> User::create(['name' => 'Test','mobile' => 17623239881,'password' => bcrypt('123456')]);
Правильный результат выполнения выглядит следующим образом:
Затем откройте Postman для тестирования API.
Правильный результат запроса выглядит следующим образом:
Вы можете видеть, что мы успешно получили токен, поэтому давайте проверим токен обновления.
Как видно из рисунка, мы получили новый токен, а следующим делом будет заниматься настроенный нами ранее перехватчик axios, который заменит локальный токен на этот токен.
научно-популярная версия
Я чувствую, что многие люди не имеют понятия о версии, так что вот общепринятая научно-популярная версия.
-
Альфа (Альфа) версия
Эта версия указывает на то, что Пакет является лишь предварительным готовым продуктом, обычно сообщаемым только разработчикам, а небольшая часть выпускается для профессиональных тестировщиков. Вообще говоря, в этой версии программы много ошибок, и обычным пользователям лучше ее не устанавливать.
-
Бета (бета) версия
Эта версия была значительно улучшена по сравнению с альфа-версией (Alpha), в ней исправлены серьезные ошибки, но все еще есть некоторые ошибки, которые необходимо устранить путем масштабного тестирования выпуска. Через тесты некоторых профессиональных энтузиастов результаты возвращаются разработчикам, которые затем вносят целевые модификации. Эта версия также не подходит для обычной пользовательской установки.
-
RC/Предварительный просмотр
RC — это сокращение от Release Candidate, и фиксированный термин означает, что окончательный релиз готов. Вообще говоря, в RC-версии реализованы все функции и исправлено большинство ошибок. Как правило, на этом этапе автор пакета только исправляет ошибки и не вносит каких-либо серьезных изменений в программное обеспечение.
-
нормальное распределение
Как правило, после прохождения трех вышеуказанных версий автор запускает эту версию. Эта версия исправляет большинство ошибок и будет поддерживаться в течение определенного периода времени. (Время определяется пожеланиями автора, т.е.Laravelобщий выпуск поддерживается в течение одного года обслуживания. )
-
Версия LTS (долгосрочная поддержка)
Этот выпуск является специальным выпуском, а обычный выпуск рассчитан на более длительную поддержку, чем обычно. (Например, LTS-версия Laravel предлагает трехлетнююТехническая поддержка. )
Эпилог
jwt-authЭто действительно отличный пакет аутентификации пользователей, простой в настройке и использовании.
Статья окончена, спасибо за прочтение.