(本文译者是胡波)
有了管理员模块后,我们需要对其进行权限控制。幸运的是,Play已经有个现成的Module——那就是Secure。
要想启用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,会自动跳转到一个登录界面:
[](/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', 你就可以看到下面的欢迎页:
[](/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}!
立即刷新!
[](/user_images/1334-5.png)
如果你点击了logout, 浏览器会重新跳转会登录界面:
[](/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权限的用户才可以看到。
[](/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了,证明验证有效。
[](/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整合在一起了:
[](/user_images/1334-13.png)
[](/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
最后刷新看效果: