デジタル忍者ブログ

デジタル忍者ブログ

2019/05/13

Python3.6 + Django2.0で2段階認証を実装する。

安全性を高めるためにGoogle Authenticatorを使って2段階認証の実装を、

Python3.6 + Django2.0で行ってみた。


以下のURLにあるソースコードを見ながら実装した。

https://github.com/shinsaka/googleauthenticator_demo


尚、以下に記載しているコードは2段階認証の主な処理の部分だけの紹介であり、Djangoを使って、簡単なアプリケーションを作成した経験者向けである。

また、当ソースコードにおいて、意見があればコメントしていただきたい。

当ソースコードをまねして実装して、何かしら問題があったとしても、責任を負いません。

自己責任でお願いします。


login.html(一部抜粋)

          <form method="post" action="{% url 'manager:login' %}">
            {% csrf_token %}
            <table>
              <tr>
                <td>ログインID</td>
                <td><input type="text" name="username" required id="id_username" value="{{username}}"/></td>
              </tr>
              <tr>
                <td>パスワード</td>
                <td><input type="password" name="password" required id="id_password" value="{{password}}"/></td>
              </tr>
              {% if twoAuth == 1 %}
              <tr>
                <td>トークン</td>
                <td><input type="password" name="auth_token" required id="id_auth_token" /></td>
              </tr>
              {% endif %}
            </table>
            <input type="hidden" name="twoAuth" id="id_twoAuth" value="{{twoAuth}}" />
          <input type="submit" value="login" />
        </form>

行った実装について、

ログインIDとパスワードを入力し、ログインボタンを押す。

2段階認証が設定してない場合は、そのままログイン。

2段階認証が設定済みであれば、トークンの入力項目が表示され、2段階認証のコードが求められる。


twoAuth.html(一部抜粋)

  <form method="post" action="{% url 'manager:twoAuth' %}">
    {% csrf_token %}
    {% if request.session.secret == 'registried' %}
    <div>
      <p>2段階認証は設定済みです。</p>
      <p>解除すると、なりすまし等の不正アクセスが発生するおそれがあります。</p>
      <input type="submit" value="解除" />
    </div>
    {% else %}
    <p style="color: red;">{{ msg }}</p>
    <div>
      <p>1. 下記のQRコードを読取り、Google認証システムへ登録してください。</p>
      <img src="data:image/png;base64,{{ request.session.img }}" alt="qrcode" width="200"/>
    </div>
    <div>
      <p>2. Google認証システムに表示されているコードを入力してください。</p>
      <input type="text" name="token" required id="id_token" />
    </div>
    <div>
      <input type="hidden" name="code" id="id_code" value="{{code}}" />
      <input type="submit" value="設定" />
    </div>
    {% endif %}
  </from>

こちらは2段階認証の設定処理。

QRコードを読み取り、コードを入力して設定する方法とした。

もちろん解除もできるようにした。


model.py

class TwoAuth(models.Model):
  username = models.CharField(max_length=100)
  secret = models.CharField(max_length=64)

本来Djangoには、ログインユーザを管理するためのUserというmodelを使って認証を行うことができるが、User.usernameと特定のコードだけを持つTwoAuthを作成した。

(もっといい方法があるけど、知恵不足である。)


view.py ( 2段階認証設定側)

