Spring Security

A Spring keretrendszer biztosít a számunkra több megoldást is az alkalmazások biztonságossá tételéhez. Csapjunk is bele a közepébe. Ahhoz a Spring nyújtotta biztonsági lehetőségeket használhassunk a pom.xml-hez hozzá kell adnunk a következő függőséget:

1
2
3
4
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>

Ellenőrizzük az IntelliJ-ben, hogy a MAven fül alatt a dependencies lenyílóban szerepel-e a spring security. Amennyiben nem jelent meg, akkor nyomjunk meg a Reimport All Maven Projects (kis újratöltős ikon) gombot, hogy ez megoldódjon. Bármennyire is hihetetlen, de mást nem kell tennünk, mint hogy elindítjuk az alkalmazásunkat. Ezután bármilyen URL-re navigálunk, akkor egy HTTP Basic Authentication dialógus ablakot kapunk ahol meg kell adni a felhasználónév és jelszó párost. A felhasználó ebben az esetben mindig user, míg a jelszó az alkalmazás indításakor a console-ra írja ki a rendszer valami hasonló formában: Using generated security password: a01b98a5-495b-4593-b9e4-7b6de9a6bbe3. A generált jelszó nyilván változik minden egyes futáskor, így az nem egyezik meg az itt megadottal (mindenki az általa megtalált egyedi jelszót használja). Miután megadjuk a felhasználónév és jelszó párost, akkor gond nélkül látnunk kell az alkalmazásunkat olyan formában amilyenben előtte is volt.

HTTP Basic Authentication (BA)

Az egyik legegyszerűbb autentikációs módszer, melynek folyamán a HTTP kliens egy felhasználónevet és egy jelszót továbbít a kéréssel együtt. Ezen adatokat a HTTP kérés fejlécében, Authorization: Basic <credentials> formában adja meg, ahol a credentials Base64-es encoding-al szerepel, mely tartalmazza a felhasználónevet és a jelszót (a plain textben ezeket : választja el egymástól). Ehhez az autentikációs módhoz nem szükséges cookie-kat, session azonosítókat, login oldalt karbantartani, cserébe viszont elég gyér a biztonság amit kapunk (sima Base64-es kódoláűssal megy a jelszó, ami nincs titkosítva semmilyen módon). Az Authorization header-t minden kérésnél el kell küldeni, így a böngészők ezt valamennyi ideig cache-elik (böngészőként eltérő lehet), hogy ne kelljen minden egyes kérés előtt ezt bekérni.

A fenti alkalmazásban, mivel minden url-t levédünk, így a szerver elsőkörben egy olyan választ ad, melynek fejlécében a HTTP 401 Unauthorized és a WWW-Authenticate elemek találhatóak meg. A kliens így fogja tudni, hogy BA-t kell használni és ezért a default 'login form'-ot használja ennek bekérésére, majd a fent leírt módon kódolja a bekért infokat és elküldi az Authorization headerrel együtt a kérést. Bővebb infoért lásd a következőt: RFC7617.

No, de visszatérve az alap Spring-es megvalósításra. A következőket kapjuk, ha csak behúzzuk a security függőséget és semmi egyebet nem teszünk:

  • minden kérést autentikálni kell
  • nincsenek szerepkörök
  • nincs saját login oldal
  • BA használata
  • egyetlen felhasználó (user)

A fentiek áthidalásához saját konfigurációt kell készítenünk.

Egyedi biztonsági konfiguráció

Hozzunk létre egy új osztályt (pl.: SecurityConfig néven)! Ahhoz, hogy a Spring konfigurációként kezelje ezt az osztály, szükséges, hogy ellássuk a @Configuration annotációval. Továbbá a security config-hoz kell a @EnableWebSecurity annotáció. Ezek mellett az osztályunkat a WebSecurityConfigurerAdapter osztályból kell származtatni.

1
2
3
4
5
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

}

A fenti konfig egyelőre nem csinál túl sok mindent, de már el tudjuk indítani az alkalmazásunkat (ugyanúgy működik mint eddig, ha minden jól megy).

Ahhoz, hogy egyedi konfigot használhassunk az ősosztály metódusait (configure nevűeket) tudjuk felüldefiniálni.

Saját magunk írjuk elő, hogy az alkalmazásban minden útvonalra csak bejelentkezett felhasználókat engedünk és HTTP Basic Auth-ot használunk

Ehhez a következőket kell tennünk:

1
2
3
4
5
6
7
8
@Override
protected void configure(HttpSecurity http) throws Exception {
    http
            .authorizeRequests()
            .antMatchers("/*").authenticated()
            .and()
            .httpBasic();
}

A paraméterül kapott HttpSecurity objektumon kell egy hívást tennünk, mely segítségével az összes kérést ellenőrizni fogjuk: ez az authorizeRequests célja. A következő, hogy megadjuk, hogy mely útvonalakhoz milyen jogok kellenek. Ezt lehet az antMatchers hívásokkal elintézni, melyekkel útvonalat vagy útvonalakat adhatunk meg és utána egy újabb láncolt hívással megadhatjuk, hogy mit várunk el ezeken az útvonalakon (jelen esetben: authenticated, azaz legyen bejelentkezve), később szofisztikáltabb megadási módokat is bemutatunk. Utolsó lépésként megadjuk, hogy a HTTP Basic Auth-al kell végezni az autentikációt: ˛httpBasic. Az and hívásokkal mindig visszakapjuk a HttpSecurity objektumunkat, így lehet szépen dinamikusan láncolni a konfigurációs megadásokat.

Ezen a ponton álljunk meg és próbáljuk ki az alkalmazásunkat. Felhasználó továbbra is user, illetve a konzolra megint kapunk egy generált jelszót.

Következő lépésként a felhasználók körét adjuk meg! A legegyszerűbb, hogy a felhasználókat in-memory adjuk meg. Ezt leginkább teszteléskor szokás használni. Ehhez egy másik konfigurációs metódust kell felüldefininálni, mégpedig valahogyan így:

1
2
3
4
5
6
7
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    auth.inMemoryAuthentication()
            .withUser("user")
            .password("user123")
            .roles("USER");
}

Az alkalmazásunk el fog indulni, de még nem fog működni, mert valami még hiányzik, de előtte vegyük sorra, hogy a fentiek mit csinálnak.

1
java.lang.IllegalArgumentException: There is no PasswordEncoder mapped for the id "null"

További anyagok

A gyakorlat anyagáról készült videó:

SUAF_04_gyak


Utolsó frissítés: 2020-10-12 11:34:46