abc-da-construcao/autenticacao-package

v1.10.1 2022-09-23 17:13 UTC

README

Laravel 和 Lumen 资源包,用于辅助实现全局认证系统 (SAG)。

此 Lumen 和 Laravel 包提供定制的认证/授权驱动程序、中间件、Facade 和命令,以便在 ABC 标准中使用全局认证系统。



安装

使用以下命令。

composer require abc-da-construcao/autenticacao-package

配置

通用

根据 SAG 注册信息,填写项目 .env 文件中的 APP_NAMEAPP_KEY 密钥。

APP_NAME="api_pedidos-production"
APP_KEY=Gb4E7xqR74Pat9gefb7nidcWFZNW8S66

Laravel

打开 config/auth.php 文件,将认证驱动程序更改为 jwt 并注释掉相应的 provider 行。

//...
'guards' => [
    // ...
    'sag' => [
        'driver' => 'sag-jwt'
    ],
]
//...

Lumen

将以下文件复制到项目的 config 目录。如果不存在,请创建该目录。

vendor/laravel/lumen-framework/config/auth.php

打开 config/auth.php 文件,将认证驱动程序更改为 jwt

'guards' => [
    // ...
    'sag' => ['driver' => 'sag-jwt'],
]

确保在 bootstrap/app.php 文件中存在以下配置。

/*
|--------------------------------------------------------------------------
| Create The Application
|--------------------------------------------------------------------------
|
| Here we will load the environment and create the application instance
| that serves as the central piece of this framework. We'll use this
| application as an "IoC" container and router for this framework.
|
*/

// ...
$app->withFacades();
// ...

/*
|--------------------------------------------------------------------------
| Register Config Files
|--------------------------------------------------------------------------
|
| Now we will register the "app" configuration file. If the file exists in
| your configuration directory it will be loaded; otherwise, we'll load
| the default version. You may register other files below as needed.
|
*/

// ...
$app->configure('auth');
// ...

/*
|--------------------------------------------------------------------------
| Register Middleware
|--------------------------------------------------------------------------
|
| Next, we will register the middleware with the application. These can
| be global middleware that run before and after each request into a
| route or middleware that'll be assigned to some specific routes.
|
*/

// ...
$app->routeMiddleware([
    'auth' => App\Http\Middleware\Authenticate::class,
]);
// ...

/*
|--------------------------------------------------------------------------
| Register Service Providers
|--------------------------------------------------------------------------
|
| Here we will register all of the application's service providers which
| are used to bind services into the container. Service providers are
| totally optional, so you are not required to uncomment this line.
|
*/

// ...
// $app->register(App\Providers\AuthServiceProvider::class);
$app->register(AbcDaConstrucao\AutenticacaoPackage\Providers\AuthServiceProvider::class);
// ...

保护路由

尽管具有与标准路由保护相同的特性,但该包不仅验证 JWT 令牌是否有效,还验证用户在应用中的权限,同时执行 ACL 功能。

认证保护的路由组示例。

// Lumen
$router->group(['middleware' => ['auth:sag', 'sag-acl']], function () use ($router) {
    // Routes
});

// Laravel API
Route::middleware(['auth:sag', 'sag-acl'])->group(function () {
    // Routes
});

认证

认证辅助方法。

Http::loginRequest($username, $base64_password).

Lumen API 示例

<?php

use AbcDaConstrucao\AutenticacaoPackage\Facades\Http;
use Illuminate\Http\Request;

$router->post('/login', function (Request $request) {
    // A senha deve ser enviada com base64 encode para API de Autenticação. 
    $response = Http::loginRequest($request->username, base64_encode($request->password));

    // Devolve o token caso status 200 ou o erro específico 
    // Ver abaixo resultado esperado do $response.
    return response()->json($response['data'], $response['status']);
});

Laravel/Lumen 前端示例

