Laravel5.3之Two-Factor Authentication神器——Duo

說明:本文主要研究利用Duo來實現雙重認證,Two-Factor Authentication就是除了username-password這種登陸認證以外,還使用第二層安全認證,引用官網What is Two-Factor Authentication?的一句話:javascript

A second layer of security to your login, in addition to your password.php

這裏,就是使用Duo來實現二次安全認證,保護程序防止被惡意者登陸。須要實現二次登陸認證的場景仍是不少的,如登陸雲服務器AWS或Aliyun時只是帳號密碼登陸是遠遠不夠,安全性較差,若是登陸AWS的private key被別人知道了,那惡意者也會登陸到你的AWS,那就麻煩了,代碼豈不暴露了;還有公司內部的一些後臺網站,若是隻是username-password這種基本認證也遠不夠安全,若是被別人知道了帳號密碼登錄進去那就泄露了公司祕密了,限制IP登陸也挺麻煩的,那豈不是除了公司外其餘地方不能訪問內部網站了,若是想在家訪問一個業務的後臺就麻煩了。css

使用Duo來作多一層保護會更安全,Duo的Web Application Protection工做原理如圖:html

上圖描述的主要內容就是除了輸入基本的帳號密碼認證外,還得通過Duo的二次認證。如在我司在登陸AWS雲時,除了private key認證外,還得必須通過Duo安全認證才能安全登陸AWS,Duo認證選擇的方式是Mobile Push Notification,這樣當有惡意者知道了我的的private key想要登陸AWS,我的的手機就會收到Duo Push Notification,只有我的在手機上選擇Approve才能登陸,並且也知道了private key被盜取了趕忙換一個key作補救措施。在登陸後臺時也是必須Duo認證才行。實際上,Duo還能集成進Github上,這樣登陸Github時也必須通過Duo認證,就算被知道了帳號密碼也不會被登陸我的的Github帳號。java

這裏主要學習下如何利用Duo來Protect Web Application,這裏假設Web程序是Laravel寫的,看如何集成進Laravel中實現二次認證。假設因爲業務需求,有一個後臺Admin,並是username-password這種HTTP Basic Authentication的(不少時候username-password認證在公司內都是SSO{Single Sign On},多個系統只須要一套username-password,這個能夠使用Atlassian Crowd來作,之後再聊這個Crowd)。jquery

開發環境:Laravel5.3 + PHP7web

Duo Account

進去Duo官網註冊個帳號先,Duo Pricing對我的使用不超過10個用戶時是免費的,其他套餐的價格也很便宜。而後在手機端下載個Duo應用。最後使用Duo帳號登陸進後臺,後臺登陸使用Push認證,這樣Duo Mobile App就會收到Push Notification,選擇Approve就會自動登陸Duo 後臺:redis

登陸後臺,建立一個Application獲取keys,不一樣的Application有不一樣的keys,這樣能夠不一樣的Admin後臺使用不一樣Application的keys,方便管理:bootstrap

選擇Web SDK,由於本文是學習使用Duo的SDK集成進Admin後臺,來保護後臺Admin程序:api

這樣就獲得了名叫Web SDK的Application了,並獲得了對應的Integration key,Secret key,API hostname這三個keys,後續集成SDK時須要這三個keys:

Two-Factor Authentication

把Duo SDK集成進Laravel中實際上就是多加一個Middleware,這裏假設名爲auth.duo,先作箇中間件:

php artisan make:middleware DuoTwoFactorAuthentication

而後寫上中間件代碼,首先通過username-password第一層認證(這裏假設是HTTP Basic Authentication),而後再是Duo Authentication第二層認證,最後認證經過再$next($request):

<?php

namespace App\Http\Middleware;

use App\Http\Controllers\TwoFactorAuthenticationController;
use Auth;
use Closure;
use Illuminate\Http\Response;

class DuoTwoFactorAuthentication
{
    /** * Handle an incoming request. * * @param \Illuminate\Http\Request $request * @param \Closure $next * @param null|string $guard * * @return mixed */
    public function handle($request, Closure $next, $guard = null)
    {
        // HTTP Basic Authentication
        if (Auth::guard($guard)->guest()) {
            // Basic authentication is not set.
            return response('Unauthorized.', Response::HTTP_UNAUTHORIZED);
        } elseif ($request->session()->get(TwoFactorAuthenticationController::SESSION_KEY) == Auth::guard($guard)->user()->getAuthIdentifier()) {
            return $next($request);
        } else {
            // Duo Authentication
            // Basic authentication is set, but the duo middleware is not set.
            return redirect()->guest('/2fa');
        }
    }
}

並在\App\Http\Kernel中加上auth.duo:

protected $routeMiddleware = [
        'auth'       => \Illuminate\Auth\Middleware\Authenticate::class,
        'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
        'auth.crowd' => Middleware\CrowdAuthentication::class,
        'auth.duo'   => Middleware\DuoTwoFactorAuthentication::class,
        'bindings'   => \Illuminate\Routing\Middleware\SubstituteBindings::class,
        'can'        => \Illuminate\Auth\Middleware\Authorize::class,
        'guest'      => Middleware\RedirectIfAuthenticated::class,
        'throttle'   => \Illuminate\Routing\Middleware\ThrottleRequests::class,
    ];

而後寫上路由就行web.php,這裏是使用auth.basicLaravel自帶的HTTP Basic Authentication(使用Crowd SSO登陸之後再聊):

//Route::group(['middleware' => 'auth.crowd'], function () {
Route::group(['middleware' => 'auth.basic'], function () {
    Route::get('/2fa', 'TwoFactorAuthenticationController@get');
    Route::post('/2fa', 'TwoFactorAuthenticationController@post');

    Route::group(['middleware' => 'auth.duo'], function () {
        Route::get('/duo', function () {
            return 'Duo Authentication';
        });
    });
    
    Route::get('/duo/users', function () {
        return view('users');
    });
    
    Route::get('/duo/accounts', function () {
        return view('accounts');
    });
    // and so on
});

這樣Admin程序後臺路由是http://sentry.app:8888/duo(假設本地配置的host是sentry.app:8888),但須要通過HTTP Basic Authentication這個第一層認證,HTTP Basic Authentication就是根據輸入的username-password來查詢users表中有沒有對應的user,這裏先在users表中造一個,使用Laravel自帶的Auth Scaffold,而後使用Register功能往users表中插入一個user,這樣也方便:

php artisan make:auth

而後輸入http://sentry.app:8888/register往users表插入一個username: user@example.com,password: lx1036

根據官方文檔Duo Web中說明,須要安裝一個package:

composer require duosecurity/duo_php

而後加上控制器TwoFactorAuthenticationController,這裏須要向session中寫入$user_id,這裏使用redis做爲session驅動,記得修改.env中SESSION_DRIVER=redis:

php artisan make:controller TwoFactorAuthenticationController


<?php

namespace App\Http\Controllers;

use Auth;
use Duo\Web;
use Illuminate\Http\Request;
use Illuminate\Http\Response;

class TwoFactorAuthenticationController extends Controller
{
    const SESSION_KEY = 'auth.duo';

    public function get()
    {
        return view('duo.2fa', [
            'host'        => config('services.duo.host'),
            'sig_request' => Web::signRequest(
                config('services.duo.integration_key'),
                config('services.duo.secret_key'),
                config('services.duo.application_key'),
                Auth::user()->getAuthIdentifier()
            ),
            'post_action' => url('2fa'),
        ]);
    }

    public function post(Request $request)
    {
        $user_id = Web::verifyResponse(
            config('services.duo.integration_key'),
            config('services.duo.secret_key'),
            config('services.duo.application_key'),
            $request->input('sig_response')
        );

        if ($user_id == Auth::user()->getAuthIdentifier()) {
            $request->session()->put(self::SESSION_KEY, $user_id);

            return redirect()->intended('/duo');
        } else {
            abort(Response::HTTP_UNAUTHORIZED);
        }
    }
}


// config/services.php
    'duo' => [
        'host'            => env('DUO_HOST'),
        'integration_key' => env('DUO_IKEY'),
        'secret_key'      => env('DUO_SKEY'),
        'application_key' => env('DUO_AKEY'),
    ],

記得在.env文件中寫入DUO_HOST,DUO_IKEY,DUO_SKEY這三個從Web SDK 這個Application中獲得的keys,DUO_AKEY根據官方文檔是我的生成的,這裏選擇Laravel的APP_KEY。

最後按照官方文檔的格式,把view頁面寫上:

// resources/views/duo/2fa.blade.php
@extends('layouts.duo')

@section('content')
    <div id="duo">
        <h2>Two Factor Authentication</h2>
        <iframe id="duo_iframe" frameborder="0">

        </iframe>
        <form method="post" id="duo_form">
            {{csrf_field()}}
        </form>
    </div>
@stop


@section('js')
    <script type="application/javascript" src="{{asset('js/duo/Duo-Web-v2.min.js')}}"></script>
    <script type="application/javascript">
        Duo.init({
            'host'       : "{{$host}}",
            'sig_request': "{{$sig_request}}",
            'post_action': "{{$post_action}}"
        });
    </script>
    <link rel="stylesheet" type="text/css" href="{{asset('css/duo/duo.css')}}">
@endsection

// resources/views/layouts/duo.blade.php
<!DOCTYPE html>
<html lang="zh-cn">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Duo: Two Factor Authentication</title>

    <!-- Bootstrap -->
    <link href="//cdn.bootcss.com/bootstrap/4.0.0-alpha.3/css/bootstrap.min.css" rel="stylesheet">
  </head>
  <body>
    <div class="container">
        <section>
            @yield('content')
        </section>
    </div>

    @yield('js')
    <!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->
    <script src="http://cdn.bootcss.com/jquery/1.11.1/jquery.min.js"></script>
    <!-- Include all compiled plugins (below), or include individual files as needed -->
    <script src="//cdn.bootcss.com/bootstrap/4.0.0-alpha.3/js/bootstrap.min.js"></script>
  </body>
</html>

// public/css/duo/duo.css
#duo_iframe {
    width: 100%;
    min-width: 304px;
    max-width: 620px;
    height: 330px;
}

#duo {
    align-content: center;
    margin: auto;
}

其中,Duo-Web-v2.min.js是duosecurity/duo_php這個package裏就有的,拷貝過來就行。

而後輸入路由http://sentry.app:8888/duo會彈出Basic Authentication Form,輸入剛剛註冊的user@example.com,lx1036實現第一層認證後,再根據中間件DuoTwoFactorAuthentication的return redirect()->guest('/2fa');邏輯就會跳轉到/2fa頁面實現第二層認證:

選擇Send me a Push後,手機端Duo APP就會就會收到Push Notification了,固然前提是手機端Duo已經登陸了。選擇Approve後桌面端程序就自動跳轉到路由http://sentry.app:8888/duo,此次走的中間件DuoTwoFactorAuthentication中邏輯是$request->session()->get(TwoFactorAuthenticationController::SESSION_KEY) == Auth::guard($guard)->user()->getAuthIdentifier(),這樣程序就通過二次認證了,程序就進入登錄後的頁面,這裏只是簡單顯示Duo Authentication

It is working.

有了Duo這個神器,就很安全的實現二次認證了,這裏是展現瞭如何使用Web SDK來保護Web Application,須要編碼,還能夠在Duo後臺配置實現服務器登陸的二次認證,這些就是配置下就行,不須要編碼,固然Duo還有不少其餘集成來實現二次保護。使用這種Modern Security Protection總比粗暴的限制IP訪問來保護安全要高效的多,一切都是爲了自動化,爲了提升生產率。Duo已經在我司RightCapital長時間使用了,用起來還很順手,值得推薦下。

總結:本文主要學習使用Duo這個神器來作Two Factor Authentication,並學習瞭如何使用Web SDK集成進Laravel程序中。之後遇到好的技術再分享下,到時見。

相關文章
相關標籤/搜索