标签归档:认证

Laravel 实践 – Api Token认证

常见的Session认证中
1 客户端如果没有传递SessionID过来,那么就认为未认证
2 如果传递了SessionID过来,服务端根据SessionID定位Session,如果定位不到,那么就认为未认证
3 如果定位到了Session,但是Session中的认证标记不存在或无效,那么就认为未认证
4 Session中的认证标记存在并有效,认证通过

客户端没有传递SessionID过来,服务端一般会回写一个携带了SessionID的Cookie(意味着启动一个会话,也可能不回写,比如浏览静态资源),下次这个Cookie就会回传到过来,这样就能确定多个请求之间是同一个会话。然后再从Session中判断认证标志从而确定是否已认证。

如果客户端没有回传SessionID(通常是使用Cookie作为载体),一般服务端就会启动一个新会话,那么之前的会话就无法定位了,但是服务端会话数据还是可能存在。如果客户端回传了SessionID,服务端定位不到这个会话,说明已经长久不登录,服务端的会话已经被删除,这个时候也产生一个新的会话,另外就是如果定位到会话(未被删除),但是会话中没有认证标记,或有标记,但是时间已经超时,这时可以不产生新的会话(一般登录成功后会更换一个SessionID)。

这里面,我们是否可以把这个SessionID(不重复),直接对应一个用户,如果传递了SessionID,但是定位不到用户,认为未认证。如果定位到了用户,并且token未超时,认为认证通过。和Session方案相比,Session可以存储认证凭证,从而不用每次都查询。这里的简化流程,就是API Token认证。

在Laravel中,提供了TokenGuard,简单来说就是比对Token,具体操作流程:
1 在users表中添加api_token字段

$table->string('api_token', 60)->unique()->nullable();

2 在users表中寻找一个用户,把api_token字段填写一个任意token

3 在routes的api.php中定义一个闭包路由,应用auth:api中间件(auth表示需要认证,默认使用web对应的Guard,冒号后可以指定Guard)

Route::middleware('auth:api')->get('/user', function (Request $request) {
    return $request->user();
});

4 测试

curl -X http://demo.text/api/user \
-H "Accept: application/json" \
-d "token=xxxxxxxxxxxxxxxxxx"

注意:定义在api.php中的路由,访问时是不会产生会话的(确少StartSession中间件)。另外,Laravel的异常是根据请求是否是ajax或是否有Accept请求头并且包含json来渲染JSON返回的,否则就是渲染Html输出。但是API应该总是返回JSON,所以应该总是携带Accept: application/json请求头。不过每次都要这么写,多少有点麻烦,所以换一个姿势。API的URL都是以api开头的,所以可以修改$request的请求头,这个可以使用容器的rebinding来进行:

#文件:app/Providers/AppServiceProvider.php

    public function register()
    {
        $this->addAcceptableJsonType();
    }

    /**
     * Add "application/json" to the "Accept" header for the current request.
     */
    protected function addAcceptableJsonType()
    {
        $this->app->rebinding('request', function ($app, $request) {
            if ($request->is('api/*')) {
                $accept = $request->header('Accept');
                if (!str_contains($accept, ['/json', '+json'])) {
                    $accept = rtrim('application/json,' . $accept, ',');
                    $request->headers->set('Accept', $accept);
                    $request->server->set('HTTP_ACCEPT', $accept);
                    $_SERVER['HTTP_ACCEPT'] = $accept;
                }
            }
        });
    }

在用户登录后,让其在后台自己产生token,然后把token分发出去使用,这是一个很传统的做法。另外,token长期有效,会存在安全问题。可以改造一下,在登录成功后更换token,登出时擦除token。另外,还可以给token设置一个超时时间:

#文件:routes/api.php
// 登录(每次登录都更换token,登录相当于更换token,登录成功后返回用户实例,包含token)
// 请求体:email  password
Route::post('backend/login', 'API\Backend\Auth\LoginController@login');
// 注册(注册成功后返回用户实例,包含token)
// 请求体: name  email  password  password_confirmation
Route::post('backend/register', 'API\Backend\Auth\RegisterController@register');
// 重置密码 - 发送邮件
// 请求体: email
Route::post('backend/password/email', 'API\Backend\Auth\ForgotPasswordController@sendResetLinkEmail');
// 重置密码 - 输入新密码
// 请求体: token  email  password  password_confirmation
Route::post('backend/password/reset', 'API\Backend\Auth\ResetPasswordController@reset');

