2016-11-18

Django 1.10.3 Tutorial : 로그인/로그아웃 처리하기


  • 명색이 관리자 화면인데, 아무나 들어와서 조작하면 안되겠지요? 이제는 마지막으로 로그인/로그아웃 처리를 해보겠습니다. 로그인/로그아웃은 세션을 이용해서 구현합니다.
  • 로그인 화면을 구성합니다. $PRJNAME/web/prjname/templates/mgmt/index.html 을 작성합니다.
    <!DOCTYPE html>
    <html lang="ko">
    <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta name="_xsrf" content="{{.xsrf_token}}" />
    <title>Management</title>
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
    <!-- HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries -->
    <!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
    <!--[if lt IE 9]>
      <script src="https://oss.maxcdn.com/html5shiv/3.7.2/html5shiv.min.js"></script>
      <script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
    <![endif]-->
    </head>
    <body>
    {% if messages %}
    <div class="alert alert-success alert-dismissible" role="alert">
      <button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">&times;</span></button>
      {% for message in messages %}
      {{ message }}
      {% endfor %}
    </div>
    {% endif %}
     
    <div class="container">
      <div style="text-align:center; margin-top:150px; margin-bottom: 40px"><h1>Management</h1></div>
      <form action="{% url 'mgmt_login' %}" method="post" class="form-horizontal" style="margin: 0 auto; max-width: 360px;">
        {% csrf_token %}
        <div class="form-group">
          <label for="userid" class="col-sm-3 control-label">아이디</label>
          <div class="col-sm-9">
            {{ form.userid }}
          </div>
        </div>
        <div class="form-group">
          <label for="passwd" class="col-sm-3 control-label">비밀번호</label>
          <div class="col-sm-9">
            {{ form.password}}
          </div>
        </div>
        <input type="submit" class="btn btn-primary btn-block" value="로그인" />
      </form>
    </div>
     
    <!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>
    <!-- Include all compiled plugins (below), or include individual files as needed -->
    <script src="/static/b/js/bootstrap.min.js"></script>
    </body>
    </html>
  • $PRJNAME/web/prjname/forms/mgmt/main.py 를 생성한다.
    # -*- coding: utf-8 -*-
    # ----------------------------------------------------------------------------
    # File : prjname/forms/mgmt/main.py
    # Create Date : 2016-01-26
    # ----------------------------------------------------------------------------
     
    # import re
    # from django.core.exceptions import ObjectDoesNotExist
    from django import forms
    # from ..models.users import UserModel
     
     
    class AdminLoginForm(forms.Form):
        userid   = forms.CharField(max_length=255, widget=forms.TextInput(attrs={'class':'form-control', 'required':True, 'autofocus':True, 'placeholder':'당신의 ID를 입력하세요...', }))
        password = forms.CharField(max_length=255, widget=forms.PasswordInput(attrs={'class':'form-control', 'required':True, 'placeholder':'비밀번호를 입력하세요...', }))
  • $PRJNAME/web/prjname/views/mgmt/main.py 를 생성합니다.
    # -*- encoding: utf-8 -*-
    # prjname/views/mgmt/main.py
     
    import hashlib
     
    from django.utils import timezone
    from django.contrib import messages
    from django.shortcuts import render, redirect
    from django.core.exceptions import ObjectDoesNotExist
     
    from ...models.admin import *
    from ...forms.mgmt.main import *
     
     
    def index(request):
        form = AdminLoginForm(request.POST)
        return render(request, 'mgmt/index.html', {'form': form})
     
     
    def login(request):
        form = AdminLoginForm(request.POST)
        if form.is_valid():
            try:
                hashed_password = hashlib.sha256(form.cleaned_data['password'].encode('utf-8')).hexdigest()
                admin = AdminModel.objects.get(userid=form.cleaned_data['userid'], password=hashed_password)
            except ObjectDoesNotExist:
                messages.add_message(request, messages.INFO, '아이디 또는 비밀번호가 틀렸습니다')
                return redirect('mgmt_index')
        else:
            messages.add_message(request, messages.INFO, '아이디 또는 비밀번호가 올바르지 않습니다.')
            return redirect('mgmt_index')
     
        admin.last_login = timezone.localtime(timezone.now())
        admin.save()
        request.session["admin_login_yn"] = "yes"
        request.session["admin_nick"] = admin.nick
        # request.session.set_expiry(0)
        return redirect('mgmt_admin_index')
     
     
    def logout(request):
        request.session["admin_login_yn"] = "no"
        del request.session["admin_login_yn"]
        return redirect('mgmt_index')
  • $PRJNAME/web/prjname/urls.py 다음을 추가합니다.
    ...
     
    from .views.mgmt import main  as mgmt_main
     
    ...
     
        url(r'^mgmt/$', mgmt_main.index, name='mgmt_index'),
        url(r'^mgmt/login$', mgmt_main.login, name='mgmt_login'),
        url(r'^mgmt/logout$', mgmt_main.logout, name='mgmt_logout'),
     
    ...
  • $PRJNAME/web/prjname/views/mgmt/helper.py 를 생성한다.
    # coding: utf-8
    # ------------------------------------------------------------------------------
    # mgmt/views/mgmt/helper.py
    # ------------------------------------------------------------------------------
     
    import hashlib
    import datetime
    from functools import wraps
     
    from django.shortcuts import redirect
    from django.contrib import messages
     
     
    def login_required(f):
        @wraps(f)
        def decorated_function(request, *args, **kwargs):
            if "admin_login_yn" not in request.session:
                # messages.add_message(request, messages.INFO, '불법적인 접근입니다. 먼저 로그인하세요~!!!')
                return redirect('mgmt_index')
                # return redirect('http://naver.com/')
                return f(request, *args, **kwargs)
            return f(request, *args, **kwargs)
        return decorated_function
  • base.html 의 Logout 부분을 아래와 같이 변경한다.
    <li><a href="{% url 'mgmt_logout' %}">Logout</a></li>
  • 마지막으로 $PRJNAME/web/prjname/views/mgmt/admin.py 에 있는 모든 메소드 위에 @login_required 데코레이터를 추가하고, 상단에 from .helper import login_required 를 추가한다.
  • 이제, http://IP주소:8080/mgmt/admin 으로 접근하면 Naver 로 이동하게 될 것이며, http://IP주소:8080/mgmt 으로 접근하면 로그인화면이 나오고, 로그인 처리도 될 것이다. 아울러, 관리자 화면의 우측 상단에 있는 Logout 을 클릭하면 로그아웃 처리되어서 로그인화면으로 이동할 것이다.