如果存在 Illuminate\Session\SessionManager 类,则 Http::loginRequest() 方法还会在会话中保存令牌,简化令牌处理并保持用户在令牌有效期间登录。

<?php

use AbcDaConstrucao\AutenticacaoPackage\Facades\Http;
use Illuminate\Http\Request;

Route::post('/login', function (Request $request) {
    // A senha deve ser enviada com base64 encode para API de Autenticação.
    $response = Http::loginRequest($request->username, base64_encode($request->password));

    // token obtido e salvo em sessão.
    // Redireciona o usuário a página desejada.
    if ($response['status'] == 200) {
        return redirect()->route('home');
    }

    // Se o token não for emitido retorna o usuário a página de login com os erros.
    return back()->with('errors', $response['data']['message']);
});

之后,可以通过以下方式访问令牌并在后续请求中将其传递给后端。

use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\Session;

$token = Session::get(Config::get('sag.session.token'));
$tokenType = Session::get(Config::get('sag.session.token_type'));
$tokenValidate = Session::get(Config::get('sag.session.token_validate'));

$response 中的预期结果。

// statuscode 200
[
    "status" => 200,
    "data" => [
        "token_tipo" => "Bearer",
        "token_validade" =>  "2022-04-28T17:49:21-03:00",
        "token" => "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwOlwvXC9zYWctc2VydmVyLmxvY2FsXC9hdXRoXC9sb2dpbiIsImlhdCI6MTY1MTE3NTIzOCwiZXhwIjoxNjUxMTc4ODM4LCJqaXQiOiJqd3RfNSIsInN1YiI6NX0.aSYc0iMkPzVb2EluK7rtXwnkjfv0TdjFFFHuSvd7VMQ",
        "payload" => "eyJpZCI6NSwibmFtZSI6IlRhbGxlcyBHYXplbCIsInVzZXJuYW1lIjoidGFsbGVzIiwiZW1haWwiOiJ0YWxsZXMuZ2F6ZWxAYWJjZGFjb25zdHJ1Y2FvLmNvbS5iciIsImVtYWlsX3ZlcmlmaWVkX2F0IjpudWxsLCJwYXNzd29yZCI6IiQyeSQxMCRnTGF0dk9zcmYwb25PUElodzV1N2hlRTFlTXNZb1Bta3M1cU5EMVdpc0x6bmZnSkdYNHUuZSIsImFjdGl2ZSI6MSwicm9vdCI6MCwiZXhwaXJlIjoxLCJjcmVhdGVkX2J5IjoxLCJ1cGRhdGVkX2J5IjoxLCJyZW1lbWJlcl90b2tlbiI6bnVsbCwiY3JlYXRlZF9hdCI6IjIwMjItMDQtMjggMTU6MDI6NDQiLCJ1cGRhdGVkX2F0IjoiMjAyMi0wNC0yOCAxNTowMzowMyIsImFwcHMiOlt7ImlkIjoxLCJuYW1lIjoiYXBpX3BlZGlkb3Nfc2VydmVyLXByb2R1Y3Rpb24iLCJ1cmwiOiJodHRwOlwvXC9sb2NhbGhvc3Q6ODAwMSIsImFjdGl2ZSI6MSwic3VwZXJfYWRtaW4iOjAsImNyZWF0ZWRfYnkiOjEsInVwZGF0ZWRfYnkiOjEsImNyZWF0ZWRfYXQiOiIyMDIyLTA0LTI4IDEwOjM4OjU2IiwidXBkYXRlZF9hdCI6IjIwMjItMDQtMjggMTA6Mzg6NTYiLCJncm91cHMiOlt7ImlkIjoyLCJhcHBfaWQiOjEsIm5hbWUiOiJGQVJNIiwiZGVzY3JpcHRpb24iOiJHcnVwbyBkbyBSYW1vbiIsImFjdGl2ZSI6MSwiY3JlYXRlZF9ieSI6MSwidXBkYXRlZF9ieSI6MSwiY3JlYXRlZF9hdCI6IjIwMjItMDQtMjggMTU6MDI6MDciLCJ1cGRhdGVkX2F0IjoiMjAyMi0wNC0yOCAxNTowMjowNyIsInBlcm1pc3Npb25zIjpbeyJhcHBfaWQiOjEsImdyb3VwX2lkIjoyLCJpZCI6NCwibWV0aG9kIjoiUE9TVCIsInVyaSI6IlwvbG9nb3V0IiwibmFtZSI6ImxvZ291dCIsInB1YmxpYyI6MH0seyJhcHBfaWQiOjEsImdyb3VwX2lkIjoyLCJpZCI6MywibWV0aG9kIjoiR0VUIiwidXJpIjoiXC9wcm9maWxlIiwibmFtZSI6InByb2ZpbGUiLCJwdWJsaWMiOjB9XX1dLCJyb3V0ZXMiOlt7ImlkIjoxLCJhcHBfaWQiOjEsIm1ldGhvZCI6IlBPU1QiLCJ1cmkiOiJcL2xvZ2luIiwibmFtZSI6ImxvZ2luIiwicHVibGljIjoxfSx7ImlkIjoyLCJhcHBfaWQiOjEsIm1ldGhvZCI6IkdFVCIsInVyaSI6Ilwvcm90YS1wdWJsaWNhIiwibmFtZSI6InJvdGEtcHVibGljYSIsInB1YmxpYyI6MX0seyJpZCI6MywiYXBwX2lkIjoxLCJtZXRob2QiOiJHRVQiLCJ1cmkiOiJcL3Byb2ZpbGUiLCJuYW1lIjoicHJvZmlsZSIsInB1YmxpYyI6MH0seyJpZCI6NCwiYXBwX2lkIjoxLCJtZXRob2QiOiJQT1NUIiwidXJpIjoiXC9sb2dvdXQiLCJuYW1lIjoibG9nb3V0IiwicHVibGljIjowfSx7ImlkIjo1LCJhcHBfaWQiOjEsIm1ldGhvZCI6IkdFVCIsInVyaSI6IlwvIiwibmFtZSI6ImhvbWUiLCJwdWJsaWMiOjB9XX0seyJpZCI6MywibmFtZSI6ImFwaV9wZWRpZG9zX2Zyb250LXByb2R1Y3Rpb24iLCJ1cmwiOiJodHRwOlwvXC9sb2NhbGhvc3Q6ODAwMiIsImFjdGl2ZSI6MSwic3VwZXJfYWRtaW4iOjAsImNyZWF0ZWRfYnkiOjEsInVwZGF0ZWRfYnkiOjEsImNyZWF0ZWRfYXQiOiIyMDIyLTA0LTI4IDE1OjMyOjI5IiwidXBkYXRlZF9hdCI6IjIwMjItMDQtMjggMTU6MzI6MjkiLCJncm91cHMiOlt7ImlkIjozLCJhcHBfaWQiOjMsIm5hbWUiOiJGQVJNIiwiZGVzY3JpcHRpb24iOm51bGwsImFjdGl2ZSI6MSwiY3JlYXRlZF9ieSI6MSwidXBkYXRlZF9ieSI6MSwiY3JlYXRlZF9hdCI6IjIwMjItMDQtMjggMTU6MzM6NDMiLCJ1cGRhdGVkX2F0IjoiMjAyMi0wNC0yOCAxNTozMzo0MyIsInBlcm1pc3Npb25zIjpbeyJhcHBfaWQiOjMsImdyb3VwX2lkIjozLCJpZCI6MTEsIm1ldGhvZCI6IkdFVHxIRUFEIiwidXJpIjoiXC9hcGlcL2hvbWUiLCJuYW1lIjoiYXBpLmhvbWUiLCJwdWJsaWMiOjB9LHsiYXBwX2lkIjozLCJncm91cF9pZCI6MywiaWQiOjksIm1ldGhvZCI6IlBPU1QiLCJ1cmkiOiJcL2FwaVwvbG9nb3V0IiwibmFtZSI6ImFwaS5sb2dvdXQiLCJwdWJsaWMiOjB9LHsiYXBwX2lkIjozLCJncm91cF9pZCI6MywiaWQiOjEwLCJtZXRob2QiOiJHRVR8SEVBRCIsInVyaSI6IlwvYXBpXC9wcm9maWxlIiwibmFtZSI6ImFwaS5wcm9maWxlIiwicHVibGljIjowfSx7ImFwcF9pZCI6MywiZ3JvdXBfaWQiOjMsImlkIjoxNSwibWV0aG9kIjoiR0VUfEhFQUQiLCJ1cmkiOiJcL2hvbWUiLCJuYW1lIjoiaG9tZSIsInB1YmxpYyI6MH0seyJhcHBfaWQiOjMsImdyb3VwX2lkIjozLCJpZCI6MTYsIm1ldGhvZCI6IkdFVHxIRUFEIiwidXJpIjoiXC9sb2dvdXQiLCJuYW1lIjoibG9nb3V0IiwicHVibGljIjowfV19XSwicm91dGVzIjpbeyJpZCI6NiwiYXBwX2lkIjozLCJtZXRob2QiOiJHRVR8SEVBRCIsInVyaSI6Ilwvc2FuY3R1bVwvY3NyZi1jb29raWUiLCJuYW1lIjpudWxsLCJwdWJsaWMiOjF9LHsiaWQiOjcsImFwcF9pZCI6MywibWV0aG9kIjoiR0VUfEhFQUQiLCJ1cmkiOiJcL2FwaVwvcHVibGljYSIsIm5hbWUiOiJhcGkucHVibGljYSIsInB1YmxpYyI6MX0seyJpZCI6OCwiYXBwX2lkIjozLCJtZXRob2QiOiJQT1NUIiwidXJpIjoiXC9hcGlcL2xvZ2luIiwibmFtZSI6ImFwaS5sb2dpbiIsInB1YmxpYyI6MX0seyJpZCI6OSwiYXBwX2lkIjozLCJtZXRob2QiOiJQT1NUIiwidXJpIjoiXC9hcGlcL2xvZ291dCIsIm5hbWUiOiJhcGkubG9nb3V0IiwicHVibGljIjowfSx7ImlkIjoxMCwiYXBwX2lkIjozLCJtZXRob2QiOiJHRVR8SEVBRCIsInVyaSI6IlwvYXBpXC9wcm9maWxlIiwibmFtZSI6ImFwaS5wcm9maWxlIiwicHVibGljIjowfSx7ImlkIjoxMSwiYXBwX2lkIjozLCJtZXRob2QiOiJHRVR8SEVBRCIsInVyaSI6IlwvYXBpXC9ob21lIiwibmFtZSI6ImFwaS5ob21lIiwicHVibGljIjowfSx7ImlkIjoxMiwiYXBwX2lkIjozLCJtZXRob2QiOiJHRVR8SEVBRCIsInVyaSI6IlwvIiwibmFtZSI6InN0YXJ0IiwicHVibGljIjoxfSx7ImlkIjoxMywiYXBwX2lkIjozLCJtZXRob2QiOiJHRVR8SEVBRCIsInVyaSI6IlwvbG9naW4iLCJuYW1lIjoibG9naW4iLCJwdWJsaWMiOjF9LHsiaWQiOjE0LCJhcHBfaWQiOjMsIm1ldGhvZCI6IlBPU1QiLCJ1cmkiOiJcL2xvZ2luIiwibmFtZSI6InBvc3QubG9naW4iLCJwdWJsaWMiOjF9LHsiaWQiOjE1LCJhcHBfaWQiOjMsIm1ldGhvZCI6IkdFVHxIRUFEIiwidXJpIjoiXC9ob21lIiwibmFtZSI6ImhvbWUiLCJwdWJsaWMiOjB9LHsiaWQiOjE2LCJhcHBfaWQiOjMsIm1ldGhvZCI6IkdFVHxIRUFEIiwidXJpIjoiXC9sb2dvdXQiLCJuYW1lIjoibG9nb3V0IiwicHVibGljIjowfV19XX0="
    ]
]

