Freewind @ Thoughtworks scala java javascript dart 工具 编程实践 月结 math python english [comments admin] [feed]

(2013-01-01) 6 .权限控制

广告: 云梯:翻墙vpn (省10元) 土行孙:科研用户翻墙http proxy (有优惠)

(本文译者是胡波)

权限控制

有了管理员模块后,我们需要对其进行权限控制。幸运的是,Play已经有个现成的Module——那就是Secure。

启用Secure module

要想启用Secure module,编辑配置文件 yabe/conf/application.conf 并重启应用程序:

# Import the secure module
module.secure=${play.path}/modules/secure

重启之后,Play应该会自动加载此Module。

Secure module的配置文件实际上就是Play的配置文件**yabe/conf/routes**,默认配置如下 (当然我们也可以自定义规则):

# Import Secure routes
*       /                                       module:secure

#### 有权限的访问Admin Module

Secure Module提供了一个Secure Controller的基类,继承它的子类都可以设置有权限的访问。但是Java只允许单重继承,如果你还想继承其他父类就比较麻烦。

出了继承以为,Secure还提供了一个标注@With来限定哪些Controller在调用时触发相应的安全机制,请看下述代码:

package controllers;

import play.*;
import play.mvc.*;

@With(Secure.class)
public class Posts extends CRUD {    
}

对于Comment,User以及Tag的Controller,我们都可以这样设置。

现在,如果你直接访问Admin,会自动跳转到一个登录界面:

[![image](/user_images/1334-1.png "image")](/user_images/1334-1.png)

实际,你现在可以输入任何用户名和密码,后台暂时没有任何验证。

#### 自定义验证处理

Play有内置的验证类Secure.Security类,继承后即可自定义验证处理。接下来我们就来创建自己的验证类来判断用户名和密码是否正确。

创建 **yabe/app/controllers/Security.java** 文件,并且覆盖父类的authenticated() 方法:

package controllers;

import models.*;

public class Security extends Secure.Security {

    static boolean authenticate(String username, String password) {
        return true;
    }

}

因为之前我们已经有了User类,很容易就可以实现这个方法:

static boolean authenticate(String username, String password) {
    return User.connect(username, password) != null;
}