// Token保护的路由
// 添加请求头:Authorization  =>  "Bearer Token"
Route::group([
    'middleware' => ['auth:backend_api']
], function () {
    // 登出(撤销token),需要传递token
    Route::any('backend/logout', 'API\Backend\Auth\LoginController@logout');

    // 获取用户
    Route::get('backend/admin', function (Request $request) {
        return [
            'data' => $request->user()
        ];
    });
});

#文件 config/auth.php
#添加backend_api
<?php

return [

    'defaults' => [
        'guard' => 'web',
        'passwords' => 'users',
    ],

    'guards' => [
        'web' => [
            'driver' => 'session',
            'provider' => 'users',
        ],

        'backend' => [
            'driver' => 'session',
            'provider' => 'admins',
        ],

        'api' => [
            'driver' => 'token',
            'provider' => 'users',
        ],

        'backend_api' => [
            'driver' => 'token',
            'provider' => 'admins',
        ],
    ],

    'providers' => [
        'users' => [
            'driver' => 'eloquent',
            'model' => App\Models\User::class,
        ],

        'admins' => [
            'driver' => 'eloquent',
            'model' => App\Models\Admin::class,
        ],
    ],

    'passwords' => [
        'users' => [
            'provider' => 'users',
            'table' => 'password_resets',
            'expire' => 60,
        ],
        'admins' => [
            'provider' => 'admins',
            'table' => 'admin_password_resets',
            'expire' => 60,
        ],
    ],

];

这里把用户存储到admins表,密码找回存储到admin_password_resets表。拷贝一套Auth文件,针对性覆盖某些方法:

#文件: app/Http/Controllers/API/Backend/Auth/LoginController.php
<?php

namespace App\Http\Controllers\API\Backend\Auth;

use Illuminate\Http\Request;
use Illuminate\Foundation\Auth\AuthenticatesUsers;
use Illuminate\Support\Facades\Auth;
use App\Http\Controllers\Controller;

class LoginController extends Controller
{
    /*
    |--------------------------------------------------------------------------
    | Login Controller
    |--------------------------------------------------------------------------
    |
    | This controller handles authenticating users for the application and
    | redirecting them to your home screen. The controller uses a trait
    | to conveniently provide its functionality to your applications.
    |
    */

    use AuthenticatesUsers;

    /**
     * Where to redirect users after login.
     *
     * @var string
     */
    protected $redirectTo = '';

    /**
     * Create a new controller instance.
     *
     * @return void
     */
    public function __construct()
    {
    }

    /**
     * 用户通过认证,返回JSON
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\Response
     */
    protected function sendLoginResponse(Request $request)
    {
        //API调用,没有启动会话
        //$request->session()->regenerate();
        $this->clearLoginAttempts($request);
        $user = with($this->guard()->user())->makeToken();

        return response()->json([
            'data' => $user,
        ], 200);
    }

    /**
     * 取回需要验证的信息
     * 覆盖来自AuthenticatesUsers中的credentials方法
     *
     * @param  \Illuminate\Http\Request $request
     * @return array
     */
    public function logout(Request $request)
    {
        with($request->user())->freshToken();

        return response()->json(['data' => 'Token已经擦除'], 200);
    }

    protected function guard()
    {
        return Auth::guard('backend');
    }

    /**
     * 取回需要验证的信息
     * 覆盖来自AuthenticatesUsers中的credentials方法
     *
     * @param  \Illuminate\Http\Request $request
     * @return array
     */
    protected function credentials(Request $request)
    {
        return $request->only($this->username(), 'password') + ['is_active' => 1];
    }
}

#文件: app/Http/Controllers/API/Backend/Auth/RegisterController.php
<?php

namespace App\Http\Controllers\API\Backend\Auth;

use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Validator;
use Illuminate\Foundation\Auth\RegistersUsers;
use Illuminate\Auth\Events\Registered;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Models\Admin;

class RegisterController extends Controller
{
    /*
    |--------------------------------------------------------------------------
    | Register Controller
    |--------------------------------------------------------------------------
    |
    | This controller handles the registration of new users as well as their
    | validation and creation. By default this controller uses a trait to
    | provide this functionality without requiring any additional code.
    |
    */