// statuscode 401
[
  "status" => 401,
  "data" => [
    "message" => "Credenciais inválidas."
  ]
]

// statuscode 401
[
  "status" => 401,
  "data" => [
    "message" => "Usuário desativado."
  ]
]

// statuscode 422
[
  "status" => 422,
  "data" => [
    "message" => "The given data was invalid."
    "errors" => [
        "username" => "O campo username é obrigatório",
        "password" => "O campo password é obrigatório"
    ]
  ]
]

用户数据访问方法。

认证后,用户数据将在 Laravel 或 Lumen 的 Auth Facade 中可用。请注意,如果不是默认守卫,则需要指定守卫。访问示例。

use Illuminate\Support\Facades\Auth;

$user = Auth::guard('sag')->user();

dd($user->toArray());
// result
[
  'id' => 3,
  'name' => 'Nome Sobrenome',
  'username' => 'nome.sobrenome',
  'email' => 'nome.sobrenome@abcdaconstrucao.com.br',
  'email_verified_at' => '2022-02-09 18:04:12',
  'active' => '1',
  'root' => '0',
  'expire' => '1',
  'created_by' => '1',
  'updated_by' => '1',
  'created_at' => '2022-02-09 21:04:12',
  'updated_at' => '2022-02-09 21:04:12',
  'apps' => [
    0 => [
      'id' => 1,
      'name' => 'API_PEDIDOS_SERVER-DEV',
      'url' => 'https://:3000',
      'created_by' => '1',
      'updated_by' => '1',
      'active' => '1',
      'created_at' => '2022-02-09 21:04:13',
      'updated_at' => '2022-02-09 21:04:13',
      'super_admin' => '1',
      'groups' => [
        0 => [
          'id' => 1,
          'app_id' => '1',
          'name' => 'Farming',
          'description' => 'Usuários do grupo Farming API_PEDIDOS_SERVER-DEV',
          'active' => '1',
          'created_by' => '3',
          'updated_by' => '3',
          'created_at' => '2022-02-09 21:04:13',
          'updated_at' => '2022-02-09 21:04:13',
          'permissions' => [
            0 => [
              'id' => 10,
              'app_id' => '1',
              'method' => 'GET',
              'uri' => '/',
              'name' => 'home',
              'public' => '0',
            ],
          ],
        ],
      ],
    ],
    1 => [
      'id' => 2,
      'name' => 'API_PEDIDOS_FRONT-DEV',
      'url' => 'https://:3100',
      'created_by' => '1',
      'updated_by' => '1',
      'active' => '1',
      'created_at' => '2022-02-09 21:04:13',
      'updated_at' => '2022-02-09 21:04:13',
      'super_admin' => '0',
      'groups' => [
        0 => [
          'id' => 2,
          'app_id' => '2',
          'name' => 'Farming',
          'description' => 'Usuários do grupo Farming API_PEDIDOS_FRONT-DEV',
          'active' => '1',
          'created_by' => '3',
          'updated_by' => '3',
          'created_at' => '2022-02-09 21:04:13',
          'updated_at' => '2022-02-09 21:04:13',
          'permissions' => [
            0 => [
              'id' => 4,
              'app_id' => '2',
              'method' => 'GET|HEAD',
              'uri' => '/home',
              'name' => 'home',
              'public' => '0',
            ],
          ],
        ],
      ],
    ],
  ],
]

