(八)play之yabe项目【身份验证】

时间:2022-06-18 16:05:56

添加身份验证

play提供了一个模块-Secure(安全模块),用来做身份验证

允许Secure模块

修改yabe\conf\dependencies.yml,加入对secure的依赖

  1. # Application dependencies
  2. require:
  3. - play -> crud
  4. - play -> secure

cmd命令行执行dependencies命令

E:\technology-hqh\proj\play-framework\yabe>play dependencies

cmd命令行执行eclipsify命令

E:\technology-hqh\proj\play-framework\yabe>play eclipsify

刷新工程,IDE中便可导入依赖包

修改yabe\conf\routes文件,为secure配置路由

  1. # Routes
  2. # This file defines all application routes (Higher priority routes first)
  3. # ~~~~
  4. # Home page
  5. GET     /                                       Application.index
  6. #import Secure routes
  7. *       /                                       module:secure
  8. # restful style route
  9. GET     /post/{id}                              Application.show
  10. POST    /post/{postId}/comments                 Application.postComment
  11. GET     /captcha                                Application.captcha
  12. #import CRUD module
  13. *       /admin                                  module:crud
  14. # Ignore favicon requests
  15. GET     /favicon.ico                            404
  16. # Map static resources from the /app/public folder to the /public path
  17. GET     /public/                                staticDir:public
  18. # Catch all
  19. *       /{controller}/{action}                  {controller}.{action}

Secure需要的配置基本完成,访问主页仍然可以直接进入

这是因为还没有对任何Controller指定是否需要验证,所以还Secure还没开始工作!

 为控制器添加身份验证

使用注解@With(Secure.class)标识Controller,Secure就会对访问该控制器进行身份验证

如,在Application类上加@With(Secure.class)

  1. @With(Secure.class)
  2. public class Application extends Controller {
  3. ......
  4. }

刷新页面,Secure开始工作了

(八)play之yabe项目【身份验证】

随便输入用户名和密码都可以登录,即系统验证功能还没有真正开始

使用http://localhost:9000/logout 可以注销登录

定制系统身份认证

应用程序必须提供一个controllers.Secure.Security实例来定制身份认证处理。

通过继承这个类来创建我们自己版本的Secure类,可以指定如何对用户身份进行认证。
yabe\app\controllers下创建Security,重写authenticate()

  1. package controllers;
  2. import models.User;
  3. public class Security extends Secure.Security {
  4. static boolean authenticate(String username, String password) {
  5. return User.login(username, password);
  6. }
  7. }

修改User类,增加username属性,以及一个以username和password为条件的查询的方法

  1. package models;
  2. import java.util.ArrayList;
  3. import java.util.List;
  4. import javax.persistence.Entity;
  5. import javax.persistence.OneToMany;
  6. import play.data.validation.Email;
  7. import play.data.validation.Required;
  8. import play.db.jpa.Model;
  9. @Entity
  10. public class User extends Model {
  11. @Required
  12. public String username;
  13. @Email
  14. @Required
  15. public String email;
  16. @Required(message="input your pwd now!")
  17. public String password;
  18. public String fullname;
  19. public boolean isAdmin;
  20. //@OneToMany 声明User与Post之间是1对多的关系
  21. //mappedBy="author" 表示将通过对方(User)的author字段来进行关联关系的维护
  22. @OneToMany(mappedBy="author")
  23. public List<Post> posts;
  24. public User(String email,String password, String fullname) {
  25. this.email = email;
  26. this.password = password;
  27. this.fullname = fullname;
  28. this.posts = new ArrayList<Post>(0);
  29. }
  30. /**
  31. * 联合email和password两个条件查询User
  32. * @param email
  33. * @param password
  34. * @return
  35. */
  36. public static User connect(String email, String password) {
  37. return find("byEmailAndPassword", email, password).first();
  38. }
  39. /**
  40. * 登陆时根据username和password查询User
  41. * 如果存在,则允许登陆
  42. * @param username
  43. * @param password
  44. * @return
  45. */
  46. public static boolean login(String username, String password) {
  47. return find("byUsernameAndPassword", username, password).first() != null;
  48. }
  49. /**
  50. * 添加Post的动作放到User中,这样可以把Post设置到User的List<Post>集合中
  51. * 这样实现了双方都持有对方的引用了
  52. * @param title
  53. * @param content
  54. * @return
  55. */
  56. public User addPost(String title, String content) {
  57. Post post = new Post(title,content,this).save();
  58. this.posts.add(post);
  59. this.save();
  60. return this;
  61. }
  62. @Override
  63. public String toString() {
  64. return "User [" + fullname + "]";
  65. }
  66. }

