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 Fő:
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 Fő 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. Írjunk most egy egyszerű élő tesztet - eltalálva az API-t és megbizonyosodva arról, hogy minden működőképes: És itt van a miénk megadottAuth () módszer: 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. 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: 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. Ezután be kell adnunk az injekciót CustomMethodSecurityExpressionRoot kifejezéskezelőnkben: Most ki kell használnunk a mi CustomMethodSecurityExpressionHandler a metódus biztonsági konfigurációjában: Itt van egy egyszerű példa a vezérlő módszerünk használatának biztosítására isMember (): Végül, itt van egy egyszerű élő teszt a felhasználó számáraJános“: 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 (). 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: 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. 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: Végül itt van az egyszerű tesztünk: 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.4.4. Az Élő teszt
@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")); }
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
5.1. Egyéni módszer biztonsági kifejezése
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 (); } ...}
5.2. Custom Expression Handler
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ó
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
@PreAuthorize ("isMember (#id)") @GetMapping ("/ organisations / {id}") @ResponseBody public Organization findOrgById (@PathVariable long id) {return organizationRepository.findOne (id); }
5.5. Élő teszt
@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
6.1. Egyéni biztonsági kifejezésgyökér
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"); } ...}
6.2. Példa - A kifejezés használata
@PreAuthorize ("hasAuthority ('FOO_READ_PRIVILEGE')") @GetMapping ("/ foos") @ResponseBody public Foo findFooByName (@RequestParam String name) {return new Foo (name); }
6.3. Élő teszt
@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