返回目录

先修改 mysite/ctrl/auth.py , 完整代码见 http://code.google.com/p/mayoufour/source/browse/trunk/stdyun/mysite/ctrl/auth.py

#coding:utf-8
from mypy.route_render import route_render_func,route
from mypy.dict_like import Jsdict, StripJsdict
from mysite.model.auth import User, UserApply, UserProfile, UserPassword, UserSession,UserEmail,UserResetPassword
from mysite.model import auth
from mysite.util.valid import email_valid,name_valid,password_valid
from mysite.util.text import WHITE_SPACE, email2link
from cgi import escape

def apply_checker(form):
    email = form.email
    name = form.name
    password = form.password

    error = Jsdict()

    name_valid(error,name)
    password_valid(error,password)

    if email_valid(error,email):
        existed = auth.is_existed(email)
        if existed:
            error.email = """该邮箱已注册,<a href="/auth/reset_password_apply/%s">点此</a>重设密码"""%escape(email)

    if not error:
        apply = auth.apply(email, password, name)
        apply.sendemail(email)

    return error

@route_render_func
def apply(email=None):
    if email:
        if auth.is_applyed(email):
            G.email_link = email2link(email)

    if request.is_post:
        form = StripJsdict(request.form)
        G.error = error = apply_checker(form)
        if not error:
            return request.redirect("/auth/apply/%s"%form.email)

这里可以看到,apply 函数用到了两个隐藏的变量 G 和 request。

G的一个用来像模板传递参数的变量。通过向“G.”的赋值,然后就可以再模板中通过同样的“G.”操作获取该变量。

request代表请求对象。

reqeust.is_post 代表请求的提交方式是否是 POST。

request.form 是当请求方式为 POST 时提交的表单对象。form对象是Jsdict类的一个实例,它和Javascript中的字典很类似。既可以同点操作来获取属性,也可以通过 ['属性名'] 的方式来获取属性。StripJsdict 和 Jsdict 功用基本相同,但是 StripJsdict 会通过字符串的strip函数去除内容两端的空白字符串。

用户提交申请后,通过apply_checker函数检查表单内容,同时将检查结果赋值给 G.error 。

当用户注册申请提交成功时,返回request.redirect,重定向网页可以避免用户刷新时重复提交表单。

apply函数有一个参数,诸如 /auth/apply/zsp007@gmail.com 会依次映射为 模块名,函数名,参数。

这样配合重定向就可以实现提醒用户申请注册成功。

接下来,再修改 mysite/htm/auth 目录下 apply.htm ,也就是本章第二节写的那个页面。

<%inherit file="/htmbase/simple_base.htm" />
<%!
from myfile import js,css
from mysite.util.valid import ErrorTip
%>
<%def name="html_head()" filter="trim">
<link href="${css.bform}" rel="stylesheet" type="text/css" />
<script src="${js.auth_apply}"></script>
</%def>

<%def name="html_body()" filter="trim">
<%
error_tip = ErrorTip(G.error)
form = request.form
%>
%if G.email_link:
    <div class="FormTip">
        申请成功,激活邮件已发出...
    </div>
    <p align="center">请到 ${G.email_link|n} 查收激活邮件</p>
%else:
<form method="POST" class="HideErr Bform Pform" onsubmit="return submit_form()">
    <div class="FormTip">
        %if error:
            <div style="color:#d30">填写有误,请检查...</div>
        %else:
            许多故事,从这里开始...
        %endif
    </div>
    <p>
        <label>邮箱</label><input autocomplete="off" type="text" value="${form.email}" class="text" name="email" id="email">
        ${error_tip.email|n}
        <span>输入Email邮箱,接收确认邮件以开始注册。 </span>
    </p>


    <p>
        <label>密码</label><input type="password" value="${form.password}" class="text" name="password" id="password">
        ${error_tip.password|n}
        <span>最少6个字符,区分大小写。 </span>
    </p>

    <p>
        <label>名号</label><input autocomplete="off" type="text" value="${form.name}" class="text" name="name" id="name">
        ${error_tip.name|n}
        <span>中英文皆可,不要超过8个汉字。 </span>
    </p>

    <p align="center"><button class="Bbtn" type="submit" id="submit">注册</button></p>
    <p align="right" style="font-size:12px;color:#333">忘记密码了?
        <a href="/auth/reset_password_apply">点此</a>找回
    </p>
