文档章节

基于 Laravel (5.1) & Ember.js (1.13.0) 的用户授权系统

Lee的白板报
 Lee的白板报
发布于 2015/08/16 16:51
字数 1456
阅读 564
收藏 5

Laravel 本身提供了完整的用户授权解决方案,对于由 PHP 驱动的多页面应用,Laravel 能够完美解决用户授权问题。但是在 SPA 中,laravel 退化成一个 API server,页面路由和表单提交完全由前端框架控制,此时面临2个问题:

  1. 如何在前端实现页面访问权限控制?

  2. 如何对 ajax 请求做授权?


如何在前端实现页面访问权限控制?

Ember.js 1.13.0 没有提供 authentication 功能,我使用了一个名为 ember-simple-auth 的第三方扩展。这是它的 Github 主页:

https://github.com/simplabs/ember-simple-auth

首先在你的 ember-cli 项目根目录下安装该扩展:

ember install ember-cli-simple-auth

然后在 ember/config/environment.js 文件中对其进行配置,具体的配置选项在文档中有详细说明,我的配置如下:

// ember/config/environment.js

ENV['simple-auth'] = {
    authorizer: 'authorizer:custom'    //我使用了一个自定义的授权模块
};

Ember-simple-auth 定义了一系列 mixin 类,只要你的 route 继承了某个 mixin, 就获得了它预定义的某些行为或功能。例如,我的 ember/app/routes/application.js 内容如下:

// ember/app/routes/application.js 

import ApplicationRouteMixin from 'simple-auth/mixins/application-route-mixin';

export default Ember.Route.extend(ApplicationRouteMixin, {
    actions: {
        invalidateSession: function() {
            this.get('session').invalidate();
        }
    }
});

application-route-mixin 已经预定义好了一系列 actions 方法。当 session 上的事件被触发时,对应的 action 将被调用来处理该事件。你也可以在 ember/app/routes/application.js 自己的 action 中覆盖这些方法(ember-simple-auth 会在本地 localStorage 中维护一个 session 对象,它保存着前端产生的所有授权信息)。

然后,在只能由已授权用户访问的页面路由中添加 authenticated-route-mixin:

// ember/app/routes/user.js 

import AuthenticatedRouteMixin from 'simple-auth/mixins/authenticated-route-mixin';

export default Ember.Route.extend(AuthenticatedRouteMixin,{
    model: function(params) {
        return this.store.find('user',params.user_id);
    }
});

authenticated-route-mixin 保证了只有授权用户才能访问 /user。如果未授权,则默认重定向到 /login 。所以在 ember/app/routes/login.js 中需要添加 unauthenticated-route-mixin :

// ember/app/routes/login.js 

import UnauthenticatedRouteMixin from 'simple-auth/mixins/unauthenticated-route-mixin';

export default Ember.Route.extend(UnauthenticatedRouteMixin);

unauthenticated-route-mixin 保证该路径不需要授权也能访问,这对于 /login 是合理的。


如何对 ajax 请求做授权?

自定义 authenticator : ember/app/authenticators/custom.js

// ember/app/authenticators/custom.js

import Base from 'simple-auth/authenticators/base';

export default Base.extend({

    /**
     * Check auth state of frontend
     *
     * @param data (传入session包含的数据)
     * @returns {ES6Promise.Promise}
     */
    restore: function(data) {
        return new Ember.RSVP.Promise(function(resolve, reject)
        {
            if ( data.is_login ){
                resolve(data);
            }
            else{
                reject();
            }
        });
    },

    /**
     * Permission to login by frontend
     *
     * @param obj credentials
     * @returns {ES6Promise.Promise}
     */
    authenticate: function(credentials) {

        var authUrl = credentials.isLogin ? '/auth/login' : '/auth/register'

        return new Ember.RSVP.Promise(function(resolve, reject) {

            Ember.$.ajax({

                url:  authUrl,
                type: 'POST',
                data: { email: credentials.identification, password: credentials.password }

            }).then(function(response) {

                if(response.login === 'success'){
                    resolve({ is_login : true });
                }

            }, function(xhr, status, error) {

                reject(xhr.responseText);

            });
        });
    },

    /**
     * Permission to logout by frontend
     *
     * @returns {ES6Promise.Promise}
     */
    invalidate: function() {

        return new Ember.RSVP.Promise(function(resolve) {

            Ember.$.ajax({

                url: '/auth/logout',
                type: 'GET'

            }).then(function(response) {

                if(response.logout === 'success'){
                    resolve();
                }
            });
        });
    }
});

