English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية

Полный процесс записи самостоятельной аутентификации в spring security

Категории использования Spring Security:

Как использовать Spring Security, я уверен, что кто-то, кто искал в Бaidu, знает, всего существует四种 способов использования, от простого к сложному:;

1. Не использовать базу данных, все данные пишутся в файл конфигурации, это также демонстрация в официальной документации;

2. Использование базы данных, проектирование базы данных на основе стандартного кода реализации Spring Security, то есть база данных уже фиксирована, этот метод не гибок и дизайн базы данных очень примитивен, его практичность низка;

3. Spring Security и Acegi различаются, так как он не позволяет изменять mặc định filter, но поддерживает вставку filter, поэтому на основе этого мы можем вставить свой filter для использования гибкого;

4. Враждебные методы, модификация исходного кода, как было сказано ранее, модификация défaut filter - это только замена файла конфигурации для замены фильтра, это直接 модифицирует исходный код, но это не соответствует принципам ОО-дизайна и не является практичным и неэффективным.

Эта статья подробно рассказывает о пользовательской аутентификации входа Spring security и предоставляет информацию для参考 и обучения. Не будем тратить время на лишние слова, давайте перейдем к детальному рассмотрению.

1.Краткое описание

1.1.Обзор

Spring security - это безопасность на основе Spring AOP и Servlet фильтров, которая используется для управления аутентификацией и разрешениями.

1.2.Пользовательский процесс аутентификации Spring security

1) Процесс аутентификации

Создание токена аутентификации с неуспешным результатом                 

 ↑ (получение информации)  (распределение provider по токену аутентификации)     
 AuthenticationFilter -> AuthenticationManager -> AuthenticationProvider
        ↓ (аутентификация)
       UserDetails (обычно извлекается из базы данных)
        ↓ (через)
        Создание токена аутентификации с успешным результатом
         ↓ (хранение)
        SecurityContextHolder

2) Добавьте AuthenticationFilter в цепочку фильтров безопасности (настройте на ресурсном сервере), например:

http.addFilterBefore(AuthenticationFilter, AbstractPreAuthenticatedProcessingFilter.class)

или:

http.addFilterAfter(AuthenticationFilter, UsernamePasswordAuthenticationFilter.class)

2.Пример входа через SMS с использованием номера телефона

2.1.Рабочая среда

  • SpringBoot
  • Spring security
  • Redis

2.2.Анализ ядра кода

2.2.1.Процесс аутентификации входа с помощью пользовательского токена

2.2.1.1.Пользовательский токен для аутентификации входа

/**
 * Токен для手机登录
 *
 * @author : CatalpaFlat
 */
public class MobileLoginAuthenticationToken extends AbstractAuthenticationToken {
 private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;
 private static final Logger logger = LoggerFactory.getLogger(MobileLoginAuthenticationToken.class.getName());
 private final Object principal;
 public MobileLoginAuthenticationToken(String mobile) {
 super(null);
 this.principal = mobile;
 this.setAuthenticated(false);
 logger.info("MobileLoginAuthenticationToken setAuthenticated ->false loading ...");
 }
 public MobileLoginAuthenticationToken(Object principal,
      Collection<? extends GrantedAuthority> authorities) {
 super(authorities);
 this.principal = principal;
 // must use super, as we override
 super.setAuthenticated(true);
 logger.info("MobileLoginAuthenticationToken setAuthenticated ->true loading ...");
 }
 @Override
 public void setAuthenticated(boolean authenticated) {
 if (authenticated) {
  throw new IllegalArgumentException;
   "Не можно установить этот токен в доверенный - используйте конструктор, который принимает список GrantedAuthority");
 }
 super.setAuthenticated(false);
 }
 @Override
 public Object getCredentials() {
 return null;
 }
 @Override
 public Object getPrincipal() {
 return this.principal;
 }
 @Override
 public void eraseCredentials() {
 super.eraseCredentials();
 }
}

Примечание:

setAuthenticated(): determine if authenticated

  • When filtering, an unauthenticated AuthenticationToken is generated, at this time, the custom token's setAuthenticated() is called, and it is set to false -> unauthenticated
  • When providing, an authenticated AuthenticationToken is generated, at this time, the parent class's setAuthenticated() is called, and it is set to true -> authenticated

2.2.1.1. Custom authentication login filter

/**
 * SMS login filter
 *
 * @author : CatalpaFlat
 */
public class MobileLoginAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
 private boolean postOnly = true;
 private static final Logger logger = LoggerFactory.getLogger(MobileLoginAuthenticationFilter.class.getName());
 @Getter
 @Setter
 private String mobileParameterName;
 public MobileLoginAuthenticationFilter(String mobileLoginUrl, String mobileParameterName,
      String httpMethod) {
 super(new AntPathRequestMatcher(mobileLoginUrl, httpMethod));
 this.mobileParameterName = mobileParameterName;
 logger.info("MobileLoginAuthenticationFilter загрузка ...
 }
 @Override
 public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException {
 if (postOnly && !request.getMethod().equals(HttpMethod.POST.name())) {
  throw new AuthenticationServiceException("Метод аутентификации не поддерживается: " + request.getMethod());
 }
 // Получить номер телефона
 String mobile = obtainMobile(request);
 // Собрать токен
 MobileLoginAuthenticationToken authRequest = new MobileLoginAuthenticationToken(mobile);
 // Разрешить подклассам устанавливать свойства "details"
 setDetails(request, authRequest);
 return this.getAuthenticationManager().authenticate(authRequest);
 }
 /**
 * Установка подробной информации для的身份认证
 */
 private void setDetails(HttpServletRequest request, MobileLoginAuthenticationToken authRequest) {
 authRequest.setDetails(authenticationDetailsSource.buildDetails(request));
 }
 /**
 * Получение номера телефона
 */
 private String obtainMobile(HttpServletRequest request) {
 return request.getParameter(mobileParameterName);
 }
 public void setPostOnly(boolean postOnly) {
 this.postOnly = postOnly;
 }
}

Примечание:attemptAuthentication()方法:

  • 过滤指定的url、httpMethod
  • 获取所需请求参数数据封装生成一个未认证的AuthenticationToken
  • 传递给AuthenticationManager认证

2.2.1.1.自定义认证登录提供者

/**
 * 手机短信登录认证提供者
 *
 * @author : CatalpaFlat
 */
public class MobileLoginAuthenticationProvider implements AuthenticationProvider {
 private static final Logger logger = LoggerFactory.getLogger(MobileLoginAuthenticationProvider.class.getName());
 @Getter
 @Setter
 private UserDetailsService customUserDetailsService;
 public MobileLoginAuthenticationProvider() {
 logger.info("MobileLoginAuthenticationProvider loading ...");
 }
 /**
 * 认证
 */
 @Override
 public Authentication authenticate(Authentication authentication) throws AuthenticationException {
 //获取过滤器封装的token信息
 MobileLoginAuthenticationToken authenticationToken = (MobileLoginAuthenticationToken) authentication;
 //获取用户信息(数据库认证)
 UserDetails userDetails = customUserDetailsService.loadUserByUsername((String) authenticationToken.getPrincipal());
 // Не успешно
 if (userDetails == null) {
  throw new InternalAuthenticationServiceException("Unable to obtain user information");
 }
 // Успешно
 MobileLoginAuthenticationToken authenticationResult = new MobileLoginAuthenticationToken(userDetails, userDetails.getAuthorities());
 authenticationResult.setDetails(authenticationToken.getDetails());
 return authenticationResult;
 }
 /**
 * Определите,哪个 Провайдер использовать, в зависимости от типа токена
 */
 @Override
 public boolean supports(Class<?> authentication) {
 return MobileLoginAuthenticationToken.class.isAssignableFrom(authentication);
 }
}

Примечание:метод authenticate()

  • Получить информацию о токене,封装енном фильтром
  • Извлечь информацию о пользователе из UserDetailsService (аутентификация в базе данных) -> определить успех или неудачу
  • Если успешна, оберните новый AuthenticationToken и верните

2.2.1.1. Настройка пользовательской конфигурации аутентификации登录а