</form>
%endif
</%def>

代码改动不大,首先判断是注册成功还是申请注册,然后给出不同的页面。

ErrorTip是一个用来提示错误的辅助函数,初始化时传入错误的描述对象(通常是一个Jsdict),然后通过点操作来显示对应错误(如果没有就不显示)。

到此为止,申请注册基本完成。

接下来还有 激活注册,找回密码 等等,写法大致类似,可以自己阅读代码,无需冗言。

再来看看登录和退出,这里涉及了cookie的操作

from mypy import cookie
from mysite.util.security import check_get_cf

def set_login_cookie(id,forever=False):
    if forever:
        expires = 'Expires=Mon, 19-Jan-2036 14:09:52 GMT;'
    else:
        expires = ""
    session = UserSession.login(id)
    cookie.set("S",session,expires=expires)

@route_render_func
def login(path=''):
    if request.is_post:
        form = StripJsdict(request.form)
        email = form.email
        password = form.password
        G.error = error = Jsdict()
        cookie.set("E",email)

        user_email = UserEmail.get(email = email)
        if user_email:
            id = user_email.id
            user = User.mc_get(id)
            if user is None:
                error.email="邮箱已注册,但尚未激活"
            else:
                user_password = UserPassword.get(id=id)
                if user_password is None:
                    user.delete()
                    error.email="该邮箱出现系统故障,请重新注册"
                elif user_password.verify(password):
                    forever = "forever" in form
                    set_login_cookie(id,forever)
                    return request.redirect("/%s"%path.replace("\\","/"))
                else:
                    error.password="密码有误,请检查"
        else:
            error.email="""该邮箱尚未注册,<a href="/auth/apply">点此</a>注册"""
    else:
        email = request.cookie.get("E")
        if email:
            email = email.value
        else:
            email = ''
    G.email = email

@antixf
@route
def logout():
    user = request.user
    if user:
        UserSession.logout(user.id)
        cookie.delete("S")
    return request.redirect("/")

登录通常分两种,一种是浏览器关了就退出登录,一种是“记住我”模式 -- 我称之为永久登录。

这是通过cookie的expires,也就是超时时间来实现的。不设置超时时间,就是第一种模式,而将超时设置为很久很久以后,就是第二种模式。

设置cookie通过import的cookie对象来进行。

而cookie的读取,删除是通过request对象cookie属性来进行。

在登录全过程涉及了两个cookie,其一是"E",用来记录email地址,这样登录前,登录框的Email地址栏中会出现默认值;其二是"S",用来记录用户session,然后凭此识别用户。

那如何在用户每次访问的时候获取用户呢?编辑 mysite/ctrl/__init__.py ,修改如下 。

import init_url
from mypy import cookie
from mypy.route_render import route
from mysite.model.auth import UserSession
from mysite.util.security import get_xf

@route
def _access():
    session = request.cookie.get("S")
    if session:
        user = UserSession.verify(session)
        if user is None:
            cookie.delete("S")
    else:
        user = None

    request.user = user

_access是一种拦截器,过滤层 -- 你可以想象为高速公路上的收费关卡,机场登机前的行李检查 。每次请求,都会去检查有没有这些_access,如果有,就执行它。

和url路由一样,_access可以在不同的层次使用。ctrl/__init__.py中的_access位于最根层次,对所有的请求都生效。在这里,_access设置了request.user,于是便可以在页面的接下来的代码中凭此来判断、获取登陆用户。

接下来看退出 -- /auth/logout 。