restore, authenticate, invalidate 3个函数分别用来获取授权,进行授权,取消授权。

自定义 authorizer : ember/app/authorizers/custom.js

// ember/app/authorizers/custom.js

import Base from 'simple-auth/authorizers/base';

export default Base.extend({

    authorize: function(jqXHR, requestOptions)
    {
        var _this = this;

        Ember.$.ajaxSetup({

            headers:
            {
                'X-XSRF-TOKEN': Ember.$.cookie('XSRF-TOKEN')    // 防止跨域攻击
            },

            complete : function(response, state)
            {
                // 检查服务器的授权状态
                if(response.status===403 && _this.get('session').isAuthenticated)  
                {
                    _this.get('session').invalidate();
                }
            }
        });
    }
});

authorize 函数做了两件事:

  1. 为每一个 ajax 请求添加 'X-XSRF-TOKEN' header

  2. 检查服务器返回的授权状态,并做处理

具体来讲:

header 内容是 laravel 所设置的 'XSRF-TOKEN' cookie 的值,laravel 会尝试从每一个请求中读取 header('X-XSRF-TOKEN'), 并检验 token 的值是否合法,如果检验通过,则认为这是一个安全的请求(该功能在 laravel/app/Http/Middleware/VerifyCsrfToken.php 中实现)。

然后,在 laravel 新建一个中间件(Middleware) ,我把它命名为 VerifyAuth:

<?php

// laravel/app/Http/Middleware/VerifyAuth.php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Contracts\Auth\Guard;

class VerifyAuth
{
    protected $include = ['api/*'];    // 需要做权限验证的 URL

    protected $auth;

    public function __construct(Guard $auth)
    {
        $this->auth = $auth;
    }

    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @abort  403
     * @return  mixed
     */
    public function handle($request, Closure $next)
    {
        if( $this->shouldPassThrough($request) || $this->auth->check() )
        {
            return $next($request);
        }

        abort(403, 'Unauthorized action.');     //抛出异常,由前端捕捉并处理
    }


    /**
     * Determine if the request has a URI that should pass through auth verification.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return bool
     */
    protected function shouldPassThrough($request)
    {
        foreach ($this->include as $include) {
            if ($request->is($include)) {
                return false;
            }
        }

        return true;
    }
}

它只对 API 请求做权限验证,因为 AUTH 请求是对权限的操作,而除此之外的其他请求都会作为无效请求重新路由给前端,或者抛出错误。如果一个请求是未被授权的,服务器抛出 403 错误提醒前端需要用户登录或者注册。

最后,在 laravel\app\Http\Controllers\Auth\AuthController.php 中实现所有的授权逻辑:

<?php

namespace App\Http\Controllers\Auth;

use App\User;
use Validator;
use Response;
use Auth;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\ThrottlesLogins;
use Illuminate\Foundation\Auth\AuthenticatesAndRegistersUsers;

class AuthController extends Controller
{
    use AuthenticatesAndRegistersUsers, ThrottlesLogins;

    protected $remember = true;    // 是否长期记住已登录的用户

    public function __construct()
    {
        $this->middleware('guest', ['except' => 'getLogout']);
    }

    public function postLogin(Request $credentials)    // 登录
    {
        return $this->logUserIn($credentials);
    }


    public function getLogout()    // 登出
    {
        Auth::logout();
        return Response::json(['logout'=>'success']);
    }


    public function postRegister(Request $credentials)    // 创建并注册新用户
    {
        $newUser = new User;
    
        $newUser->email = $credentials['email'];
        $newUser->password = bcrypt($credentials['password']);
    
        $newUser->save();
    
        return $this->logUserIn($credentials);
    }
    
    
    protected function logUserIn(Request $credentials)    // 实现用户登录
    {
        $loginData = ['email' => $credentials['email'], 'password' => $credentials['password']];
    
        if ( Auth::attempt($loginData, $this->remember) )
        {
            return Response::json(['login'=>'success']);
        }
        else
        {
            return Response::json(['login'=>'failed']);
        }
    }
}


