Vlastný bezpečnostný výraz s jarným zabezpečením

1. Prehľad

V tejto príručke sa zameriame na vytvorenie vlastného bezpečnostného výrazu pomocou Spring Security.

Niekedy nie sú výrazy dostupné v rámci rámca dostatočne expresívne. A v týchto prípadoch je pomerne jednoduché vytvoriť nový výraz, ktorý je sémanticky bohatší ako tie súčasné.

Najprv si povieme, ako vytvoriť zvyk Hodnotiteľ povolení, potom úplne prispôsobený výraz - a nakoniec, ako prepísať jeden zo zabudovaných bezpečnostných výrazov.

2. Subjekt používateľa

Najskôr si pripravme základ pre vytváranie nových bezpečnostných výrazov.

Pozrime sa na našu Používateľ subjekt - ktorý má a Výsady a an Organizácia:

@Entity verejná trieda Používateľ {@Id @GeneratedValue (strategy = GenerationType.AUTO) private Long id; @Column (nullable = false, unique = true) private String username; súkromné ​​reťazcové heslo; @ManyToMany (fetch = FetchType.EAGER) @JoinTable (name = "users_privileges", joinColumns = @JoinColumn (name = "user_id", referencedColumnName = "id"), inverseJoinColumns = @JoinColumn (name = "privilege_id") id ")) privátne nastavenia privilégií; @ManyToOne (fetch = FetchType.EAGER) @JoinColumn (name = "organization_id", referencedColumnName = "id") súkromná organizácia organizácie; // štandardné getre a setre}

A tu je náš jednoduchý Výsada:

@Entity public class Privilege {@Id @GeneratedValue (strategy = GenerationType.AUTO) private Long id; @Column (nullable = false, unique = true) private String name; // štandardné getre a setre}

A náš Organizácia:

@Entity public class Organisation {@Id @GeneratedValue (strategy = GenerationType.AUTO) private Long id; @Column (nullable = false, unique = true) private String name; // štandardní zakladatelia a obstarávatelia}

Na záver - použijeme jednoduchší zvyk Principal:

verejná trieda MyUserPrincipal implementuje UserDetails {súkromný používateľ; public MyUserPrincipal (užívateľský užívateľ) {this.user = užívateľ; } @Override public String getUsername () {return user.getUsername (); } @Override public String getPassword () {return user.getPassword (); } @ Verzia Verejná zbierka getAuthorities () {Zoznam autorít = nový ArrayList (); pre (Privilege privilege: user.getPrivileges ()) {Authority.add (new SimpleGrantedAuthority (privilege.getName ())); } návratové orgány; } ...}

Keď budú všetky tieto triedy pripravené, použijeme svoj zvyk Principal v zákl UserDetailsService implementácia:

@ Verejná trieda služby MyUserDetailsService implementuje UserDetailsService {@Autowired private UserRepository userRepository; @Override public UserDetails loadUserByUsername (String username) {User user = userRepository.findByUsername (username); if (user == null) {throw new UsernameNotFoundException (username); } vrátiť nový MyUserPrincipal (užívateľ); }}

Ako vidíte, na týchto vzťahoch nie je nič zložité - používateľ má jedno alebo viac privilégií a každý používateľ patrí do jednej organizácie.

3. Nastavenie údajov

Ďalej - inicializujme našu databázu jednoduchými testovacími údajmi:

@ Komponenta verejná trieda SetupData {@Autowired private UserRepository userRepository; @Autowired private PrivilegeRepository privilegeRepository; @Autowired private OrganizationRepository organizationRepository; @PostConstruct public void init () {initPrivileges (); initOrganizations (); initUsers (); }}

Tu je náš init metódy:

private void initPrivileges () {Privilege privilege1 = new Privilege ("FOO_READ_PRIVILEGE"); privilegeRepository.save (privilégium1); Privilege privilege2 = new Privilege ("FOO_WRITE_PRIVILEGE"); privilegeRepository.save (privilégium2); }
private void initOrganizations () {Organizácia org1 = nová organizácia ("FirstOrg"); organizationRepository.save (org1); Organizácia org2 = nová organizácia („SecondOrg“); organizationRepository.save (org2); }
private void initUsers () {Privilege privilege1 = privilegeRepository.findByName ("FOO_READ_PRIVILEGE"); Privilege privilege2 = privilegeRepository.findByName ("FOO_WRITE_PRIVILEGE"); Používateľ user1 = nový Používateľ (); user1.setUsername ("john"); user1.setPassword ("123"); user1.setPrivileges (new HashSet (Arrays.asList (privilege1))); user1.setOrganization (organizationRepository.findByName ("FirstOrg")); userRepository.save (user1); Používateľ user2 = nový používateľ (); user2.setUsername ("tom"); user2.setPassword ("111"); user2.setPrivileges (new HashSet (Arrays.asList (privilege1, privilege2))); user2.setOrganization (organizationRepository.findByName ("SecondOrg")); userRepository.save (user2); }

Poznač si to:

  • Používateľ „john“ má iba FOO_READ_PRIVILEGE
  • Používateľ „tom“ má oboje FOO_READ_PRIVILEGE a FOO_WRITE_PRIVILEGE

4. Hodnotiteľ vlastného povolenia

V tomto okamihu sme pripravení začať implementovať náš nový výraz - prostredníctvom nového, vlastného hodnotiča povolení.

Na zabezpečenie našich metód použijeme oprávnenie používateľa. Namiesto používania pevne zakódovaných názvov privilégií však chceme dosiahnuť otvorenejšiu a flexibilnejšiu implementáciu.

Začnime.

4.1. Hodnotiteľ povolení

Na to, aby sme si mohli vytvoriť vlastného hodnotiteľa povolení, musíme implementovať Hodnotiteľ povolení rozhranie:

verejná trieda CustomPermissionEvaluator implementuje PermissionEvaluator {@Override public boolean hasPermission (Authentication auth, Object targetDomainObject, Object permission) {if ((auth == null) || (targetDomainObject == null) ||! (permission instanceof String)) {return false ; } Reťazec targetType = targetDomainObject.getClass (). GetSimpleName (). ToUpperCase (); návrat hasPrivilege (auth, targetType, permission.toString (). toUpperCase ()); } @Override public boolean hasPermission (Authentication auth, Serializable targetId, String targetType, Object permission) {if ((auth == null) || (targetType == null) ||! (Permission instanceof String)) {return false; } návrat hasPrivilege (auth, targetType.toUpperCase (), permission.toString (). toUpperCase ()); }}

Tu je náš hasPrivilege () metóda:

private boolean hasPrivilege (Authentication auth, String targetType, String permission) {for (GrantedAuthority GrantedAuth: auth.getAuthorities ()) {if (GrantedAuth.getAuthority (). StartWith (TargetType)) {if (GrantedAuth.getAuthority (). povolenie)) {návrat pravdivý; }}} return false; }

Teraz máme k dispozícii nový bezpečnostný výraz pripravený na použitie: máPovolenie.

A tak namiesto použitia programovanejšej verzie:

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

Môžeme použiť:

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

alebo

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

Poznámka: #id odkazuje na parameter metódy a „Foo‘Odkazuje na typ cieľového objektu.

4.2. Konfigurácia zabezpečenia metódy

Nestačí definovať CustomPermissionEvaluator - musíme to tiež použiť v našej konfigurácii zabezpečenia metódy:

@Configuration @EnableGlobalMethodSecurity (prePostEnabled = true) verejná trieda MethodSecurityConfig rozširuje GlobalMethodSecurityConfiguration {@Override chránený MethodSecurityExpressionHandler createExpressionHandler () {DefaultMethodSecurityExpressionHandler expressionHandler = nový DefaultMethodSeturityConfig expressionHandler.setPermissionEvaluator (nový CustomPermissionEvaluator ()); návrat výrazHandler; }}

4.3. Príklad v praxi

Začnime teraz využívať nový výraz - niekoľkými jednoduchými metódami radiča:

@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; }}

A sme tu - všetci sme pripravení a nový výraz využívame v praxi.

4.4. Test naživo

Poďme teraz napísať jednoduché živé testy - zásah do API a uistenie sa, že je všetko v poriadku:

@Test public void givenUserWithReadPrivilegeAndHasPermission_whenGetFooById_thenOK () {Response response = givenAuth ("john", "123"). Get ("// localhost: 8082 / foos / 1"); assertEquals (200, response.getStatusCode ()); assertTrue (response.asString (). contains ("id")); } @Test public void givenUserWithNoWritePrivilegeAndHasPermission_whenPostFoo_thenForbidden () {Response response = givenAuth ("john", "123"). ContentType (MediaType.APPLICATION_JSON_VALUE) .body (new Foo ("sample") foos "); assertEquals (403, response.getStatusCode ()); } @Test public void givenUserWithWritePrivilegeAndHasPermission_whenPostFoo_thenOk () {Response response = givenAuth ("tom", "111"). ContentType (MediaType.APPLICATION_JSON_VALUE) .body (new Foo ("sample")) .post ("//" foos "); assertEquals (201, response.getStatusCode ()); assertTrue (response.asString (). contains ("id")); }

A tu je náš danéAuth () metóda:

private RequestSpecification givenAuth (meno používateľa reťazca, heslo reťazca) {FormAuthConfig formAuthConfig = nový FormAuthConfig ("// localhost: 8082 / login", "meno používateľa", "heslo"); vrátiť RestAssured.given (). auth (). form (používateľské meno, heslo, formAuthConfig); }

5. Nový bezpečnostný výraz

S predchádzajúcim riešením sme boli schopní definovať a používať máPovolenie výraz - čo môže byť celkom užitočné.

Stále nás tu však niečo obmedzuje názov a sémantika samotného výrazu.

A tak v tejto časti pôjdeme úplne na mieru - a implementujeme bezpečnostný výraz s názvom isMember () - kontrola, či je príkazca členom organizácie.

5.1. Bezpečnostný výraz vlastnej metódy

Aby sme mohli vytvoriť tento nový vlastný výraz, musíme začať implementáciou koreňovej poznámky, kde začína hodnotenie všetkých bezpečnostných výrazov:

public class CustomMethodSecurityExpressionRoot extends SecurityExpressionRoot implements MethodSecurityExpressionOperations {public CustomMethodSecurityExpressionRoot (Authentication authentication) {super (authentication); } public boolean isMember (Long OrganizationId) {User user = ((MyUserPrincipal) this.getPrincipal ()). getUser (); návrat user.getOrganization (). getId (). longValue () == OrganizationId.longValue (); } ...}

Teraz, ako sme poskytli túto novú operáciu priamo v koreňovej poznámke tu; isMember () slúži na kontrolu, či je aktuálny užívateľ členom Organizácia.

Všimnite si tiež, ako sme predĺžili SecurityExpressionRoot zahrnúť aj zabudované výrazy.

5.2. Obsluha vlastných výrazov

Ďalej si musíme vpichnúť náš CustomMethodSecurityExpressionRoot v našom ovládači výrazov:

verejná trieda CustomMethodSecurityExpressionHandler rozširuje DefaultMethodSecurityExpressionHandler {private AuthenticationTrustResolver trustResolver = nový AuthenticationTrustResolverImpl (); @Override chránený MethodSecurityExpressionOperations createSecurityExpressionRoot (autentifikácia autentifikácia, vyvolanie MethodInvocation) {CustomMethodSecurityExpressionRoot root = nový CustomMethodSecurityExpressionRoot (autentifikácia); root.setPermissionEvaluator (getPermissionEvaluator ()); root.setTrustResolver (this.trustResolver); root.setRoleHierarchy (getRoleHierarchy ()); návratový koreň; }}

5.3. Konfigurácia zabezpečenia metódy

Teraz musíme použiť naše CustomMethodSecurityExpressionHandler v konfigurácii zabezpečenia metódy:

@Configuration @EnableGlobalMethodSecurity (prePostEnabled = true) verejná trieda MethodSecurityConfig rozširuje GlobalMethodSecurityConfiguration {@Override chránený MethodSecurityExpressionHandler createExpressionHandler () {CustomMethodSecurityExpressionHandler expressionHandler = nový CustomMethodHandler = nový expressionHandler.setPermissionEvaluator (nový CustomPermissionEvaluator ()); návrat výrazHandler; }}

5.4. Používanie nového výrazu

Tu je jednoduchý príklad na zabezpečenie našej metódy radiča pomocou isMember ():

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

5.5. Živý test

Na záver je tu jednoduchý test používateľa „john“:

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

6. Zakážte vstavaný bezpečnostný výraz

Na záver sa pozrime, ako prepísať zabudovaný bezpečnostný výraz - diskutujeme o deaktivácii hasAuthority ().

6.1. Root vlastného bezpečnostného výrazu

Začneme podobne tým, že napíšeme svoje vlastné SecurityExpressionRoot - hlavne preto, že zabudované metódy sú konečné a preto ich nemôžeme prepísať:

verejná trieda MySecurityExpressionRoot implementuje MethodSecurityExpressionOperations {public MySecurityExpressionRoot (autentifikácia autentifikácie) {if (autentifikácia == null) {hodiť novú IllegalArgumentException ("autentifikačný objekt nemôže byť null"); } this.authentication = autentifikácia; } @Override public final boolean hasAuthority (reťazcový orgán) {hodiť novú RuntimeException ("metóda hasAuthority () nie je povolená"); } ...}

Po definovaní tejto koreňovej poznámky ju budeme musieť vložiť do obslužnej rutiny výrazov a potom ju zapojiť do našej konfigurácie - rovnako ako sme to urobili vyššie v časti 5.

6.2. Príklad - použitie výrazu

Teraz, ak chceme použiť hasAuthority () na zabezpečenie metód - takto bude vrhať RuntimeException keď sa pokúšame získať prístup k metóde:

@PreAuthorize ("hasAuthority ('FOO_READ_PRIVILEGE')") @GetMapping ("/ foos") @ResponseBody public Foo findFooByName (@RequestParam názov reťazca) {return new Foo (name); }

6.3. Živý test

Na záver je tu náš jednoduchý test:

@Test public void givenDisabledSecurityExpression_whenGetFooByName_thenError () {Response response = givenAuth ("john", "123"). Get ("// localhost: 8082 / foos? Name = sample"); assertEquals (500, response.getStatusCode ()); assertTrue (response.asString (). contains ("metóda hasAuthority () nie je povolená")); }

7. Záver

V tejto príručke sme sa hlboko ponorili do rôznych spôsobov, ako môžeme implementovať vlastný bezpečnostný výraz v Spring Security, ak tie súčasné nestačia.

Celý zdrojový kód nájdete ako vždy na serveri GitHub.


$config[zx-auto] not found$config[zx-overlay] not found