修改yabe\conf\initial-data.yml ,为User对象加入username初始化值

注意,该文件对TAB键不友好,只认空格符作为间隔

  1. # Test data
  2. User(bob):
  3. username:       bob
  4. email:          bob@gmail.com
  5. password:       secret
  6. fullname:       Bob
  7. isAdmin:        true
  8. User(jeff):
  9. username:       jeff
  10. email:          jeff@gmail.com
  11. password:       secret
  12. fullname:       Jeff
  13. User(paul):
  14. username:       paul
  15. email:          paul@gmail.com
  16. password:       secret
  17. fullname:       Paul
  18. ...

http://localhost:9000/logout  注销,重新登陆

此时,只有输入正确的用户名和密码才能进入系统了

有效账户yml中的初始用户:[bob,secret] :[jeff,secret]:[paul,secret]

但是,对CRUD页面 http://localhost:9000/admin/ 控制不起作用!

(八)play之yabe项目【身份验证】

集成CRUD管理域到博客中

超级用户可以管理所有的博客

普通用户可以管理自己的博客

首先,看一下用户登陆验证的内部执行逻辑

public class Secure extends Controller中的方法

  1. /**
  2. * 登陆页面,点击登陆,将执行此方法
  3. * username
  4. * password
  5. * checkbox框 --- remember
  6. */
  7. public static void authenticate(@Required String username, String password, boolean remember) throws Throwable {
  8. // Check tokens
  9. Boolean allowed = false;
  10. try {
  11. // This is the deprecated method name
  12. // 该方法废弃,所以这里总会抛异常,进而执行catch块的代码
  13. allowed = (Boolean)Security.invoke("authentify", username, password);
  14. } catch (UnsupportedOperationException e ) {
  15. // This is the official method name
  16. // 子类(class Security extends Secure.Security)复写了authenticate(),所以这里将调用我们自己的authenticate(),根据用户名和密码查询数据库
  17. allowed = (Boolean)Security.invoke("authenticate", username, password);
  18. }
  19. if(validation.hasErrors() || !allowed) {
  20. flash.keep("url");
  21. flash.error("secure.error");
  22. params.flash();
  23. login();
  24. }
  25. // Mark user as connected
  26. // 如果登陆成功,session中存入登陆用户名
  27. session.put("username", username);
  28. // Remember if needed
  29. // 如果需要保存登陆状态,则应勾选登陆页面的checkbox框
  30. if(remember) {
  31. Date expiration = new Date();
  32. String duration = "30d";  // maybe make this override-able
  33. expiration.setTime(expiration.getTime() + Time.parseDuration(duration));
  34. response.setCookie("rememberme", Crypto.sign(username + "-" + expiration.getTime()) + "-" + username + "-" + expiration.getTime(), duration);
  35. }
  36. // Redirect to the original URL (or /)
  37. redirectToOriginalURL();
  38. }

创建一个新的Controller,该控制器用来对CRUD管理界面进行控制

  1. package controllers;
  2. import models.User;
  3. import play.mvc.Before;
  4. import play.mvc.Controller;
  5. public class Admin extends Controller {
  6. /**
  7. * 首先,用户登陆会被Security拦截,登陆成功会把username放入session中
  8. * session.put("username",username);
  9. * 通过session.contains("username");判断用户是否已经登陆
  10. *
  11. * @Before 访问Admin控制器时,将先执行由该注解标注的方法,进行拦截(过滤/检查)
  12. */
  13. @Before
  14. static void setConnectedUser() {
  15. //Security.isConnected() 检查session中是否有username为key的map存在
  16. //因为用户登陆后会用username作为key存储登陆信息
  17. if(Security.isConnected()) {
  18. //Security.connected() 取得session中以username为key的value,即用户名
  19. User user = User.find("byUsername", Security.connected()).first();
  20. renderArgs.put("user", user.fullname);
  21. }
  22. }
  23. //返回管理CRUD功能模块的主页面
  24. public static void index(){
  25. render();
  26. }
  27. }

