Laravel使用Laravel Passport輕松實(shí)現(xiàn)API身份驗(yàn)證,Passport建立在由Andy Millington和Simon Hamp維護(hù)的League OAuth2服務(wù)器之上。
關(guān)于passport的具體使用方法,可以參考Laravel官方文檔,這里記錄當(dāng)使用grant_type為password時(shí)遇到401 Unauthorized的一種解決方法。
首先,重現(xiàn)案發(fā)現(xiàn)場(chǎng):
//請(qǐng)求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);
//返回結(jié)果
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安裝完成并已成功執(zhí)行migrate和passport:install,這時(shí),可以在數(shù)據(jù)庫(kù)中查看oauth_clients表,表中默認(rèn)有一個(gè)password客戶端,當(dāng)然,也可以通過(guò)passport:client --password來(lái)創(chuàng)建一個(gè)password客戶端,password客戶端在表中的password_client字段的值為1。
2、其次,確定在請(qǐng)求token的post請(qǐng)求中正確填寫(xiě)了參數(shù)值,尤其是client_id <oauth_clients表的主鍵id>和client_secret,另外,scope盡量不為空,可設(shè)置為*,通常,client_id和client_secret填寫(xiě)錯(cuò)誤或不是對(duì)應(yīng)的password客戶端id的情況較多,另外scope為空也可能出現(xiàn)異常,關(guān)于scope只是別人反應(yīng)的情況,自己未遇到所以并未細(xì)尋。
3、通過(guò)post參數(shù)我們也會(huì)發(fā)現(xiàn),password grant會(huì)用到username和password兩個(gè)值,而這里也是passport在校驗(yàn)用戶名和密碼是否匹配,所以,我們需要保證passport能夠正確取到用戶并驗(yàn)證其密碼。我出問(wèn)題的地方也是在這里。
passport校驗(yàn)username和password部分過(guò)程,可直接跳過(guò)。
//在源碼中也可以看到,passwordGrant授權(quán)中會(huì)有這么一步:
//from vendor/league/oauth2-server/src/Grant/PasswordGrant.php -> function respondToAccessTokenRequest
$user = $this->validateUser($request, $client);
//在這一步會(huì)用到我們post參數(shù)中的username和password的值來(lái)驗(yàn)證用戶名密碼是否正確。
//在往后找,會(huì)看到具體的驗(yàn)證過(guò)程
//from vendor/laravel/passport/src/Bridge/UserRepository.php -> function getUserEntityByUserCredentials
//首先,要正確配置config/auth配置文件中的provider,把model配置成自己實(shí)際使用的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通過(guò)username查找指定用戶的的地方,可以看到,passport默認(rèn)使用email字段當(dāng)做用戶賬號(hào)來(lái)尋找用戶,如果我們不是使用的email,需要添加findForPassport方法來(lái)自定義賬號(hào)。
if (method_exists($model, 'findForPassport')) {
$user = (new $model)->findForPassport($username);
} else {
$user = (new $model)->where('email', $username)->first();
}
if (! $user) {
return;
//這里是passport驗(yàn)證用戶密碼的地方,默認(rèn)是通過(guò)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());
解決方法:
//在自己實(shí)際使用的userModel中
//默認(rèn)主鍵字段為id
use Authenticatable,HasApiTokens;
//如果自己使用的賬號(hào)字段是email則無(wú)需添加此方法,否則添加此方法并手動(dòng)返回用戶模型。
public function findForPassport($username) {
return $this->where('your username column name', $username)->first();
}
//如果你的密碼使用的是hash算法并且post傳遞的是原始密碼(即沒(méi)有手動(dòng)hash)則無(wú)需添加此方法,否則需自行驗(yàn)證密碼。
public function validateForPassportPasswordGrant($password) {
//你自己的密碼驗(yàn)證邏輯
return true or false;
}
到此為止,關(guān)于username和password引起的Unauthorized問(wèn)題就基本解決了,能夠正常獲取token。