    use RegistersUsers;

    /**
     * Where to redirect users after registration.
     *
     * @var string
     */
    protected $redirectTo = '';

    /**
     * Create a new controller instance.
     *
     * @return void
     */
    public function __construct()
    {
    }

    /**
     * Get a validator for an incoming registration request.
     *
     * @param  array  $data
     * @return \Illuminate\Contracts\Validation\Validator
     */
    protected function validator(array $data)
    {
        return Validator::make($data, [
            'name' => 'required|string|max:255',
            'email' => 'required|string|email|max:255|unique:users',
            'password' => 'required|string|min:6|confirmed',
        ]);
    }

    /**
     * Create a new user instance after a valid registration.
     *
     * @param  array  $data
     * @return \App\Models\Admin
     */
    protected function create(array $data)
    {
        return Admin::create([
            'name' => $data['name'],
            'email' => $data['email'],
            'password' => bcrypt($data['password']),
        ]);
    }

    protected function guard()
    {
        return Auth::guard('backend');
    }

    /**
     * Handle a registration request for the application.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\Response
     */
    public function register(Request $request)
    {
        $this->validator($request->all())->validate();

        event(new Registered($user = $this->create($request->all())));

        //不需要登录
        //$this->guard()->login($user);

        return response()->json([
            'data' => Admin::find($user->id)->makeToken(),
        ], 200);
    }
}

#文件: app/Http/Controllers/API/Backend/Auth/ForgotPasswordController.php
<?php

namespace App\Http\Controllers\API\Backend\Auth;

use Illuminate\Foundation\Auth\SendsPasswordResetEmails;
use Illuminate\Support\Facades\Password;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;

class ForgotPasswordController extends Controller
{
    /*
    |--------------------------------------------------------------------------
    | Password Reset Controller
    |--------------------------------------------------------------------------
    |
    | This controller is responsible for handling password reset emails and
    | includes a trait which assists in sending these notifications from
    | your application to your users. Feel free to explore this trait.
    |
    */

    use SendsPasswordResetEmails;

    /**
     * Create a new controller instance.
     *
     * @return void
     */
    public function __construct()
    {
    }

    /**
     * Get the response for a successful password reset link.
     *
     * @param  string  $response
     * @return \Illuminate\Http\Response
     */
    protected function sendResetLinkResponse($response)
    {
        return response()->json([
            'data' => trans($response),
        ], 200);
    }

    /**
     * Get the response for a failed password reset link.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  string  $response
     * @return \Illuminate\Http\Response
     */
    protected function sendResetLinkFailedResponse(Request $request, $response)
    {
        return response()->json([
            'message' => '发生错误',
            'errors' => ['email' => trans($response)],
        ], 500);
    }

    /**
     * 验证邮件地址
     *
     * @param  \Illuminate\Http\Request  $request
     * @return void
     */
    protected function validateEmail(Request $request)
    {
        $this->validate($request, ['email' => 'required|email']);
    }

    public function broker()
    {
        return Password::broker('admins');
    }
}


#文件: app/Http/Controllers/API/Backend/Auth/ResetPasswordController.php
<?php

namespace App\Http\Controllers\API\Backend\Auth;

use Illuminate\Support\Str;
use Illuminate\Foundation\Auth\ResetsPasswords;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Password;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use Illuminate\Auth\Events\PasswordReset;
use App\Http\Controllers\Controller;

class ResetPasswordController extends Controller
{
    /*
    |--------------------------------------------------------------------------
    | Password Reset Controller
    |--------------------------------------------------------------------------
    |
    | This controller is responsible for handling password reset requests
    | and uses a simple trait to include this behavior. You're free to
    | explore this trait and override any methods you wish to tweak.
    |
    */

    use ResetsPasswords;

    /**
     * Where to redirect users after resetting their password.
     *
     * @var string
     */
    protected $redirectTo = '';

    /**
     * Create a new controller instance.
     *
     * @return void
     */
    public function __construct()
    {

    }

    /**
     * 如果账户已经禁用,不能再重置密码
     *
     * @param  \Illuminate\Http\Request  $request
     * @return array
     */
    protected function credentials(Request $request)
    {
        return $request->only(
            'email', 'password', 'password_confirmation', 'token'
        ) + ['is_active' => 1];
    }

