Zabezpečenie metód Spring MVC pomocou Spring Security

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

  1. Pripravíme pom.xml so závislosťami.
  2. Nakonfigurujeme overovanie metód REST kontrolérov.
  3. Vytvoríme cvičný bean a REST API.
  4. Pripravíme vlastnú triedu PermissionEvaluatora, 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 projects = new LinkedList();

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 PermissionEvaluatora. 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 funkcie hasPermission 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 to project
  • permission je požadované oprávnenie, v našom prípade to bude view

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.

Pridaj komentár

Vaša e-mailová adresa nebude zverejnená. Vyžadované polia sú označené *