Python Learning Notes: Day 10 User Registration and Login

Posted by fortnox007 on Wed, 15 May 2019 16:56:40 +0200

Preface

Recently, in the study of in-depth learning, several models have been run out, but the foundation of Pyhton is not solid enough. Therefore, we began to tutor Python. Everyone recommended Liao Xuefeng's course. Therefore, we started to learn, but whether optics is useful or not. We also need to discuss with you. Therefore, write down these posts. Liao Xuefeng's course is connected here: Xue Feng Liu
For an introduction to Python, and its historical stories and operational mechanisms, see this article: python introduction
For installation of Python, see this article: Python installation
Python's operating mode and input and output can be seen in this article: Python IO
The basic concepts of Python can be found in this article: Python Foundation
For an introduction to Python strings and encoding, see this article: Python String and Coding
Python basic data structure: list and tuple introduction, you can see this article: Python list and tuple
Python Control Statement Introduction: ifelse, see this article: Python Conditional Judgment
Python control statement introduction: loop implementation, you can see this article: Python Loop Statement
Python Data Structure: Introduction to dict and set Python Data Structures dict and set
Python function correlation: Python function
Python high-order properties: Python Advanced Features
Python higher order functions: Python higher order functions
Python anonymous function: Python anonymous functions
Python Decorator: Python Decorator
Python partial function: Python partial function
Python module: Python module
Python object-oriented programming (1): Python Object-Oriented
Python object-oriented programming (2): Python Object Oriented (2)
Python object-oriented programming (3): Python Object Oriented (3)
Python object-oriented programming (4): Pyhton Object Oriented (4)
Python Object-Oriented Advanced Programming (Part 1): Python Object-Oriented Advanced Programming (Part I)
Python Object-Oriented Advanced Programming (upper and middle level): Python Object-Oriented Advanced Programming (Middle and Upper)
Python Object-Oriented Advanced Programming (Lower and Middle): Python Object-Oriented Advanced Programming (Lower and Middle)
Python Object-Oriented Advanced Programming (End): Python Object-Oriented Advanced Programming (End)
Python error debugging (start): Python Debugging: Start
Python error debugging (acceptance): Python debugging:
Python error debugging (turn): Python debugging: turn
Python error debugging python debugging:
Python file IO programming: Python file IO
Python file IO programming 2: Python file IO2
Python file IO programming 3: PYthon file IO3
Python processes and threads (starting): Python processes and threads start
Python processes and threads (undertake): Python processes and thread commitment
Python processes and threads (rotation): Python process and thread rotation
Python processes and threads (combined): Python Process and Thread Combination
Python regular expressions: Python regular expressions
Python Learning Notes: Common Internal Modeling Module 1: Python Learning Notes: Common Internal Modeling Module 1
Python Learning Notes: Common Internal Modeling Module 2: Python Learning Notes: Common Internal Modeling Module 2
Python Learning Notes: Common Internal Modeling Module 3: Python Learning Notes: Common Internal Modeling Module 3
Python Learning Notes: Common Internal Modeling Module 4: Python Learning Notes: Common Internal Modeling Module 4
Python Learning Notes: Common Internal Modeling Module 5: Python Learning Notes: Common Internal Modeling Module 5
Python Learning Notes: Common Internal Modeling Module 6: Python Learning Notes: Common Internal Modeling Module 6
Python Learning Notes: Third Party Module 1: Common third-party modules for Python
Python Learning Notes: Third Party Module 2: Common third-party modules for Python
Python Learning Notes: Third Party Module 3: Common third-party modules for Python
Pytho Learning Notes: Network Programming: Python Network Programming
Python Learning Notes: E-mail: Python e-mail 1
Python Learning Notes: SMTP Server: Python SMTP Server
Python Learning Notes: POP3 Server: Python POP3 Server
Python Learning Notes: Python Database Python database 1
Python Learning Notes: Python Database 2 Python database 2
Python Learning Notes: web Development 1 Python Learning Notes: web Development 1
Python Learning Notes: web Development 2 Python Learning Notes: web Development 2
Python Learning Notes: web Development 3 Python Learning Notes: web Development 3
Python Learning Notes: Asynchronous IO (1) Python Learning Notes: Asynchronous IO (1)
Python Learning Notes: Asynchronous IO (2) Python Learning Notes: Asynchronous IO (2)
Python Learning Notes: Asynchronous IO (3) Python Learning Notes: Asynchronous IO (3)
Python Learning Notes: Development of Day 1-2 Python Learning Notes: Day1-2 Development
Python Learning Notes: Day 3 ORM Python Learning Notes: Day3 ORM
Python Learning Notes: Day 4 Model Python Learning Notes: Day4Model
Python Learning Notes: Day 5 web Framework PYTHON Learning Notes: DAy5
Python Learning Notes: Day 6 Profile Python Learning Notes: Day 6 Profile
Python Learning Notes: Day 7 Writes MVC Python Learning Notes: Day 7 Writes MVC
Python Learning Notes: Day 8 Building Front End Python Learning Notes: Day 8 Building Front End
Python Learning Notes: Day 9 Writes API Writing API