    /**
     * 重置密码,重置后不需要重新登录
     *
     * @param  \Illuminate\Contracts\Auth\CanResetPassword  $user
     * @param  string  $password
     * @return void
     */
    protected function resetPassword($user, $password)
    {
        $user->password = Hash::make($password);

        $user->setRememberToken(Str::random(60));

        $user->save();

        event(new PasswordReset($user));

        //$this->guard()->login($user);
    }

    /**
     * Get the response for a successful password reset.
     *
     * @param  string  $response
     * @return \Illuminate\Http\Response
     */
    protected function sendResetResponse($response)
    {
        return response()->json([
            'data' => trans($response),
        ], 200);
    }

    /**
     * Get the response for a failed password reset.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  string  $response
     * @return \Illuminate\Http\Response
     */
    protected function sendResetFailedResponse(Request $request, $response)
    {
        return response()->json([
            'message' => '发生错误',
            'errors' => ['email' => trans($response)],
        ], 500);
    }

    public function broker()
    {
        return Password::broker('admins');
    }

    protected function guard()
    {
        return Auth::guard('backend');
    }
}

发生错误时,状态码返回500,成功操作时状态码返回200,验证不通过状态码返回422,登录错误超过阈值状态码返回423,没有权限状态码返回401。

另外,返回的格式:

// 状态码
[
    'message' => '', // 成功时可不返回
    'data' => [],    // 成功时返回的数据
    'errors' => []   // 不成功时,返回的错误详细
]

Laravel 实践 – 多后台登录

一个简单站点,前台需要提供用户登录,后台需要实现管理员登录。这里就涉及两套登录系统。

前台的用户登录直接使用默认的。按照文档设置:

php artisan make:auth
php artisan migrate

以上命令最终添加路由和产生视图文件和产生一张用户表。
0 首先拷贝users为admins,password_resets为admin_password_resets; 拷贝app/User.php为app/Admin.php。

1 打开routes/web.php,添加路由

// 后台登录注册流程
Route::get('backend', 'Backend\Dashboard@index');
// 后台登录-Dashboard
Route::get('backend/dashboard', 'Backend\Dashboard@index')->name('backend.dashboard');
// 后台登录-认证
$this->get('backend/login', 'Backend\Auth\LoginController@showLoginForm')->name('backend.login');
$this->post('backend/login', 'Backend\Auth\LoginController@login');
$this->any('backend/logout', 'Backend\Auth\LoginController@logout')->name('backend.logout');
// 后台登录-注册
$this->get('backend/register', 'Backend\Auth\RegisterController@showRegistrationForm')->name('backend.register');
$this->post('backend/register', 'Backend\Auth\RegisterController@register');
// 后台登录-找回密码
$this->get('backend/password/reset', 'Backend\Auth\ForgotPasswordController@showLinkRequestForm')->name('backend.password.request');
$this->post('backend/password/email', 'Backend\Auth\ForgotPasswordController@sendResetLinkEmail')->name('backend.password.email');
$this->get('backend/password/reset/{token}', 'Backend\Auth\ResetPasswordController@showResetForm')->name('backend.password.reset');
$this->post('backend/password/reset', 'Backend\Auth\ResetPasswordController@reset');

2 进入app/Http/Controllers,建立目录Backend,把app/Http/Controllers下的Auth和home.php拷贝到新建的目录中(backend目录)。

#文件: app/Http/Controllers/Backend/Home.php
<?php
// 命名空间改为Backend
namespace App\Http\Controllers\Backend;

use Illuminate\Http\Request;
use App\Http\Controllers\Controller;

class Dashboard extends Controller
{
    /**
     * Create a new controller instance.
     *
     * @return void
     */
    public function __construct()
    {
        // 中间件改为auth:admin
        // 原来为auth
        $this->middleware('auth:admin');
    }

    /**
     * Show the application dashboard.
     *
     * @return \Illuminate\Http\Response
     */
    public function index()
    {
        // 视图文件改为backend.home
        // 原来为home
        return view('backend.home');
    }
}

#文件: app/Http/Controllers/Backend/Auth/LoginController.php
<?php
// 命名空间改为Backend\Auth
namespace App\Http\Controllers\Backend\Auth;

