Egyéni biztonsági kifejezés tavaszi biztonsággal

1. Áttekintés

Ebben az oktatóanyagban a következőkre fogunk összpontosítani egyedi biztonsági kifejezés létrehozása a Spring Security alkalmazással.

Néha a keretrendszerben elérhető kifejezések egyszerűen nem elég kifejezőek. Ezekben az esetekben viszonylag egyszerű egy új kifejezést felépíteni, amely szemantikailag gazdagabb, mint a meglévők.

Először megbeszéljük, hogyan hozzunk létre egy egyéni PermissionEvaluator, majd egy teljesen egyedi kifejezés - és végül hogyan lehet felülbírálni a beépített biztonsági kifejezést.

2. Felhasználói entitás

Először készítsük el az alapot az új biztonsági kifejezések létrehozásához.

Vessünk egy pillantást a mi Felhasználó entitás - amelynek van egy Kiváltságok és egy Szervezet:

@Entity public class User {@Id @GeneratedValue (strategy = GenerationType.AUTO) private Long id; @ Oszlop (nullable = false, egyedi = true) private String felhasználónév; privát karakterlánc jelszó; @ManyToMany (fetch = FetchType.EAGER) @JoinTable (név = "felhasználók_jogosultságok", joinColumns = @JoinColumn (név = "felhasználó_azonosító", hivatkozottColumnName = "id"), inverzJoinColumns = @JoinColumn (név = "privilege_umn" név, hivatkozott id ")) privát Set privilégiumok; @ManyToOne (fetch = FetchType.EAGER) @JoinColumn (név = "szervezet_azonosító", hivatkozottColumnName = "id") magánszervezeti szervezet; // szabványos mérőeszközök és beállítók}

És itt van a mi egyszerűnk Kiváltság:

@Entity public class Privilege {@Id @GeneratedValue (strategy = GenerationType.AUTO) private Long id; @ Oszlop (nullable = hamis, egyedi = true) privát karakterlánc neve; // szabványos mérőeszközök és beállítók}

És a mi Szervezet:

@Entity public class Organization {@Id @GeneratedValue (strategy = GenerationType.AUTO) private Long id; @ Oszlop (nullable = hamis, egyedi = true) privát karakterlánc neve; // szabványos beállítók és szerelők}

Végül - használunk egy egyszerűbb szokást :

public class A MyUserPrincipal megvalósítja a UserDetails {private User user; public MyUserPrincipal (Felhasználó felhasználó) {this.user = user; } @Orride public String getUsername () {return user.getUsername (); } @Orride public String getPassword () {return user.getPassword (); } @Orride public collection getAuthorities () {List rights = new ArrayList (); for (Privilege privilege: user.getPrivileges ()) {hatóságok.add (új SimpleGrantedAuthority (privilege.getName ())); } visszatérési hatóságok; } ...}

Ha mindezen osztályok készen állnak, akkor a szokásainkat fogjuk használni alapban UserDetailsService végrehajtás:

@Service public class A MyUserDetailsService megvalósítja a UserDetailsService {@Autowired private UserRepository userRepository; @ Nyilvános felhasználói adatok felülírása loadUserByUsername (String felhasználónév) {User user = userRepository.findByUsername (felhasználónév); if (user == null) {dob új felhasználónévNotFoundException (felhasználónév); } return new MyUserPrincipal (felhasználó); }}

Mint láthatja, nincs semmi bonyolult ezekben a kapcsolatokban - a felhasználónak egy vagy több jogosultsága van, és minden felhasználó egy szervezethez tartozik.

3. Az adatok beállítása

Ezután inicializáljuk adatbázisunkat egyszerű tesztadatokkal:

@ Component public class SetupData {@Autowired private UserRepository userRepository; @Autowired privát PrivilegeRepository privilegeRepository; @Autowired private OrganizationRepository organizationRepository; @PostConstruct public void init () {initPrivileges (); initOrganizations (); initUsers (); }}

Itt van a miénk benne mód:

private void initPrivileges () {Privilege privilege1 = new Privilege ("FOO_READ_PRIVILEGE"); privilegeRepository.save (privilege1); Privilege privilege2 = új privilégium ("FOO_WRITE_PRIVILEGE"); privilegeRepository.save (privilege2); }
private void initOrganizations () {Szervezet org1 = új szervezet ("FirstOrg"); organizationRepository.save (org1); Szervezet org2 = új szervezet ("SecondOrg"); organizationRepository.save (org2); }
private void initUsers () {Privilege privilege1 = privilegeRepository.findByName ("FOO_READ_PRIVILEGE"); Privilege privilege2 = privilegeRepository.findByName ("FOO_WRITE_PRIVILEGE"); User user1 = new User (); user1.setUsername ("john"); user1.setPassword ("123"); user1.setPrivileges (new HashSet (Arrays.asList (privilege1))); user1.setOrganization (organizationRepository.findByName ("FirstOrg")); userRepository.save (user1); User user2 = new User (); user2.setUsername ("tom"); user2.setPassword ("111"); user2.setPrivileges (new HashSet (Arrays.asList (privilege1, privilege2))); user2.setOrganization (organizationRepository.findByName ("SecondOrg")); userRepository.save (user2); }

Vegye figyelembe, hogy:

  • A „john” felhasználó csak FOO_READ_PRIVILEGE
  • A „tom” felhasználó mindkettővel rendelkezik FOO_READ_PRIVILEGE és FOO_WRITE_PRIVILEGE

4. Egyéni engedély-kiértékelő

Ezen a ponton készen állunk az új kifejezés bevezetésére - egy új, egyedi engedély-kiértékelőn keresztül.

A felhasználói jogosultságokat fogjuk használni a módszereink biztosításához - de a hardveresen kódolt privilégiumnevek helyett nyitottabb, rugalmasabb megvalósítást szeretnénk elérni.

Kezdjük el.

4.1. PermissionEvaluator

Saját egyéni engedély-kiértékelőnk létrehozásához be kell vezetnünk a PermissionEvaluator felület:

public class CustomPermissionEvaluator implementálja a PermissionEvaluator {@Orride public boolean hasPermission (Hitelesítési hitelesítés, Object targetDomainObject, Objektumengedély) {if ((auth == null) || [targetDomainObject == null) ||! (String engedélyes példánya)] {return false ; } String targetType = targetDomainObject.getClass (). GetSimpleName (). ToUpperCase (); return hasPrivilege (auth, targetType, license.toString (). toUpperCase ()); } @Orride public boolean hasPermission (Authentication auth, Serializable targetId, String targetType, Object license) {if ((auth == null) || (targetType == null) ||! (String engedélypéldánya)) {return false; } return hasPrivilege (auth, targetType.toUpperCase (), license.toString (). toUpperCase ()); }}

Itt van a miénk hasPrivilege () módszer:

privát logikai hasPrivilege (Hitelesítési hitelesítés, String targetType, String engedély) {for (GrantedAuthority guaranteedAuth: auth.getAuthorities ()) {if (megadottAuth.getAuthority (). startsWith (targetType)) {if (megadottAuth.getAuthority (). tartalmaz ( engedély)) {return true; }}} return false; }

Most egy új biztonsági kifejezés áll rendelkezésünkre és készen áll a használatra: hasPermission.

Tehát ahelyett, hogy a keményebben kódolt verziót használná:

@PostAuthorize ("hasAuthority ('FOO_READ_PRIVILEGE')")

Használhatjuk:

@PostAuthorize ("hasPermission (returnObject, 'read')")

vagy

@PreAuthorize ("hasPermission (#id, 'Foo', 'read')")

Jegyzet: #id metódusparaméterre és „Foo’A célobjektum típusára utal.

4.2. Módszer Biztonsági konfiguráció

Nem elég meghatározni a CustomPermissionEvaluator - a módszer biztonsági konfigurációjában is használnunk kell:

A @Configuration @EnableGlobalMethodSecurity (prePostEnabled = true) public class MethodSecurityConfig kiterjeszti a GlobalMethodSecurityConfiguration {@Override protected MethodSecurityExpressionHandler createExpressionHandler () {DefaultMethodSecurityExpressionHandler kifejezés kifejezésHandler.setPermissionEvaluator (új CustomPermissionEvaluator ()); return kifejezésHandler; }}

4.3. Példa a gyakorlatban

Most kezdjük el használni az új kifejezést - néhány egyszerű vezérlő módszerrel:

@Controller public class MainController {@PostAuthorize ("hasPermission (returnObject, 'read')") @GetMapping ("/ foos / {id}") @ResponseBody public Foo findById (@PathVariable long id) {return new Foo ("Sample "); } @PreAuthorize ("hasPermission (#foo, 'write')") @PostMapping ("/ foos") @ResponseStatus (HttpStatus.CREATED) @ResponseBody public Foo create (@RequestBody Foo foo) {return foo; }}

És ott tartunk - mindannyian készen vagyunk, és a gyakorlatban használjuk az új kifejezést.

4.4. Az Élő teszt

Írjunk most egy egyszerű élő tesztet - eltalálva az API-t és megbizonyosodva arról, hogy minden működőképes:

@Test public void givenUserWithReadPrivilegeAndHasPermission_whenGetFooById_thenOK () {Response response = megadottAuth ("john", "123"). Get ("// localhost: 8082 / foos / 1"); assertEquals (200, response.getStatusCode ()); assertTrue (response.asString (). tartalmazza ("id")); } @Test public void givenUserWithNoWritePrivilegeAndHasPermission_whenPostFoo_thenForbidden () {Response response = megadottAuth ("john", "123"). ContentType (MediaType.APPLICATION_JSON_VALUE) .body (új Foo ("minta"). foos "); assertEquals (403, response.getStatusCode ()); } @Test public void givenUserWithWritePrivilegeAndHasPermission_whenPostFoo_thenOk () {Response response = megadottAuth ("tom", "111"). ContentType (MediaType.APPLICATION_JSON_VALUE) .body (új Foo ("minta" / 80) / post: "" // / local / ") post. foos "); assertEquals (201, response.getStatusCode ()); assertTrue (response.asString (). tartalmazza ("id")); }

És itt van a miénk megadottAuth () módszer:

privát RequestSpecification megadottAuth (karakterlánc felhasználónév, karakterlánc jelszó) {FormAuthConfig formAuthConfig = új FormAuthConfig ("// localhost: 8082 / login", "felhasználónév", "jelszó"); adja vissza a RestAssured.given (). auth (). űrlapot (felhasználónév, jelszó, formAuthConfig); }

5. Új biztonsági kifejezés

Az előző megoldással meg tudtuk határozni és felhasználni a hasPermission kifejezés - ami elég hasznos lehet.

Azonban itt még mindig némileg korlátozódik bennünk a kifejezés neve és szemantikája.

Tehát ebben a szakaszban teljes szokássá válunk - és egy úgynevezett biztonsági kifejezést fogunk megvalósítani isMember () - annak ellenőrzése, hogy az igazgató tagja-e egy szervezetnek.

5.1. Egyéni módszer biztonsági kifejezése

Ennek az új egyéni kifejezésnek a létrehozásához el kell indítanunk a gyökérmegjegyzés végrehajtását, ahol az összes biztonsági kifejezés értékelése megkezdődik:

public class CustomMethodSecurityExpressionRoot kiterjeszti a SecurityExpressionRoot végrehajtja a MethodSecurityExpressionOperations {public CustomMethodSecurityExpressionRoot (hitelesítési hitelesítés) {super (hitelesítés); } nyilvános logikai érték isMember (Long OrganizationId) {Felhasználói felhasználó = (((MyUserPrincipal) this.getPrincipal ()). getUser (); return user.getOrganization (). getId (). longValue () == OrganizationId.longValue (); } ...}

Most hogyan nyújtottuk be ezt az új műveletet közvetlenül a gyökérjegyzetben; isMember () annak ellenőrzésére szolgál, hogy az aktuális felhasználó tagja-e az adottnak Szervezet.

Vegye figyelembe azt is, hogyan hosszabbítottuk meg a SecurityExpressionRoot hogy a beépített kifejezéseket is tartalmazza.

5.2. Custom Expression Handler

Ezután be kell adnunk az injekciót CustomMethodSecurityExpressionRoot kifejezéskezelőnkben:

public class CustomMethodSecurityExpressionHandler kiterjeszti a DefaultMethodSecurityExpressionHandler {private AuthenticationTrustResolver trustResolver = new AuthenticationTrustResolverImpl (); @Orride védett MethodSecurityExpressionOperations createSecurityExpressionRoot (hitelesítési hitelesítés, MethodInvocation meghívás) {CustomMethodSecurityExpressionRoot root = új CustomMethodSecurityExpressionRoot (hitelesítés); root.setPermissionEvaluator (getPermissionEvaluator ()); root.setTrustResolver (this.trustResolver); root.setRoleHierarchy (getRoleHierarchy ()); visszatérő gyökér; }}

5.3. Módszer Biztonsági konfiguráció

Most ki kell használnunk a mi CustomMethodSecurityExpressionHandler a metódus biztonsági konfigurációjában:

A @Configuration @EnableGlobalMethodSecurity (prePostEnabled = true) public class MethodSecurityConfig kiterjeszti a GlobalMethodSecurityConfiguration {@Override protected MethodSecurityExpressionHandler createExpressionHandler () {CustomMethodSecurityExpressionHandler expressionHandler; kifejezésHandler.setPermissionEvaluator (új CustomPermissionEvaluator ()); return kifejezésHandler; }}

5.4. Az Új kifejezés használata

Itt van egy egyszerű példa a vezérlő módszerünk használatának biztosítására isMember ():

@PreAuthorize ("isMember (#id)") @GetMapping ("/ organisations / {id}") @ResponseBody public Organization findOrgById (@PathVariable long id) {return organizationRepository.findOne (id); }

5.5. Élő teszt

Végül, itt van egy egyszerű élő teszt a felhasználó számáraJános“:

@Test public void givenUserMemberInOrganization_whenGetOrganization_thenOK () {Response response = megadottAuth ("john", "123"). Get ("// localhost: 8082 / szervezetek / 1"); assertEquals (200, response.getStatusCode ()); assertTrue (response.asString (). tartalmazza ("id")); } @Test public void givenUserMemberNotInOrganization_whenGetOrganization_thenForbidden () {Response response = megadottAuth ("john", "123"). Get ("// localhost: 8082 / szervezetek / 2"); assertEquals (403, response.getStatusCode ()); }

6. Tiltsa le a beépített biztonsági kifejezést

Végül nézzük meg, hogyan lehet felülbírálni a beépített biztonsági kifejezést - megbeszéljük a letiltást hasAuthority ().

6.1. Egyéni biztonsági kifejezésgyökér

Hasonlóan kezdjük azzal, hogy megírjuk a sajátunkat SecurityExpressionRoot - főleg azért, mert a beépített módszerek azok végső és így nem tudjuk felülírni őket:

public class MySecurityExpressionRoot implementálja a MethodSecurityExpressionOperations {public MySecurityExpressionRoot (Hitelesítési hitelesítés) {if (hitelesítés == null) {dobjon új IllegalArgumentException ("A hitelesítési objektum nem lehet null"); } this.authentication = hitelesítés; } @Orride public final boolean hasAuthority (String authority) {dobjon új RuntimeException-t ("a módszer hasAuthority () nem engedélyezett"); } ...}

Miután meghatároztuk ezt a gyökérjegyzetet, be kell injektálnunk a kifejezéskezelőbe, majd bekötni a kezelőt a konfigurációnkba - ugyanúgy, ahogy fentebb, az 5. szakaszban tettük.

6.2. Példa - A kifejezés használata

Most, ha használni akarjuk hasAuthority () a módszerek biztosításához - az alábbiak szerint dobni fog RuntimeException amikor megpróbálunk hozzáférni a módszerhez:

@PreAuthorize ("hasAuthority ('FOO_READ_PRIVILEGE')") @GetMapping ("/ foos") @ResponseBody public Foo findFooByName (@RequestParam String name) {return new Foo (name); }

6.3. Élő teszt

Végül itt van az egyszerű tesztünk:

@Test public void givenDisabledSecurityExpression_whenGetFooByName_thenError () {Response response = megadottAuth ("john", "123"). Get ("// localhost: 8082 / foos? Név = minta"); assertEquals (500, response.getStatusCode ()); assertTrue (response.asString (). tartalmazza ("a módszer hasAuthority () nem engedélyezett")); }

7. Következtetés

Ebben az útmutatóban mélyrehatóan elmélyültünk abban, hogy miként valósíthatunk meg egy egyéni biztonsági kifejezést a Spring Security alkalmazásban, ha a meglévők nem elegendőek.

És mint mindig, a teljes forráskód megtalálható a GitHubon.