开发当中踩了不少坑,做个备忘。

代码环境:

laravel 5.1

oauth2-server服务组件:

lucadegasperi/oauth2-server-laravel 也就是 thephpleague/oauth2-server 的laravel 包装版本

目前插件的版本是~4.1

需要注意的点是:

一般而言最常见的应用场景是grant_type为authorization_code的情景,

thephpleague的oauth2-server要求的数据提交必须是POST数据编码方式是application/x-www-form-urlencoded,默认情况下如果你用的是curl组件会以multipart/form-data模式编码提交的post数据,所以后端提交请求的时候注意一下, 如果你用的是curl,需要设置:

1
curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Type: application/x-www-form-urlencoded'));

如果你用的是GuzzleHttp的组件:参考官方的说明

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
$response = $client->post('http://httpbin.org/post', [
    'form_params' => [
        'field_name' => 'abc',
        'other_field' => '123',
        'nested_field' => [
            'nested' => 'hello'
        ]
    ]
]);

其他的基础配置设定,插件作者的wiki中已经有了说明,我这里做了一些自己的设定:

  • 不想关闭全局的csrf保护咋办? 如果你是直接安装的laravel 5.1版不要关闭全局$middleware的csrf:
1
2
3
4
5
6
7
8
9
        protected $middleware = array(
        \Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode::class,
        \LucaDegasperi\OAuth2Server\Middleware\OAuthExceptionHandlerMiddleware::class,
        \App\Http\Middleware\EncryptCookies::class,
        \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
        \Illuminate\Session\Middleware\StartSession::class,
        \Illuminate\View\Middleware\ShareErrorsFromSession::class,
        \App\Http\Middleware\VerifyCsrfToken::class, // 不要关闭
    );

按wiki中的说明添加$routeMiddleware中的设定:

1
2
3
4
5
6
7
8
9
    protected $routeMiddleware = [
        'csrf' => \App\Http\Middleware\VerifyCsrfToken::class, // 添加 csrf配置
        'auth' => \App\Http\Middleware\Authenticate::class,
        'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
        'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
        'oauth' => \LucaDegasperi\OAuth2Server\Middleware\OAuthMiddleware::class,
        'oauth-owner' => \LucaDegasperi\OAuth2Server\Middleware\OAuthOwnerMiddleware::class,
        'check-authorization-params' => \LucaDegasperi\OAuth2Server\Middleware\CheckAuthCodeRequestMiddleware::class,
    ];

在你的\App\Http\Middleware\VerifyCsrfToken类中的$except变量添加:

1
2
3
4
5
6
    protected $except = [
        //
        'api',
        'api/*',
        'oauth/access_token',
    ];

也就是:

  1. 你的oauth服务获取access_token的入口地址,如果你换了地址修改这里对应的设置即可。
  2. 你使用oauth中间件保护的服务接口也不需要csrf做多余的防护,在此排除掉 api/* 这对应的前缀即可 如果是5.0之类的升级上来的,VerifyCsrfToken可能还是老的写法,不支持$except,自己改造一下符合新版规范:
 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
<?php namespace App\Http\Middleware;

use Closure;
use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken as BaseVerifier;
use Illuminate\Session\TokenMismatchException;

class VerifyCsrfToken extends BaseVerifier {

    /**
     * The URIs that should be excluded from CSRF verification.
     *
     * @var array
     */
    protected $except = [
        //
        'api/*',
        'oauth/access_token',
    ];

	/**
	 * Handle an incoming request.
	 *
	 * @param  \Illuminate\Http\Request  $request
	 * @param  \Closure  $next
	 * @return mixed
	 */
	public function handle($request, Closure $next)
	{
        if ($this->isReading($request) || $this->shouldPassThrough($request) || $this->tokensMatch($request)) {
            return $this->addCookieToResponse($request, $next($request));
        }

        throw new TokenMismatchException;
		//return parent::handle($request, $next);
	}

    /**
     * Determine if the request has a URI that should pass through CSRF verification.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return bool
     */
    protected function shouldPassThrough($request)
    {
        foreach ($this->except as $except) {
            if ($request->is($except)) {
                return true;
            }
        }

        return false;
    }

}
  • 本地登录授权的页面(View::make(‘oauth.authorization-form’))该怎么写? 原来官方的wiki中没有,放狗找了一圈的issue list才凑合着写了一个放了上去,作者插件的wiki里我已改过了:

注意提交的form原先GET请求中的querystring是需要一并post的 这个坑要注意一下。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
@extends('app')

@section('content')
    <div class="row">
        {!! Form::open(['method' => 'POST','class'=>'form-horizontal', 'url'=> route('oauth.authorize.post',$params)]) !!}
        <div class="form-group">
            <dl class="dl-horizontal">
                <dt>Client Name</dt>
                <dd>{{$client->getName()}}</dd>
            </dl>
        </div>
        {!! Form::hidden('client_id', $params['client_id']) !!}
        {!! Form::hidden('redirect_uri', $params['redirect_uri']) !!}
        {!! Form::hidden('response_type', $params['response_type']) !!}
        {!! Form::hidden('state', $params['state']) !!}
        {!! Form::submit('Approve', ['name'=>'approve', 'value'=>1, 'class'=>'btn btn-success']) !!}
        {!! Form::submit('Deny', ['name'=>'deny', 'value'=>1, 'class'=>'btn bg-danger']) !!}
        {!! Form::close() !!}
    </div>
@endsection

而对应的$params在controller中的设置:

1
2
3
4
        $authParams = Authorizer::getAuthCodeRequestParams();
        $formParams = array_except($authParams,'client');
        $formParams['client_id'] = $authParams['client']-&gt;getId();
        return View::make('oauth.authorization-form', ['params'=&gt;$formParams,'client'=&gt;$authParams['client']]);

最后在你被oauth保护的api接口中你就可以获得到对应的当前用户id了:

1
$uid = Authorizer::getResourceOwnerId();