Django + redis Publie une URL dédiée valable 1 heure

environnement

python:2.7 Django:1.6 pip install redis

Moment auquel vous souhaitez émettre une URL dédiée

En ce qui concerne les pages dédiées, la connexion + les cookies sont courants avec les comptes, mais il y a des moments où vous souhaitez émettre une URL dédiée pour chaque utilisateur sans vous connecter. Par exemple, nous ne délivrons pas de compte aux utilisateurs car il est courant de ne pas émettre d'identifiant de connexion pour les e-mails au moment de l'inscription initiale ou pour les jeux récents sur smartphone.

Attaques et contre-mesures possibles

J'ai peur des attaques par dictionnaire et des attaques à tour de rôle, donc ça devrait être bien si vous prenez des mesures contre cela

  1. Pour contrer les attaques par dictionnaire, la chaîne de caractères de l'URL doit être une chaîne de caractères jetable qui ne peut pas être déduite de l'ID utilisateur.
  2. Mettez une somme de contrôle dans la chaîne de caractères jetables émise en 1 comme contre-mesure contre les attaques à tour de rôle

↓ Il est probablement plus rapide de lire le code

m.py


from uuid import uuid4

# 1.Émission d'une pièce d'identité jetable
_uuid4 = str(uuid4())
print 'uuid4:', _uuid4

# 2.Pouce de contrôle d'identité jetable
uuid4_char_list = [ord(_char) for _char in _uuid4]
print 'uuid4_char_list:', uuid4_char_list
checksum = sum(uuid4_char_list)
print 'checksum:', checksum

# 3.Émission d'URL
print "http://hoge/page?token={}%20cs={}".format(_uuid4, checksum)

Résultat d'exécution


> python m.py 
uuid4: 6da25bb0-5d9c-4c1e-becc-51e3d5078fe4
uuid4_char_list: [54, 100, 97, 50, 53, 98, 98, 48, 45, 53, 100, 57, 99, 45, 52, 99, 49, 101, 45, 98, 101, 99, 99, 45, 53, 49, 101, 51, 100, 53, 48, 55, 56, 102, 101, 52]
checksum: 2606
http://hoge/page?token=6da25bb0-5d9c-4c1e-becc-51e3d5078fe4%20cs=2606

Si vous examinez ce mécanisme du point de vue de la sécurité ...

Lors de la révision, on dit souvent: «N'est-il pas dangereux d'être analysé comme une somme de contrôle? 』
Je vais arranger la logique en endurant l'endroit où je veux dire qu'il n'y a pas de pirate étrange qui analyse notre système dépeuplé.

m.py


# -*- coding: utf-8 -*-
from __future__ import absolute_import, unicode_literals

import binascii
from uuid import uuid4


def checksum_base64_crc(_str):
    """
Inverse la chaîne saisie, l'encode en base64 et renvoie la somme de contrôle crc32
    :param _str: str
    :rtype : int
    """
    #Inverser
    _str_r = _str[::-1]

    #base64 encoder et prendre la somme de contrôle crc32
    return binascii.crc32(binascii.b2a_base64(_str_r))


# 1.Émission d'une pièce d'identité jetable
_uuid4 = str(uuid4())
print 'uuid4:', _uuid4

# 2.Pouce de contrôle d'identité jetable
checksum = checksum_base64_crc(_uuid4)
print 'checksum:', checksum

# 3.Émission d'URL
print "http://hoge/page?token={}%20cs={}".format(_uuid4, checksum)

Résultat d'exécution 2


>python m.py 
uuid4: 6a1d87e0-0518-4aa0-a2ca-cced091f254b
checksum: -2147023629
http://hoge/page?token=6a1d87e0-0518-4aa0-a2ca-cced091f254b%20cs=-2147023629

Gérez les URL émises par redis.

Si les jetons sont trop émis, j'ai peur que la base de données augmente. Il est pratique de gérer des données qui disparaissent avec le temps et qui n'ont aucun problème avec redis. Étant donné que les données disparaîtront avec le temps, ce sera plus facile plus tard si vous conservez l'URL émise comme contre-mesure contre les demandes dans le journal.

Produit fini

token_manager.py


# -*- coding: utf-8 -*-
from __future__ import absolute_import, unicode_literals
import binascii
from redis import Redis
from uuid import uuid4


