Spring Security Web и OAuth2

Spring

предисловие

Spring Security — многомодульный проект, в котором разобрались доПроцесс аутентификации Spring Security, только сейчас я обнаружил, что часть гребенки — это больше контента в основном модуле Spring Security Core.

При повседневном использовании он будет включать в себя больше вещей в Spring Security Web и Spring Security OAuth 2. Основное содержание этого блога — разобраться во взаимоотношениях между ними и понять их соответствующие роли.

Spring Security Core

Spring Security Core играет важную роль во всей структуре Spring Security, предоставляя абстракции, связанные с аутентификацией и контролем разрешений.

Однако в процессе использования мы можем столкнуться с дополнительными абстракциями, связанными с аутентификацией, такими как:

  • пройти черезAuthenticationManagerОбеспечивает абстракцию для методов аутентификации пользователей, позволяяProviderManagerиAuthenticationProviderсобрать и реализовать собственный метод аутентификации
  • пройти черезUserDetailsиUserDetailsServiceПредоставляет абстракцию для сведений о пользователе и способ получения сведений о пользователе.
  • пройти черезAuthenticationОбеспечивает абстракцию информации об аутентификации пользователя и результатов аутентификации.
  • пройти черезSecurityContextиSecurityContextHolderПредоставляет способ сохранить результаты аутентификации
  • ...

Эти вещи на самом деле абстрагируют ключевые компоненты традиционного процесса аутентификации. В сочетании с традиционным процессом аутентификации легко понять взаимосвязь между этими компонентами. Вы также можете увидеть это изображение изSpring Security (1) — Обзор архитектуры | Исходный код Taro — Блог о чистом анализе исходного кодаКартина:

Абстракция части контроля разрешений в основномAccessDecisionManagerиAccessDecisionVoterТеперь я не управлял этими двумя вещами вручную.Я могу только сказать, что услуги, предоставляемые Spring Security Web, слишком интимны, а реализация части контроля разрешений не требует от меня слишком много беспокойства.

Для получения дополнительной информации о модуле Spring Security Core см.:

Spring Security Web

Если Spring Security Core предоставляет только абстракции, связанные с аутентификацией и контролем разрешений, Spring Security Web предоставляет нам конкретную реализацию и применение этих абстракций.

Spring Security Web черезцепочка фильтровЧтобы реализовать ряд функций, связанных с веб-безопасностью, аутентификация пользователя и контроль разрешений являются лишь частью этого. В этой части реализации фильтр действует как идентификатор вызывающего объекта Spring Security Core. Общий процесс таков:

  • Информация об аутентификации в запросе извлечения фильтра инкапсулируется какAuthenticationПерейти кAuthenticationManagerАутентифицируйтесь, а затем поместите результат аутентификации вSecurityContextдля последующих фильтров
  • Фильтр используется на основе результата аутентификации до того, как запрос поступит в конечную точку.AccessDecisionManagerОпределите, есть ли у вас соответствующие разрешения

Здесь Spring Security Core — это только часть функциональности, которую использует Spring Security Web, и, что более важно, вся цепочка фильтров.

Построение цепочки фильтров

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

Разобравшись с построением и логикой вызова цепочки построителей, я чувствую, что логика построения цепочки фильтров не кажется очень полезной, лучше смотреть непосредственно на логику вызова цепочки фильтров...

Процесс разбора этой части логики немного сложен, так или иначе, когда я отлаживал, точка останова была наbuild()Повторяющиеся горизонтальные прыжки рядом с методом, здесь для простоты сразу ставим результат1:

Временная диаграмма не очень стандартна, достаточно общего смысла ( ̄▽ ̄), анализ следующий:

  1. Конструкция цепочки фильтров в Spring Security Web в основном состоит изWebSecurityиHttpSecurityЗаконченный
  2. WebSecurityв зависимости от контекстаWebSecurityConfigurerстроить изHttpSecurityобъект, а затем передатьHttpSecurityстроить изSecurityFilterChainПосле этогоSecurityFilterChainставитьFilterChainProxyсередина. Среди них распространенной реализацией WebSecurityConfigurer являетсяWebMvcConfigurerAdapter, иSecurityFilterChainОбычная реализацияDefaultSecurityFilterChain
  3. HttpSecurityСогласно непосредственно добавленномуFilterи черезAbstractHttpConfigurerРеализация конструкции классаFilterСоздать цепочку фильтров