@Configuration(SpringBeanNameConstant.DEFAULT_CUSTOM_MOBILE_LOGIN_AUTHENTICATION_SECURITY_CONFIG_BN)
public class MobileLoginAuthenticationSecurityConfig extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {
 private static final Logger logger = LoggerFactory.getLogger(MobileLoginAuthenticationSecurityConfig.class.getName());
 @Value("${login.mobile.url}")
 private String defaultMobileLoginUrl;
 @Value("${login.mobile.parameter}")
 private String defaultMobileLoginParameter;
 @Value("${login.mobile.httpMethod}")
 private String defaultMobileLoginHttpMethod;
 @Autowired
 private CustomYmlConfig customYmlConfig;
 @Autowired
 private UserDetailsService customUserDetailsService;
 @Autowired
 private AuthenticationSuccessHandler customAuthenticationSuccessHandler;
 @Autowired
 private AuthenticationFailureHandler customAuthenticationFailureHandler;
 public MobileLoginAuthenticationSecurityConfig() {
 logger.info("MobileLoginAuthenticationSecurityConfig loading ...");
 }
 @Override
 public void configure(HttpSecurity http) throws Exception {
 MobilePOJO mobile = customYmlConfig.getLogins().getMobile();
 String url = mobile.getUrl();
 String parameter = mobile.getParameter().getMobile();
 String httpMethod = mobile.getHttpMethod();
 MobileLoginAuthenticationFilter mobileLoginAuthenticationFilter = new MobileLoginAuthenticationFilter(StringUtils.isBlank(url) ? defaultMobileLoginUrl : url,
  StringUtils.isBlank(parameter) ? defaultMobileLoginUrl : parameter, StringUtils.isBlank(httpMethod) ? defaultMobileLoginHttpMethod : httpMethod); mobileLoginAuthenticationFilter.setAuthenticationManager(http.getSharedObject(AuthenticationManager.class)); mobileLoginAuthenticationFilter.setAuthenticationSuccessHandler(customAuthenticationSuccessHandler); mobileLoginAuthenticationFilter.setAuthenticationFailureHandler(customAuthenticationFailureHandler);
 MobileLoginAuthenticationProvider mobileLoginAuthenticationProvider = new MobileLoginAuthenticationProvider(); mobileLoginAuthenticationProvider.setCustomUserDetailsService(customUserDetailsService);
 http.авторизацияПоставщик(mobileLoginAuthenticationProvider)
  .добавитьФильтрПосле(mobileLoginAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
 }
}

Примечание:метод configure()

Инсталлировать AuthenticationFilter и AuthenticationProvider

Добавить AuthenticationFilter и AuthenticationProvider в Spring Security.

2.2.2. Настройка проверки кода безопасности на основе Redis

2.2.2.1. Настройка фильтра проверки кода безопасности на основе Redis

/**
 * Фильтр проверки кода безопасности
 *
 * @author : CatalpaFlat
 */
@Component(SpringBeanNameConstant.DEFAULT_VALIDATE_CODE_FILTER_BN)
public class ValidateCodeFilter extends OncePerRequestFilter implements InitializingBean {
 private static final Logger logger = LoggerFactory.getLogger(ValidateCodeFilter.class.getName());
 @Autowired
 private CustomYmlConfig customYmlConfig;
 @Autowired
 private RedisTemplate<Object, Object> redisTemplate;
 /**
  * Инструмент класса для проверки соответствия запрашиваемого URL URL, настроенному в конфигурации
  */
 private AntPathMatcher pathMatcher = new AntPathMatcher();
 public ValidateCodeFilter() {
  logger.info("Loading ValidateCodeFilter...");
 }
 @Override
 protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
         FilterChain filterChain) throws ServletException, IOException {
  String url = customYmlConfig.getLogins().getMobile().getUrl();
  if (pathMatcher.match(url, request.getRequestURI())) {}}
   String deviceId = request.getHeader("deviceId");
   if (StringUtils.isBlank(deviceId)) {
    throw new CustomException(HttpStatus.NOT_ACCEPTABLE.value(), "deviceId не найден в заголовке запроса");
   }
   String codeParamName = customYmlConfig.getLogins().getMobile().getParameter().getCode();
   String code = request.getParameter(codeParamName);
   if (StringUtils.isBlank(code)) {
    throw new CustomException(HttpStatus.NOT_ACCEPTABLE.value(), "Код не найден в параметрах запроса");
   }
   String key = SystemConstant.DEFAULT_MOBILE_KEY_PIX + deviceId;
   SmsCodePO smsCodePo = (SmsCodePO) redisTemplate.opsForValue().get(key);
   if (smsCodePo.isExpried()){
    throw new CustomException(HttpStatus.BAD_REQUEST.value(), "Код проверки истек");
   }
   String smsCode = smsCodePo.getCode();
   if (StringUtils.isBlank(smsCode)) {
    throw new CustomException(HttpStatus.BAD_REQUEST.value(), "Код проверки не существует");
   }
   if (StringUtils.equals(code, smsCode)) {
    redisTemplate.delete(key);
    //let it go
    filterChain.doFilter(request, response);
   }
    throw new CustomException(HttpStatus.BAD_REQUEST.value(), "Validation code is incorrect");
   }
  }
   //let it go
   filterChain.doFilter(request, response);
  }
 }
}

Примечание:doFilterInternal()

Пользовательская проверка кода проверки

2.2.2.2. Добавить пользовательский фильтр проверки кода в цепочку фильтров spring security

http.addFilterBefore(validateCodeFilter, AbstractPreAuthenticatedProcessingFilter.class)

Примечание:Добавить в фильтр предварительной аутентификации

3. Результат тестирования

В конце прилагается адрес исходного кода:https://gitee.com/CatalpaFlat/springSecurity.git  (Локальная загрузка)

Обобщение

Вот и все, что есть в этой статье, надеюсь, что содержимое статьи будет иметь определенную образовательную или профессиональную ценность для вас. Если у вас есть вопросы, пожалуйста, оставляйте комментарии для обсуждения, спасибо за поддержку呐喊 руководства.

Заявление: содержимое этой статьи взято из Интернета, авторские права принадлежат соответствующему владельцу. Контент был предложен пользователями Интернета и загружен самостоятельно. Этот сайт не обладает правами собственности, не производил редактирование вручную и не несет ответственности за соответствующие юридические обязательства. Если вы обнаружите подозрительное нарушение авторских прав, пожалуйста, отправьте письмо по адресу: notice#oldtoolbag.com (во время отправки письма, пожалуйста, замените # на @) для сообщения о нарушении и предоставьте соответствующие доказательства. При подтверждении факта нарушения сайт незамедлительно удалят涉嫌侵权的内容。

Основной учебник
Давай посмотришь