为Admin控制器的index()添加模板

创建yabe\app\views\Admin\index.html

  1. Welcome ${user}!

在主页面为Admin为CRUD功能模块加入超链接

更改Log in to write something的href属性,使其指向Admin的index()

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

刷新页面

(八)play之yabe项目【身份验证】

跳转到管理页面

当然,这里只是简单取了一下Admin.java中 @Before标注的setConnectedUser()方法所设置的用户名
(八)play之yabe项目【身份验证】
 到这里,完成了2个操作

一是Admin控制器中使用了拦截器,通过@Before进行设置

二是从Security.connected()中获得当前登陆的用户名,再使用renderArgs.put(key,value)将信息传递到页面中,以便进行显示当前登陆用户。

为CRUD模块管理页面配置一个模板

这里为admin/index.html配置一个父模板,对页面进行统一的设置(标题,附加信息,版权声明等)

  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <title>Administration</title>
  5. <meta charset="utf-8">
  6. <link rel="stylesheet" media="screen" href="@{'/public/stylesheets/main.css'}">
  7. <link rel="shortcut icon" type="image/png" href="@{'/public/images/favicon.png'}">
  8. <script src="@{'/public/javascripts/jquery-1.6.4.min.js'}" type="text/javascript"></script>
  9. <script src="@{'/public/javascripts/jquery.tools-1.2.5.toolbox.expose.min.js'}" type="text/javascript"></script>
  10. </head>
  11. <body id="admin">
  12. <!-- 页面顶部显示的信息 -->
  13. <div id="header">
  14. <div id="log">yabe. <span>administration</span></div>
  15. <ul id="tools">
  16. <!-- 调用Secure的logout()进行注销 -->
  17. <li><a href="@{Secure.logout()}">Log out</a></li>
  18. </ul>
  19. </div>
  20. <!-- 子模板内容显示区 -->
  21. <div id="main">
  22. #{doLayout /}
  23. </div>
  24. <!-- 页脚 -->
  25. <p id="footer">
  26. Yabe is a (not that) powerful bolg engine built with the
  27. <a href="http://playframework.org">Play framework</a> as a tutorial application.
  28. </p>
  29. </body>
  30. </html>

刷新页面

(八)play之yabe项目【身份验证】

可见模板已经开始生效了,点击logout,则登出

这里调用的是Secure内部的logout(),而且可以复写Secure中的其它方法

比如,登出之后需要进行某些操作,则复写onDisconnected()

比如,登录成功之后要进行某些操作,则复写onAuthenticated()

  1. package controllers;
  2. import play.Logger;
  3. import models.User;
  4. public class Security extends Secure.Security {
  5. static boolean authenticate(String username, String password) {
  6. return User.login(username, password);
  7. }
  8. /**
  9. * 登陆成功后会调用onAuthenticated()
  10. */
  11. static void onAuthenticated() {
  12. Logger.info(Secure.Security.connected()+"\tlogin");
  13. //Admin.index();
  14. //登陆成功后,自动跳转到管理页面
  15. }
  16. /**
  17. * 注销后会调用onDisconnected()
  18. */
  19. static void onDisconnected() {
  20. Logger.info(Secure.Security.connected()+"\tloginlogout");
  21. Application.index();
  22. }
  23. }

到此,CRUD管理页面尚未提供任何可操作的功能