use Illuminate\Http\Request;
use Illuminate\Foundation\Auth\AuthenticatesUsers;
use Illuminate\Support\Facades\Auth;
use App\Http\Controllers\Controller;

class LoginController extends Controller
{
    use AuthenticatesUsers;

    // 登录后重定向改为/backend/home
    // 原来为home
    protected $redirectTo = '/backend/home';

    public function __construct()
    {
        // 中间件改为guest:admin
        // 原来为guest
        // 注:except不需要修改,except表示此中间件忽略logout方法
        $this->middleware('guest:admin')->except('logout');
    }

    // 新增方法,对应修改视图
    public function showLoginForm()
    {
        return view('backend.auth.login');
    }

    // 新增方法,指定使用admin这个拦截器
    protected function guard()
    {
        return Auth::guard('admin');
    }

    // 新增方法,管理员登出
    public function logout(Request $request)
    {
        $this->guard('admin')->logout();

        $request->session()->invalidate();

        return redirect('/backend/home');
    }
}

#文件: app/Http/Controllers/Backend/Auth/ForgotPasswordController.php
<?php
// 修改命名空间为Backend\Auth
namespace App\Http\Controllers\Backend\Auth;

use Illuminate\Foundation\Auth\SendsPasswordResetEmails;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;

class ForgotPasswordController extends Controller
{
    use SendsPasswordResetEmails;

    // 修改中间件(添加admin)
    public function __construct()
    {
        $this->middleware('guest:admin');
    }

    // 添加方法,修正视图
    public function showLinkRequestForm()
    {
        return view('backend.auth.passwords.email');
    }
}

#文件: app/Http/Controllers/Backend/Auth/RegisterController.php
<?php
// 修改命名空间为Backend\Auth
namespace App\Http\Controllers\Backend\Auth;

use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Validator;
use Illuminate\Foundation\Auth\RegistersUsers;
use App\Http\Controllers\Controller;
use App\Models\Admin;

class RegisterController extends Controller
{

    use RegistersUsers;

    // 修正为/backend/home,原来为home
    protected $redirectTo = '/backend/home';

    // 修正中间件为guest:admin
    public function __construct()
    {
        $this->middleware('guest:admin');
    }

    // 验证时针对的表改为unique:admins(原来为unique:users)
    protected function validator(array $data)
    {
        return Validator::make($data, [
            'name' => 'required|string|max:255',
            'email' => 'required|string|email|max:255|unique:admins',
            'password' => 'required|string|min:6|confirmed',
        ]);
    }

    /**
     * Create a new user instance after a valid registration.
     *
     * @param  array  $data
     * @return \App\Models\Admin   改为\App\Models\Admin
     */
    protected function create(array $data)
    {
        // 模型修改为Admin
        return Admin::create([
            'name' => $data['name'],
            'email' => $data['email'],
            'password' => bcrypt($data['password']),
        ]);
    }
    // 新增,注册成功后登陆
    protected function guard()
    {
        return Auth::guard('admin');
    }

    // 新增,修正视图
    public function showRegistrationForm()
    {
        return view('backend.auth.register');
    }
}

#文件: app/Http/Controllers/Backend/Auth/ResetPasswordController.php
<?php
// 修正命名空间为:Backend\Auth
namespace App\Http\Controllers\Backend\Auth;

// 新加Auth和Pawword
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Password;
use Illuminate\Foundation\Auth\ResetsPasswords;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;

class ResetPasswordController extends Controller
{
    use ResetsPasswords;

    // 修正为/backend/home,原来为home
    protected $redirectTo = '/backend/home';

    // 修正中间件为guest:admin
    public function __construct()
    {
        $this->middleware('guest:admin');
    }

    // 新增,指定broker为admins(config/auth.php中设置)
    public function broker()
    {
        return Password::broker('admins');
    }

    // 新增,重设密码后需要自动登录,设置使用正确的Guard
    protected function guard()
    {
        return Auth::guard('admin');
    }

    // 新增,修正视图
    public function showResetForm(Request $request, $token = null)
    {
        return view('backend.auth.passwords.reset')->with(
            ['token' => $token, 'email' => $request->email]
        );
    }
}

3 布局视图等
进入resources/views/layouts,拷贝app.blade.php为app_backend.blade.php。
进入resources/views,建立目录backend,把resources/views下的auth和home.php拷贝到新建的目录中(backend目录)。

