Spring Security dokáže veľmi jednoducho zabezpečiť REST API. V kombinácii so Spring Bootom je to navyše záležitosť na pár riadkov. Dajme si ukážku!
Plán práce
- Pripravíme
pom.xml
so závislosťami. - Nakonfigurujeme overovanie metód REST kontrolérov.
- Vytvoríme cvičný bean a REST API.
- Pripravíme vlastnú triedu
PermissionEvaluator
a, ktorá bude rozhodovať, ktoré metódy pustí a ktoré nie.
Závislosti v pom.xml
V prvom kroku vytvoríme mavenovský pom.xml
a nastavíme ho pre Spring Boot.
Za rodiča vyhlásime spring-boot-starter-parent
, a pri
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.3.1.RELEASE</version>
</parent>
Dodajme dve závislosti:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
REST API
Náš projekt sa bude zaoberať správou…ehm… projektov. Základnou entitou bude projekt, ktorého jediným atribútom bude kódové označenie (pamätáte si na longhorn
)? Bude mu prislúchať trieda Project
public class Project {
private String codeName;
// gettre a settre
}
K projektu si urobíme REST API, ktoré bude podporovať predbežne dve metódy a to výpis všetkých projektov a hľadanie projektu podľa kódového mena.
@RestController
@RequestMapping(“/projects”)
public class ProjectController {
private List
public ProjectController() {
projects.add(new Project("cassovia"));
projects.add(new Project("fragopolis"));
projects.add(new Project("leuchovia"));
}
@RequestMapping
public List<Project> getProjects() {
return this.projects;
}
@RequestMapping("/{codeName}")
public Project getProject(@PathVariable String codeName) {
for (Project project : this.projects) {
if(project.getCodeName().equals(codeName)) {
return project;
}
}
throw new ProjectNotFoundException();
}
@ResponseStatus(HttpStatus.NOT_FOUND)
private class ProjectNotFoundException extends RuntimeException {
}
}
Samozrejme, nesmieme zabudnúť na hlavnú triedu:
@SpringBootApplication
public class ApplicationConfiguration {
public static void main(String[] args) {
SpringApplication.run(ApplicationConfiguration.class, args);
}
}
Spustiť to môžeme jednoducho. Stačí spustiť nasledovné a projekt sa spustí:
mvn spring-boot:run
Navštívte http://localhost:8080/projects
a uvidíte v akcii…
Zabezpečenie REST API
…sa prejaví štandardným dialógom prehliadača pre zadanie mena a hesla. Áno, už sa zapojil Spring Security, ktorý zabezpečil vaše API, vytvoril implicitného používateľa user
a pridelil mu náhodné heslo, ktoré uvidíte v logu servera.
V tomto prípade netreba naozaj nič konfigurovať; všetko sa vyrieši automaticky.
Zabezpečenie metód
Čo ak chceme zabezpečiť nielen samotný prístup do aplikácie, ale vyriešiť overenie prístupových práv? Spring Security umožňuje definovať tzv. permission evaluator, ktorý sa použije pred, či po zavolaní konkrétnej metódy.
Predstavme si situáciu, že chceme zabrániť prístupu k neverejnému projektu. Namiesto komplexného overenia prístupových práv v kontroléri vieme túto funkcionalitu presunúť do samostatnej triedy. Overenie práv je potom deklaratívne a využíva anotáciu @PreAuthorize
.
@PreAuthorize("hasPermission(#codeName, 'project', 'view')")
@RequestMapping("/{codeName}")
public Project getProject(@PathVariable String codeName) {
Táto anotácia očakáva jediný parameter a to výraz v jazyku SpEL, v ktorom môžeme využiť zabudovanú funkciu hasPermission
s troma parametrami:
- objekt, ku ktorému chceme overiť oprávnenie
- akého typu je objekt
- aké oprávnenie je požadované na prístup k objektu
V tomto prípade chceme vyjadriť, že metódu má právo vyvolať len ten, kto má oprávnenie prezerať (view) projekt s kódovým označením v parametri codeName
. Ak to preložíme na parametre funkcie:
- objektom je parameter metódy s názvom
codeName
. Mriežka vo výraze SpEL reprezentuje odkaz na parameter zabezpečenej metódy. - typ objektu je project.
- tretím parametrom je oprávnenie, kde sme nastavili view.
Spring Security nijak neobmedzuje ani nedefinuje typy objektov ani oprávnenia. Môžeme si ich teda zvoliť ľubovoľne. Ako sa to však naozaj vyhodnotí?
Permission Evaluator
Spring Security umožní vyhodnotiť oprávnenie prístupu k metóde pomocou PermissionEvaluator
a. Je to interfejs s dvoma variantami metódy hasPermission()
, ktorý sa zavolá pri vyhodnocovaní funkcie hasPermission
v anotáciach zabezpečujúcich metódy. Metódy vracajú buď true
alebo false
, podľa toho, či povolia alebo odoprú prístup.
Založme si vlastný ProjectPermissionEvaluator
, implementujme príslušný interfejs a označme ho ako @Component
, aby sa zaregistroval v Springu.
@Component
public class ProjectPermissionEvaluator implements PermissionEvaluator {
//...
}
Najprv prekryme prvú metódu so štyroma parametrami.
@Override
public boolean hasPermission(Authentication authentication, Serializable targetId, String targetType, Object permission) {
if(! (targetId instanceof String)) {
return false;
}
if(! targetType.equals("project")) {
return false;
}
String codeName = (String) targetId;
return isAllowed(codeName);
}
Parametre sú postupne:
- objekt s autentifikovaným používateľom, z ktorého môžeme vytiahnuť login a ďalšie informácie o používateľovi a jeho principalovi
targetId
je objekt, v ktorom sa objaví prvý parameter funkciehasPermission
z anotácie. V našom prípade bude potrebné objekt pretypovať na reťazec, keďže parameter zabezpečenej metódy je tohto typu.targetType
je typ objektu, v našom prípade je toproject
permission
je požadované oprávnenie, v našom prípade to budeview
Opäť je vidieť, že parametre sú ponechané na nás. Cieľové typy a požadované oprávnenia môžu byť akékoľvek: jediný, kto ich vyhodnotí, je práve naša trieda.
Implementácia druhej metódy hasPermission
Permission Evaluator má ešte jednu metódu s dvoma parametrami. V nej sa vyhodnotí, čí požadovaný objekt dovolí prístup autentifikovanému používateľovi, ak sa vyžadujú oprávnenia v parametri permission
. Jeho použitie si ukážeme o chvíľu, ale zato si môžeme dokončiť kompletný kód tejto triedy. V skratke: povolíme prístup k projektu cassovia
a k všetkým ostatným prístup zamietneme. (A naozaj, zamietneme ho bez ohľadu na používateľa, či jeho oprávnenia. Ak chcete môžete si kód vylepšiť.)
@Component
public class ProjectPermissionEvaluator implements PermissionEvaluator {
public static final String[] PUBLIC_PROJECTS = { "cassovia" };
@Override
public boolean hasPermission(Authentication authentication, Object targetDomainObject, Object permission) { if(! (targetDomainObject instanceof Project)) {
return false;
}
Project project = (Project) targetDomainObject;
return isAllowed(project);
}
@Override
public boolean hasPermission(Authentication authentication, Serializable targetId, String targetType, Object permission) {
if(! (targetId instanceof String)) {
return false;
}
if(! targetType.equals("project")) {
return false;
}
String codeName = (String) targetId;
return isAllowed(codeName);
}
private boolean isAllowed(Project project) {
return Arrays.asList(PUBLIC_PROJECTS).contains(project.getCodeName());
}
private boolean isAllowed(String projectCodeName) {
return Arrays.asList(PUBLIC_PROJECTS).contains(projectCodeName);
}
}
Rozbehnutie overovania zabezpečenia metódy
Ak chceme, aby sa zabezpečenie metódy rozbehlo, dodajme anotáciu @EnableGlobalMethodSecurity
nad hlavnú konfiguračnú triedu.
@EnableGlobalMethodSecurity(prePostEnabled = true)
@SpringBootApplication
public class ApplicationConfiguration {
}
Nezabudnime deklarovať atribút prePostEnabled
, ktorým povolíme anotáciu @PreAuthorize
a jej kamarátky.
Zabezpečenie výpisu všetkých projektov
Teraz zabezpečme prístup k výpisu všetkých projektov. Anotácia @PostFilter
umožní zobrať návratovú hodnotu typu Collection
a vyhodiť z nej všetky prvky, na ktoré nemá aktuálne prihlásený používateľ právo.
Použitie je nasledovné:
@PostFilter("hasPermission(filterObject, 'view')")
@RequestMapping
public List<Project> getProjects() {
Opäť použijeme metódu hasPermission
, tentokrát len s dvoma parametrami. filterObject
je špeciálna premenná, ktorá reprezentuje iterovaný prvok v kolekcii, pre ktorý sa overia oprávnenie view
. Vyhodnotenie vyrieši samozrejme permission evaluator a jeho trojparametrový variant metódy hasPermission
. Na rozdiel od predošlej verzie bude filterObject
vždy typu Project
.
Ďalšie anotácie
Vďaka permission evaluatorom môžeme veľmi flexibilne deklarovať oprávnenia k metódam. K dispozícii je sada viacerých metód:
@PreFilter
: odfiltruje z parametra zabezpečenej metódy objekty, na ktoré používateľ nemá právo@PostFilter
: odfiltruje z návratovej kolekcie objekty s odopretým prístupom@PreAuthorize
: umožňuje odoprieť prístup k metóde ešte pred jej spustením@PostAuthorize
: umožňuje odoprieť prístup k výsledku metódy po jej vyvolaní
Ako to funguje?
Detaily o tejto mágii a ďalšie možnosti sú v dokumentácii Spring Security. Mapovanie medzi funkciou hasPermission
vo výrazoch SpEL a ich vyhodnocovaním rieši primárne trieda org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler
.
Okrem toho, deklarovanie anotácie @EnableGlobalMethodSecurity
zaregistruje v Springu inštanciu triedy org.springframework.security.config.annotation.method.configuration.GlobalMethodSecurityConfiguration
, ktorá sa postará o vyhľadanie permission evaluatora v aplikačnom kontexte a jeho registrácie s expression handlerom.
Zdrojové kódy
Zdrojové kódy možno nájsť na GitHube.