Django 1.10.3 Tutorial : 개인정보 양방향암호화(AES256) 하기


  • 이 예제에는 개인정보 데이터가 없긴 하지만, 핸드폰(Mobile)을 개인정보라 간주하고 암호화를 진행해보겠습니다.
  • $PRJNAME/web/prjname/models/crypto.py 파일을 생성한다. 여기에 암호화 함수가 들어갑니다.
    # -*- coding: utf-8 -*-
    # prjname/models/crypto.py
     
    # 참고 :
    # http://third9.tistory.com/261
    # http://blog.dokenzy.com/archives/1997
     
    import base64
    from Crypto.Cipher import AES
     
    from django.conf import settings
     
     
    class MyCrypto:
        def __init__(self):
            BLOCK_SIZE = 32
            PADDING = '|'
            # The key argument should be the AES key,
            # either 16, 24, or 32 bytes to select AES-128, AES-192, or AES-256.
            # secret = "140b41b22a29beb4061bda66b6747e14"
            secret = settings.SECRET_KEY[:32] # AES-256
            cipher = AES.new(secret)
     
            pad = lambda s: s + (BLOCK_SIZE - len(s.encode('utf-8')) % BLOCK_SIZE) * PADDING
     
            self.encodeAES = lambda s: base64.b64encode(cipher.encrypt(pad(s)))
            self.decodeAES = lambda e: cipher.decrypt(base64.b64decode(e)).decode("utf-8").rstrip(PADDING)
     
        def encodeAES(self, data):
            encoded = self.encodeAES(data)
            return encoded
     
        def decodeAES(self, data):
            decoded = self.decodeAES(data)
            return decoded
  • $PRJNAME/web/prjname/models/admin.py 에 다음을 추가합니다.
    ...
     
    from .crypto import MyCrypto
     
    ...
     
        def _get_mobile(self):
            crypto = MyCrypto()
            return crypto.decodeAES(self.mobile)
     
        def _set_mobile(self, value):
            crypto = MyCrypto()
            self.mobile = crypto.encodeAES(value)
     
        enc_mobile = property(_get_mobile, _set_mobile)
     
    ...
  • $PRJNAME/web/prjname/views/mgmt/admin.py 에서 관리자추가, 관리자수정에 관련된 메소드를 수정합니다.
    ...
     
    # 관리자 추가
    ...
            # admin.mobile = form.cleaned_data['mobile']
            admin.enc_mobile = form.cleaned_data['mobile']
    ...
    # 관리자 수정 폼
    ...
                 # 'mobile': admin.mobile }
                 'mobile': admin.enc_mobile }
    ...
    # 관리자 수정
    ...
            # admin.mobile = admin_mobile
            admin.enc_mobile = admin_mobile
    ...
  • $PRJNAME/web/prjname/templates/mgmt/index.html 에서
  • xyz/views/mgmt/admin/index.ejs 와 xyz/views/mgmt/admin/update_form.ejs 에서 admin.mobile 으로 표시된 부분을 admin.enc_mobile 으로 변경합니다.
  • DB의 내용을 모두 삭제한 후에, 관리자를 추가해봅니다. 그리고, DB에서 Nick 이 암호화되어 있는지도 봅시다.
    $ sqlite3 db.sqlite3
    SQLite version 3.15.0 2016-10-14 10:20:30
    Enter ".help" for usage hints.
    sqlite> select * from tb_admin;
    8|user1|03ac674216f3e15c761ee1a5e255f067953623c8b388b4459e13f978d7c846f4|nick1|t22TvgBZXvjlV6cu8Bi1P3kowOEPA052zOkEP9KIQM8=|2016-11-18|
    sqlite> .quit
    제대로 암호화되어서 무슨 내용인지 알아볼 수 없네요. ^^;

