Пролетна сигурност - роли и привилегии

1. Общ преглед

Тази статия продължава поредицата Регистрация с Spring Security с поглед към това как правилно да се приложат роли и привилегии .

2. Потребителска роля и привилегия

Първо, нека започнем с нашите обекти. Имаме три основни обекта:

  • на потребителя
  • на ролята - това означава ролите на високо равнище на потребителя в системата; всяка роля ще има набор от привилегии на ниско ниво
  • на Privilege - представлява ниско ниво, на гранули привилегия / орган в системата

Ето потребителя :

@Entity public class User { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id; private String firstName; private String lastName; private String email; private String password; private boolean enabled; private boolean tokenExpired; @ManyToMany @JoinTable( name = "users_roles", joinColumns = @JoinColumn( name = "user_id", referencedColumnName = "id"), inverseJoinColumns = @JoinColumn( name = "role_id", referencedColumnName = "id")) private Collection roles; }

Както можете да видите, потребителят съдържа ролите, но и няколко допълнителни подробности, необходими за правилния механизъм за регистрация.

Следва - тук е ролята :

@Entity public class Role { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id; private String name; @ManyToMany(mappedBy = "roles") private Collection users; @ManyToMany @JoinTable( name = "roles_privileges", joinColumns = @JoinColumn( name = "role_id", referencedColumnName = "id"), inverseJoinColumns = @JoinColumn( name = "privilege_id", referencedColumnName = "id")) private Collection privileges; }

И накрая привилегията :

@Entity public class Privilege { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id; private String name; @ManyToMany(mappedBy = "privileges") private Collection roles; }

Както можете да видите, ние обмисляме както потребителската роля, така и връзките между привилегиите на ролята двупосочно много към много .

3. Привилегии за настройка и роли

След това - нека се съсредоточим върху извършването на някои ранни настройки на привилегиите и ролите в системата.

Ще обвържем това със стартирането на приложението и ще използваме ApplicationListener на ContextRefreshedEvent, за да заредим първоначалните си данни при стартиране на сървъра:

@Component public class SetupDataLoader implements ApplicationListener { boolean alreadySetup = false; @Autowired private UserRepository userRepository; @Autowired private RoleRepository roleRepository; @Autowired private PrivilegeRepository privilegeRepository; @Autowired private PasswordEncoder passwordEncoder; @Override @Transactional public void onApplicationEvent(ContextRefreshedEvent event) { if (alreadySetup) return; Privilege readPrivilege = createPrivilegeIfNotFound("READ_PRIVILEGE"); Privilege writePrivilege = createPrivilegeIfNotFound("WRITE_PRIVILEGE"); List adminPrivileges = Arrays.asList( readPrivilege, writePrivilege); createRoleIfNotFound("ROLE_ADMIN", adminPrivileges); createRoleIfNotFound("ROLE_USER", Arrays.asList(readPrivilege)); Role adminRole = roleRepository.findByName("ROLE_ADMIN"); User user = new User(); user.setFirstName("Test"); user.setLastName("Test"); user.setPassword(passwordEncoder.encode("test")); user.setEmail("[email protected]"); user.setRoles(Arrays.asList(adminRole)); user.setEnabled(true); userRepository.save(user); alreadySetup = true; } @Transactional Privilege createPrivilegeIfNotFound(String name) { Privilege privilege = privilegeRepository.findByName(name); if (privilege == null) { privilege = new Privilege(name); privilegeRepository.save(privilege); } return privilege; } @Transactional Role createRoleIfNotFound( String name, Collection privileges) { Role role = roleRepository.findByName(name); if (role == null) { role = new Role(name); role.setPrivileges(privileges); roleRepository.save(role); } return role; } }

И така, какво се случва по време на този прост код за настройка? Нищо сложно:

  • ние създаваме привилегиите
  • ние създаваме ролите и им присвояваме привилегиите
  • създаваме потребител и му възлагаме роля

Обърнете внимание как използваме flag на SetSetup, за да определим дали настройката трябва да се изпълни или не . Това е просто защото, в зависимост от това колко контекста сте конфигурирали във вашето приложение - ContextRefreshedEvent може да се задейства няколко пъти. И ние искаме настройката да бъде изпълнена веднъж.

Две бързи бележки тук - първо, за терминологията . Тук използваме условията на Privilege - Role , но през пролетта те са малко по-различни. През пролетта нашата привилегия се нарича Роля, а също и като (предоставен) орган - което е малко объркващо. Не е проблем за изпълнението, разбира се, но определено си струва да се отбележи.

Второ - тези пролетни роли (нашите привилегии) ​​се нуждаят от префикс ; по подразбиране този префикс е „РОЛЯ“, но може да бъде променен. Тук не използваме този префикс, само за да улесним нещата, но имайте предвид, че ако не го променяте изрично, ще се изисква.

4. Персонализирана UserDetailsService

Сега - нека проверим процеса на удостоверяване.

Ще видим как да извлечем потребителя в нашата персонализирана UserDetailsService и как да картографираме правилния набор от пълномощия от ролите и привилегиите, които потребителят е назначил:

@Service("userDetailsService") @Transactional public class MyUserDetailsService implements UserDetailsService { @Autowired private UserRepository userRepository; @Autowired private IUserService service; @Autowired private MessageSource messages; @Autowired private RoleRepository roleRepository; @Override public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException { User user = userRepository.findByEmail(email); if (user == null) { return new org.springframework.security.core.userdetails.User( " ", " ", true, true, true, true, getAuthorities(Arrays.asList( roleRepository.findByName("ROLE_USER")))); } return new org.springframework.security.core.userdetails.User( user.getEmail(), user.getPassword(), user.isEnabled(), true, true, true, getAuthorities(user.getRoles())); } private Collection getAuthorities( Collection roles) { return getGrantedAuthorities(getPrivileges(roles)); } private List getPrivileges(Collection roles) { List privileges = new ArrayList(); List collection = new ArrayList(); for (Role role : roles) { collection.addAll(role.getPrivileges()); } for (Privilege item : collection) { privileges.add(item.getName()); } return privileges; } private List getGrantedAuthorities(List privileges) { List authorities = new ArrayList(); for (String privilege : privileges) { authorities.add(new SimpleGrantedAuthority(privilege)); } return authorities; } }

Интересното нещо, което трябва да се проследи тук, е как привилегиите (и ролите) се съпоставят с обекти на GrantedAuthority.

Това картографиране прави цялата конфигурация на защитата изключително гъвкава и мощна - можете да смесвате и съчетавате ролите и привилегиите, колкото е необходимо, и накрая те ще бъдат правилно картографирани на властите и върнати обратно в рамката.

5. Регистрация на потребителя

И накрая - нека да разгледаме регистрацията за нов потребител.

Видяхме как настройката става за създаването на потребителя и му присвоява роли (и привилегии) ​​- нека сега разгледаме как това трябва да се направи по време на регистрацията на нов потребител:

@Override public User registerNewUserAccount(UserDto accountDto) throws EmailExistsException { if (emailExist(accountDto.getEmail())) { throw new EmailExistsException ("There is an account with that email adress: " + accountDto.getEmail()); } User user = new User(); user.setFirstName(accountDto.getFirstName()); user.setLastName(accountDto.getLastName()); user.setPassword(passwordEncoder.encode(accountDto.getPassword())); user.setEmail(accountDto.getEmail()); user.setRoles(Arrays.asList(roleRepository.findByName("ROLE_USER"))); return repository.save(user); }

В тази проста реализация приемаме, че се регистрира стандартен потребител, така че му се възлага ролята ROLE_USER .

Разбира се, по-сложната логика може лесно да бъде внедрена по същия начин - или чрез наличието на множество твърдо кодирани методи за регистрация, или чрез разрешаване на клиента да изпрати типа потребител, който е регистриран.

6. Заключение

В този урок ние илюстрирахме как да внедрим роли и привилегии с JPA за система, подкрепена от Spring Security.

Най- пълното прилагане на тази регистрация с Пролет сигурност урок може да се намери в проекта GitHub - това е Maven-базиран проект, така че трябва да бъде лесен за внос и работи като такъв.