现在,继续编辑模板,加入CRUD的超链接到管理页面

  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <title>Administration</title>
  5. <meta charset="utf-8">
  6. <link rel="stylesheet" media="screen" href="@{'/public/stylesheets/main.css'}">
  7. <link rel="shortcut icon" type="image/png" href="@{'/public/images/favicon.png'}">
  8. <script src="@{'/public/javascripts/jquery-1.6.4.min.js'}" type="text/javascript"></script>
  9. <script src="@{'/public/javascripts/jquery.tools-1.2.5.toolbox.expose.min.js'}" type="text/javascript"></script>
  10. </head>
  11. <body id="admin">
  12. <!-- 页面顶部显示的信息 -->
  13. <div id="header">
  14. <div id="log">yabe. <span>administration</span></div>
  15. <ul id="tools">
  16. <!-- 调用Secure的logout()进行注销 -->
  17. <li><a href="@{Secure.logout()}">Log out</a></li>
  18. </ul>
  19. </div>
  20. <!-- 子模板内容显示区 -->
  21. <div id="main">
  22. <ul id="adminMenu">
  23. <!-- 如果登陆用户是超级管理员,则显示 -->
  24. <li class="${request.controller == 'Admin' ?  'selected' : ''}">
  25. <a href="@{Admin.index()}">My Posts</a>
  26. </li>
  27. <!-- 使用Secure.check() 控制是否为超级用户,如果是,则显示下面的<li/> -->
  28. #{secure.check 'admin'}
  29. <li class="${request.controller == 'Posts' ? 'selected' : ''}">
  30. <a href="@{Posts.list()}">Posts</a>
  31. </li>
  32. <li class="${request.controller == 'Comments' ? 'selected' : ''}">
  33. <a href="@{Comments.list()}">Comments</a>
  34. </li>
  35. <li class="${request.controller == 'Users' ? 'selected' : ''}">
  36. <a href="@{Users.list()}">Users</a>
  37. </li>
  38. <!-- 注意这里的结束标签是写在前面的! -->
  39. #{/secure.check}
  40. </ul>
  41. #{doLayout /}
  42. </div>
  43. <!-- 页脚 -->
  44. <p id="footer">
  45. Yabe is a (not that) powerful bolg engine built with the
  46. <a href="http://playframework.org">Play framework</a> as a tutorial application.
  47. </p>
  48. </body>
  49. </html>

登出,使用一个isAdmin属性为false的账户(jeff或paul)进行登陆

打开管理页面,注意,当前是用paul进行登陆的,非管理员权限,但是其可以看到所有的博文

这是不行的!非管理员只应该看到自己的博文!

(八)play之yabe项目【身份验证】

虽然admin.html模板中,已经使用了判断是否为'admin'属性了,但是,请注意,play默认的check()返回的是true。就好像做登陆那里一样,子类没有重写authenticate()时,不管用什么用户登陆都成功一样。

在Security控制器中覆盖Secure.Security的check()

  1. package controllers;
  2. import play.Logger;
  3. import models.User;
  4. public class Security extends Secure.Security {
  5. /**
  6. * 覆盖Secure.Security中的authenticate()
  7. * 这样,play在进行登录验证时,就会调用到子类写的方法了
  8. * @param username
  9. * @param password
  10. * @return
  11. */
  12. static boolean authenticate(String username, String password) {
  13. return User.login(username, password);
  14. }
  15. /**
  16. * 登陆成功后会调用onAuthenticated()
  17. */
  18. static void onAuthenticated() {
  19. Logger.info(Secure.Security.connected()+"\tlogin");
  20. }
  21. /**
  22. * 注销后会调用onDisconnected()
  23. */
  24. static void onDisconnected() {
  25. Logger.info(Secure.Security.connected()+"\tloginlogout");
  26. Application.index();
  27. }
  28. /**
  29. * 用户登陆成功后,继续对其操作权限进一步校验
  30. * 如果User的isAdmin属性为true,则返回true,即其状态为'admin'
  31. * @param profile  play将登陆用户的用户名传入
  32. * @return
  33. */
  34. static boolean check(String profile) {
  35. if("admin".equals(profile)) {
  36. return User.find("byUsername", Secure.Security.connected()).<User>first().isAdmin;
  37. }
  38. return false;
  39. }
  40. }

刷新页面,可以看到系统中所有实体对象的CRUD链接都没有呈现了,因为paul不是管理员

(八)play之yabe项目【身份验证】

登出,使用bob进行登陆

由于bob的isAdmin属性为true,所以,他能看到所有的实体对象的CRUD链接
(八)play之yabe项目【身份验证】

到这里,完成一半的工作了。

接下来考虑的是,普通用户登陆后,使用超管进行CRUD的链接直接操作资源

虽然他的权限不够导致页面无法呈现管理模块的链接,但他可以自己手动输入链接来访问资源

比如,paul不是超管,但是他知道管理博文的地址:http://localhost:9000/admin/posts