总结

设置页面访问权限能防止未授权用户访问不属于他的页面,但总归前端是完全暴露给用户的,所以用户的授权状态必须由服务器维护。前端一方面为每个 ajax 请求添加防止跨域攻击的 token, 另一方面当每个请求返回后检查 http status code 是否为 403 权限错误,如果是,则重定向到登录页要求用户取得授权。

© 著作权归作者所有

共有 人打赏支持
Lee的白板报
粉丝 93
博文 31
码字总数 45338
作品 0
高级程序员
laravel (5.1) & Ember.js (1.13.0) 的整合

Lavavel 不必过多介绍了, 作为全世界最流行的PHP框架,有着清晰的架构、完善的文档、丰富的工具等等,能够帮助开发者快速构建多页面web应用程序。 然而,随着技术的发展,web程序的另一面—...

Lee的白板报
2015/08/15
0
1
Ember.js 1.13.0/2.0 Beta 发布

Ember是一个雄心勃勃的Web应用程序,消除了样板,并提供了一个标准的应用程序架构的JavaScript框架。 Ember.js 1.13.0 和 Ember.js 2.0 Beta 发布。Ember.js 1.13.0 至少有 43 名贡献者参与,...

oschina
2015/06/14
1K
5
Flarum 技术架构 Architecture

Flarum 使用三层体系结构。 1、核心层 - core 负责管理数据、事件、实体、命令等。 2、中间层 - API 使用 Web API 来操作 Flarum。 3、最外层 - Web App 使用 Ember.js 展示数据。 这样的分层...

justjavac
2014/12/31
311
0
Laravel 不权威导航

Laravel不权威导航 Hi 这里是Roy整理的Laravel相关索引,希望能帮到大家 ,目前只是一小部分,正在努力整理中... Laravel 文档 Laravel 官方文档 —— 英文 Laravel 中文文档 —— laravel-...

weixingo
2016/02/05
180
0
Laravel 5.1 LTS 发布,支持 PSR-2

Laravel 5.1 是 Laravel 首个 LTS 版本,包含很多新特性。 Laravel 5.1 现在开始会包括 3 年的安全修复。此版本也重新编写了文档,而且添加了实时搜索的自动完成功能。 应用和生成器转换为 ...

raykwok
2015/06/09
2.3K
9

没有更多内容

加载失败,请刷新页面

加载更多

TypeScript基础入门之高级类型的索引类型(Index types)

转发 TypeScript基础入门之高级类型的索引类型(Index types) 高级类型 索引类型(Index types) 使用索引类型,编译器就能够检查使用了动态属性名的代码。 例如,一个常见的JavaScript模式是从...

durban
25分钟前
0
0
利用碎片化时间Get Linux系统

起初,我做着一份与IT毫无关系的工作,每月领着可怜的工资,一直想改变现状,但无从下手,也就是大家熟知的迷茫。我相信,每一个人都会或多或少的经历过迷茫,迷茫每一个选择,迷茫工作或者生...

Linux就该这么学
今天
1
0
图像显示深入学习一:Activity启动过程

一个月左右写了图像显示深入学习之文章开篇文章表明了自己近期的计划,前半年重新学习了opengl es,c++以及Linux的一些知识,觉得是时候开始看图像这一块的源码了,边看边补缺补漏吧。 作为该...

JerryLin123
今天
3
0
给MySQL授权远程访问

putty登录服务器; 登录MySQL: mysql -u root -p 新建远程用户: CREATE USER 'myusername' IDENTIFIED BY 'mypassword'; 授权: grant all on *.* to john@'101.102.103.104' identified by......

sweethome
今天
3
0
在t-io老巢造谣,不过有造谣的就会有反造谣的!

只发当事人的截图,不发表评论,以免有引导嫌疑 PS: 截图是由不同的人发过来的 本人已经不在此微信群 图3:有造谣的,就有反造谣的 图4是2018-09-23的t-io官方群的一个发言小统计,有助于让...

talent-tan
今天
104
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部