Symfony2 和 Angular。用户认证

2024-01-11

我正在开发一个涉及 Symfony2 和 AngularJs 的 Web 应用程序。我对网站中用户身份验证的正确方法有疑问。

我在 API REST(在 Symfony 中内置)中构建了一个函数,该函数通过请求中传递的参数对用户进行身份验证。

/**
 * Hace el login de un usuario
 * 
 * @Rest\View()
 * @Rest\Post("/user/login")
 * @RequestParam(name="mail", nullable=false, description="user email")
 * @RequestParam(name="password", nullable=false, description="user password")
 */
public function userLoginAction(Request $request, ParamFetcher $paramFetcher) {
    $mail = $paramFetcher->get('mail');
    $password = $paramFetcher->get("password");
    $response = [];
    $userManager = $this->get('fos_user.user_manager');
    $factory = $this->get('security.encoder_factory');
    $user = $userManager->findUserByUsernameOrEmail($mail);          
    if (!$user) {
        $response = [
            'error' => 1,
            'data' => 'No existe ese usuario'
        ];
    } else {
        $encoder = $factory->getEncoder($user);
        $ok = ($encoder->isPasswordValid($user->getPassword(),$password,$user->getSalt()));

        if ($ok) {
            $token = new UsernamePasswordToken($user, null, "main", $user->getRoles());
            $this->get("security.context")->setToken($token);
            $event = new InteractiveLoginEvent($request, $token);
            $this->get("event_dispatcher")->dispatch("security.interactive_login", $event);
            if ($user->getType() == 'O4FUser') {
                $url = $this->generateUrl('user_homepage'); 
            } else {
                $url = $this->generateUrl('gym_user_homepage'); 
            }
            $response = [
                'url' => $url
            ];
        } else {
            $response = [
                'error' => 1,
                'data' => 'La contraseña no es correcta'
            ];
        }
    }
    return $response;
}

如您所见,该函数设置了令牌,一切正常。

但昨天,我一直在读到最好使用无状态系统,为此使用 JSON 令牌,如该捆绑包提供的:

https://github.com/lexik/LexikJWTAuthenticationBundle/blob/master/Resources/doc/index.md https://github.com/lexik/LexikJWTAuthenticationBundle/blob/master/Resources/doc/index.md

所以我的问题是这两种选择哪个更好。

Thanks!


正如我最近使用 Symfony2 和 Angular 完成的身份验证实现一样,经过大量研究,我最终选择了最好的方法API平台 https://api-platform.com/(使用 JSON-LD / Hydra 新词汇来提供 REST-API,而不是我想您使用的 FOSRest)和来自 Angular 前端应用程序的 reangular。

关于无状态,确实这是一个更好的解决方案,但您必须构建登录场景才能选择最佳技术。

登录系统和 JWT 并不兼容,两种解决方案都可以使用。在使用 JWT 之前,我对 OAuth 进行了大量研究,这显然实施起来很痛苦,并且需要一个完整的开发团队。 JWT 提供了实现这一目标的最佳且简单的方法。

您应该首先考虑使用FOSUser https://symfony.com/doc/master/bundles/FOSUserBundle/index.html按照@chalasr 的建议捆绑。 另外,使用API平台 https://github.com/dunglas/DunglasApiBundle and Lexik 的 JWT 捆绑包 https://github.com/lexik/LexikJWTAuthenticationBundle/blob/master/Resources/doc/index.md#installation你需要内尔米奥·科尔斯 https://github.com/nelmio/NelmioCorsBundle对于应该出现的跨域错误:

(仔细阅读该捆绑包的文档)

HTTPS 协议是 api 和 front 之间通信的强制协议!

在下面的示例代码中,我使用了特定的实体映射。 联系实体获得了抽象的CommunicationWays,其中获得了电话。稍后我将提供完整的映射和类示例)。

根据您的需求进行调整。

# composer.json

