Spring security (3) --- Процесс аутентификации

Spring Boot

   в первых двух разделахSpring security (1) Структура архитектуры — анализ компонентов, служб, фильтровиSpring Security (2) — конфигурация WebSecurityConfigurer и порядок фильтрацииПодготовка к аутентификации Spring Security позволяет нам лучше понять процесс аутентификации и написание кода проекта.

1. Рабочий процесс процесса сертификации

Рабочий процесс аутентификации:

    AbstractAuthenticationProcessingFilter
	doFilter()(attemptAuthentication()获取Authentication实体)
		->UsernamePasswordAuthenticationFilter(AbstractAuthenticationProcessingFilter的子类)
			attemptAuthentication() (在UsernamePasswordAuthenticationToken()中将username 和 password 生成 UsernamePasswordAuthenticationToken对象,getAuthenticationManager().authenticate进行认证以及返回获取Authentication实体)
	            ->AuthenticationManager
		     ->ProviderManager()(AuthenticationManager接口实现)
		             authenticate()(AuthenticationProvider.authenticate()进行认证并获取Authentication实体)
                        ->AbstractUserDetailsAuthenticationProvider(内置缓存机制,如果缓存中没有用户信息就调用retrieveUser()获取用户)
				authenticate()  (获取Authentication实体需要userDetails,在缓存中或者retrieveUser()获取userDetails;验证additionalAuthenticationChecks();     createSuccessAuthentication()生成Authentication实体)
				->DaoAuthenticationProvider
					retrieveUser()  (调用自定义UserDetailsService中loadUserByUsername()加载userDetails)
				    ->UserDetailsService   
					    loadUserByUsername()(获取userDetails)

Пожалуйста, смотрите следующий раздел для конкретного процесса.

1.1: Сначала запрос обрабатывается фильтрами AbstractAuthenticationProcessingFilter и UsernamePasswordAuthenticationFilter.

   Когда приходит запрос, по умолчанию запрос сначала проходит через фильтр UsernamePasswordAuthenticationFilter, подкласс AbstractAuthenticationProcessingFilter. Вызов метода tryAuthentication() в фильтре UsernamePasswordAuthenticationFilter реализует основной двухэтапный процесс:

  1. Создайте объект аутентификации с подробной информацией о пользователе, и объект аутентификации UsernamePasswordAuthenticationToken будет создан в фильтре UsernamePasswordAuthenticationFilter по умолчанию;
  2. AuthenticationManager вызывает метод authentication() для процесса аутентификации.По умолчанию для аутентификации используется класс ProviderManager.

Анализ исходного кода UsernamePasswordAuthenticationFilter:

    public class UsernamePasswordAuthenticationFilter extends
    	AbstractAuthenticationProcessingFilter {
    	....
    	public Authentication attemptAuthentication(HttpServletRequest request,
			HttpServletResponse response) throws AuthenticationException {
	        .....
	        //1.创建拥有用户的详情信息的Authentication对象
		UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
				username, password);

		// Allow subclasses to set the "details" property
		setDetails(request, authRequest);
                //2.AuthenticationManager进行认证
		return this.getAuthenticationManager().authenticate(authRequest);
	}
	...
   }  

1.2 После обработки запроса фильтром он проходит аутентификацию в AuthenticationManager и ProviderManager

   В UsernamePasswordAuthenticationFilter видно, что метод authentication() интерфейса AuthenticationManager будет вызываться для детальной аутентификации. По умолчанию для аутентификации будет использоваться authentication() подкласса AuthenticationManager ProviderManager, которую можно разделить на три основных процесса:

  1. Для аутентификации используется AuthenticationProvide.authenticate() По умолчанию для аутентификации будет использоваться AbstractUserDetailsAuthenticationProvider;
  2. После успешной аутентификации удалите учетные данные и другие конфиденциальные данные из аутентификации, иначе будет выдано исключение или аутентификация завершится неудачно;
  3. Опубликуйте событие успешной проверки подлинности и сохраните объект проверки подлинности в контексте безопасности.

Анализ исходного кода ProviderManager:

public class ProviderManager implements AuthenticationManager, MessageSourceAware,
	InitializingBean {
	...
        public Authentication authenticate(Authentication authentication)
			throws AuthenticationException {
                ...
                //AuthenticationProvider依次进行认证
		for (AuthenticationProvider provider : getProviders()) {
	        	...
			try {
			        //1.1进行认证,并返回Authentication对象
				result = provider.authenticate(authentication);

				if (result != null) {
					copyDetails(authentication, result);
					break;
				}
			}
		        ...
			catch (AuthenticationException e) {
				lastException = e;
			}
		}
 		if (result == null && parent != null) {
			// Allow the parent to try.
			try {
		        	//1.2如果1.1认证中没有一个验证通过,则使用父类型AuthenticationManager进行验证
				result = parent.authenticate(authentication);
			}
			catch (ProviderNotFoundException e) {
				// ignore as we will throw below if no other exception occurred prior to
				// calling parent and the parent
				// may throw ProviderNotFound even though a provider in the child already
				// handled the request
			}
			catch (AuthenticationException e) {
				lastException = e;
			}
		}
                //2.从authentication中删除凭据和其他机密数据
		if (result != null) {
			if (eraseCredentialsAfterAuthentication
					&& (result instanceof CredentialsContainer)) {
				// Authentication is complete. Remove credentials and other secret data
				// from authentication
				((CredentialsContainer) result).eraseCredentials();
			}
              //3.发布认证成功事件,并将Authentication对象保存到security context中
			eventPublisher.publishAuthenticationSuccess(result);
			return result;
		}
	}

1.3 Подробная обработка процесса аутентификации: AuthenticationProvider, AbstractUserDetailsAuthenticationProvider и DaoAuthenticationProvider

  В процессе детальной обработки аутентификации по умолчанию аутентификация AuthenticationProvider выполняется абстрактным классом AbstractUserDetailsAuthenticationProvider и подклассом DaoAuthenticationProvider класса AbstractUserDetailsAuthenticationProvider, чтобы помочь в совместной работе для аутентификации. В основном его можно разделить на следующие этапы:

  1. Чтобы получить информацию о пользователе UserDetails, сначала прочитайте информацию из кеша, если в кеше нет информации, загрузите ее в UserDetailsService, самое главное, можете прочитать информацию о пользователе UserDetails из нашего пользовательского UserDetailsService;

  2. Три шага для проверки:
    1). preAuthenticationChecks

    2). Дополнительные проверки подлинности: используйте метод PasswordEncoder.matches() для аутентификации. В методе проверки данные проверки были зашифрованы алгоритмом PasswordEncoder. Метод шифрования алгоритма может быть определен путем реализации интерфейса PasswordEncoder.

    3). postAuthenticationChecks

  3. Инкапсулируйте информацию о аутентифицированном пользователе в объект UsernamePasswordAuthenticationToken и верните его; объект инкапсулирует идентификационную информацию пользователя и соответствующую информацию о разрешениях.

  Основная функция AbstractUserDetailsAuthenticationProvider предоставляет метод аутентификации authentication() и анализ исходного кода метода перезаписи DaoAuthenticationProvider:

    public abstract class AbstractUserDetailsAuthenticationProvider implements
    	AuthenticationProvider, InitializingBean, MessageSourceAware {
    	...
		public Authentication authenticate(Authentication authentication)
		throws AuthenticationException {
                   ...
    		boolean cacheWasUsed = true;
    		//1.1获取缓存中UserDetails信息
    		UserDetails user = this.userCache.getUserFromCache(username);
                  //1.2 如果缓存中没有信息,从UserDetailsService中获取
    		if (user == null) {
    			cacheWasUsed = false;
    
    			try {
    			        //使用DaoAuthenticationProvider中重写的方法去获取信息
    				user = retrieveUser(username,
    						(UsernamePasswordAuthenticationToken) authentication);
    			}catch{
    			...
    			}
    			...
    		try {
    		        //进行检验认证
    			preAuthenticationChecks.check(user);
    			additionalAuthenticationChecks(user,
    					(UsernamePasswordAuthenticationToken) authentication);
    		}catch{
    		...
    		}
    	        ...
    		postAuthenticationChecks.check(user);
                   ....
                   // 将已通过验证的用户信息封装成 UsernamePasswordAuthenticationToken对象并返回
    		return createSuccessAuthentication(principalToReturn, authentication, user);
    }

Функции   DaoAuthenticationProvider в основном заключаются в шифровании PasswordEncoder для учетных данных аутентификации и в переписывании методов retrieveUser и AdditionalAuthenticationChecks абстрактного класса AbstractUserDetailsAuthenticationProvider.Среди них, retrieveUser в основном получает информацию UserDetails, анализ исходного кода

    protected final UserDetails retrieveUser(String username,
		UsernamePasswordAuthenticationToken authentication)
		throws AuthenticationException {
	prepareTimingAttackProtection();
	try {
	        //根据UserDetailsService获取UserDetails信息,从自定义的UserDetailsService获取
		UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);
		if (loadedUser == null) {
			throw new InternalAuthenticationServiceException(
					"UserDetailsService returned null, which is an interface contract violation");
		}
		return loadedUser;
	}
	catch (UsernameNotFoundException ex) {
		mitigateAgainstTimingAttack(authentication);
		throw ex;
	}
	catch (InternalAuthenticationServiceException ex) {
		throw ex;
	}
	catch (Exception ex) {
		throw new InternalAuthenticationServiceException(ex.getMessage(), ex);
	}
}

AdditionalAuthenticationChecks в основном использует PasswordEncoder для проверки пароля, анализа исходного кода:

protected void additionalAuthenticationChecks(UserDetails userDetails,
		UsernamePasswordAuthenticationToken authentication)
		throws AuthenticationException {
	if (authentication.getCredentials() == null) {
		logger.debug("Authentication failed: no credentials provided");

		throw new BadCredentialsException(messages.getMessage(
				"AbstractUserDetailsAuthenticationProvider.badCredentials",
				"Bad credentials"));
	}

	String presentedPassword = authentication.getCredentials().toString();
        //进行密码验证
	if (!passwordEncoder.matches(presentedPassword, userDetails.getPassword())) {
		logger.debug("Authentication failed: password does not match stored value");

		throw new BadCredentialsException(messages.getMessage(
				"AbstractUserDetailsAuthenticationProvider.badCredentials",
				"Bad credentials"));
	}
}

1.4 Получение учетных данных для аутентификации, необходимых для аутентификации: UserDetailsService

   Учетные данные для аутентификации должны быть получены во время аутентификации. Учетные данные для аутентификации получены из UserDetailsService. Интерфейс UserDetailsService имеет только один метод:

UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;

Вызов метода LoaduserbyUsername с именем пользователя username Возвращает интерфейс UserDetails:

public interface UserDetails extends Serializable {
	//1.权限集合
	Collection<? extends GrantedAuthority> getAuthorities();
	//2.密码	
	String getPassword();
	//3.用户名
	String getUsername();
	//4.用户是否过期
	boolean isAccountNonExpired();
	//5.是否锁定	
	boolean isAccountNonLocked();
	//6.用户密码是否过期	
	boolean isCredentialsNonExpired();
	//7.账号是否可用(可理解为是否删除)
	boolean isEnabled();
}

Мы можем получить учетные данные для аутентификации из разных источников данных, внедрив UserDetailsService для настройки класса UserDetails.

1.5 Резюме

СуммироватьSpring Security (2) — конфигурация WebSecurityConfigurer и порядок фильтрацииИ этот раздел Spring security (3) хочет реализовать простой процесс аутентификации:

  1. Шаг 1. Настройте WebSecurityConfig
  2. Шаг 2. Реализуйте пользовательскую службу UserDetailsService и настройте учетные данные для проверки подлинности, полученные из исходного кода данных.

2 Spring загрузка и интеграция безопасности Spring

2.1 Настройка WebSecurityConfig

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
  @Override
  protected void configure(HttpSecurity http) throws Exception {
        // TODO Auto-generated method stub
        //super.configure(http);
        http .csrf().disable()
             .authorizeRequests()
             .anyRequest().authenticated()
              .and()
             .formLogin()
                .loginPage("/login")
                .loginProcessingUrl("/login/form")
                .failureUrl("/login-error")
                .permitAll()  //表单登录,permitAll()表示这个不需要验证 登录页面,登录失败页面
              .and()
                .logout().permitAll();
        }
}

2.2 Реализация UserDetailsService

@service
public class CustomUserService implements UserDetailsService {
 @Autowired
 private UserInfoMapper userInfoMapper;
 @Autowired
 private PermissionInfoMapper permissionInfoMapper;
 @Autowired
 private BCryptPasswordEncoderService bCryptPasswordEncoderService;
  @Override
  public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        // TODO Auto-generated method stub
        //这里可以可以通过username(登录时输入的用户名)然后到数据库中找到对应的用户信息,并构建成我们自己的UserInfo来返回。
        UserInfoDTO user = userInfoMapper.getUserInfoByUserName(username);
         if (user != null) {
        List<PermissionInfoDTO> permissionInfoDTOS = permissionInfoMapper.findByAdminUserId(userInfo.getId());
        List<GrantedAuthority> grantedAuthorityList = new ArrayList<>();
        for (PermissionInfoDTO permissionInfoDTO : permissionInfoDTOS) {
            if (permissionInfoDTO != null && permissionInfoDTO.getPermissionName() != null) {
                GrantedAuthority grantedAuthority = new SimpleGrantedAuthority(
                        permissionInfoDTO.getPermissionName());
                grantedAuthorityList.add(grantedAuthority);
                 }
            }
             return new User(userInfo.getUserName(), bCryptPasswordEncoderService.encode(userInfo.getPasswaord()), grantedAuthorityList);
         }else {
        throw new UsernameNotFoundException("admin" + username + "do not exist");
         }
    }
}

2.3 код на гитхабе

Ссылка на сайт

  Расширенные знания об аутентификации Spring Security, Spring Security OAuth2 и т. д., а также демонстрация проекта: Spring Security OAuth2 интегрирует JWT, ip, SMS и анализ кода входа в WeChat и совместное использование. Наконец, если есть какие-либо ошибки, пожалуйста, прокомментируйте.

Вы в порядке, офицеры? Если вам это нравится, проведите пальцем, чтобы нажать 💗, нажмите, чтобы подписаться! ! Спасибо за Вашу поддержку!

Добро пожаловать в публичный аккаунт【Технический блог Ccww], впервые была запущена оригинальная техническая статья