def checksum_base64_crc(_str):
    """
Inverse la chaîne saisie, l'encode en base64 et renvoie la somme de contrôle crc32
    :param _str: str
    :rtype : int
    """
    #Inverser
    _str_r = _str[::-1]

    #base64 encoder et prendre la somme de contrôle crc32
    return binascii.crc32(binascii.b2a_base64(_str_r))


class IncorrectCheckSumError(Exception):
    #Somme de contrôle incorrecte
    pass


class TokenExpiredError(Exception):
    #Le jeton a expiré
    pass


class TokenRepository(object):
    """
Le jeton pour l'URL est stocké et géré dans Redis pendant une certaine période de temps
    """
    EXPIRE_SEC = 3600
    _KEY_BASE = "project_name/url/disposable/{}"
    _cli = None

    @property
    def client(self):
        """
        :rtype : Redis
        """
        if self._cli is None:
            self._cli = Redis(host='localhost', port=6379, db=10)
            return self._cli
        return self._cli

    def get_key(self, token):
        return self._KEY_BASE.format(str(token))

    def set(self, token, value):
        self.client.setex(self.get_key(token), value, self.EXPIRE_SEC)
        return

    def get(self, token):
        """
Renvoie la valeur associée
        :param token:
        :return: str
        """
        value = self.client.get(self.get_key(token))
        return str(value)

    def exist(self, token):
        """
Vérifier si le jeton existe dans le référentiel
        :param token: unicode or string
        :rtype: bool
        """
        return bool(self.get(token))


class TokenManager(object):
    @classmethod
    def validate(cls, token, check_sum):
        """
Vérifiez si le jeton est correct
        :param token: unicode or str
        :param check_sum: int
        :rtype : bool
        """
        token = str(token)
        check_sum = int(check_sum)

        #Vérifiez si le jeton est correct avec la somme de contrôle
        if checksum_base64_crc(token) != check_sum:
            raise IncorrectCheckSumError

        user_id = TokenRepository().get(token)
        return bool(user_id)

    @classmethod
    def generate(cls, user_id):
        """
Générer un jeton et une somme de contrôle
        :param user_id:
        :return: str, int
        """
        #produire
        token = str(uuid4())

        #Jeton et utilisateur générés_Enregistrer l'identifiant en l'associant à redis
        TokenRepository().set(token, user_id)

        return token, checksum_base64_crc(token)

    @classmethod
    def get_user_id_from_token(cls, token, check_sum):
        """
du jeton à l'utilisateur_Soustraire l'ID
        :param token: str or unicode
        :param check_sum: int
        :rtype: str
        """
        token = str(token)
        if not cls.validate(token, check_sum):
            raise TokenExpiredError
        return TokenRepository().get(token)


# 1.Émettre une URL jetable
url_base = "http://hogehoge.com/hoge?token={}&cs={}"
user_id = "1111222333"
_token, check_sum = TokenManager.generate(user_id)
url = url_base.format(_token, str(check_sum))
print url

# 2.Test routier
assert TokenManager.get_user_id_from_token(_token, check_sum) == str(user_id)

Vue côté Django


# -*- coding: utf-8 -*-
from __future__ import absolute_import, unicode_literals
from django.http import HttpResponse
from django.views.generic import View


class ExclusiveView(View):
    def get(self, request, *args, **kwargs):
        # HTTP GET
        token = request.GET.get("token", None)
        check_sum = request.GET.get("cs", None)

        #du jeton à l'utilisateur_Soustraire l'ID
        error_message = None
        message = None
        try:
            user_id = TokenManager.get_user_id_from_token(token, check_sum)
            message = "Votre utilisateur_id est{}est".format(user_id)
        except IncorrectCheckSumError:
            error_message = "Paramètre illégal"
        except TokenExpiredError:
            error_message = "Page expirée"
        if error_message:
            message = error_message

        return HttpResponse(message,
                            mimetype='text/html; charset=UTF-8',
                            *args, **kwargs)

Résultat d'exécution

Émission d'URL


>python m.py 
http://hogehoge.com/hoge?token=e359b20e-4c60-48da-9294-2ea9fcca0a6c&cs=-2066385284

■ Accès avec navigateur Système normal スクリーンショット 2015-10-15 15.20.04.png

■ Accès avec navigateur Système anormal スクリーンショット 2015-10-15 15.20.16.png

Recommended Posts

Django + redis Publie une URL dédiée valable 1 heure
Créez un modèle pour votre planning Django
Commandes pour créer un nouveau projet django
Émettre une URL signée avec AWS SQS
Ecrire brièvement if-else du template Django
Créez un tableau de bord pour les appareils réseau avec Django!