В этой части логики ключевыми объектами являютсяWebSecurityи классы конфигурации, от которых это зависитWebSecurityConfigurer, HttpSecurityи классы конфигурации, от которых это зависитAbstractHttpConfigurer.

В реальном использовании мы обычно наследуемWebMvcConfigurerAdapterэтоWebSecurityConfigurerкласс реализации, а затем переопределить егоconfigure(HttpSecurity)метод:

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    public void configure(HttpSecurity http) throws Exception {
        // @formatter:off
        http
            .authorizeRequests()
              .antMatchers("/oauth/**")
              .authenticated()
              .and()
            .requestMatchers()
              .antMatchers("/oauth/**","/login/**","/logout/**")
              .and()
            .csrf()
              .disable()
            .formLogin()
              .permitAll();
        // @formatter:on
    }
}

В приведенном выше классе мы наследуемWebSecurityConfigurerAdapterЭтот класс, когда мы помещаем пользовательский класс в контекст Spring, может использоваться WebSecurity для построения HttpSecurity, а переписанныйconfigure(HttpSecurity)Он будет вызываться до того, как HttpSecurity создаст фильтр для завершения настройки цепочки фильтров.

Среди них такие какcsrf()такие методы, какAbstractHttpConfigurerреализация, позволяющая нам настраивать определенные фильтры.

В конце HttpSecurity может завершить построение цепочки фильтров согласно соответствующей конфигурации, а затем WebSecurity поставит их вFilterChainProxyвернулся в экземпляре.

Вызов цепочки фильтров

Вызов цепочки фильтров в основном включает в себя два объекта: FilterChainProxy и DefaultSecurityFilterChain, ключ находится на самом FilterChainProxy.

Однако исходный код этих двух объектов достаточно прост, поэтому я не буду его здесь выкладывать, если интересно, можете глянуть, вот краткое описание результатов:

  • FilterChainProxy будет использоваться в качестве фильтра в цепочке фильтров контейнера сервлетов.После получения запроса он определит, есть ли соответствующая цепочка фильтров в удерживаемой цепочке фильтров.
  • При наличии совпадающей цепочки фильтров запрос обрабатывается непосредственно с первым совпадением.
  • Отсутствует совпадающая цепочка фильтров или после того, как совпадающая цепочка фильтров будет завершена, она вернется к цепочке фильтров контейнера сервлета для продолжения выполнения.

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

Прикрепил:

Использование цепочек фильтров

Использование веб-фильтров Spring Security в основном предназначено для настройки цепочки фильтров. Цепочка фильтров по умолчанию добавит некоторые фильтры, поставляемые с Spring Security Web. При ее использовании вам необходимо решить, следует ли удалить некоторые из фильтров по умолчанию (или не использовать их).Конфигурация по умолчанию) и добавьте пользовательский фильтр в подходящее место в цепочке фильтров.

Вот краткое введение в роль некоторых встроенных фильтров и порядок фильтров, в первую очередь встроенных фильтров:

  • фильтрSecurityContextPersistenceFilterИнформацию об аутентифицированных пользователях можно получить из сеанса.
  • фильтрAnonymousAuthenticationFilterв открытииSecurityContextHolderКогда вSecurityContextHolder
  • фильтрExceptionTranslationFilterможет справитьсяFilterSecurityInterceptorВозникающие исключения, перенаправление, вывод сообщений об ошибках и т. д.
  • фильтрFilterSecurityInterceptorОценка полномочий аутентификационной информации, выдача исключения, когда полномочий недостаточно.

При настройке фильтров (обычно фильтров проверки подлинности) нам необходимо учитывать расположение пользовательских фильтров, например, мы не должны помещать настраиваемые фильтры проверки подлинности вAnonymousAuthenticationFilterПосле официальная документация объясняет порядок фильтров: После удаления некоторых фильтров приблизительный порядок такой:

Среди них AuthenticationProcessingFilter относится к реализации фильтра аутентификации, такой как обычно используемыйUsernamePasswordAuthenticationFilterэтот фильтр.

Полная последовательность может относиться к:

В этом порядке, посколькуSecurityContextPersistenceFilterМожно получить информацию об аутентифицированном пользователе из сеанса, поэтому при настройке фильтра следует учитывать, содержит ли SecurityContextHolder информацию об аутентификации пользователя. Или установите фильтр для аутентификации пароля учетной записи пользователя в цепочке фильтров URL-адреса, связанного с входом/регистрацией, и установите фильтр для токена аутентификации в другой цепочке фильтров.

Spring Security OAuth2

Spring Security OAUTH2 создает на основе Spring Security Core и Spring Security Security, обеспечивая поддержку Framework для авторизации OAUTT2.

Среди них наиболее сложной частью являетсяСервер авторизацииНапротив, сервер ресурсов в основном повторно использует цепочку фильтров, предоставляемую Spring Security Web, через фильтрOAuth2AuthenticationProcessingFilterи запрос был выполненTokenПолучите информацию об аутентификации, поэтому основное внимание здесь будет уделено серверу авторизации.

Сервер авторизации

Для традиционных методов аутентификации в принципе достаточно простой аутентификации информации о пользователе, но для OAuth2 этого недостаточно.Для серверов авторизации OAuth2, помимо аутентификации пользователя, также требуется аутентификация клиента.Также необходимо проверить Scope, запрошенный Таким образом, одной цепочки фильтров недостаточно для завершения аутентификации обоих, поскольку SecurityContextHolder может содержать только один результат аутентификации.

Поэтому стратегия аутентификации, принятая Spring Security OAuth2, такова: завершить аутентификацию клиента или пользователя в цепочке фильтров, а затем завершить проверку оставшейся информации во внутренней логике конечной точки. И эта стратегия аутентификации отличается в разных режимах.

В основном здесьРежим кода авторизацииирежим пароляСтратегия аутентификации вAuthorizationEndpointиTokenEndpointДве основные цепочки фильтров закрыты.

Режим кода авторизации

Первым является режим кода авторизации.Для режима кода авторизации процесс запроса обычно является первым./oauth/authorizeПолучите код авторизации, а затем перейдите на/oauth/tokenПолучить токен, для/oauth/authorizeДля цепочки фильтров этой конечной точки аутентификацией является информация пользователя.После прохождения аутентификации он входит в конечную точку и запрашивает клиента.Scopeи пользователяApprovalПосле проверки будет сгенерирован код авторизации, который будет возвращен клиенту, если проверка пройдена.

Собственно, вот почему/oauth/authorizeЭта конечная точка должна аутентифицировать пользователя, потому что здесь нужно получитьПользовательавторизация.

Затем клиент берет код авторизации на/oauth/tokenКогда конечная точка получает маркер, цепочка фильтров конечной точки аутентифицирует клиента. После прохождения аутентификации он войдет в конечную точку. В это время конечная точка проверит область действия, запрошенную клиентом. После прохождения проверки , это пройдет.TokenGranterСоздайте токен и верните его клиенту.

То есть для режима кода авторизации:

  • конечная точка/oauth/authorizeПолная аутентификация пользователя, проверка области действия по запросу клиента и проверки авторизации пользователя.
  • конечная точка/oauth/tokenЗавершите аутентификацию клиента, проверку объема, запрошенного клиентом, и проверку кода авторизации клиента.

На самом деле это можно рассматривать как кодовую интерпретацию режима кода авторизации, потому что в режиме кода авторизации часто не клиент, управляемый пользователем, получает Токен, поэтому необходимо проверить, является ли клиент доверенным.

Соответствует логике, связанным со связанным исходным кодом, код удалена часть эффективности:

@RequestMapping(value = "/oauth/authorize")
public ModelAndView authorize(Map<String, Object> model, @RequestParam Map<String, String> parameters, SessionStatus sessionStatus, Principal principal) {
  AuthorizationRequest authorizationRequest = getOAuth2RequestFactory().createAuthorizationRequest(parameters);

  try {
    // 未通过认证的请求会抛异常
    if (!(principal instanceof Authentication) || !((Authentication) principal).isAuthenticated()) {
      throw new InsufficientAuthenticationException("User must be authenticated with Spring Security before authorization can be completed.");
    }

    ClientDetails client = getClientDetailsService().loadClientByClientId(authorizationRequest.getClientId());

    // 效验 Scope
    oauth2RequestValidator.validateScope(authorizationRequest, client);

    // 效验用户的授权
    authorizationRequest = userApprovalHandler.checkForPreApproval(authorizationRequest, (Authentication) principal);
    boolean approved = userApprovalHandler.isApproved(authorizationRequest, (Authentication) principal);
    authorizationRequest.setApproved(approved);

    // Validation is all done, so we can check for auto approval...
    if (authorizationRequest.isApproved()) {
      if (responseTypes.contains("token")) {
        return getImplicitGrantResponse(authorizationRequest);
      }
      if (responseTypes.contains("code")) {
        return new ModelAndView(getAuthorizationCodeResponse(authorizationRequest, (Authentication) principal));
      }
    }

    return getUserApprovalPageResponse(model, authorizationRequest, (Authentication) principal);
  }
  catch (RuntimeException e) {
    sessionStatus.setComplete();
    throw e;
  }
}