logout的有两个装饰器(decorator),其中是 @route,代表该url映射不需要模板。route装饰器必须是最后一个装饰器,因为它会给函数添加隐藏的request,G变量。

另一个装饰器 antixf,是用来防止XSRF(又称CRSF,cross-site request forgery 跨站请求伪造)攻击的。XSRF是一种黑客攻击网站的方式,通过欺骗用户的点击链接,或伪造POST请求来进行非法操作 -- 比如,盗取密码,散布蠕虫。事实上,一个恶意网站能让你在毫不知情的情况下点击任意链接,任意按钮或网站上的任意东西。

常用的预防方式是加上一个效验值,让每个用户进行操作的链接都不相同,这里我们用session cookie的最后四位用作效验。

先编写函数获取ck,如下

from mypy.render import get_request
def get_xf():
    request = get_request()
    user = request.user
    if request.user:
        xf = request.cookie.get("S","")[-4:]
    else:
        xf = None
    return xf

然后在模板中的写法如下

<!%
from mysite.util.security import get_xf
%>
<a href="/auth/logout?xf=${get_xf()}">退出</a>

将生成的类似如下的链接

<a href="/auth/logout?xf=J4LU">退出</a>

对于那些会产生写操作的链接,加上antixf是很有必要的。

同样,对于登录用户的表单POST提交,也应该加上antixf效验。技术上,通过一个隐藏的input字段来实现,比如

<input type="hidden" value="${get_xf()}" name="xf">

因为POST的操作在网页编写中非常频繁,手工的到处写这句话很是麻烦,而且一不小心就忘了。因此我们利用模板的namespace语法,定义了一个form,他会自动包含ck。不过,因为class是Python的关键词,因此只有用className来替代class,比如用户设置中修改个人信息的页面模板如下:

...
<%namespace file="/htmlib/htm.htm" name="htm"/>
...
<%htm:form className="HideErr Bform Pform" style="margin-top:20px">
    %if request.is_post and not G.error:
    <div class="Ok FormTip">修改成功 !</div>
    %endif

    <p>
        <label>邮箱</label>${G.email}
    </p>

    <p>
        <label>名号</label><input name="name" id="name" value="${G.name}" type="text">${error_tip.name|n}
    </p>
    <p><label>密码</label><a href="/setting/reset_password">修改</a></p>
    <p align="center"><button class="Bbtn" type="submit">提交改动</button></p>
</%htm:form>
...

再一次编辑 mysite/ctrl/__init__.py 的 _access 函数,对所有登陆用户的post进行 xf 的检查。

@route
def _access():
    session = request.cookie.get("S")
    if session:
        user = UserSession.verify(session)
        if user is None:
            cookie.delete("S")
    else:
        user = None

    request.user = user

    if request.is_post and user:
        if request.form.xf != get_xf():
            return request.redirect("/")

同样,样我们需要修改一下jQuery的函数,以便用在Ajax的POST操作中自动加上xf,编辑 myfile/js/my.js 。

cookie = {
set:function(dict, days){
    days = days || 30;
    var date = new Date();
    date.setTime(date.getTime()+(days*24*60*60*1000));
    var expires = "; expires="+date.toGMTString();
    for (var i in dict){
        document.cookie = i+"="+dict[i]+expires+"; path=/";
    }
},
get:function(name) {
    var e = name + "=";
    var ca = document.cookie.split(';');
    for(var i=0;i < ca.length;i++) {
        var c = ca[i];
        while (c.charAt(0)==' ') c = c.substring(1,c.length);
        if (c.indexOf(e) == 0) {
            return c.substring(e.length,c.length).replace(/\"/g,'');
        }
    }
    return null;
}
}
$.__ajax = $.ajax
$.ajax = function(options){
    if(options.type=="POST"){
        var s = cookie.get('S')
        if(s){
            options.data=$.extend(options.data||{},{xf:s.slice(-4)});
        }
    }
    return $.__ajax(options)
}

OK,到此为止,用户注册告一段落。

上一节 返回目录 下一节
京ICP备10008809号