Spring security (5) - совершенная система управления полномочиями (анализ процесса авторизации)

Spring Boot

1. Концепции, связанные с управлением правами

   Управление полномочиями является важной частью почти всех фоновых систем, и основная цель состоит в том, чтобы контролировать полномочия всей фоновой системы управления. Общий контроль доступа на основе ролей, его модель авторизации "разрешение роли пользователя"Коротко говоря, у пользователя есть несколько ролей, а у роли - несколько разрешений. Среди них

  • Пользователь:Излишне говорить, что все это знают;
  • Роль:Концепция коллекции, управление ролями — это процесс определения разрешений, которые имеет роль;
  • Разрешения:1) Права доступа к страницам, которые определяют, какие страницы вы можете видеть, а какие нет;
        2) Операционные полномочия, которые контролируют, какие операции вы можете выполнять на странице (запрос, удаление, редактирование и т. д.);
        3) Разрешение на доступ к данным определяет, какие данные вы можете видеть.

Суть в следующем:
 Permission (Разрешение) = Resource (Ресурс) + Action (Привилегия)
 Роль = набор низкоуровневых разрешений
 Пользователь = набор ролей (роли высокого уровня)

Процесс управления правами:

  1. Управление аутентификацией, то есть логика определения полномочий, например, управление меню (после того, как обычный бизнес-персонал входит в систему, они не могут видеть меню [Управление пользователями]), управление полномочиями функций (управление доступом к URL-адресам), управление полномочиями на уровне строк , и т.д.
  2. Управление авторизацией, то есть процесс назначения прав, таких как прямая авторизация пользователям, права, непосредственно назначенные пользователям, имеют наивысший приоритет, и авторизация на должности, к которым принадлежат пользователи. принадлежность можно рассматривать как группу, которая имеет те же функции, что и роли, но каждый пользователь может быть связан только с одной информацией о работе и т. д.

В реальном проекте много пользователей, и крайне обременительно авторизовать каждого системного пользователя по одному, поэтому вы можете узнать то же самое, что и система управления файлами 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. Корпус - пользовательский компонент

Пользовательские компоненты:

  1. Пользовательский FilterSecurityInterceptor, который можно имитироватьFilterSecurityInterceptor, который реализует абстрактный классAbstractSecurityInterceptorа такжеFilterинтерфейс, главное поставитьПользовательский SecurityMetadataSourceиПользовательский доступDecisionManagerНастроен в перехватчик пользовательского FilterSecurityInterceptor

  2. Пользовательский SecurityMetadataSource, реализовать интерфейс FilterInvocationSecurityMetadataSource и реализовать загрузку ConfigAttribute из базы данных или других источников данных (то есть загрузку разрешений ресурсов из базы данных или других источников данных).

  3. Пользовательский доступDecisionManager, вы можете использовать официальный UnanimousBased на основе AccessDecisionVoter для аутентификации полномочий

  4. Пользовательский доступ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], впервые была запущена оригинальная техническая статья