进入到backend目录,对各个文件修改引用布局文件由app改为app_backend(@extends(‘layouts.app_backend’))。

然后对各个视图文件(包括resources/views/layouts/app_backend.blade.php)做修改(没有忽略):

@guest 改为 @guest('admin')
Auth::user() 改为 Auth::guard('admin')->user()
route('login') 改为 route('backend.login')
route('password.request') 改为 route('backend.password.request')
route('password.email') 改为 route('backend.password.email')

4 修改配置

#文件:config/auth.php
return [
    'defaults' => [
        'guard' => 'web',
        'passwords' => 'users',
    ],

    'guards' => [
        'web' => [
            'driver' => 'session',
            'provider' => 'users',
        ],
	// 添加
        'admin' => [
            'driver' => 'session',
            'provider' => 'admins',
        ],
        'api' => [
            'driver' => 'token',
            'provider' => 'users',
        ],
    ],
    'providers' => [
        'users' => [
            'driver' => 'eloquent',
            'model' => App\User::class,
        ],
        // 添加
        'admins' => [
            'driver' => 'eloquent',
            'model' => App\Admin::class,
        ],
    ],

    'passwords' => [
        'users' => [
            'provider' => 'users',
            'table' => 'password_resets',
            'expire' => 60,
        ],
        // 新增,provider需要准确对应providers数组
        'admins' => [
            'provider' => 'admins',
            'table' => 'admin_password_resets',
            'expire' => 60,
        ],
    ],
];

5 调整中间件

#文件 app/Http/Middleware/RedirectIfAuthenticated
<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Support\Facades\Auth;

class RedirectIfAuthenticated
{
    public function handle($request, Closure $next, $guard = null)
    {
        // 检查是否处于登录状态
        if (Auth::guard($guard)->check()) {
            if ($guard === 'admin') {
                return redirect('/backend/home');
            } else {
                return redirect('/home');
            }
        }

        return $next($request);
    }
}

注意,RedirectIfAuthenticated中间件别名是guest,实际也是调用对应Guard来判断是否是Guest。 这个Guest中间件只针对认证路由,用来判断当访问认证路由时如果已经登录那么将跳转到什么地方(这个跟auth中间件完全不同,auth中间件用于需要认证的路由)。

#涉及路由
backend/login
backend/register
backend/password/reset
backend/password/email
backend/password/reset/{token}

注意,backend/logout被排除了,logout不需要检查是否处于登录状态。

6 异常渲染调整
访问一个需要认证的页面(比如backend/home),这个时候如果检查到没有登录(认证不成功,auth中间件),那么这个时候就需要重定向到首页(如果是JSON,则返回JSON),默认直接跳转到/home没有问题,但是由于现在多加了一个登录,那么就必须针对这个做处理,否则总是跳转到/home。认证不通过抛出\Illuminate\Auth\AuthenticationException异常,这个异常中包括是由什么Guard认证不通过而抛的异常,异常处理自定义可以通过app/Exception/Handler.php来处理:

public function render($request, Exception $exception)
    {
        if ($exception instanceof \Illuminate\Auth\AuthenticationException) {
            $guards = $exception->guards();
            if (is_array($guards) && in_array('admin', $guards)) {
                return $request->expectsJson()
                    ? response()->json(['message' => $exception->getMessage()], 401)
                    : redirect()->guest(route('backend.login'));
            }
        }
        return parent::render($request, $exception);
    }

这里判断如果是admin这个Guard认证不通过抛的\Illuminate\Auth\AuthenticationException异常,则让其跳转到backend.login。

7 其它
对于使用了admin这个Guard保护的路由,需要使用auth:admin(默认是auth)。

如果登录了,session(‘status’)是ture,前后台登录都是如此。所以前后台登录由于使用不同的Guard,所以不存在问题。登出就有问题,如果前后台都已登录,其一登出,那么全部登出(由于销户Session的原因),不过这个没有什么关系了。

HTTP BASIC认证操作实践

HTTP BASIC认证是HTTP层次内容,以下使用PHP来实现这个过程:

vi basic.php

