Autentikointi, istunnonhallinta ja pääsynhallinta ovat tärkeitä osia web-sovellusten turvallisuutta. Autentikointi tarkoittaa käyttäjän todentamista, kun taas istunnonhallinta viittaa käyttäjän istunnon hallintaan web-sovelluksessa. Pääsynhallinta tai auktorisointi viittaa siihen ettei käyttäjän anneta suorittaa sellaisia toimintoja, tai päästä käsiksi sellaisiin tietoihin, joihin käyttäjällä ei ole lupaa. Tässä on lyhyt selitys siitä, miten nämä toiminnot toimivat web-sovelluksissa:
Autentikointi:
- Käyttäjä syöttää käyttäjätunnuksensa ja salasanansa web-sovelluksen kirjautumissivulle.
- Web-sovellus vastaanottaa tiedot ja tarkistaa, vastaavatko ne tallennettuja käyttäjätietoja.
- Jos tiedot ovat oikein, web-sovellus antaa käyttäjän kirjautua sisään ja luo istunnon, joka liittyy käyttäjän tietoihin.
- Jos tiedot ovat virheellisiä, käyttäjä saa virheilmoituksen ja hänet ohjataan takaisin kirjautumissivulle.
Autentikointi käytännössä
Kun käyttäjä kirjautuu sisään web-sovellukseen käyttäjätunnuksella ja salasanalla, web-sovellus vertaa käyttäjän syöttämiä tietoja tallennettuihin tietoihin tietokannassa. Tietokanta sisältää käyttäjätietoja, kuten käyttäjänimen, salasanan ja muita tietoja käyttäjästä.
Salasanatiivisteistä
Tärkeää on, että salasanaa ei tallenneta tietokantaan sellaisenaan, vaan salasanasta luodaan hash, joka tallennetaan tietokantaan. Hash on yksisuuntainen funktio, joka muuntaa salasanan satunnaisen näköiseksi merkkijonoksi, joka ei ole enää palautettavissa takaisin alkuperäiseen muotoonsa. Tämä tarkoittaa sitä, että tietokannassa ei tallenneta varsinaista salasanaa, vaan sen hash.
Kun käyttäjä syöttää käyttäjätunnuksen ja salasanan web-sovelluksen kirjautumissivulle, web-sovellus hakee käyttäjätiedot tietokannasta käyttäjänimen perusteella. Tämän jälkeen web-sovellus käyttää hash-toimintoa syöttääkseen käyttäjän antaman salasanan ja vertaa tätä hash-arvoa tietokannassa tallennettuun hash-arvoon. Jos hash-arvot ovat samat, käyttäjän syöttämä salasana on oikein ja käyttäjä pääsee kirjautumaan sisään.
Tämä on oleellista, koska jos salasanat tallennettaisiin tietokantaan sellaisenaan, hakkerit voivat suoraan varastaa vaarantuneesta tietokannasta käyttäjien salasanat ja helposti käyttää niitä väärin.
Istunnonhallinta:
- Kun käyttäjä on kirjautunut sisään, web-sovellus luo istunnon, joka on yksilöllinen käyttäjälle.
- Istuntoon liittyy käyttäjän tietoja, kuten käyttäjänimi ja sijainti, sekä tietoja istunnon tilasta, kuten aika, jolloin käyttäjä viimeksi toimi web-sovelluksessa.
- Web-sovellus käyttää istuntoa varmistaakseen, että käyttäjä on edelleen kirjautuneena sisään ja että hänellä on oikeus käyttää tiettyjä toimintoja tai sivuja.
- Kun käyttäjä kirjautuu ulos, web-sovellus poistaa istunnon ja käyttäjä ei voi enää käyttää web-sovellusta ilman uutta kirjautumista.
Istunnonhallinta käytännössä
Istunnonhallinta web-sovelluksissa toteutetaan usein käyttämällä evästeitä (cookies) selaimen ja palvelimen välisen tiedonvaihdon avulla. Kun käyttäjä kirjautuu sisään, web-sovellus luo istunnon ja tallentaa istunnon tunnisteen (session ID) evästeeseen, joka lähetetään selaimelle.
Jokaisella evästeellä on nimi, arvo ja käyttöaika, ja istunnonhallinnassa käytetään istunnon tunnisteen arvoa. Kun käyttäjä siirtyy sivulta toiselle web-sovelluksessa, selain lähettää evästeen palvelimelle, joka tarkistaa istunnon tunnisteen ja käyttää sitä löytääkseen vastaavan istunnon käyttäjän tietoihin.
Tämä mahdollistaa web-sovelluksen pitämisen ajan tasalla käyttäjän toimista ja tiedoista koko istunnon ajan. Kun käyttäjä kirjautuu ulos, web-sovellus poistaa istunnon ja evästeen, joten käyttäjä ei voi enää käyttää web-sovellusta ilman uutta kirjautumista.
Istuntotunnisteiden turvallisuudesta
Oikeanlainen entropia on erittäin tärkeää web-sovellusten istuntoevästeiden turvallisuuden kannalta. Istuntoevästeet ovat yksilöllisiä tunnisteita, jotka luodaan käyttäjän kirjautuessa web-sovellukseen. Istuntoevästeitä käytetään seuraamaan käyttäjän toimintaa web-sovelluksessa istunnon aikana.
Entropia kuvaa satunnaisuuden määrää istuntoevästeiden arvossa. Mitä enemmän satunnaisuutta istuntoevästeen arvossa on, sitä vaikeampi on arvata evästeen arvo. Jos evästeen arvo on ennakoitavissa tai helppo arvata, hyökkääjä voi simuloida käyttäjän istunnon ja päästä käsiksi käyttäjän henkilökohtaisiin tietoihin.
Tämän vuoksi on tärkeää, että istuntoevästeiden arvot generoidaan riittävän satunnaisesti ja että istuntoevästeen arvoa päivitetään säännöllisesti. Lisäksi on tärkeää, että istuntoevästeen arvo tallennetaan salatun yhteyden kautta käyttäjän selaimessa, jotta hyökkääjät eivät voi siepata istuntoevästeen esimerkiksi verkkoanalyysin tai verkkoliikenteen seurannan avulla.
Oikeanlainen entropia on siis tärkeää estämään hyökkääjiä arvaamasta istuntoevästeen arvoa ja simuloida käyttäjän istuntoa.
Kokonainen web-sovellus
Laajennetaan edellisen moduulin todo-sovellusta hieman niin, että sovellus tukee useampaa käyttäjää, rekisteröitymistä, kirjautumista ja uloskirjautumista.
Otetaan myös käyttöön ulkoinen CSS-kirjasto, eli valmis kokoelma CSS-luokkia. Tällaisia kirjastoja on paljon, esim. TailwindCSS on suosittu nykyisin. Tässä esimerkissä käytimme BootStrappia joka on hieman yksinkertaisempi.
Toinen uusi juttu tässä on Jinja2 layout templaatit, joissa idea on tehdä yksi layoutti jossa on sivuston tyylit yms. jonka sisällä määritellä "blokki" content. Sitten voi tehdä erillisiä HTML-tiedostoja jotka käyttävät layouttia (extends "base.html").
from flask import Flask, render_template, redirect, request, session, url_for
from flask_sqlalchemy import SQLAlchemy
from werkzeug.security import generate_password_hash, check_password_hash
app = Flask(__name__)
app.secret_key = 'salainen_avain'
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///todo.db'
db = SQLAlchemy(app)
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(50), nullable=False, unique=True)
password_hash = db.Column(db.String(100), nullable=False)
def __repr__(self):
return f'<User {self.username}>'
class Task(db.Model):
id = db.Column(db.Integer, primary_key=True)
description = db.Column(db.String(200), nullable=False)
completed = db.Column(db.Boolean, nullable=False, default=False)
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
user = db.relationship('User', backref=db.backref('tasks', lazy=True))
def __repr__(self):
return f'<Task {self.description}>'
@app.route('/', methods=['GET', 'POST'])
def index():
if 'username' not in session:
return redirect('/login')
if request.method == 'POST':
task_description = request.form['task']
user_id = User.query.filter_by(username=session['username']).first().id
new_task = Task(description=task_description, user_id=user_id)
db.session.add(new_task)
db.session.commit()
user_tasks = Task.query.filter_by(user_id=User.query.filter_by(
username=session['username']).first().id).all()
return render_template('index.html', tasks=user_tasks)
@app.route('/login', methods=['GET', 'POST'])
def login():
if 'username' in session:
return redirect('/')
error = None
if request.method == 'POST':
username = request.form['username']
password = request.form['password']
user = User.query.filter_by(username=username).first()
if user and check_password_hash(user.password_hash, password):
session['username'] = username
print("SETTING SESSION")
print(session)
return redirect('/')
else:
error = 'Väärä käyttäjätunnus tai salasana'
return render_template('login.html', error=error)
@app.route('/signup', methods=['GET', 'POST'])
def signup():
if 'username' in session:
return redirect('/')
error = None
if request.method == 'POST':
username = request.form['username']
password = request.form['password']
password_confirmation = request.form['password_confirmation']
if not username:
error = 'Käyttäjätunnus on pakollinen'
elif not password:
error = 'Salasana on pakollinen'
elif password != password_confirmation:
error = 'Salasanat eivät täsmää'
elif User.query.filter_by(username=username).count() > 0:
error = 'Käyttäjätunnus on jo käytössä'
else:
new_user = User(username=username,
password_hash=generate_password_hash(password))
db.session.add(new_user)
db.session.commit()
session['username'] = username
return redirect('/')
return render_template('signup.html', error=error)
@app.route('/logout')
def logout():
session.pop('username', None)
return redirect('/login')
if __name__ == '__main__':
with app.app_context():
db.create_all()
app.run(host='0.0.0.0', port=81)
Aluksi sovellus luo Flask-sovelluksen ja asettaa salaisuusavaimen SECRET_KEY. Tämä avain on tärkeä turvallisuuden kannalta, sillä se suojaa käyttäjän istuntoa ja evästeitä.
Sitten luodaan SQLite-tietokanta, joka sisältää kaksi taulua: User ja Task. User sisältää käyttäjätunnukset ja salasanat, ja Task sisältää käyttäjän lisäämät tehtävät.
Seuraavaksi määritellään User-luokka, joka periytyy db.Model-luokasta. Luokka sisältää kolme muuttujaa: id, username ja password_hash. id on uniikki tunniste, joka luodaan automaattisesti, kun uusi käyttäjä lisätään tietokantaan. username on käyttäjänimi, ja password_hash on käyttäjän salasanan hash-arvo, joka tallennetaan tietokantaan.
Sitten määritellään Task-luokka, joka myös periytyy db.Model-luokasta. Luokka sisältää neljä muuttujaa: id, description, complete ja user_id. id on taas uniikki tunniste, joka luodaan automaattisesti, kun uusi tehtävä lisätään tietokantaan. description on tehtävän kuvaus, complete kertoo, onko tehtävä suoritettu vai ei, ja user_id kertoo, mikä käyttäjä on lisännyt tehtävän.
Seuraavaksi määritellään kirjautumissivu ja sen toiminnallisuus. Kun käyttäjä syöttää käyttäjätunnuksensa ja salasanansa, sovellus tarkistaa, että käyttäjätunnus ja salasana ovat oikein. Jos käyttäjätunnus ja salasana ovat oikein, käyttäjän nimi tallennetaan istuntoon session-olion avulla, jolloin käyttäjä kirjautuu sisään sovellukseen. Jos käyttäjätunnus ja salasana eivät ole oikein, käyttäjälle näytetään virheilmoitus.
Sitten määritellään sovelluksen pääsivu, joka näyttää käyttäjän tehtävät. Jos käyttäjä ei ole kirjautunut sisään, heidät ohjataan kirjautumissivulle. Jos käyttäjä on kirjautunut sisään, sovellus hakee käyttäjän tehtävät tietokannasta Task-taulusta user_id-muuttujan perusteella. Tehtävät näytetään käyttäjälle HTML-muodossa.
Sitten määritellään tehtävän lisäämistoiminnallisuus. Kun käyttäjä lisää uuden tehtävän, sovellus luo uuden Task-olion ja tallentaa sen tietokantaan. user_id-muuttujan arvoksi asetetaan käyttäjän id, joka on tallennettu istuntoon. Tämän avulla sovellus yhdistää käyttäjän ja tehtävän tietokannassa.
Lopuksi määritellään käyttäjän uloskirjautumistoiminto. Kun käyttäjä kirjautuu ulos, istunto poistetaan ja käyttäjä ohjataan takaisin kirjautumissivulle.
Autentikointi ja istunnonhallinta toimivat käyttämällä evästeitä. Kun käyttäjä kirjautuu sisään, sovellus luo evästeen, joka tallentaa käyttäjän istunnon tiedot. Evästeessä on satunnainen avain, joka on riittävän pitkä ja monimutkainen, jotta sitä ei voi helposti arvata. Kun käyttäjä siirtyy sovelluksen eri sivuille, eväste lähetetään mukana pyynnössä, ja sovellus voi käyttää evästettä löytääkseen käyttäjän istunnon tiedot. Istunto tallennetaan myös session-olion avulla, joka tallennetaan palvelimen muistiin. Tämä tarkoittaa sitä, että kun käyttäjä kirjautuu sisään, heidän istuntonsa tiedot tallennetaan muistiin, jotta sovellus voi käyttää niitä eri sivuilla.
Harjoittele
Klikkaa "Open website" avataksesi sovelluksen erillisessä ikkunassa, kirjautuminen ei toimi oikein Replitin upotetussa web viewissä.
Valitettavasti Replit-palvelu on muuttnut lennosta eikä enää anna suorittaa näitä koodeja suoraan selaimessa. Voit klikata alla olevaa "Open in Replit" linkkiä ja avata koodin Replit-palvelussa. Meillä on työn alla etsiä Replitille korvaaja.
Valmis ryhtymään eettiseksi hakkeriksi?
Aloita jo tänään.
Hakatemian jäsenenä saat rajoittamattoman pääsyn Hakatemian moduuleihin, harjoituksiin ja työkaluihin, sekä pääset discord-kanavalle jossa voit pyytää apua sekä ohjaajilta että muilta Hakatemian jäseniltä.