Django 1.10.3 Tutorial : 비밀번호 단방향암호화(SHA256) 하기


  • 정보통신망법, 개인정보보호법 등에 의하면 비밀번호는 단방향암호화(SHA256 등), 개인정보는 양방향암호화(AES256 등)를 하도록 되어 있습니다. 지금까지 작성한 예제는 이런 법에 맞지 않아서 서비스할 수 없겠네요. 여기에서는 비밀번호의 단방향 암호화를 구현해보도록 하겠습니다.
  • 먼저, 기존 DB에 들어있던 사용자 정보를 모두 삭제합시다.
    $ cd $PRJNAME/web
    $ sqlite db.sqlite3
    SQLite version 3.7.9 2011-11-01 00:52:41
    Enter ".help" for instructions
    Enter SQL statements terminated with a ";"
    sqlite> select * from tb_admin;
    1|testid1|passwd1|nick1|010-1111-1111|2016-11-16|
    2|testid2|passwd2|nick2|010-2222-2222|2016-11-16|
    3|testid3|passwd3|nick3|010-3333-3333|2016-11-16|
    4|testid4|passwd4|nick4|010-4444-4444|2016-11-16|
    5|testid5|passwd5|nick5|010-5555-5555|2016-11-16|
    sqlite> delete from tb_admin;
    sqlite> select * from tb_admin;
    sqlite> .quit
  • $PRJNAME/web/prjname/models/admin.py 에 다음을 추가합니다.
    ...
     
    import hashlib
     
    ...
     
        def _get_password(self):
            return self.password
     
        def _set_password(self, passwd):
            self.password = hashlib.sha256(passwd.encode('utf-8')).hexdigest()
     
     
        hashed_password = property(_get_password, _set_password)
     
    ...
  • $PRJNAME/web/prjname/views/mgmt/admin.py 에서 관리자추가, 비밀번호수정에 관련된 메소드를 수정합니다.
    ...
    # 관리자 추가
    ...
     
            # admin.password = form.cleaned_data['passwd1']
            admin.hashed_password = form.cleaned_data['passwd1']
     
    ...
     
    # 비밀번호 수정
    ...
            # admin.password = passwd1
            admin.hashed_password = passwd1
    ...
  • 이제, 관리자를 추가해보고, DB에서 비밀번호가 어떻게 들어가 있는지 확인해봅니다.
    > sqlite3 db.sqlite3
    SQLite version 3.15.0 2016-10-14 10:20:30
    Enter ".help" for usage hints.
    sqlite> select * from tb_admin;
    7|user1|3b1d7e9a7c37141350fb473fa099b8b18030cde1909f363e3758e52d4ea1a7b4|nick1|010-1111-1111|2016-11-18|
    sqlite> .quit
    비밀번호가 사람이 알아볼 수 없는 형태로 되어 있지요? 그러면 잘 된 것입니다. *^^*

