Python version - blog site <3> introduces python-markdown2 and user registration and login

 

Open source address: https://github.com/leebingbin/Python3.WebAPP.Blog

 

1. python-markdown2

    User management is a problem that most Web sites need to solve. User management involves nothing more than the core of user registration and login. However, before that, we need to introduce the python-markdown module (github homepage: https://github.com/trentm/python-markdown2 ): The python-markdown module uses two modes, one is as an independent The command line, the other is used as a python module. The markdown tool can convert txt to html format. The role of this type of tool is to convert a readable text file written in a certain format into a structured standard xhtml or html. Markdown was originally written in perl and was later ported to python, java, and php. Here we mainly introduce the usage of python-markdown. The markdown tool is used in many programs, such as wordpress, pybb, etc., especially suitable for wiki, blog, forum, etc.

markdown2.py

    Example:

python markdown2.py foo.md > foo.html

    The blog website I am currently doing is used as a python module. User registration is relatively simple, we can first realize the function of user registration through API (refer to: https://my.oschina.net/u/3375733/blog/1547261 ):

@post('/api/users')
def api_register_user(*, email, name, passwd):
    if not name or not name.strip():
        raise APIValueError('name')
    if not email or not _RE_EMAIL.match(email):
        raise APIValueError('email')
    if not passwd or not _RE_SHA1.match(passwd):
        raise APIValueError('passwd')
    users = yield from User.findAll('email=?', [email])
    if len(users) > 0:
        raise APIError('register:failed', 'email', 'Email is already in use.')
    uid = next_id()
    sha1_passwd = '%s:%s' % (uid, passwd)
    user = User(id=uid, name=name.strip(), email=email, passwd=hashlib.sha1(sha1_passwd.encode('utf-8')).hexdigest(), image='http://www.gravatar.com/avatar/%s?d=mm&s=120' % hashlib.md5(email.encode('utf-8')).hexdigest())
    yield from user.save()
    # make session cookie:
    r = web.Response()
    r.set_cookie(COOKIE_NAME, user2cookie(user, 86400), max_age=86400, httponly=True)
    user.passwd = '******'
    r.content_type = 'application/json'
    r.body = json.dumps(user, ensure_ascii=False).encode('utf-8')
    return r

2. Registration

    Note that the user password is a 40-bit Hash string passed by the client after SHA1 calculation, so the server does not know the original password of the user. Next, you can create a registration page, let the user fill out the registration form, and then submit the data to the API of the registered user:

{% extends '__base__.html' %}

{% block title %}注册{% endblock %}

{% block beforehead %}

<script>

function validateEmail(email) {
    var re = /^[a-z0-9\.\-\_]+\@[a-z0-9\-\_]+(\.[a-z0-9\-\_]+){1,4}$/;
    return re.test(email.toLowerCase());
}

$(function () {
    var vm = new Vue({
        el: '#vm',
        data: {
            name: '',
            email: '',
            password1: '',
            password2: ''
        },
        methods: {
            submit: function (event) {
                event.preventDefault();
                var $form = $('#vm');
                if (! this.name.trim()) {
                    return $form.showFormError('请输入名字');
                }
                if (! validateEmail(this.email.trim().toLowerCase())) {
                    return $form.showFormError('请输入正确的Email地址');
                }
                if (this.password1.length < 6) {
                    return $form.showFormError('口令长度至少为6个字符');
                }
                if (this.password1 !== this.password2) {
                    return $form.showFormError('两次输入的口令不一致');
                }
                var email = this.email.trim().toLowerCase();
                $form.postJSON('/api/users', {
                    name: this.name.trim(),
                    email: email,
                    passwd: CryptoJS.SHA1(email + ':' + this.password1).toString()
                }, function (err, r) {
                    if (err) {
                        return $form.showFormError(err);
                    }
                    return location.assign('/');
                });
            }
        }
    });
    $('#vm').show();
});

</script>

{% endblock %}

{% block content %}

    <div class="uk-width-2-3">
        <h1>欢迎注册!</h1>
        <form id="vm" v-on="submit: submit" class="uk-form uk-form-stacked">
            <div class="uk-alert uk-alert-danger uk-hidden"></div>
            <div class="uk-form-row">
                <label class="uk-form-label">名字:</label>
                <div class="uk-form-controls">
                    <input v-model="name" type="text" maxlength="50" placeholder="名字" class="uk-width-1-1">
                </div>
            </div>
            <div class="uk-form-row">
                <label class="uk-form-label">电子邮件:</label>
                <div class="uk-form-controls">
                    <input v-model="email" type="text" maxlength="50" placeholder="[email protected]" class="uk-width-1-1">
                </div>
            </div>
            <div class="uk-form-row">
                <label class="uk-form-label">输入口令:</label>
                <div class="uk-form-controls">
                    <input v-model="password1" type="password" maxlength="50" placeholder="输入口令" class="uk-width-1-1">
                </div>
            </div>
            <div class="uk-form-row">
                <label class="uk-form-label">重复口令:</label>
                <div class="uk-form-controls">
                    <input v-model="password2" type="password" maxlength="50" placeholder="重复口令" class="uk-width-1-1">
                </div>
            </div>
            <div class="uk-form-row">
                <button type="submit" class="uk-button uk-button-primary"><i class="uk-icon-user"></i> 注册</button>
            </div>
        </form>
    </div>

{% endblock %}

In this way, we have completed the function of user registration:

 

3. Login

    User login is more complicated than user registration. Since the HTTP protocol is a stateless protocol, the server can only use cookies to track the user's state. Most web frameworks provide Session functionality to encapsulate cookies that save user state. The advantage of Session is that it is simple and easy to use, and user login information can be directly retrieved from the Session. The disadvantage of Session is that the server needs to maintain a mapping table in memory to store user login information. If there are more than two servers, the Session needs to be clustered. Therefore, it is difficult to expand the Web App using Session.

    We use the method of directly reading the cookie to verify the user's login. Every time the user accesses any URL, the cookie will be verified. The advantage of this method is to ensure that the server processes any URL without any state, which can be extended to multiple servers. . Since the server generates a cookie and sends it to the browser after successful login, it is necessary to ensure that the cookie will not be forged by the client. The key to implementing anti-forgery cookies is through a one-way algorithm (such as SHA1), for example:

    When the user enters the correct password to log in successfully, the server can get the user's id from the database and calculate a string as follows:

"用户id" + "过期时间" + SHA1("用户id" + "用户口令" + "过期时间" + "SecretKey")

    When the browser sends a cookie to the server, the information that the server can obtain includes:

  • userid

  • Expiration

  • SHA1 value

    If the expiration time is not reached, the server looks up the user password according to the user id, and calculates:

SHA1("用户id" + "用户口令" + "过期时间" + "SecretKey")

    And compare it with the MD5 in the browser cookie, if it is equal, it means that the user is logged in, otherwise, the cookie is forged. The key to this algorithm is that SHA1 is a one-way algorithm, that is, the SHA1 result can be calculated from the original string, but the original string cannot be deduced from the SHA1 result.

    So the login API can be implemented as follows:

@post('/api/authenticate')
def authenticate(*, email, passwd):
    if not email:
        raise APIValueError('email', 'Invalid email.')
    if not passwd:
        raise APIValueError('passwd', 'Invalid password.')
    users = yield from User.findAll('email=?', [email])
    if len(users) == 0:
        raise APIValueError('email', 'Email not exist.')
    user = users[0]
    # check passwd:
    sha1 = hashlib.sha1()
    sha1.update(user.id.encode('utf-8'))
    sha1.update(b':')
    sha1.update(passwd.encode('utf-8'))
    if user.passwd != sha1.hexdigest():
        raise APIValueError('passwd', 'Invalid password.')
    # authenticate ok, set cookie:
    r = web.Response()
    r.set_cookie(COOKIE_NAME, user2cookie(user, 86400), max_age=86400, httponly=True)
    user.passwd = '******'
    r.content_type = 'application/json'
    r.body = json.dumps(user, ensure_ascii=False).encode('utf-8')
    return r
# 计算加密cookie
def user2cookie(user, max_age):
    '''
    Generate cookie str by user.
    '''
    # build cookie string by: id-expires-sha1
    expires = str(int(time.time() + max_age))
    s = '%s-%s-%s-%s' % (user.id, user.passwd, expires, _COOKIE_KEY)
    L = [user.id, expires, hashlib.sha1(s.encode('utf-8')).hexdigest()]
    return '-'.join(L)

    For each URL handler, if we were to write the code to parse the cookie, it would cause the code to repeat many times.

    Use middle to parse the cookie before processing the URL, and bind the logged-in user to the  request object, so that the subsequent

The URL processing function can directly get the logged in user:

@asyncio.coroutine
def auth_factory(app, handler):
    @asyncio.coroutine
    def auth(request):
        logging.info('check user: %s %s' % (request.method, request.path))
        request.__user__ = None
        cookie_str = request.cookies.get(COOKIE_NAME)
        if cookie_str:
            user = yield from cookie2user(cookie_str)
            if user:
                logging.info('set current user: %s' % user.email)
                request.__user__ = user
        if request.path.startswith('/manage/') and (request.__user__ is None or not request.__user__.admin):
            return web.HTTPFound('/signin')
        return (yield from handler(request))
    return auth
# 解密cookie
@asyncio.coroutine
def cookie2user(cookie_str):
    '''
    Parse cookie and load user if cookie is valid.
    '''
    if not cookie_str:
        return None
    try:
        L = cookie_str.split('-')
        if len(L) != 3:
            return None
        uid, expires, sha1 = L
        if int(expires) < time.time():
            return None
        user = yield from User.find(uid)
        if user is None:
            return None
        s = '%s-%s-%s-%s' % (uid, user.passwd, expires, _COOKIE_KEY)
        if sha1 != hashlib.sha1(s.encode('utf-8')).hexdigest():
            logging.info('invalid sha1')
            return None
        user.passwd = '******'
        return user
    except Exception as e:
        logging.exception(e)
        return None

    So far, the functions of user registration and login are completed.

 

This article is an original article by the blogger, please indicate the source for reprinting!

https://my.oschina.net/u/3375733/blog/

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325452994&siteId=291194637