随着越来越多的人(像我一样)对Scala感兴趣,而不是一个问题,我想讨论一个基于Lift的网络应用程序登录/注销片段的实现。
我刚开始学习Scala和Lift,所以这可能不是实现此类功能的最佳方式,但我想与其他初学者分享,并与更有经验的开发人员讨论。请注意,我也不是网络开发方面的专家。我们将非常感谢对改进的任何帮助(尤其是性能和安全相关的帮助);-)
1)首先,片段需要易于插入,就像默认模板中的1行代码一样。我使用嵌入式Lift功能完成了这项工作(注意下划线,这样它就不能被呈现为页面本身,只能从呈现的页面中调用,简而言之,是某种"私人"片段):
<lift:embed what="_logInForm" />
2)然后,在_logInForm.html中,我使用以下标记和条件显示来处理所有内容:
<div>
<!-- User is not logged in, show a form to log in using the method loggedOut -->
<lift:LogInForm.loggedOut>
<form class="lift:LogInForm.logIn?form=post">
<label for="textName">Username: </label><input type="text" id="textName" name="name" /> <span class="lift:Msg?id=name;errorClass=error"/><br/>
<label for="textPassword">Password: </label><input type="password" id="textPassword" name="password" /> <span class="lift:Msg?id=password;errorClass=error"/><br/>
<input type="submit" value="Log in" />
</form>
</lift:LogInForm.loggedOut>
<!-- User is logged in, show who she is and a way to log out using the method loggedIn -->
<lift:LogInForm.loggedIn>
<form class="lift:LogInForm.logOut?form=post">
Connected as <span class="lift:LogInForm.getName" />.<br />
<input type="submit" id="btnLogOut" value="Log out" />
</form>
</lift:LogInForm.loggedIn>
</div>
3)。。。现在是这个标记背后的Scala/Lift逻辑:
object LogInForm {
private object name extends SessionVar("")
private object password extends RequestVar("")
private object referer extends RequestVar(S.referer openOr "/")
var isLoggedIn = false
def loggedIn(html: NodeSeq) =
if (isLoggedIn) html else NodeSeq.Empty
def loggedOut(html: NodeSeq) =
if (!isLoggedIn) html else NodeSeq.Empty
def logIn = {
def processLogIn() {
Validator.isValidName(name) match {
case true => {
Validator.isValidLogin(name, password) match {
case true => { isLoggedIn = true } // Success: logged in
case _ => S.error("password", "Invalid username/password!")
}
}
case _ => S.error("name", "Invalid username format!")
}
}
val r = referer.is
"name=name" #> SHtml.textElem(name) &
"name=password" #> (
SHtml.textElem(password) ++
SHtml.hidden(() => referer.set(r))) &
"type=submit" #> SHtml.onSubmitUnit(processLogIn)
}
def logOut = {
def processLogOut() { isLoggedIn = false }
val r = referer.is
"type=submit" #> SHtml.onSubmitUnit(processLogOut)
}
def getName = "*" #> name.is
}
评论:
- 两种形式之间的选择由逻辑进行,根据用户登录或注销的事实,呈现所提供的标记或NodeSeq.Empty
- 我使用Lift:Msg在相应的字段(名称/密码)旁边显示错误消息。消息是使用S.error后面的逻辑和相应的id发送的
- 实际上,我使用regexp和格式检查等在Validator助手中执行检查。每次返回布尔值到模式匹配都很简单
- 我使用referer,这样用户就可以在同一页面上登录/注销
- 用户名保留为会话变量,并在登录时显示
4)您可以在Boot.scala中控制对其他页面的访问:
def sitemap() = SiteMap(
Menu("Home") / "index",
Menu("Protected page") / "protectedPageName" >> If(() => LogInForm.isLoggedIn, ""),
// etc.
问题:
- 没有SSL保护(这可能是一个改进,还没有在Scala/Lift中看到这一点)。其他人的经验可能有用吗
- 会话变量的使用。也许在Scala/Lift中有更好的方法来保持状态
- 是否已经有我可能错过的专门为登录/注销而制作的东西
- 它不是很长,但可能更紧凑?(不过,我不想牺牲太多的可读性。其他开发人员需要快速理解它)
- 还有其他建议吗
干杯,
马克。
- 这显示了如何强制使用SSL,您通常会将其用于登录表单页面菜单:Lift filter以强制使用SSL
- 会话变量通常是保存会话信息的最佳方式
- ProtoUser(你可以使用Lifty(http://lifty.github.com/)以登录为例设置项目)
Lift为这些用例提供了一个脚手架框架。您需要了解net.liftweb.proto.ProtoUser
的源代码。尤其是CCD_ 2和CCD_。
Lift的基本示例如下所示:
-
创建一个自己的映射用户类,使用所有讨厌的提升代码
package yourcompany.model import net.liftweb.mapper._ import net.liftweb.util._ import net.liftweb.common._ class User extends MegaProtoUser[User] { // your code, mostly overrides } object User extends User with MetaMegaProtoUser[User] { override def dbTableName = "users" // other database related code }
-
在您定义网站地图后的引导中,请执行以下操作:
// do not forget to import your user def sitemap() = Sitemap(Menu("Home") / "index" >> User.AddUserMenusAfter) // now comes the magic LiftRules.setSiteMap(User.sitemapMutator(sitemap()))
这将包括您页面上的许多链接,所有链接都收集在/user_mgt
下,例如sign_up, login, lost_password
。对我来说,这是一个很好的工作基地。你可以覆盖ProtoUser中发生的几乎所有事情,或者只是复制好的部分并自己实现所有事情。
此外,如果有人有关于这方面的额外文档,这将非常有帮助。目前,我试图利用这些来源找出大部分。