Давайте сначала поговорим об идее разработки разрешений. После того, как мы спроектируем разрешения RBAC, особенно на уровне кода, у нас есть две идеи реализации:
Некоторые друзья думают, что второе решение не может обеспечить управление разрешениями на уровне кнопок. На самом деле это недоразумение. Если вы хотите добиться контроля разрешений на уровне кнопок, вам нужно только уточнить конфигурацию в базе данных.
В vhr Songge реализует динамические разрешения, переписывая два класса.
Первый класс — это тот, который собирает метаданные разрешений:
@Component
public class CustomFilterInvocationSecurityMetadataSource implements FilterInvocationSecurityMetadataSource {
@Override
public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
//...
}
@Override
public Collection<ConfigAttribute> getAllConfigAttributes() {
return null;
}
@Override
public boolean supports(Class<?> clazz) {
return true;
}
}
В методе getAttributes на основе URL-адреса текущего запроса (который можно извлечь из параметра Object), а затем на основе конфигурации в таблице разрешений он анализирует, какие разрешения требуются текущему запросу, и возвращает их.
Кроме того, я также переписал устройство принятия решений. На самом деле, устройство принятия решений не нужно переписывать, это зависит от ваших собственных потребностей. Если устройство принятия решений, поставляемое с Spring Security, не может удовлетворить ваши потребности, вы можете написать устройство принятия решений. сам:
@Component
public class CustomUrlDecisionManager implements AccessDecisionManager {
@Override
public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException {
//...
}
@Override
public boolean supports(ConfigAttribute attribute) {
return true;
}
@Override
public boolean supports(Class<?> clazz) {
return true;
}
}
Метод решения принимает решения. Первый параметр может определить, какие разрешения есть у текущего пользователя. Третий параметр — какие разрешения требуются текущему запросу. Если у текущего пользователя нет необходимых разрешений, укажите его напрямую. Возникает исключение AccessDeniedException.
Наконец, через постпроцессор компонента BeanPostProcessor эти два класса конфигурации помещаются в перехватчик FilterSecurityInterceptor Spring Security:
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
@Override
public <O extends FilterSecurityInterceptor> O postProcess(O object) {
object.setAccessDecisionManager(customUrlDecisionManager);
object.setSecurityMetadataSource(customFilterInvocationSecurityMetadataSource);
return object;
}
})
.and()
//...
}
Общая логика такова. Полный приведенный выше код друзья могут найти на https://github.com/lenve/vhr.
Однако приведенный выше код нельзя использовать в последней версии Spring Security 6 не потому, что срок действия класса истек, а потому, что класс был удален! Какой класс удалили? Фильтр Безопасность Перехватчик.
FilterSecurityInterceptor Этот фильтр использовался для обработки разрешений, но в новой версии Spring Security 6 этот перехватчик был заменен на AuthorizationFilter.
Честно говоря, новая версия решения на самом деле более разумна. Традиционное решение, похоже, имеет много нюансов разделения передней и задней части. Теперь мы движемся к более чистому разделению передней и задней части.
Поскольку FilterSecurityInterceptor больше не используется в новой версии, решение старой версии, очевидно, не будет работать. Решение новой версии на самом деле проще.
Хотя старые и новые методы письма различны, основная идея одна и та же.
Давайте посмотрим на конфигурацию новой версии:
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.authorizeHttpRequests(register -> register.anyRequest().access((authentication, object) -> {
//Указывает на запрос из URL Соответствует ли адрес адресу базы данных?
boolean isMatch = false;
//Получаем текущий запрос из URL адрес
String requestURI = object.getRequest().getRequestURI();
List<MenuWithRoleVO> menuWithRole = menuService.getMenuWithRole();
for (MenuWithRoleVO m : menuWithRole) {
if (antPathMatcher.match(m.getUrl(), requestURI)) {
isMatch = true;
//Указывает, что запрос изадрес найден
//Это роль, которую требует текущий запрос
List<Role> roles = m.getRoles();
//Получаем текущую роль вошедшего в систему пользователя
Collection<? extends GrantedAuthority> authorities = authentication.get().getAuthorities();
for (GrantedAuthority authority : authorities) {
for (Role role : roles) {
if (authority.getAuthority().equals(role.getName())) {
//Указывает, что у текущего вошедшего в систему пользователя есть необходимые роли для текущего запроса
return new AuthorizationDecision(true);
}
}
}
}
}
if (!isMatch) {
//Объясняем запрос URL Нет совпадения между адресом и адресом базы данных.,Для такого рода запроса,Вам нужно только войти в систему, чтобы получить доступ к единой
if (authentication.get() instanceof AnonymousAuthenticationToken) {
return new AuthorizationDecision(false);
} else {
//Указывает, что пользователь прошел аутентификацию
return new AuthorizationDecision(true);
}
}
return new AuthorizationDecision(false);
}))
.formLogin(form ->
//...
)
.csrf(csrf ->
//...
)
.exceptionHandling(e ->
//...
)
.logout(logout ->
//...
);
return http.build();
}
Основная идея остается той же, что и раньше, за исключением того, что теперь работа выполняется в методе доступа.
В обратном вызове метода доступа есть два параметра. Первый параметр — аутентификация. Очевидно, это объект пользователя, который успешно вошел в систему. Отсюда мы можем извлечь разрешения текущего пользователя.
Второй объект параметра на самом деле представляет собой RequestAuthorizationContext, из которого можно извлечь текущий объект запроса HttpServletRequest, а затем извлечь URL-адрес текущего запроса, а затем на основе информации в таблице разрешений можно определить, какие разрешения требуется текущий запрос, а затем в сочетании с аутентификацией. Просто сравните разрешения текущего пользователя, извлеченные из .
Если у текущего вошедшего в систему пользователя есть разрешения, необходимые для запроса, верните new AuthorizationDecision(true);
,В противном случае верните new AuthorizationDecision(false);
Вот и все.
Фактически, независимо от того, какой фреймворк, если вы можете освоить 70% одной версии, независимо от того, как она будет обновляться в будущем, вы сможете быстро приступить к работе!