为登录用户提供补充数据。

如果您的应用程序需要为登录用户添加更多字段,只需在配置中指定一个实现了接口 \AbcDaConstrucao\AutenticacaoPackage\Contracts\MergeLocalUserInterface 的类。该类必须包含一个名为 getUserFromMerge(int $abcUserId) 的方法,该方法返回一个数组,该数组将与登录用户的数据合并。该方法接收登录用户的 id 作为参数,以方便与本地数据关联。示例实现。

<?php

namespace App\Repositories;

use AbcDaConstrucao\AutenticacaoPackage\Contracts\MergeLocalUserInterface;

class UserRepository implements MergeLocalUserInterface
{
    /**
     * @param int $abcUserId
     * @return array
     */
    public function getUserFromMerge(int $abcUserId)
    {
        $userLocal = User::getByAbcId($abcUserId);

        return [
            'cpf' => $userLocal->cpf
        ];
    }
}

打开 .env 文件并在下面的键中添加类的命名空间。

USER_LOCAL_CLASS=\App\Repositories\UserRepository

现在访问 Auth 面板时,用户的新键将可访问。

[
  'id' => 3,
  'name' => 'Nome Sobrenome',
  'username' => 'nome.sobrenome',
  'email' => 'nome.sobrenome@abcdaconstrucao.com.br',
  'email_verified_at' => '2022-02-09 18:04:12',
  'active' => '1',
  'root' => '0',
  'created_by' => '1',
  'updated_by' => '1',
  'created_at' => '2022-02-09 21:04:12',
  'updated_at' => '2022-02-09 21:04:12',
  'apps' => [...],
  'cpf' => '03614568953' // dado adicionado pela aplicação local
]