def CMStwoAuth(request):
  if not request.user.is_authenticated:
    return HttpResponseRedirect('/cmsadmin/logout')

  if request.method == 'POST':
    if request.session['secret'] != 'registried':
      token = request.POST['token']
      inputToken = ' '.join([token[:3], token[3:]])
      value = str(utils.get_token(request.session['secret'])).zfill(6)
      checkToken = ' '.join([value[:3], value[3:]])
    
      if inputToken != checkToken:
        return render(request, 'manager/twoAuth.html', {'msg': u'トークンが一致しません。'})
      data = TwoAuth()
      data.username = request.user.username
      data.secret = request.session['secret']
      data.save()
      request.session['secret'] = 'registried'
    else:
      data = TwoAuth.objects.filter(username=request.user.username).delete()
      secret = utils.get_secret()
      request.session['secret'] = secret
      request.session['img'] = utils.get_image_b64(utils.get_auth_url(request.user.username, secret))
  else:
    try:
      data = TwoAuth.objects.filter(username=request.user.username)
    except TwoAuth.DoesNotExist:
      data = TwoAuth()

    if data is None or len(data) == 0:
      secret = utils.get_secret()
      request.session['secret'] = secret
      request.session['img'] = utils.get_image_b64(utils.get_auth_url(request.user.username, secret))
    else:
      request.session['secret'] = 'registried'
      
  return render(request, 'manager/twoAuth.html', {'msg': ''})
TwoAuthでレコードが存在しない場合は2段階認証が設定されていないため、
新たなsecretコードとQRコードをセッションに保持して画面を表示する。

2段階認証を設定したら、そのsecretコードをTwoAuthに保管する。
解除する場合は、TwoAuth内のsecretを持つレコードを削除する。


view.py (ログイン処理)
def loginAuth(request, username, password):
  user = authenticate(request, username = username, password = password)
  if user is not None:
    login(request, user)
    return user
  else:
    return None
  
def CMSLogin(request):
  if request.method == 'POST':
    username = request.POST['username']
    password = request.POST['password']
    print(request.POST['twoAuth'])
    if request.POST['twoAuth'] == '0':
      try:
        data = TwoAuth.objects.filter(username=username)
        if data is not None and len(data) != 0:
          return render(request, 'manager/login.html', { 'msg': '', 'twoAuth': 1, 'username': username, 'password': password })
        else:
          user = loginAuth(request, username, password)
          if user is not None:
            return render(request, 'manager/toppage.html', {'user': user })
          else:
            return render(request, 'manager/login.html', { 'msg': u'ユーザID、あるいはパスワードが一致しません。', 'twoAuth': 0, 'username': '', 'password': ''})
      except TwoAuth.DoesNotExist:
        user = loginAuth(request, username, password)
        if user is not None:
          return render(request, 'manager/toppage.html', {'user': user })
        else:
          return render(request, 'manager/login.html', { 'msg': u'ユーザID、あるいはパスワードが一致しません。', 'twoAuth': 0, 'username': '', 'password': ''})
    else:
      try:
        data = TwoAuth.objects.filter(username=username)
        token = request.POST['auth_token']
        inputToken = ' '.join([token[:3], token[3:]])
        value = str(utils.get_token(data[0].secret)).zfill(6)
        checkToken = ' '.join([value[:3], value[3:]])
        if inputToken == checkToken:
          user = loginAuth(request, username, password)
          if user is not None:
            return render(request, 'manager/toppage.html', {'user': user })
          else:
            return render(request, 'manager/login.html', { 'msg': u'ユーザID、あるいはパスワードが一致しません。', 'twoAuth': 0, 'username': '', 'password': ''})
        else:
          return render(request, 'manager/login.html', { 'msg': u'ユーザID、あるいはパスワードが一致しません。', 'twoAuth': 0, 'username': '', 'password': ''})
      except TwoAuth.DoesNotExist:
        return render(request, 'manager/login.html', { 'msg': u'ユーザID、あるいはパスワードが一致しません。', 'twoAuth': 0, 'username': '', 'password': ''})
  else:
    return render(request, 'manager/login.html', { 'msg': '', 'twoAuth': 0, 'username': '', 'password': '' })

ログイン処理が、それなりに汚いコードになってしまった。

(もう少し手直ししたいところだが、残念なことにエレガントに仕上げる知恵がない。)



Comment Form

コメント内容(必須)

Comment

2024年1月13日3:56  Dopchoolf@hmaill.xyz

管理者がコメントの内容を確認中・・・