@RequestMapping(value = "/oauth/token", method=RequestMethod.POST)
public ResponseEntity<OAuth2AccessToken> postAccessToken(Principal principal, @RequestParam Map<String, String> parameters)
  throws HttpRequestMethodNotSupportedException {

  // 可以看到,通过效验的是客户端
  String clientId = getClientId(principal);
  ClientDetails authenticatedClient = getClientDetailsService().loadClientByClientId(clientId);

  TokenRequest tokenRequest = getOAuth2RequestFactory().createTokenRequest(parameters, authenticatedClient);

  // 效验请求的 Scope
  if (authenticatedClient != null) {
    oAuth2RequestValidator.validateScope(tokenRequest, authenticatedClient);
  }

  if (isAuthCodeRequest(parameters)) {
    // The scope was requested or determined during the authorization step
    if (!tokenRequest.getScope().isEmpty()) {
      tokenRequest.setScope(Collections.<String> emptySet());
    }
  }

  // 调用 TokenGranter 进行授权
  OAuth2AccessToken token = getTokenGranter().grant(tokenRequest.getGrantType(), tokenRequest);
  if (token == null) {
    throw new UnsupportedGrantTypeException("Unsupported grant type: " + tokenRequest.getGrantType());
  }
  return getResponse(token);
}

Блок-схема режима кода авторизации:

режим пароля

Режим шифрования или упрощенный режим имеет только одну конечную точку, т.е./oauth/tokenЭта конечная точка, то есть эта конечная точка выполняет аутентификацию как пользователя, так и клиента.

Однако для этой конечной точки невозможно одновременное наличие двух цепочек фильтров.Для поддержки режима кода авторизации определена ответственность цепочки фильтров этой конечной точки, то есть завершение аутентификации клиента . Следовательно, аутентификация пользователя может выполняться только в рамках внутренней логики конечной точки.

когдаTokenEndpointМодель лицензирования найденарежим пароля, будетResourceOwnerPasswordTokenGranterположить вTokenGranter, иResourceOwnerPasswordTokenGranterВызывается при авторизацииAuthenticationManagerЧтобы завершить аутентификацию пользователя, аутентификация пройдет только в том случае, если аутентификация прошла успешно.TokenServiceТокен генерирует доход.

// AuthorizationServerEndpointsConfigurer.getDefaultTokenGranters
private List<TokenGranter> getDefaultTokenGranters() {
  List<TokenGranter> tokenGranters = new ArrayList<TokenGranter>();
  tokenGranters.add(new AuthorizationCodeTokenGranter(tokenServices, authorizationCodeServices, clientDetails, requestFactory));
  tokenGranters.add(new RefreshTokenGranter(tokenServices, clientDetails, requestFactory));
  tokenGranters.add(new ImplicitTokenGranter(tokenServices, clientDetails, requestFactory));
  tokenGranters.add(new ClientCredentialsTokenGranter(tokenServices, clientDetails, requestFactory));
  if (authenticationManager != null) {
    tokenGranters.add(new ResourceOwnerPasswordTokenGranter(authenticationManager, tokenServices, clientDetails, requestFactory));
  }
  return tokenGranters;
}

Блок-схема режима пароля:

Аутентификация клиента

сквозьРежим кода авторизацииирежим пароляМы знаем, что аутентификация клиента выполняется в цепочке фильтров, и эта аутентификация может пройтиBasicAuthenticationFilterПолный, но, вероятно, более распространенныйClientCredentialsTokenEndpointFilterэтот фильтр.