辅助注销方法

Http::logoutRequest($tokenTipo, $token); 在所有应用程序的 SAG 中使令牌无效。
JWT::forgetToken(); 仅在当前应用程序的缓存中删除令牌,但不在 SAG 中使令牌无效。其他应用程序将继续登录。

API 应用程序

全局注销

use AbcDaConstrucao\AutenticacaoPackage\Facades\Http;

$router->post('/logout', ['as' => 'logout', function (Request $request) {
    $header = $request->header('Authorization');
    $token = explode(' ', $header);
    $response = Http::logoutRequest($token[0], $token[1]);
    
    return response()->json($response['data'], $response['status']);
}]);

对于 Laravel/Lumen 前端应用程序

全局注销

use AbcDaConstrucao\AutenticacaoPackage\Facades\Http;

Route::post('/logout', ['as' => 'logout', function (Request $request) {

    $response = Http::logoutRequest();

    // Redireciona a página desejada.
    if ($response['status'] == 200) {
        return redirect()->route('login');
    }
    
    // Retorna erro caso exista.
    return back()->with('error', $response['data']);
}]);

预期结果在 $response。

[
    'status' => 200,
    'data' => [
        'message' => 'Desconectado com sucesso.'
    ]
]

本地应用程序注销

use AbcDaConstrucao\AutenticacaoPackage\Facades\JWT;

Route::post('/logout', ['as' => 'logout', function (Request $request) {

    JWT::forgetToken();
    
    return redirect()->route('login');
}]);

辅助验证访问权限方法

ACL::hasRouteAccess(string $routeNameOrUri) 方法接收一个路由的 名称uri,并返回一个布尔值,表示当前用户是否有访问权限。这可以在多种使用情况下应用。

// Lumen - routes/web.php
$router->get('/api/profile', ['as' => 'api.profile', function (Request $request) use ($router) {
    // ...
}]);

use AbcDaConstrucao\AutenticacaoPackage\Facades\ACL;
// Nome da rota.
ACL::hasRouteAccess('api.profile');

// URI da rota
ACL::hasRouteAccess('/api/profile');

授权

同步应用程序路由与认证 API

在创建或更新应用程序的路由后,必须使用同步命令。

php artisan abc-sag:sync-routes

可选地,可以在代码的任何位置使用辅助器。

use AbcDaConstrucao\AutenticacaoPackage\Facades\ACL;

ACL::syncRoutes();