1. Концепции, связанные с управлением правами
Управление полномочиями является важной частью почти всех фоновых систем, и основная цель состоит в том, чтобы контролировать полномочия всей фоновой системы управления. Общий контроль доступа на основе ролей, его модель авторизации "разрешение роли пользователя"Коротко говоря, у пользователя есть несколько ролей, а у роли - несколько разрешений. Среди них
- Пользователь:Излишне говорить, что все это знают;
- Роль:Концепция коллекции, управление ролями — это процесс определения разрешений, которые имеет роль;
-
Разрешения:1) Права доступа к страницам, которые определяют, какие страницы вы можете видеть, а какие нет;
2) Операционные полномочия, которые контролируют, какие операции вы можете выполнять на странице (запрос, удаление, редактирование и т. д.);
3) Разрешение на доступ к данным определяет, какие данные вы можете видеть.
Суть в следующем:
Permission (Разрешение) = Resource (Ресурс) + Action (Привилегия)
Роль = набор низкоуровневых разрешений
Пользователь = набор ролей (роли высокого уровня)
Процесс управления правами:
- Управление аутентификацией, то есть логика определения полномочий, например, управление меню (после того, как обычный бизнес-персонал входит в систему, они не могут видеть меню [Управление пользователями]), управление полномочиями функций (управление доступом к URL-адресам), управление полномочиями на уровне строк , и т.д.
- Управление авторизацией, то есть процесс назначения прав, таких как прямая авторизация пользователям, права, непосредственно назначенные пользователям, имеют наивысший приоритет, и авторизация на должности, к которым принадлежат пользователи. принадлежность можно рассматривать как группу, которая имеет те же функции, что и роли, но каждый пользователь может быть связан только с одной информацией о работе и т. д.
В реальном проекте много пользователей, и крайне обременительно авторизовать каждого системного пользователя по одному, поэтому вы можете узнать то же самое, что и система управления файлами Linux, установить групповой режим, группа имеет несколько пользователей, вы можете авторизовать те же разрешения группы пользователей намного проще. В этом режиме:
Все разрешения для каждого пользователя = разрешения для отдельных пользователей + разрешения для групп пользователей
Отношения между группами пользователей, пользователями и ролями следующие:
В сочетании с правами доступа к странице и полномочиями управления правами доступа, такими как доступ к меню, работа с функциональными модулями, работа с кнопками и т. д., функциональными операциями и ресурсами можно управлять унифицированным образом, то есть они могут быть напрямую связаны с полномочиями. Схема отношений выглядит следующим образом:
2. Анализ процесса авторизации
2.1 Рабочий процесс авторизации доступа:
FilterSecurityInterceptor
doFilter()->invoke()
->AbstractSecurityInterceptor
beforeInvocation()
->SecurityMetadataSource 获取ConfigAttribute属性信息(从数据库或者其他数据源地方)
getAttributes()
->AccessDecisionManager() 基于AccessDecisionVoter实现授权访问
Decide()
->AccessDecisionVoter 受AccessDecisionManager委托实现授权访问
vote()
Процесс авторизации по умолчанию будет использовать такой рабочий процесс, а затем анализировать функцию и исходный код каждого компонента.
2.2 Анализ AbstractSecurityInterceptor
FilterSecurityInterceptorдля перехватчика авторизации, вFilterSecurityInterceptorодин из пакетовцепочка фильтров,requestа такжеresponseизFilterInvocationобъект для работыFilterSecurityInterceptor,В основном поinvoke()вызвать его родительский классAbstractSecurityInterceptorМетоды.
Анализ вызова():
public void invoke(FilterInvocation fi) throws IOException, ServletException {
.....
// 获取accessDecisionManager权限决策后结果状态、以及权限属性
InterceptorStatusToken token = super.beforeInvocation(fi);
try {
fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
}
finally {
super.finallyInvocation(token);
}
super.afterInvocation(token, null);
}
}
AbstractSecurityInterceptorОсновной метод фильтра авторизацииbeforeInvocation(),afterInvocation()а такжеauthenticateIfRequired(), самый главный методbeforeInvocation()проанализируйте, как показано ниже:
protected InterceptorStatusToken beforeInvocation(Object object) {
....
//从SecurityMetadataSource的权限属性
Collection<ConfigAttribute> attributes = this.obtainSecurityMetadataSource()
.getAttributes(object);
if (attributes == null || attributes.isEmpty()) {
.....
publishEvent(new PublicInvocationEvent(object));
return null; // no further work post-invocation
}
//调用认证环节获取authenticated(包含用户的详细信息)
Authentication authenticated = authenticateIfRequired();
// Attempt authorization
try {
//进行关键的一步:授权的最终决策
this.accessDecisionManager.decide(authenticated, object, attributes);
}
catch (AccessDeniedException accessDeniedException) {
publishEvent(new AuthorizationFailureEvent(object, attributes, authenticated,
accessDeniedException));
throw accessDeniedException;
}
// Attempt to run as a different user
Authentication runAs = this.runAsManager.buildRunAs(authenticated, object,
attributes);
if (runAs == null) {
if (debug) {
logger.debug("RunAsManager did not change Authentication object");
}
// no further work post-invocation
return new InterceptorStatusToken(SecurityContextHolder.getContext(), false,
attributes, object);
}
else {
if (debug) {
logger.debug("Switching to RunAs Authentication: " + runAs);
}
SecurityContext origCtx = SecurityContextHolder.getContext();
SecurityContextHolder.setContext(SecurityContextHolder.createEmptyContext());
SecurityContextHolder.getContext().setAuthentication(runAs);
// need to revert to token.Authenticated post-invocation
return new InterceptorStatusToken(origCtx, true, attributes, object);
}
}
2.3 SecurityMetadataSource
SecurityMetadataSourceзагружается из базы данных или другого источника данныхConfigAttribute, чтобыAccessDecisionManager.decide()Матч в окончательном решении. Он имеет три метода:
Collection<ConfigAttribute> getAttributes(Object var1) throws IllegalArgumentException;//加载权限资源
Collection<ConfigAttribute> getAllConfigAttributes();//加载所有权限资源
boolean supports(Class<?> var1);
2.4 AccessDecisionManager
AccessDecisionManagerодеялоAbstractSecurityInterceptorВызовы-перехватчики принимают окончательные решения по управлению доступом. и поAuthenticationManagerGrantedAuthority в созданном объекте Authentication сначала авторизуется модулем авторизации.AccessDecisionManagerЧтение использования, когда сложный GrantedAuthority, getAuthority() имеет значение null, поэтому нужноAccessDecisionManagerСпециальная поддержкаGrantedAuthorityреализации, чтобы понять ее содержание.
AccessDecisionManagerМетод интерфейса:
void decide(Authentication authentication, Object secureObject, Collection<ConfigAttribute> attrs) throws AccessDeniedException;
boolean supports(ConfigAttribute attribute);
boolean supports(Class clazz);
2.5 AccessDecisionVoter
AccessDecisionManager.decide() будет использоватьAccessDecisionVoterПринимать решения голосования.AccessDecisionVoterПринимать решения по управлению доступом для голосования и отбрасывать, если доступ невозможен.AccessDeniedException.
Метод интерфейса **AccessDecisionVoter**:
int vote(Authentication authentication, Object object, Collection<ConfigAttribute> attrs);
boolean supports(ConfigAttribute attribute);
boolean supports(Class clazz);
AccessDecisionVoterизГолос основного метода()обычно получаютGrantedAuthority аутентификациииОпределенные атрибуты конфигурациипровестиmatch, в случае успеха это голосование «за», а если совпадение не удалось, это голосование «отклонено».Если в ConfigAttributes нет атрибута, голосование воздерживается.
Spring Security предоставляет три метода голосования для достиженияAccessDecisionManagerИнтерфейс принимает решения по управлению доступом путем голосования:
-
На основе консенсуса:Доступ разрешен, когда большинство избирателей согласны на доступ
-
УтвердительноОснованный:Доступ разрешен, если более одного избирателя согласны на доступ, все
-
ЕдиногласноНа основе:Доступ разрешен только с согласия всех
иAccessDecisionVoterИспользуйте три статические переменные для представления голосования избирателей:
- ACCESS_ABSTAIN:Воздержаться
- ДОСТУП ЗАКРЫТ:доступ закрыт
- ДОСТУП ПРЕДОСТАВЛЕН:разрешить доступ
Примечание: Когда все избиратели воздерживаются, используйте переменную allowIfEqualGrantedDeniedDecisions, чтобы оценить, передано ли значение true, а false выдает исключение AccessDeniedException.
Также настраиваемыйAccessDecisionManagerРеализуйте интерфейс, потому что, возможно, какой-то AccessDecisionVoter имеет более высокий вес голоса или какой-то AccessDecisionVoter имеет отрицательный голос. Весенний класс реализации безопасности AccessDecisionVoterRoleVoterиAuthenticatedVoter.RoleVoter — это наиболее распространенный AccessDecisionVoter, представляющий собой простое представление разрешений с префиксом ROLE_, а правила сопоставления голосования такие же, как и выше.
Анализ исходного кода:
Public int vote(Authentication authentication,Object object,Collection<ConfigAttribute>attributes){
//用户传递的authentication为null,拒绝访问
if(authentication==null){
return ACCESS_DENIED;
}
int result=ACCESS_ABSTAIN;
Collection<?extendsGrantedAuthority>authorities=extractAuthorities(authentication);
//依次进行投票
for(ConfigAttributeattribute:attributes){
if(this.supports(attribute)){
result=ACCESS_DENIED;
//Attempt to find a matching granted authority
for(GrantedAuthorityauthority:authorities){
if(attribute.getAttribute().equals(authority.getAuthority())){
returnACCESS_GRANTED;
}
}
}
}
3. Корпус - пользовательский компонент
Пользовательские компоненты:
-
Пользовательский FilterSecurityInterceptor, который можно имитироватьFilterSecurityInterceptor, который реализует абстрактный классAbstractSecurityInterceptorа такжеFilterинтерфейс, главное поставитьПользовательский SecurityMetadataSourceиПользовательский доступDecisionManagerНастроен в перехватчик пользовательского FilterSecurityInterceptor
-
Пользовательский SecurityMetadataSource, реализовать интерфейс FilterInvocationSecurityMetadataSource и реализовать загрузку ConfigAttribute из базы данных или других источников данных (то есть загрузку разрешений ресурсов из базы данных или других источников данных).
-
Пользовательский доступDecisionManager, вы можете использовать официальный UnanimousBased на основе AccessDecisionVoter для аутентификации полномочий
-
Пользовательский доступDecisionVoter
3.1 Пользовательский MyFilterSecurityInterceptor
Пользовательский MyFilterSecurityInterceptorОсновная работа это:
-
Загрузите пользовательский SecurityMetadataSource в пользовательский FilterSecurityInterceptor;
-
Загрузите пользовательский AccessDecisionManager в пользовательский FilterSecurityInterceptor;
-
Переопределить метод вызова
@Component public class MyFilterSecurityInterceptor extends AbstractSecurityInterceptor implements Filter { private FilterInvocationSecurityMetadataSource securityMetadataSource; @Override public void init(FilterConfig filterConfig) throws ServletException { } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { FilterInvocation fi = new FilterInvocation(request, response, chain); invoke(fi); } private void invoke(FilterInvocation fi) throws IOException, ServletException { //fi里面有一个被拦截的url //里面调用MyInvocationSecurityMetadataSource的getAttributes(Object object)这个方法获取fi对应的所有权限 //再调用MyAccessDecisionManager的decide方法来校验用户的权限是否足够 InterceptorStatusToken token = super.beforeInvocation(fi); try { //执行下一个拦截器 fi.getChain().doFilter(fi.getRequest(), fi.getResponse()); } finally { super.afterInvocation(token, null); } } @Override public void destroy() { } @Override public Class<?> getSecureObjectClass() { return null; } @Override public SecurityMetadataSource obtainSecurityMetadataSource() { return this.securityMetadataSource; } public FilterInvocationSecurityMetadataSource getSecurityMetadataSource() { return this.securityMetadataSource; } //设置自定义的FilterInvocationSecurityMetadataSource @Autowired public void setSecurityMetadataSource(MyFilterInvocationSecurityMetadataSource messageSource) { this.securityMetadataSource = messageSource; } //设置自定义的AccessDecisionManager @Override @Autowired public void setAccessDecisionManager(AccessDecisionManager accessDecisionManager) { super.setAccessDecisionManager(accessDecisionManager); } }
3.2 Настройка MyFilterInvocationSecurityMetadataSource
Пользовательский MyFilterInvocationSecurityMetadataSourceОсновная работа это:
-
Загрузите ConfigAttribute из источника данных в ресурс SecurityMetadataSource.
-
Переопределите getAttributes(), чтобы загрузить ConfigAttribute для подготовки к решению об авторизации AccessDecisionManager.decide().
@Component public class MyFilterInvocationSecurityMetadataSource implements FilterInvocationSecurityMetadataSource { private Map<String, Collection<ConfigAttribute>> configAttubuteMap = null; private void loadResourceDefine() { //todo 加载数据库的所有权限 Collection<ConfigAttribute> attributes; } @Override public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException { AntPathRequestMatcher matcher; String resUrl; HttpServletRequest request = ((FilterInvocation) object).getRequest(); //1.加载权限资源数据 if (configAttubuteMap == null) { loadResourceDefine(); } Iterator<String> iterator = configAttubuteMap.keySet().iterator(); while (iterator.hasNext()) { resUrl = iterator.next(); matcher = new AntPathRequestMatcher(resUrl); if (matcher.matches(request)) { return configAttubuteMap.get(resUrl); } } return null; } @Override public Collection<ConfigAttribute> getAllConfigAttributes() { return null; } @Override public boolean supports(Class<?> clazz) { return FilterInvocation.class.isAssignableFrom(clazz); } }
3.3 Настройка MyAccessDecisionManager
Пользовательский MyAccessDecisionManagerОсновная работа это:
-
Перепишите окончательное решение об авторизации для настройки политики доступа к авторизации.
@Component public class MyAccessDecisionManager implements AccessDecisionManager { @Override public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException { ConfigAttribute c; String needRole; if(null== configAttributes || configAttributes.size() <=0) { return; } //1.获取已定义的好资源权限配置 Iterator<ConfigAttribute> iterable=configAttributes.iterator(); while (iterable.hasNext()){ c=iterable.next(); needRole=c.getAttribute(); //2.依次比对用户角色对应的资源权限 for (GrantedAuthority grantedAuthority:authentication.getAuthorities()){ if(needRole.trim().equals(grantedAuthority.getAuthority())){ return; } } } } @Override public boolean supports(ConfigAttribute attribute) { return true; } @Override public boolean supports(Class<?> clazz) { return true; }
}
3.4 Настройка SecurityConfig
Настроить SecurityConfigОсновная работа это:
-
Загрузите перехватчик FilterSecurityInterceptor в WebSecurityConfig.
protected void configure(HttpSecurity http) throws Exception { http.headers().frameOptions().disable().and() //表单登录 .formLogin() .loginPage(SecurityConstants.APP_FORM_LOGIN_PAGE) .loginProcessingUrl(SecurityConstants.APP_FORM_LOGIN_URL) .successHandler(authenticationSuccessHandler()) .failureHandler(authenticationFailureHandler()) .and() //应用sms认证配置 .apply(smsAuthenticationSecurityConfig) .and() //允许通过 .authorizeRequests() .antMatchers(SecurityConstants.APP_MOBILE_VERIFY_CODE_URL, SecurityConstants.APP_USER_REGISTER_URL, SecurityConstants.APP_FORM_LOGIN_INDEX_URL) .permitAll()//以上的请求都不需要认证 .and() //“记住我”配置 .rememberMe() .tokenRepository(jdbcTokenRepository())//token入库处理类 .tokenValiditySeconds(SecurityConstants.REMEMBER_ME_VERIFY_TIME)//remember-me有效时间设置 .rememberMeParameter(SecurityConstants.REMEMBER_ME_PARAM_NAME)//请求参数名设置 .and() .csrf().disable(); //增加自定义权限授权拦截器 http.addFilterBefore(myFilterSecurityInterceptor,FilterSecurityInterceptor.class); }
Суммировать
В процессе авторизации Spring Security могут быть задействованы упомянутые выше компоненты.Главное - несколько раз пройтись по исходному коду, чтобы понять принцип, чтобы кодировать код более плавно. На данный момент процесс анализа аутентификации и авторизации Spring Security написан, а затем мы объединим предыдущие разделы, чтобы написать идеальную систему управления разрешениями для Spring Security.
Вы в порядке, офицеры? Если вам это нравится, проведите пальцем, чтобы нажать 💗, нажмите, чтобы подписаться! ! Спасибо за Вашу поддержку!
Добро пожаловать в публичный аккаунт【Технический блог Ccww], впервые была запущена оригинальная техническая статья