Django 1.10.3 Tutorial : 관리자 삭제하기


  • $PRJNAME/web/prjname/views/mgmt/admin.py 에 다음을 추가한다.
    ...
     
    # 관리자 삭제
    def delete(request, id):
        AdminModel.objects.filter(id=id).delete()
        messages.add_message(request, messages.INFO, '해당 관리자를 정상적으로 삭제하였습니다.')
        return redirect('mgmt_admin_index')
     
    ...
  • $PRJNAME/web/prjname/urls.py 에 다음을 추가하고, 관리자 삭제를 해봅니다.
        url(r'^mgmt/admin/delete/(?P<id>[0-9]+)$', mgmt_admin.delete, name='mgmt_admin_delete'),
  • 여기까지 하면, 기본적인 CRUD 기능을 모두 작성할 수 있게 됩니다. *^^*

Django 1.10.3 Tutorial : 관리자 수정하기


  • $PRJNAME/web/prjname/templates/mgmt/admin/update_form.html 을 작성한다.
    <div class="modal-header">
      <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
      <h4 class="modal-title">관리자 수정</h4>
    </div>
    <div class="modal-body">
      <form name="update_form" action="/mgmt/admin/update" method="post">
        {% csrf_token %}
        <div class="form-group">
          <label>아이디</label>
          {{ form.userid }}
          {{ form.id }}
        </div>
        <div class="form-group">
          <label>별명 <small>(필수)</small></label>
          {{ form.nick }}
        </div>
        <div class="form-group">
          <label>핸드폰 <small>(필수)</small></label>
          {{ form.mobile }}
        </div>
        <div class="form-group" style="text-align: right">
          <input class="btn btn-primary" type="submit" value="관리자 수정" />
        </div>
      </form>
    </div>
  • 폼($PRJNAME/web/prjname/forms/mgmt/admin.py)에 다음을 추가한다.
    ...
     
    class AdminUpdateForm(forms.Form):
        id     = forms.CharField(widget=forms.HiddenInput)
        userid = forms.CharField(max_length=255, widget=forms.TextInput(attrs={'class':'form-control', 'readonly':True, }))
        nick   = forms.CharField(max_length=255, widget=forms.TextInput(attrs={'class':'form-control', 'required':True, }))
        mobile = forms.CharField(max_length=255, widget=forms.TextInput(attrs={'class':'form-control', 'required':True, }))
     
    ...
  • 뷰($PRJNAME/web/prjname/views/mgmt/admin.py)에 다음을 추가한다.
    ...
     
    # 관리자 수정 폼
    def update_form(request, id):
        admin = AdminModel.objects.get(id=id)
        data = { 'id': admin.id,
                 'userid': admin.userid,
                 'nick': admin.nick,
                 'mobile': admin.mobile }
        form = AdminUpdateForm(data)
        return render(request, 'mgmt/admin/update_form.html', {'form': form, 'admin_id': id})
     
    ...
  • $PRJNAME/web/prjname/urls.py 에 다음을 추가한다.
        url(r'^mgmt/admin/update_form/(?P<id>[0-9]+)$', mgmt_admin.update_form, name='mgmt_admin_update_form'),
  • 이제, 수정 버튼을 클릭하면 수정할 수 있는 폼이 나타나게 될 것이다. 마지막으로 실제로 수정을 처리하는 루틴을 작성하고 URL라우터를 등록하자.
  • $PRJNAME/web/prjname/views/mgmt/admin.py 에 다음을 추가한다.
    ...
     
    # 관리자 수정
    def update(request):
        form = AdminUpdateForm(request.POST)
        if form.is_valid():
            admin_id     = form.cleaned_data['id']
            admin_nick   = form.cleaned_data['nick']
            admin_mobile = form.cleaned_data['mobile']
     
            admin = AdminModel.objects.get(id=admin_id)
            admin.nick = admin_nick
            admin.mobile = admin_mobile
            admin.save()
     
            messages.add_message(request, messages.INFO, '관리자가 정상적으로 수정되었습니다.')
        else:
            # messages.add_message(request, messages.INFO, '관리자 입력폼의 값을 제대로 입력해주세요.')
            messages.add_message(request, messages.INFO, form.errors)
        return redirect('mgmt_admin_index')
     
    ...
  • $PRJNAME/web/prjname/urls.py 에 다음을 추가하고, 수정 작업을 진행해보자.
        url(r'^mgmt/admin/update$', mgmt_admin.update, name='mgmt_admin_update'),