User Log-in operations (Kullanıcı giriş işlemleri)
Password Hashing
User
modelindepassword_hash
alanını tanımladık ancak password hashing işlemini gerçekleştirmedik.Hashing
; matematiksel bir fonksiyon kullanarak belirli bir string veya karakterlerden bir değer veya değerler üretmektir.- Password Hashing ise parolaların belirli fonksiyonlar kullanılarak mevcut parola stringi yerine daha güçlü, karmaşık, güvenli değer üretme işlemidir.
- Werkzeug'un bize sunduğu method yardımı ile projemizde parolalarımızı güvenli şekilde veritabanında tutacağız.
xxxxxxxxxx
>>> from werkzeug.security import generate_password_hash
>>> hashed_password = generate_password_hash("gizli-parolam")
>>> hashed_password
'pbkdf2:sha256:50000$xhYmUxv5$5094f0409457656a174c65b7807381751a2c261b20220c9b0319cc713eb603c0'
- Gördüğünüz üzere
gizli-parolam
stringine karşılık üretilen değer oldukça güçlü. - Her seferinde farklı string üretilir. Böylece aynı parolaya sahip kullanıcılar için hash sonucu farklı olur ve tespit edilmesi imkansız değerler üretilir.
xxxxxxxxxx
>>> from werkzeug.security import check_password_hash
>>> check_password_hash(hashed_password, 'gizli-parolam')
True
>>> check_password_hash(hashed_password, 'cok-cok-gizli-parolam')
False
- Hash işlemi yapılmış parolanın kontrolü için de
check_password_hash()
methodunu kullanıyoruz. - Şimdi password hash işlemini
User
modelimize uygulayalım.myapp/models.py
xxxxxxxxxx
from werkzeug.security import generate_password_hash, check_password_hash
class User(db.Model):
#...
def set_password(self, password):
"""Parola için hash oluşturur"""
self.password_hash = generate_password_hash(password)
def check_password(self, password):
"""Parolayı kontrol et"""
return check_password_hash(self.password_hash, password)
- Bu methodlar yardımı ile kullanıcı güçlü ve güvenli bir parolaya sahip olacak.
flask shell
ile terminalden Shell ortamına geçelim.
xxxxxxxxxx
>>> User
<class 'myapp.models.User'>
>>> u = User(username='adnan', email='adnan@kaya.com')
>>> u.set_password('cok-gizli-parolam')
>>> u.password_hash
'pbkdf2:sha256:50000$IqvJtY5p$d3c2b06a8c7cb79fc40bf267863b3afc9f524366579bca8d899f0597df97e908'
>>> u.check_password('parolan-guclu-mu-?')
False
>>> u.check_password('cok-gizli-parolam')
True
Flask Log-in kullanımı
Flask Log-in Extension'ı:
- Kullanıcı log-in olmuşsa, görebileceği sayfalar arasında tekrar tekrar giriş yapmadan (log-in olmadan) gezinebilir.
Remember me
(Beni hatırla) fonksiyonelliği sağlar. Böylece browser kapansa bile giriş yapmaya olanak sağlar.pip install flask-login
ile extension'ı kuruyoruz.- Diğer extension'lar gibi Flask-Login de
myapp/__init__.py
içerisinde application instance oluşturulduktan sonra eklenecek.
xxxxxxxxxx
from flask_login import LoginManager
#...
app = Flask(__name__)
#...
login = LoginManager(app)
Flask-Login için
User
modelin hazırlanması
is_authenticated
: Kullanıcı geçerli yetkilere sahip ise True değilse False döner.is_active
: Kullanıcı aktif ise True değilse False döner.is_anonymous
: Kullanıcı sıradan kullanıcı ise False, değilse True döner.get_id()
: Kullanıcı için unique identifier dönen method.- Yukarıdaki dörtlüyü
UserMixin
sınıfı ile kolaycaUser
modelimize implement edebiliriz.myapp/models.py
xxxxxxxxxx
# ...
from flask_login import UserMixin
class User(UserMixin, db.Model):
# ...
- Burada ayrıca pythondaki multiple inheritance örneğini de görmektesiniz.
user_loader Fonksiyonu.
- Flask Login, Flask'in user sessionında(uygulamaya bağlanan her kullanıcı için atanmış depolama alanı) giriş yapan kullanıcının unique ID'sini izler.
- Kullanıcı sayfayı değiştirdikçe Flask Login user session'dan kullanıcı ID'sini okur ve kullanıcıyı hafızaya yükler.
- Flask Login'in kullanıcıyı ID'sinden yüklemesi için
myapp/models.py
modülümüzeload_user
methodu ekleyeceğiz.
xxxxxxxxxx
from myapp import login
# ...
user_loader .
def load_user(id):
return User.query.get(int(id))
user_loader
Flask Login extension'ı ile kayıtlıdır.- Flask Login
id
'yi string olarak fonksiyona göndereceği için database query işleminde integer'a convert ettik.
Projemizde Kulanıcı Giriş İşlemleri (User Login Operations)
myapp/routes.py
xxxxxxxxxx
from flask_login import current_user, login_user
route('/login', methods=['GET', 'POST']) .
def login():
"""Kullanıcı login isteklerini karşılayacak method"""
if current_user.is_authenticated:
return redirect(url_for('index'))
form = LoginForm()
if form.validate_on_submit():
user = User.query.filter_by(username=form.username.data).first()
if user is None or not user.check_password(form.password.data):
flash("Geçersiz kullanıcı adı veya parola")
return redirect(url_for('login'))
login_user(user, remember=form.remember_me.data)
return redirect(url_for('index'))
return render_template('login.html', title="Giriş Yap", form=form)
current_user
: Flask Login extension'ının bize sağladığı, istemciyi(client) temsil eden user objesidir.is_authenticated
daha önce bahsedildiği gibi kullanıcı login olmuşsa True değilse False dönen özelliktir(property).filter_by()
methodunu kullanarak formdan gelen kullanıcı adıyla database query çalıştırılıyor vefirst()
methodu ile query tamamlanıyor.first()
methodu unutulmamalı.
xxxxxxxxxx
>>> user = User.query.filter_by(username='adnan').first()
>>> type(user)
<class 'myapp.models.User'>
>>> user = User.query.filter_by(username='adnan')
>>> type(user)
<class 'flask_sqlalchemy.BaseQuery'>
- Gördüğünüz üzere tipler farklı. query tamamlanması için first( ) methodunu da yazacağız.
User
modele yazdığımızcheck_password()
methodu yardımı ile formdan gelen password bigisini kontrol ediyoruz. username veya password geçersiz iselogin
ekranına yönlendiriyoruz.login_user()
methodu da Flask Login extension'ının bize sağladığı bir method. Aldığıuser
objesi ile login işlemini tamamlar.
Log-out (Kullanıcı çıkış) işlemi:
myapp/routes.py
xxxxxxxxxx
from flask_login import current_user, login_user, logout_user
route('/logout') .
def logout():
"""Kullanıcı logout isteklerini karşılar"""
logout_user()
return redirect(url_for('index'))
- Kullanıcılara log-in & log-out linklerini göstermek için,
base.html
'de düzenleme yapalım.
xxxxxxxxxx
<td><a href="{{url_for('index')}}">Anasayfa</a></td>
{% if current_user.is_anonymous %}
<td><a href="{{url_for('login')}}">Giriş Yap</a></td>
{%else%}
<td><a href="{{url_for('logout')}}">Çıkış Yap</a></td>
{%endif%}
- Eğer kullanıcı log-in olmamışsa anonymous'tur. Giriş Yap linki görünecektir.
Eğer kullanıcıyı login olmaya mecbur bırakmak istiyorsanız:
myapp/__init__.py
içerisine şu satırı ekleyin.
xxxxxxxxxx
# ...
login = LoginManager(app)
login.login_view = 'login'
- Bu satır ile beraber yetki gerektiren sayfalarda gezinmek için login olmak mecburi hale getirildi.
Belirli sayfalara yetki sınırlaması eklemek istiyorsanız
@app.route
'dan sonra@login_required
decorator ekleyerek, log-in olmayan kullanıcılara sınırlama getirebilirsiniz.myapp/routes.py
xxxxxxxxxx
from flask_login import login_required
route('/') .
route('/index') .
def index():
# ...
- Eğer kullanıcı
/index
sayfasına istek yaparsa@login_required
decorator, URL sonuna bir string ekleyecek. URL :/login?next=%2Findex
olacak.next
stringi orjinal URL'e ayarlanmıştır. Böylecek kullanıcı log-in olduktan sonra uygulamaredirect
ederek next'ten sonra gelen sayfaya yönlendirme yapabilir.
xxxxxxxxxx
from flask import request
from werkzeug.urls import url_parse
route('/login', methods=['GET', 'POST']) .
def login():
# ...
if form.validate_on_submit():
user = User.query.filter_by(username=form.username.data).first()
if user is None or not user.check_password(form.password.data):
flash('Invalid username or password')
return redirect(url_for('login'))
login_user(user, remember=form.remember_me.data)
next_page = request.args.get('next')
if not next_page or url_parse(next_page).netloc != '':
next_page = url_for('index')
return redirect(next_page)
# ...
Flask
request
değişkeni ile istemcinin gönderdiği bütün bilgiyi tutar.request.args
dictionary tipinde query string içeriğini barındırır.Login olduktan sonra 3 muhtemel durum var:
- Eğer login URL
next
parametresine sahip değilse , kullanıcı index sayfasına yönlendirilir. - Eğer login URL
next
parametresine sahipse, index parametresi de ilgili path'e ayarlanmış(atanmış, belirlenmiş) ise o zaman kullanıcı belirlenmiş URL'e yönlendirilir. - Eğer login URL
next
parametresine sahipse ve tam URL'e(domain name'i içeren) ayarlanmış ise o zamanindex
sayfasına yönlendirilir. - 3.durum (tam domain name'i içeren URL) daha güvenlidir çünkü saldırganlar
next
parametresine zaafiyetlerinizden yararlanarak başka siteye gidecek şekilde URL ataması yapabilir. - absolute URL örneği:
<a href="https://adnankayace.blogspot.com">Blog</a>
- relative URL örneği:
<a href="/blog">Blog</a>
. - URL'imizin absolute mü relative mi olup olmadığını tespit etmek için
url_parse()
kullanıyoruz. Aşağıdaki örnek ilenetloc
'un (network location part) değerini görebilirsiniz. Bizim örneğimizde de netloc componenti ayarlanmış mı ayarlanmamış mı diye kontrol ediyoruz.
- Eğer login URL
xxxxxxxxxx
>>> from werkzeug.urls import url_parse
>>> url_parse('https://adnankayace.blogspot.com:80/python/flask.html')
URL(scheme='https', netloc='adnankayace.blogspot.com:80', path='/python/flask.html', query='', fragment='')
index.html
içinde ufak bir değişiklik yapıp, gerçek kullanıcıları gösterelim artık.
xxxxxxxxxx
...
<h1>Selam {{current_user.username}}! </h1>
...
- Ve
myapp/routes.py
içindeindex()
metodunu aşağıdaki gibi düzenleyelim. Geçici belirlediğimiz user değişkenini kaldırdık.posts
değişkeni de database query'den gelen dataları alıyor.
xxxxxxxxxx
route('/') .
route('/index') .
def index():
posts = Post.query.all()
return render_template('index.html', title='Anasayfa', posts=posts)
Kullanıcı Kayıt İşlemleri (User Registration)
xxxxxxxxxx
# external
from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, BooleanField, SubmitField
from wtforms.validators import DataRequired, Email, EqualTo, ValidationError
#internal
from myapp.models import User
#...
class RegistrationForm(FlaskForm):
""" Kullanıcı kayıt formu"""
username = StringField('Kullanıcı Adı', validators=[DataRequired()])
email = StringField('Email', validators=[DataRequired(), Email()])
password = PasswordField('Parola', validators=[DataRequired()])
password2 = PasswordField('Parola Doğrula', validators=[DataRequired(), EqualTo('password')])
submit = SubmitField('Register')
def validate_username(self, username):
user = User.query.filter_by(username=username.data).first()
if user is not None:
raise ValidationError("Lütfen başka bir kullanıcı adı giriniz")
def validate_email(self, email):
user = User.query.filter_by(email=email.data).first()
if user is not None:
raise ValidationError("Lütfen başka bir e-posta giriniz")
validators=
'teki Email( ) validator sınıfı, email formatında adres girilmesi için eklendi.- İki adet parola alıyoruz, bu kullanıcıyı doğru parola girmeye yönlendirmek ve yanlış parola riskini azaltmak içindir.
EqualTo()
başka bir validator olarak kullanılıyor. İlk parolaya eşit olup olmadığını kontrol eden sınıftır. validate_<field_name>
ile WTForms bu methodları özel validator(doğrulayıcı) olarak çağırır.
templates/register.html
xxxxxxxxxx
{%extends 'base.html'%} {%block content%}
<h1>Kaydol</h1>
<form action="" method="POST">
{{form.hidden_tag()}}
<table>
<tr>
<td>{{form.username.label}}</td>
<td>{{form.username(size=20)}}</td>
<td>{% for error in form.username.errors %}
<span style="color: red;">[{{ error }}]</span>
{% endfor %}
</td>
</tr>
<tr>
<td>{{form.email.label}}</td>
<td>{{form.email(size=20)}}</td>
<td>{% for error in form.email.errors %}
<span style="color: red;">[{{ error }}]</span>
{% endfor %}
</td>
</tr>
<tr>
<td>{{form.password.label}}</td>
<td>{{form.password(size=20)}}</td>
<td>{% for error in form.password.errors %}
<span style="color: red;">[{{ error }}]</span>
{% endfor %}
</td>
</tr>
<tr>
<td>{{form.password2.label}}</td>
<td>{{form.password2(size=20)}}</td>
<td>{% for error in form.password2.errors %}
<span style="color: red;">[{{ error }}]</span>
{% endfor %}
</td>
</tr>
</table>
<p>{{ form.submit() }}</p>
</form>
{%endblock%}
base.html
'de kayıt sayfası için link ekleyelim.
xxxxxxxxxx
<td><a href="{{url_for('login')}}">Giriş Yap</a></td>
<td><a href="{{url_for('register')}}">Kaydol</a></td>
myapp/routes.py
modülüne deregister()
methodumuzu yazalım.
xxxxxxxxxx
from myapp import db
from myapp.forms import RegistrationForm
# ...
route('/register', methods=['GET', 'POST']) .
def register():
"""Yeni kullanıcı kayıt"""
if current_user.is_authenticated:
return redirect(url_for('index'))
form = RegistrationForm()
if form.validate_on_submit():
user = User(username=form.username.data, email=form.email.data)
user.set_password(form.password.data)
db.session.add(user)
db.session.commit()
flash("Tebrikler yeni kullanıcı eklendi!")
return redirect(url_for('index'))
return render_template('register.html', title="Kaydol", form=form)
- Böylece kullanıcı kaydını da tamamlamış olduk.
Yorumlar