Его внутренний процесс аутентификации на самом деле очень прост, и наиболее важным моментом является то, что он по-прежнему использует набор Spring Security Core!

@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
  throws AuthenticationException, IOException, ServletException {

  String clientId = request.getParameter("client_id");
  String clientSecret = request.getParameter("client_secret");

  // If the request is already authenticated we can assume that this filter is not needed
  Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
  if (authentication != null && authentication.isAuthenticated()) {
    return authentication;
  }

  UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(clientId, clientSecret);

  // 通过 AuthenticationManager 完成认证
  return this.getAuthenticationManager().authenticate(authRequest);
}

Мы знаем, что Spring Security OAuth2 предоставляет две абстракции, ClientDetails и ClientDetailsService, которые несовместимы с UserDetails и UserDetailsService.В настоящее время вы можете самостоятельно реализовать AuthenticationProvider для использования ClientDetails и ClientDetailsService, но вы также можете преобразовать ClientDetails и ClientDetailsService в UserDetails. и UserDetailsService, Spring Security OAuth2 выполняет это преобразование через ClientDetailsUserDetailsService:

public class ClientDetailsUserDetailsService implements UserDetailsService {
  private final ClientDetailsService clientDetailsService;

  public ClientDetailsUserDetailsService(ClientDetailsService clientDetailsService) {
    this.clientDetailsService = clientDetailsService;
  }

  public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
    ClientDetails clientDetails;
    try {
      clientDetails = clientDetailsService.loadClientByClientId(username);
    } catch (NoSuchClientException e) {
      throw new UsernameNotFoundException(e.getMessage(), e);
    }
    String clientSecret = clientDetails.getClientSecret();
    if (clientSecret== null || clientSecret.trim().length()==0) {
      clientSecret = emptyPassword;
    }
    return new User(username, clientSecret, clientDetails.getAuthorities());
  }
}

TokenGranter

Генерация кода авторизации в Spring Security OAuth2 выполняется через TokenGranter.Когда код авторизации генерируется, каждая принадлежащая реализация TokenGranter будет проходиться до тех пор, пока токен не будет успешно сгенерирован или все реализации TokenGranter не смогут сгенерировать токен.

Создание токена также является ссылкой, которую можно абстрагировать, поэтому Spring Security OAuth2 генерирует, получает и сохраняет токен через TokenService и TokenStore.

public abstract class AbstractTokenGranter implements TokenGranter {
  private final AuthorizationServerTokenServices tokenServices;

  private final ClientDetailsService clientDetailsService;

  private final OAuth2RequestFactory requestFactory;

  private final String grantType;

  protected AbstractTokenGranter(AuthorizationServerTokenServices tokenServices,
      ClientDetailsService clientDetailsService, OAuth2RequestFactory requestFactory, String grantType) {
    this.clientDetailsService = clientDetailsService;
    this.grantType = grantType;
    this.tokenServices = tokenServices;
    this.requestFactory = requestFactory;
  }

  public OAuth2AccessToken grant(String grantType, TokenRequest tokenRequest) {
    // 每个 TokenGranter 对应一种授权类型
    if (!this.grantType.equals(grantType)) {
      return null;
    }

    String clientId = tokenRequest.getClientId();
    ClientDetails client = clientDetailsService.loadClientByClientId(clientId);
    validateGrantType(grantType, client);

    // 获取授权码
    return getAccessToken(client, tokenRequest);
  }

  protected OAuth2AccessToken getAccessToken(ClientDetails client, TokenRequest tokenRequest) {
    return tokenServices.createAccessToken(getOAuth2Authentication(client, tokenRequest));
  }
}

// 默认的 TokenServices 的部分代码
public class DefaultTokenServices {
  @Transactional
  public OAuth2AccessToken createAccessToken(OAuth2Authentication authentication) throws AuthenticationException {
    // 首先从 TokenStore 中获取 Token
    OAuth2AccessToken existingAccessToken = tokenStore.getAccessToken(authentication);
    OAuth2RefreshToken refreshToken = null;
    if (existingAccessToken != null) {
      if (existingAccessToken.isExpired()) {
        if (existingAccessToken.getRefreshToken() != null) {
          refreshToken = existingAccessToken.getRefreshToken();
          tokenStore.removeRefreshToken(refreshToken);
        }
        tokenStore.removeAccessToken(existingAccessToken);
      }
      else {
        // Re-store the access token in case the authentication has changed
        tokenStore.storeAccessToken(existingAccessToken, authentication);
        return existingAccessToken;
      }
    }

    if (refreshToken == null) {
      refreshToken = createRefreshToken(authentication);
    }

    OAuth2AccessToken accessToken = createAccessToken(authentication, refreshToken);
    // 保存 accessToken
    tokenStore.storeAccessToken(accessToken, authentication);
    refreshToken = accessToken.getRefreshToken();
    if (refreshToken != null) {
      tokenStore.storeRefreshToken(refreshToken, authentication);
    }
    return accessToken;
  }