paul登陆后,手动链接到这个地址,一样可以进行CRUD操作

该如何是好?

第一,对CURD进行控制

当前,任何人都能通过http://localhost:9000/admin/访问CRUD管理对象

现在就对其进行控制

在每个实体对象对应的Controller上加入注解@With(Secure.class),表示访问该资源需要进行身份认证操作,如果认证失败,则由play自动跳转到index页面

  1. package controllers;
  2. import play.mvc.With;
  3. @With(Secure.class)
  4. public class Posts extends CRUD {
  5. }
  1. package controllers;
  2. import play.mvc.With;
  3. @With(Secure.class)
  4. public class Comments extends CRUD {
  5. }
  1. package controllers;
  2. import play.mvc.With;
  3. @With(Secure.class)
  4. public class Users extends CRUD {
  5. }

现在,未登陆用户虽然能进入CRUD页面,但是无法进行对象的操作了!

第二,防止无超管权限的用户手工输入CRUD的管理链接进行非法操作

同样是通过注解来完成@check("admin"),检查当前用户是否具备超管权限

play考虑很周到,每个小功能都用一个注解来完成,很贴心!

  1. package controllers;
  2. import play.mvc.With;
  3. //访问Post对象的列表,需要检查是是否登陆成功,没有,则返回登陆页面
  4. @With(Secure.class)
  5. //操作Post对象,是否具有超管权限,没有,则提示Access Denied
  6. @Check("admin")
  7. public class Posts extends CRUD {
  8. }

Comments、Users进行同样的操作即可。

使用非管理员账号登陆,手动输入Post对象的管理链接http://localhost:9000/admin/posts

(八)play之yabe项目【身份验证】

到此,未登陆用户不能访问CRUD页面,普通用户不能进行实体对象的CRUD操作!

修改CRUD模块的布局

为了让CRUD模块的布局与博客系统整体布局一致,需要重写其模板

首先要得到CRUD模板元素的布局方案

(  play实际上就是将CRUD模块拷贝到了项目的views路径下:

Copied

E:\technology-hqh\soft\play-1.2.5\modules\crud\app/views/CRUD/layout.html

to

E:\technology-hqh\proj\play-framework\yabe\app/views/CRUD/layout.html

)

E:\technology-hqh\proj\play-framework\yabe>play crud:ov --layout

然后运行eclipsify命令,以便让IDE能够更新到新的内容

E:\technology-hqh\proj\play-framework\yabe>play eclipsify

此时,IDE的views目录下便多了一个目录-CRUD,其下有一个layout.html文件

修改这个文件,让其继承admin.html,并做一些调整

  1. #{extends 'admin.html' /}
  2. #{set 'moreStyles'}
  3. <link rel="stylesheet" type="text/css" media="screen" href="@{'/public/stylesheets/crud.css'}" />
  4. #{/set}
  5. <div id="crud">
  6. #{if flash.success}
  7. <div class="crudFlash flashSuccess">
  8. ${flash.success}
  9. </div>
  10. #{/if}
  11. #{if flash.error || error}
  12. <div class="crudFlash flashError">
  13. ${error ?: flash.error}
  14. </div>
  15. #{/if}
  16. <div id="crudContent">
  17. #{doLayout /}
  18. </div>
  19. </div>

刷新页面,此时CRUD页面的布局发生了变化,与系统的基本保持你一致了

(八)play之yabe项目【身份验证】
 
(八)play之yabe项目【身份验证】

修改登陆页面的样式

为登陆页面定制样式

cmd命令行:

E:\technology-hqh\proj\play-framework\yabe>play secure:ov --css

E:\technology-hqh\proj\play-framework\yabe>play eclipsify

刷新IDE

编辑yabe\public\stylesheets\secure.css

在其文本最上方加入一行

  1. @import url(main.css);

默认登陆页面

(八)play之yabe项目【身份验证】

刷新页面

继承了main.css,感觉没默认的漂亮哦~~~
(八)play之yabe项目【身份验证】

修改登陆页面的文字

打开yabe\conf\messages,加入以下内容替换登陆页面的文字

  1. secure.username=用户名:
  2. secure.password=密码:
  3. secure.remember=下次自动登陆
  4. secure.signin=登陆

刷新页面

(八)play之yabe项目【身份验证】