<?php
$users = [
    "admin" => "xxxxxx"
];
$needAuth = true;
if(!empty($_SERVER['PHP_AUTH_USER']) && !empty($_SERVER['PHP_AUTH_PW'])) {
    // 用户名和密码
    $user = trim($_SERVER['PHP_AUTH_USER']);
    $pwd = trim($_SERVER['PHP_AUTH_PW']);

    if(isset($users[$user]) && ($users[$user] === $pwd)) {
        $needAuth = false;
    }
}

if($needAuth) {
    header("Content-type:text/html;charset=utf-8");
    header('WWW-Authenticate: Basic realm="admin xxxxxx"');
    header('HTTP/1.0 401 Unauthorized');
    echo date("Y-m-d H:i:s")." -> Need Auth...";
    exit;
}

echo date("Y-m-d H:i:s")." -> Auth...";

http_basic_auth
对于一个小应用程序,如果需要做隐藏保护,这个方式会非常便利。不过这个认证方式更多见于API访问中。

查看发送的HTTP请求头:
http_basic_header
用户名和密码通过HTTP的一个请求头Authorization来传输的,内容是Basic YWRtaW46eHh4eHh4,第一个字符串Basic为认证方式,第二个字符串是用户名和密码冒号分隔的字符串的base64编码(admin:xxxxxx -> YWRtaW46eHh4eHh4)。

这个用户名和密码传递到服务器端,对于Nginx(Apache等),它可以首先处理,也可以继续转发到PHP,让PHP来处理(这里就是这个情况)。PHP接收这两个变量使用:

$_SERVER['PHP_AUTH_USER']
$_SERVER['PHP_AUTH_PW']

这两个变量是经过了base64解码之后得到的,这个解码应该是HTTP服务进行的,把得到的变量传递给PHP。注意,这里的base64编码目的不是在加密,而是方便传输。所有如果直接通过HTTP传输是不安全的(其它的一般用户名密码登录也一样),所以,对于API设计,为了安全,一般通过HTTPS传送数据。

BASCIC认证是HTTP层次的内容,所以对于Nginx这样的HTTP服务器软件,当然可以配置其进行BASIC认证,这样就不需要由PHP来处理。Nginx配置参考:

server {
    listen       80;
    server_name  xx.xx.xx.xx;
    root /var/www/xxx/public;
    index index.php

    error_page 404 /index.php;
    
    auth_basic "Restricted";
    auth_basic_user_file /etc/nginx/conf.d/htpasswd;

    if (!-e $request_filename) {
        rewrite ^/(.+)$ /index.php last;
        break;
    }

    location / {
	root /var/www/xxx/public;
	try_files $uri $uri/ /index.php?$query_string;
    }

    location ~* ^.+\.(css|js|jpeg|jpg|gif|png|ico|eot|ttf|woff|svg) {
        expires 30d;
    }

    location ~* \.(eot|ttf|woff|svg|html)$ {
        add_header Access-Control-Allow-Origin *;
    }

    location ~ .(php|php5)?$ {
        fastcgi_connect_timeout 300;
        fastcgi_send_timeout 300;
        fastcgi_read_timeout 300;
        fastcgi_buffer_size 32k;
        fastcgi_buffers 256 32k;

        fastcgi_pass   127.0.0.1:9000;
	fastcgi_index  index.php;
        fastcgi_param  SCRIPT_FILENAME  $document_root$fastcgi_script_name;
        include        fastcgi_params;
   }
}

主要是添加auth_basic指令和auth_basic_user_file指令,auth_basic和PHP中的如下两行设置类似:

header('WWW-Authenticate: Basic realm="admin xxxxxx"');
header('HTTP/1.0 401 Unauthorized');

指令auth_basic的值直接对应Basic realm=”xxx”中的xxx值,表示是BASIC认证,认证的用户名和密码是auth_basic_user_file指定的密码文件,这个文件中保存的用户名密码可不是用base64编码的,它使用的是Hash算法,格式:

vfeelit:CQArTEgiT84So:注释

匹配过程大体应该是这样:获取HTTP的Authorization请求头,从中获取经过base64编码的字符串,解码获取用户名和密码,然后匹配用户名,再通过Hash算法得到密码的Hash值,最后和保存的Hash值进行比较。

这个密码文件产生(htpasswd或者openssl):

# printf "vfeelit:$(openssl passwd -crypt 123456)\n" >> conf.d/htpasswd
# cat conf.d/htpasswd 
vfeelit:DmZ3GXV9zFegY