gpt4 book ai didi

angularjs - Symfony2 和 Angular。用户认证

转载 作者:行者123 更新时间:2023-12-04 09:23:47 24 4
gpt4 key购买 nike

我正在开发一个涉及 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;
}

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

但是昨天,我一直在阅读最好使用无状态系统,为此使用一个 JSON token ,就像这个包提供的一样:

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

所以我的问题是这两个选项中哪个更好。

谢谢!

最佳答案

正如我最近使用 Symfony2 和 Angular 完成的身份验证实现一样,经过大量研究后,我最终选择了最佳方式 API-Platform (使用 JSON-LD/Hydra 新词汇来提供 REST-API,而不是我想你使用的 FOSRest)和来自 Angular 前端应用程序的 restangular。

关于无状态,它确实是一个更好的解决方案,但你必须建立你的登录场景来选择最好的技术。

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

您应该首先考虑使用 FOSUser正如@chalasr 建议的那样捆绑。
此外,使用 API-PlatformJWT Bundle from Lexik你需要NelmioCors对于应该出现的跨域错误:

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

HTTPS 协议(protocol)是 api 和 front 之间通信的强制性协议(protocol)!

在下面的示例代码中,我使用了特定的实体映射。
联系实体得到了抽象的通信方式,它得到了电话。稍后我将放置完整的映射和类示例)。

根据您的需要进行调整。

# 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 公有和私有(private)文件( see doc)。

您现在应该能够(例如使用 POSTMAN)发送 api 调用或使用对 http://your_vhost/login_check 的 post 请求生成 token

我们通常在这里完成 Symfony api 部分。做你的测试!

现在,如何从 Angular 处理 api?

这是我们的场景:
  • 通过登录表单,向 Symfony login_check url 发送一个 POST 请求,这将返回一个 JSON Web Token
  • 将该 token 存储在 session /本地存储中
  • 在我们对 header 进行的每个 api 调用中传递这个存储的 token 并访问我们的数据

  • 这是 Angular 部分:

    首先安装了所需的 Angular 全局模块:
    $ 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 启动 Angular 安装:
    $ yo angular

    回答提问:
  • … 吞咽……………….. 否
  • … Sass/指南针… 是
  • … Bootstrap ……….是
  • … 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>

    配置restangular:
    # 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;
    });
    }])
    ;

    配置 Controller :
    # 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 系统。有一天我会写一篇博文,以便更清楚地说明这一点,并有时更新这篇文章。

    我只是希望它有所帮助。

    最好的祝福。

    关于angularjs - Symfony2 和 Angular。用户认证,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/35344563/

    24 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com