  // 从 TokenStore 中获取 Token
  public OAuth2AccessToken getAccessToken(OAuth2Authentication authentication) {
    return tokenStore.getAccessToken(authentication);
  }
}

Проще говоря:

  1. Полная аутентификация клиента и пользователя и проверка области в цепочках фильтров и внутренней логике конечной точки
  2. Сгенерируйте токен через TokenGranter, а TokenGranter создаст токен через TokenService, TokenStore может сохранить токен

сервер ресурсов

Сервер ресурсов намного проще, чем сервер авторизации, аналогичный традиционному процессу, через фильтрOAuth2AuthenticationProcessingFilterиOAuth2AuthenticationManagerПодтвердите токен и получите информацию для аутентификации:

public class OAuth2AuthenticationProcessingFilter implements Filter, InitializingBean {
  public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
    final HttpServletRequest request = (HttpServletRequest) req;
    final HttpServletResponse response = (HttpServletResponse) res;

    // 从请求头中提取 Token
    Authentication authentication = tokenExtractor.extract(request);

    Authentication authResult = authenticationManager.authenticate(authentication);

    SecurityContextHolder.getContext().setAuthentication(authResult);

    chain.doFilter(request, response);
  }
}

public class OAuth2AuthenticationManager implements AuthenticationManager, InitializingBean {
  public Authentication authenticate(Authentication authentication) throws AuthenticationException {
    String token = (String) authentication.getPrincipal();

    // 通过 TokenService 获取认证信息
    OAuth2Authentication auth = tokenServices.loadAuthentication(token);

    if (auth == null) {
      throw new InvalidTokenException("Invalid token: " + token);
    }

    checkClientDetails(auth);

    if (authentication.getDetails() instanceof OAuth2AuthenticationDetails) {
      OAuth2AuthenticationDetails details = (OAuth2AuthenticationDetails) authentication.getDetails();
      // Guard against a cached copy of the same details
      if (!details.equals(auth.getDetails())) {
        // Preserve the authentication details from the one loaded by token services
        details.setDecodedDetails(auth.getDetails());
      }
    }
    auth.setDetails(authentication.getDetails());
    auth.setAuthenticated(true);
    return auth;
  }
}

Spring Security JWT

Использование JWT в OAuth2 можно увидеть во многих местах Spring Security JWT играет роль TokenService и TokenStore в Spring Security OAuth2 для создания и проверки токенов.

Тем не менее, я все еще хочу разглагольствовать о JWT. Это было очень интересно, когда я впервые увидел это: использование JWT может напрямую передавать некоторую информацию в токене, и серверу не нужно хранить информацию о токене.

Однако в некоторых фактических целях использования может возникнуть необходимость недействительна действительного токена JWT, что невозможно для JWT. Для достижения этого требования только некоторая информация может быть сохранена на стороне сервера.

Однако, если вам нужно хранить информацию на сервере, зачем использовать JWT? Пока вам нужно хранить информацию на сервере, не имеет большого значения, используете ли вы JWT или нет...

Эпилог

Spring Security действительно очень сложный фреймворк. В настоящее время он предназначен только для применения в программах сервлетов. Однако я внезапно заинтересовался Spring WebFlux. Я не знаю, что такое Spring Security в Spring WebFlux...

Кроме того, что я хочу сказать, так это то, что официальный учебник по Spring Security действительно хорош, он ясно объясняет общую структуру, но жаль, что он страдает английским языком.T

Ссылка на ссылку

Информация о Spring Security в целом:

Информация, связанная с Spring Security Web:

Информация, связанная с Spring Security OAuth2:

Footnotes

1Если вас интересует подробный процесс, вы можете прочитать мои заметкиПостроение цепочки веб-фильтров Spring Security