Catalog

User registration and login

User management is a problem that most Web sites need to solve. User management involves user registration and login.

User registration is relatively simple, we can first register users through the API to achieve this function:

_RE_EMAIL = re.compile(r'^[a-z0-9\.\-\_]+\@[a-z0-9\-\_]+(\.[a-z0-9\-\_]+){1,4}$')
_RE_SHA1 = re.compile(r'^[0-9a-f]{40}$')

@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

Note that the user password is a 40-bit Hash string computed by SHA1 passed by the client, so the server does not know the user's original password.

Next, you can create a registration page to let the user fill in the registration form, and then submit the data to the registered user's API:

{% extends '__base__.html' %}

{% block title %}register{% 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('Please enter your name.');
                }
                if (! validateEmail(this.email.trim().toLowerCase())) {
                    return $form.showFormError('Please enter the correct one. Email address');
                }
                if (this.password1.length < 6) {
                    return $form.showFormError('Password length is at least 6 characters');
                }
                if (this.password1 !== this.password2) {
                    return $form.showFormError('Two inconsistent passwords');
                }
                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>Welcome to register!</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">Name:</label>
                <div class="uk-form-controls">
                    <input v-model="name" type="text" maxlength="50" placeholder="Name" class="uk-width-1-1">
                </div>
            </div>
            <div class="uk-form-row">
                <label class="uk-form-label">E-mail:</label>
                <div class="uk-form-controls">
                    <input v-model="email" type="text" maxlength="50" placeholder="your-name@example.com" class="uk-width-1-1">
                </div>
            </div>
            <div class="uk-form-row">
                <label class="uk-form-label">enter your password:</label>
                <div class="uk-form-controls">
                    <input v-model="password1" type="password" maxlength="50" placeholder="enter your password" class="uk-width-1-1">
                </div>
            </div>
            <div class="uk-form-row">
                <label class="uk-form-label">Repeat password:</label>
                <div class="uk-form-controls">
                    <input v-model="password2" type="password" maxlength="50" placeholder="Repeat password" 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> register</button>
            </div>
        </form>
    </div>

{% endblock %}

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

User login is more complicated than user registration. Because HTTP protocol is a stateless protocol, the server can only track user status through cookie. Most Web frameworks provide Session functionality to encapsulate cookies that preserve user status.

The advantage of Session is that it is easy to use and can directly extract user login information from 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, it needs to cluster Session. Therefore, it is difficult to expand Web App using Session.

We validate user login by reading cookies directly. Every time a user accesses any URL, the cookie is validated. The advantage of this way is to ensure that any URL handled by the server is stateless and can be extended to multiple servers.

Since a cookie is generated by the server and sent to the browser after successful login, it is necessary to ensure that the cookie is not forged by the client.

The key to anti-counterfeiting cookie s is through a one-way algorithm (such as SHA1). Examples are as follows:

When the user enters the correct password and logs in successfully, the server can retrieve the user's id from the database and calculate a string as follows:
"User id"+ "expiration time"+SHA1("user id"+ "user password"+ "expiration time"+ "SecretKey")
When the browser sends cookie s to the server, the information the server can get includes:
User id
Default time
• SHA1 value

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

SHA1("user id"+ "user password"+ "expiration time"+ "SecretKey")

Compared with hash in browser cookie, if it is equal, the user has logged in. Otherwise, cookie is forged.

The key of this algorithm is that SHA1 is a one-way algorithm, which can calculate the result of SHA1 from the original string, but can not deduce the original string from the result of SHA1.

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

Compute the encrypted cookie:

def user2cookie(user, max_age):
    # 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 all write code that parses cookie s, it will cause code to repeat many times.

Using middle s, cookie s are parsed out before processing URLs and the logged-in user is bound to the request object, so that the subsequent URL processing function can be directly taken to 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
        return (yield from handler(request))
    return auth

Decrypt 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

In this way, we have completed the function of user registration and login.

Topics: Python SHA1 Programming Session