Laravel使用Laravel Passport轻松实现API身份验证,Passport建立在由Andy Millington和Simon Hamp维护的League OAuth2服务器之上。
关于passport的具体使用方法,可以参考Laravel官方文档,这里记录当使用grant_type为password时遇到401 Unauthorized
的一种解决方法。
首先,重现案发现场:
//请求token
$http = new \GuzzleHttp\Client();
$response = $http->post('http://127.0.0.1/oauth/token', [ 'form_params' => [
'grant_type' => 'password',
'client_id' => '3',
'client_secret' => 'rYu6KUBJYKs7jDpG0Bl0hXV791hM89CZa7MSwzSE',
'username' => 'my account',
'password' => 'my password',
'scope' => '*'
], ]);
$tokenData = json_decode((string)$response->getBody(), true);
//返回结果
Client error: `POST http://127.0.0.1/oauth/token` resulted in a `401 Unauthorized` response:
{"error":"invalid_credentials","message":"The user credentials were incorrect."}
我的解决方法:
1、首先,确定passport安装完成并已成功执行migrate
和passport:install
,这时,可以在数据库中查看oauth_clients
表,表中默认有一个password客户端,当然,也可以通过passport:client --password
来创建一个password客户端,password客户端在表中的password_client
字段的值为1。
2、其次,确定在请求token的post请求中正确填写了参数值,尤其是client_id <oauth_clients表的主键id>
和client_secret
,另外,scope
尽量不为空,可设置为*
,通常,client_id
和client_secret
填写错误或不是对应的password客户端id的情况较多,另外scope为空也可能出现异常,关于scope只是别人反应的情况,自己未遇到所以并未细寻。
3、通过post参数我们也会发现,password grant会用到username和password两个值,而这里也是passport在校验用户名和密码是否匹配,所以,我们需要保证passport能够正确取到用户并验证其密码。我出问题的地方也是在这里。
passport校验username和password部分过程,可直接跳过。
//在源码中也可以看到,passwordGrant授权中会有这么一步:
//from vendor/league/oauth2-server/src/Grant/PasswordGrant.php -> function respondToAccessTokenRequest
$user = $this->validateUser($request, $client);
//在这一步会用到我们post参数中的username和password的值来验证用户名密码是否正确。
//在往后找,会看到具体的验证过程
//from vendor/laravel/passport/src/Bridge/UserRepository.php -> function getUserEntityByUserCredentials
//首先,要正确配置config/auth配置文件中的provider,把model配置成自己实际使用的userModel
$provider = config('auth.guards.api.provider');
if (is_null($model = config('auth.providers.'.$provider.'.model'))) {
throw new RuntimeException('Unable to determine authentication model from configuration.');
}
//这里是passport通过username查找指定用户的的地方,可以看到,passport默认使用email字段当做用户账号来寻找用户,如果我们不是使用的email,需要添加findForPassport方法来自定义账号。
if (method_exists($model, 'findForPassport')) {
$user = (new $model)->findForPassport($username);
} else {
$user = (new $model)->where('email', $username)->first();
}
if (! $user) {
return;
//这里是passport验证用户密码的地方,默认是通过hash check方法,如果自己使用的不是hash算法生成密码,则需要添加validateForPassportPasswordGrant方法。
} elseif (method_exists($user, 'validateForPassportPasswordGrant')) {
if (! $user->validateForPassportPasswordGrant($password)) {
return;
}
} elseif (! $this->hasher->check($password, $user->getAuthPassword())) {
return;
}
//获取用户主键id
return new User($user->getAuthIdentifier());
解决方法:
//在自己实际使用的userModel中
//默认主键字段为id
use Authenticatable,HasApiTokens;
//如果自己使用的账号字段是email则无需添加此方法,否则添加此方法并手动返回用户模型。
public function findForPassport($username) {
return $this->where('your username column name', $username)->first();
}
//如果你的密码使用的是hash算法并且post传递的是原始密码(即没有手动hash)则无需添加此方法,否则需自行验证密码。
public function validateForPassportPasswordGrant($password) {
//你自己的密码验证逻辑
return true or false;
}
到此为止,关于username和password引起的Unauthorized问题就基本解决了,能够正常获取token。