// ...
    "require": {
        // ...
        "friendsofsymfony/user-bundle": "~2.0@dev",
        "lexik/jwt-authentication-bundle": "^1.4",
        "nelmio/cors-bundle": "~1.4",
        "dunglas/api-bundle": "~1.1@beta"
// ...


# app/AppKernel.php

    public function registerBundles()
    {
        $bundles = array(
            // ...
            new Symfony\Bundle\SecurityBundle\SecurityBundle(),
            new FOS\UserBundle\FOSUserBundle(),
            new Lexik\Bundle\JWTAuthenticationBundle\LexikJWTAuthenticationBundle(),
            new Nelmio\CorsBundle\NelmioCorsBundle(),
            new Dunglas\ApiBundle\DunglasApiBundle(),
            // ...
        );

然后更新你的配置:

# app/config/config.yml

imports:
    // ...
    - { resource: security.yml }
// ...
framework:
    // ...
    csrf_protection: ~
    form: ~
    session:
        handler_id: ~
    // ...
fos_user:
    db_driver: orm
    firewall_name: main
    user_class: AppBundle\Entity\User
lexik_jwt_authentication:
    private_key_path: %jwt_private_key_path%
    public_key_path:  %jwt_public_key_path%
    pass_phrase:      %jwt_key_pass_phrase%
    token_ttl:        %jwt_token_ttl%
// ...
dunglas_api:
    title:       "%api_name%"
    description: "%api_description%"
    enable_fos_user: true
nelmio_cors:
    defaults:
        allow_origin:   ["%cors_allow_origin%"]
        allow_methods:  ["POST", "PUT", "GET", "DELETE", "OPTIONS"]
        allow_headers:  ["content-type", "authorization"]
        expose_headers: ["link"]
        max_age:       3600
    paths:
        '^/': ~
// ...

和参数 dist 文件:

parameters:
    database_host:     127.0.0.1
    database_port:     ~
    database_name:     symfony
    database_user:     root
    database_password: ~
    # You should uncomment this if you want use pdo_sqlite
    # database_path: "%kernel.root_dir%/data.db3"

    mailer_transport:  smtp
    mailer_host:       127.0.0.1
    mailer_user:       ~
    mailer_password:   ~

    jwt_private_key_path: %kernel.root_dir%/var/jwt/private.pem
    jwt_public_key_path:  %kernel.root_dir%/var/jwt/public.pem
    jwt_key_pass_phrase : 'test'
    jwt_token_ttl:        86400

    cors_allow_origin: http://localhost:9000

    api_name:          Your API name
    api_description:   The full description of your API

    # A secret key that's used to generate certain security-related tokens
    secret: ThisTokenIsNotSecretSoChangeIt

创建使用 ORM yml 文件扩展 baseUser 的用户类:

# src/AppBundle/Entity/User.php

<?php

namespace AppBundle\Entity;

use Doctrine\ORM\Mapping as ORM;
use FOS\UserBundle\Model\User as BaseUser;

class User extends BaseUser
{
    protected $id;
    protected $username;
    protected $email;
    protected $plainPassword;
    protected $enabled;
    protected $roles;
}

# src/AppBundle/Resources/config/doctrine/User.orm.yml

AppBundle\Entity\User:
    type:  entity
    table: fos_user
    id:
        id:
            type: integer
            generator:
                strategy: AUTO

然后放入 security.yml 配置:

# app/config/security.yml

security:
    encoders:
        FOS\UserBundle\Model\UserInterface: bcrypt

    role_hierarchy:
        ROLE_ADMIN:       ROLE_USER
        ROLE_SUPER_ADMIN: ROLE_ADMIN

    providers:
        fos_userbundle:
            id: fos_user.user_provider.username

    firewalls:
        dev:
            pattern: ^/(_(profiler|wdt)|css|images|js)/
            security: false

        api:
            pattern: ^/api
            stateless: true
            lexik_jwt:
                authorization_header:
                    enabled: true
                    prefix: Bearer
                query_parameter:
                    enabled: true
                    name: bearer
                throw_exceptions: false
                create_entry_point: true

        main:
            pattern: ^/
            provider: fos_userbundle
            stateless: true
            form_login: 
                check_path: /login_check
                username_parameter: username
                password_parameter: password
                success_handler: lexik_jwt_authentication.handler.authentication_success
                failure_handler: lexik_jwt_authentication.handler.authentication_failure
                require_previous_session: false
            logout: true
            anonymous: true


    access_control:
        - { path: ^/api, role: IS_AUTHENTICATED_FULLY }

和 services.yml :

# app/config/services.yml

services:
    // ...
    fos_user.doctrine_registry:
        alias: doctrine

最后是路由文件:

# app/config/routing.yml

api:
    resource: "."
    type:     "api"
    prefix: "/api"

api_login_check:
    path: "/login_check"

此时,composer更新,使用学说控制台命令创建数据库/更新模式,创建fosuser用户并生成JWT Lexik包所需的SSL公共和私有文件(see doc https://github.com/lexik/LexikJWTAuthenticationBundle/blob/master/Resources/doc/index.md#installation).

您应该能够(例如使用 POSTMAN)立即发送 api 调用或使用 post 请求生成令牌http://your_vhost/login_check http://your_vhost/login_check

我们通常已经完成了 Symfony api 部分。做你的测试!

现在,Angular 将如何处理 api?

这是我们的场景:

  1. 通过登录表单,向 Symfony login_check url 发送 POST 请求,该请求将返回 JSON Web Token
  2. 将该令牌存储在会话/本地存储中
  3. 在我们对标头进行的每个 api 调用中传递此存储的令牌并访问我们的数据

这是角度部分:

首先安装所需的角度全局模块:

$ npm install -g yo generator-angular bower
$ npm install -g ruby sass compass less
$ npm install -g grunt-cli karma-cli jshint node-gyp registry-url

使用 yeoman 启动角度安装:

$ yo angular

回答提问:

  • … 咕噜……………….. 没有
  • … Sass/Compass… 是的
  • … 引导程序………. 是的
  • … Bootstrap-Sass。 是的

并取消选中所有其他询问的模块。

安装本地 npm 包:

$ npm install karma jasmine-core grunt-karma karma-jasmine --save-dev
$ npm install phantomjs phantomjs-prebuilt karma-phantomjs-launcher --save-dev

最后是凉亭包:

$ bower install --save lodash#3.10.1
$ bower install --save restangular

打开index.html文件并设置如下:

# app/index.html

<!doctype html>
<html>
  <head>
    <meta charset="utf-8">
    <title></title>
    <meta name="description" content="">
    <meta name="viewport" content="width=device-width">
    <link rel="stylesheet" href="styles/main.css">
  </head>
  <body ng-app="angularApp">
    <div class="container">
    <div ng-include="'views/main.html'" ng-controller="MainCtrl"></div>
    <div ui-view></div>

    <script src="bower_components/jquery/dist/jquery.js"></script>
    <script src="bower_components/angular/angular.js"></script>
    <script src="bower_components/bootstrap-sass-official/assets/javascripts/bootstrap.js"></script>

    <script src="bower_components/restangular/dist/restangular.js"></script>
    <script src="bower_components/lodash/lodash.js"></script>

    <script src="scripts/app.js"></script>
    <script src="scripts/controllers/main.js"></script>
  </body>
</html>

配置矩形:

# app/scripts/app.js

'use strict';

angular
    .module('angularApp', ['restangular'])
    .config(['RestangularProvider', function (RestangularProvider) {
        // URL ENDPOINT TO SET HERE !!!
        RestangularProvider.setBaseUrl('http://your_vhost/api');

        RestangularProvider.setRestangularFields({
            id: '@id'
        });
        RestangularProvider.setSelfLinkAbsoluteUrl(false);

        RestangularProvider.addResponseInterceptor(function (data, operation) {
            function populateHref(data) {
                if (data['@id']) {
                    data.href = data['@id'].substring(1);
                }
            }

            populateHref(data);

            if ('getList' === operation) {
                var collectionResponse = data['hydra:member'];
                collectionResponse.metadata = {};

                angular.forEach(data, function (value, key) {
                    if ('hydra:member' !== key) {
                        collectionResponse.metadata[key] = value;
                    }
                });

                angular.forEach(collectionResponse, function (value) {
                    populateHref(value);
                });

                return collectionResponse;
            }

            return data;
        });
    }])
;

配置控制器:

# app/scripts/controllers/main.js

'use strict';

angular
    .module('angularApp')
    .controller('MainCtrl', function ($scope, $http, $window, Restangular) {
        // fosuser user
        $scope.user = {username: 'johndoe', password: 'test'};

        // var to display login success or related error
        $scope.message = '';

        // In my example, we got contacts and phones
        var contactApi = Restangular.all('contacts');
        var phoneApi = Restangular.all('telephones');

        // This function is launched when page is loaded or after login
        function loadContacts() {
            // get Contacts
            contactApi.getList().then(function (contacts) {
                $scope.contacts = contacts;
            });

            // get Phones (throught abstrat CommunicationWays alias moyensComm)
            phoneApi.getList().then(function (phone) {
                $scope.phone = phone;
            });

            // some vars set to default values
            $scope.newContact = {};
            $scope.newPhone = {};
            $scope.contactSuccess = false;
            $scope.phoneSuccess = false;
            $scope.contactErrorTitle = false;
            $scope.contactErrorDescription = false;
            $scope.phoneErrorTitle = false;
            $scope.phoneErrorDescription = false;

            // contactForm handling
            $scope.createContact = function (form) {
                contactApi.post($scope.newContact).then(function () {
                    // load contacts & phones when a contact is added
                    loadContacts();

                    // show success message
                    $scope.contactSuccess = true;
                    $scope.contactErrorTitle = false;
                    $scope.contactErrorDescription = false;

                    // re-init contact form
                    $scope.newContact = {};
                    form.$setPristine();

                    // manage error handling
                }, function (response) {
                    $scope.contactSuccess = false;
                    $scope.contactErrorTitle = response.data['hydra:title'];
                    $scope.contactErrorDescription = response.data['hydra:description'];
                });
            };

            // Exactly same thing as above, but for phones
            $scope.createPhone = function (form) {
                phoneApi.post($scope.newPhone).then(function () {
                    loadContacts();

                    $scope.phoneSuccess = true;
                    $scope.phoneErrorTitle = false;
                    $scope.phoneErrorDescription = false;

                    $scope.newPhone = {};
                    form.$setPristine();
                }, function (response) {
                    $scope.phoneSuccess = false;
                    $scope.phoneErrorTitle = response.data['hydra:title'];
                    $scope.phoneErrorDescription = response.data['hydra:description'];
                });
            };
        }

        // if a token exists in sessionStorage, we are authenticated !
        if ($window.sessionStorage.token) {
            $scope.isAuthenticated = true;
            loadContacts();
        }

        // login form management
        $scope.submit = function() {
            // login check url to get token
            $http({
                method: 'POST',
                url: 'http://your_vhost/login_check',
                headers: {
                    'Content-Type': 'application/x-www-form-urlencoded'
                },
                data: $.param($scope.user)

                // with success, we store token to sessionStorage
            }).success(function(data) {
                $window.sessionStorage.token = data.token;
                $scope.message = 'Successful Authentication!';
                $scope.isAuthenticated = true;

                // ... and we load data
                loadContacts();

                // with error(s), we update message
            }).error(function() {
                $scope.message = 'Error: Invalid credentials';
                delete $window.sessionStorage.token;
                $scope.isAuthenticated = false;
            });
        };

        // logout management
        $scope.logout = function () {
            $scope.message = '';
            $scope.isAuthenticated = false;
            delete $window.sessionStorage.token;
        };

        // This factory intercepts every request and put token on headers
    }).factory('authInterceptor', function($rootScope, $q, $window) {
    return {
        request: function (config) {
            config.headers = config.headers || {};

            if ($window.sessionStorage.token) {
                config.headers.Authorization = 'Bearer ' + $window.sessionStorage.token;
            }
            return config;
        },
        response: function (response) {
            if (response.status === 401) {
                // if 401 unauthenticated
            }
            return response || $q.when(response);
        }
    };
// call the factory ...
}).config(function ($httpProvider) {
    $httpProvider.interceptors.push('authInterceptor');
});

最后我们需要带有表单的 main.html 文件:

<!—Displays error or success messages-->
<span>{{message}}</span><br><br>

<!—Login/logout form-->
<form ng-show="!isAuthenticated" ng-submit="submit()">
    <label>Login Form:</label><br>
    <input ng-model="user.username" type="text" name="user" placeholder="Username" disabled="true" />
    <input ng-model="user.password" type="password" name="pass" placeholder="Password" disabled="true" />
    <input type="submit" value="Login" />
</form>
<div ng-show="isAuthenticated">
    <a ng-click="logout()" href="">Logout</a>
</div>
<div ui-view ng-show="isAuthenticated"></div>
<br><br>

<!—Displays contacts list-->
<h1 ng-show="isAuthenticated">Liste des Contacts</h1>
<article ng-repeat="contact in contacts" ng-show="isAuthenticated" id="{{ contact['@id'] }}" class="row marketing">
    <h2>{{ contact.nom }}</h2>
    <!—Displays contact phones list-->
    <h3 ng-repeat="moyenComm in contact.moyensComm">Tél : {{ moyenComm.numero }}</h3>
</article><hr>

<!—Create contact form-->
<form name="createContactForm" ng-submit="createContact(createContactForm)" ng-show="isAuthenticated" class="row marketing">
    <h2>Création d'un nouveau contact</h2>
    <!—Displays error / success message on creating contact-->
    <div ng-show="contactSuccess" class="alert alert-success" role="alert">Contact publié.</div>
    <div ng-show="contactErrorTitle" class="alert alert-danger" role="alert">
        <b>{{ contactErrorTitle }}</b><br>
        {{ contactErrorDescription }}
    </div>
    <div class="form-group">
        <input ng-model="newContact.nom" placeholder="Nom" class="form-control">
    </div>
    <button type="submit" class="btn btn-primary">Submit</button>
</form>

<!—Phone form-->
<form name="createPhoneForm" ng-submit="createPhone(createPhoneForm)" ng-show="isAuthenticated" class="row marketing">
    <h2>Création d'un nouveau téléphone</h2>
    <div ng-show="phoneSuccess" class="alert alert-success" role="alert">Téléphone publié.</div>
    <div ng-show="phoneErrorTitle" class="alert alert-danger" role="alert">
        <b>{{ phoneErrorTitle }}</b><br>
        {{ phoneErrorDescription }}
    </div>
    <div class="form-group">
        <input ng-model="newPhone.numero" placeholder="Numéro" class="form-control">
    </div>
    <div class="form-group">
        <label for="contact">Contact</label>
        <!—SelectBox de liste de contacts-->
        <select ng-model="newPhone.contact" ng-options="contact['@id'] as contact.nom for contact in contacts" id="contact"></select>
    </div>
    <button type="submit" class="btn btn-primary">Submit</button>
</form>

好吧,我知道这是很多精简的代码,但是您拥有使用 Symfony 和 Angular 启动完整 api 系统的所有武器。有一天我会写一篇博客文章,以便更清楚地说明这一点,并有时更新这篇文章。

我只是希望它有帮助。

此致。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Symfony2 和 Angular。用户认证 的相关文章

随机推荐

  • openID 的安全性如何?

    这是可用于高度安全信息的东西还是应该被单站点身份验证系统绕过 这可能是一个愚蠢的问题 因为它听起来不安全 但我想要一些建议 OpenID本身的安全性并不亚于传统的用户名 密码登录 显然 您将大部分安全性委托给了提供商 例如暴力破解预防 密码
  • 如何在 OSX 上的终端中关闭 ls 输出的颜色

    my ls无论我是否键入 所有目录的输出颜色都与文件不同ls or bin ls 我没有LS COLOR东西设置在 bashrc或我能找到的相关文件 我该如何转向off这些颜色 我很高兴只是ls F Thanks 正如评论中指出的 OSXl
  • 按依赖顺序编写 Sql Server 数据库中所有视图/函数/过程的脚本

    Sql Server 2008 可能还有大多数其他版本 Management Studio 有一个 生成脚本 选项 理论上可以使用所有对象编写整个数据库的脚本 右键单击 任务 生成脚本 它适用于大多数情况 但是当您使用它为数据库中的所有视图
  • 无法使用 gcloud 将私有 IP (VPC) 添加到新的 Google Cloud SQL 实例

    我一直在尝试创建一个新的 CloudSQL 实例 并从一开始就附加了私有 IP 文档唯一提示我的是添加 VPC 网络 我正在尝试添加默认值 首先 我为我的项目启用正确的 API 服务 gcloud services enable servi
  • 为什么 nil / NULL 块在运行时会导致总线错误?

    我开始大量使用块 很快就注意到 nil 块会导致总线错误 typedef void SimpleBlock void SimpleBlock aBlock nil aBlock bus error 这似乎违背了 Objective C 忽略
  • 无法理解css的position属性

    If p 标签位置是绝对的div连续出现 但在评论绝对位置之后div下降 即使position relative 谁能告诉我为什么会发生这种情况 before after box sizing border box main content
  • 如何使隐式 ScrollViewer 出现在左侧而不是右侧

    我有一个ScrollViewer当列表中有足够的对象时 它会出现在右侧 如何让它出现在左侧
  • 在jshell中创建自定义反馈模式

    从 jshell 中 set Feedback 的文档来看 有以下几种内置模式 verbose normal concise and silent 是否可以打造一种兼具简洁和静音功能的反馈模式 或者我们可以改变上述任何一种模式吗 或者我们可
  • AS3使用Loader加载受htaccess保护的文件

    我正在尝试将网站上托管的外部 SWF 加载到本地 SWF 文件中 外部 SWF 位于使用 htaccess 的密码保护目录中 这是我当前尝试使用的代码 var loaderUrlRequest URLRequest new URLReque
  • jquery .validate() 变量错误消息

    我在此网站上使用多种语言 并希望以不同的语言显示错误 我想知道是否可以在自定义错误消息中使用变量 这是 JavaScript 代码片段 form validate ajax url notification php dataType jso
  • commitAllowingStateLoss() 和 commit() 片段

    我想在网络后台操作后提交一个片段 我在成功的网络操作后调用 commit 但如果活动进入暂停或停止状态 它就会导致应用程序崩溃 并显示 IllegalState 异常 所以我尝试使用 commitAllowingStateLoss 并且它现
  • 将 SWIG 与采用 std::string 作为参数的方法结合使用

    我使用 SWIG 来包装我的 C 类 有些方法有一个const std string 作为参数 SWIG 创建一个名为的类型SWIGTYPE p std string但是 在 C 中调用该方法时 不能仅为此传递普通字符串 下面的示例只是 S
  • 如何将数据附加到 JTA 事务? (或唯一标识它)

    我有一个 getStockQuote 函数 它将从股票市场获取某个符号的当前股票报价 我的目标是在 JTA 事务中 第一次调用 getStockQuote 将获取股票报价 但同一事务中的所有后续调用将重用相同的股票报价 例如 它不会尝试获取
  • 检查javascript中多维数组中的键是否存在

    希望是一个简单的问题 为什么要检查多维数组中是否存在键 a new Array Array a 0 0 1 a 0 1 2 if a 1 2 undefined alert sorry that key doesn t exist else
  • assembleDebug.dependsOn 不起作用

    首先 这不是这个的重复 错误 在项目 app 上找不到属性 assembleDebug https stackoverflow com q 38547400 770467 问题 自从更新到Android Studio 2 2 gradle插
  • 在 SQL Server 中编写架构脚本

    我想在 SQL Server 数据库中创建一个架构 数据库中还有一堆其他脚本 我可以编写现有架构的脚本吗 我的意思是 假设我们有一张桌子 我们可以 编写一个脚本CREATE TABLE脚本 我们可以编写一个创建架构的脚本吗 谢谢你们的期待
  • 良好的Java进程控制库[关闭]

    Closed 这个问题正在寻求书籍 工具 软件库等的推荐 不满足堆栈溢出指南 help closed questions 目前不接受答案 Java 进程控制是出了名的糟糕 主要是由于 Java VM JDK 类 例如 java lang P
  • 在 bash 脚本中使用 find 命令

    我刚刚开始使用 bash 脚本 并且需要对多种文件类型使用 find 命令 list find home user Desktop name pdf 此代码适用于 pdf 类型 但我想同时搜索多种文件类型 例如 txt 或 bmp 您有什么
  • 在无效 Syskeypress 上禁用 MessageBeep

    简单的问题 如果用户按 Alt Whatever 并且没有与之关联的热键 通常程序会产生 MessageBeep 我可以调用哪些 API 函数来避免这种情况 处理 WM KEYDOWN WM KEYUP WM SYSKEYDOWN 和 WM
  • Symfony2 和 Angular。用户认证

    我正在开发一个涉及 Symfony2 和 AngularJs 的 Web 应用程序 我对网站中用户身份验证的正确方法有疑问 我在 API REST 在 Symfony 中内置 中构建了一个函数 该函数通过请求中传递的参数对用户进行身份验证