先注销当前会话 [http://localhost:9000/logout](http://localhost:9000/logout) ,让后用先前inital-data.yml导入的测试数据中随便选一个用来测试,这里我用的是 bob@gmail.com/secret.

#### 重构Admin部分

最初的Admin部分用的是现成的CRUD Module, 但是它的UI不能很好的融合到我们的Blog中。因此,我们要开发一个新的Admin模块,允许每个作者管理自己发的帖子,而超级用户则可以进行任何操作(**注意:以下都是基于用户登录成功后的操作**)。

现在先来创建一个Admin Controller:

package controllers;

import play.*;
import play.mvc.*;

import java.util.*;

import models.*;

@With(Secure.class)
public class Admin extends Controller {

    @Before
    static void setConnectedUser() {
        if(Security.isConnected()) {
            User user = User.find("byEmail", Security.connected()).first();
            renderArgs.put("user", user.fullname);
        }
    }

    public static void index() {
        render();
    }

}

接下来,修改 **yabe/conf/routes** 文件:

# Administration
GET     /admin/?                                Admin.index
*       /admin                                  module:crud

注意routes文件中的URL顺序。第一行是用来匹配HTTP的GET请求,同时也表示映射到Admin Controller的请求第一行的优先级高于第二行。这样的话/admin/?下的所以请求都得经过Admin.index, 而/admin则用的是CRUD.index.

修改模板 **yabe/app/views/main.html**, 增加超链接'Log in to write something':

…
<ul id="tools">
    <li>
        <a href="@{Admin.index()}">Log in to write something</a>
    </li>
</ul>
…

最后一步就是创建新的模板 **yabe/app/views/Admin/index.html**, 来显示登录成功后的信息:

Welcome ${user}!

现在回到blog的主页,点击'Log in to write something', 你就可以看到下面的欢迎页:

[![image](/user_images/1334-3.png "image")](/user_images/1334-3.png)

看到了吗? 好样的。但是因为我们的Admin模块会有很多新的页面,我们需要一个admin的layout模板,马上来创建**yabe/app/views/admin.html** 文件:

<!DOCTYPE html>
<html>
    <head>
        <title>Administration</title>        
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
        #{get 'moreStyles' /}    
        <link rel="stylesheet" type="text/css" media="screen" 
                href="@{'/public/stylesheets/main.css'}" />
        <link rel="shortcut icon" type="image/png" 
                href="@{'/public/images/favicon.png'}" />
        <script src="@{'/public/javascripts/jquery-1.4.2.min.js'}"></script>
        <script src="@{'/public/javascripts/jquery.tools-1.2.5.toolbox.expose.min.js'}"></script>
    </head>
    <body id="admin">

        <div id="header">
            <div id="logo">
                yabe. <span>administration</span>
            </div>
            <ul id="tools">
                <li>
                    <a href="@{Secure.logout()}">Log out</a>
                </li>
            </ul>
        </div>

        <div id="main">
            #{doLayout /} 
        </div>

        <p id="footer">
            Yabe is a (not so) powerful blog engine built with the 
            <a href="http://www.playframework.org">Play framework</a>
            as a tutorial application.
        </p>

    </body>
</html>

正如你看到,这个模板已经非常接近blog前端所用的模板了。接下来把URL login改成URL logout,注意这里用的是Secure Model的自带logout。

最后修改 **yabe/app/views/Admin/index.html**, 将admin模板套用上:

#{extends 'admin.html' /}

Welcome ${user}!

立即刷新!

[![image](/user_images/1334-5.png "image")](/user_images/1334-5.png)

如果你点击了logout, 浏览器会重新跳转会登录界面:

[![image](/user_images/1334-7.png "image")](/user_images/1334-7.png)

那个注销是SecureModule默认的处理方式。当然如果想自定义的话,只要覆盖controllers.Security类中的onDisconnected()方法即可:

static void onDisconnected() {
    Application.index();
}
You can do the same for the onAuthenticated() event:

static void onAuthenticated() {
    Admin.index();
}

#### 添加角色

实际上Admin模块有两块:一块是一些简单的编辑用户,另一块则是超级用户了。回过头看看User类,你会发现我们准备了一个叫isAdmin的字段,它可以标示那些用户拥有超级用户的权限。

Secure Model不仅仅提供权限验证,还可以对授权进行管理。这个称之为profiles。如何创建属于Admin的profile呢,只需要覆盖controllers.Security类的check()方法即可:

static boolean check(String profile) {
    if("admin".equals(profile)) {
        return User.find("byEmail", connected()).<User>first().isAdmin;
    }
    return false;
}

接下来我们修改layout页面**app/views/admin.html**,将Admin的操作菜单保护起来:

...
<div id="main">

    <ul id="adminMenu">
        <li class="${request.controller == 'Admin' ? 'selected' : ''}">
            <a href="@{Admin.index()}">My posts</a>
        </li>
        #{secure.check 'admin'}
            <li class="${request.controller == 'Posts' ? 'selected' : ''}">
                <a href="@{Posts.list()}">Posts</a>
            </li>
            <li class="${request.controller == 'Tags' ? 'selected' : ''}">
                <a href="@{Tags.list()}">Tags</a>
            </li>
            <li class="${request.controller == 'Comments' ? 'selected' : ''}">
                <a href="@{Comments.list()}">Comments</a>
            </li>
            <li class="${request.controller == 'Users' ? 'selected' : ''}">
                <a href="@{Users.list()}">Users</a>
            </li>
        #{/secure.check}
    </ul>

    #{doLayout /} 
</div>
...

注意上述页面使用了#{secure.check /}来保证只有当拥有Admin权限的用户才可以看到。

[![image](/user_images/1334-9.png "image")](/user_images/1334-9.png)

回过头看看继承CRUD的Controller,这部分还没有任何验证。如果有人知道URL,就可以直接进入。要保护这些controllers,我们照样可以使用标注@Check,以下就是对Posts controller的修改:

package controllers;

import play.*;
import play.mvc.*;

@Check("admin")
@With(Secure.class)
public class Posts extends CRUD {    
}

像Tags, Comments和Users controllers都可以类似处理。现在直接访问URL[http://localhost:9000/admin/users](http://localhost:9000/admin/users) 看看,出现403了,证明验证有效。

[![image](/user_images/1334-11.png "image")](/user_images/1334-11.png)

#### 自定义 CRUD layout

因为CRUD Model有自带的layout,所以当我们访问继承CRUD的controllers,是无法继承我们编写的admin.html layout。现在我们就来改变这一切。在Play命令行输入:

play crud:ov --layout

系统会生成自动生成 **/yabe/app/views/CRUD/layout.html** 文件. 修改里面的内容,将我们的 admin.html layout 融入其中:

#{extends 'admin.html' /}
#{set 'moreStyles'}
    <link rel="stylesheet" type="text/css" media="screen" 
        href="@{'/public/stylesheets/crud.css'}" />
#{/set}

<div id="crud">

    #{if flash.success}
        <div class="crudFlash flashSuccess">
            ${flash.success}
        </div>
    #{/if}
    #{if flash.error || error}
        <div class="crudFlash flashError">
            ${error ?: flash.error}
        </div>
    #{/if}

    <div id="crudContent">
        #{doLayout /}
    </div>

</div>

这里我们还是复用curd.css样式,不做大的改动。再次访问该页面,会发现已经和我们的admin layout整合在一起了:

[![image](/user_images/1334-13.png "image")](/user_images/1334-13.png)

[![image](/user_images/1334-15.png "image")](/user_images/1334-15.png)

#### 修改登录页的样式

Admin模块的样式已经基本一致了。最后我们来完善登录页面的样式。要自定义样式非常简单,用Play在命令行运行:

play secure:ov --css

这里我们不修改生成的 **yabe/public/stylesheets/secure.css** 样式文件,而是将mains.css 导入其中:

@import url(main.css);
…

最后别忘了在资源文件**yabe/conf/messages**中加入新的键值:

secure.username=Your email:
secure.password=Your password:
secure.signin=Log in now

最后刷新看效果:

image

comments powered by Disqus