如何在 Spring Security 无状态(jwt 令牌)中注销用户?

Posted

技术标签:

【中文标题】如何在 Spring Security 无状态(jwt 令牌)中注销用户?【英文标题】:How to logout user in spring security stateless (jwt token)? 【发布时间】:2019-02-02 15:18:13 【问题描述】:

我在spring创建了一个微服务系统,在我的项目中我有3个前端和多个微服务,这些前端使用(并存储到cookie中)jwt令牌从我的微服务中获取资源。

我的用户登录场景: 当用户想要从前端登录时,我重定向到我的实时项目(另一个前端)用户在我的实时前端登录并重定向到前端并在查询参数中传递令牌,所以前端可以使用这个令牌来获取资源。 当用户从前端 2 登录时重定向到 live,当用户在存储的 live 令牌的 cookie 中登录并且没有获取用户名和密码时重定向到具有存在令牌的前端 2。 这是我的 sso :)。

但我无法注销用户,因为当用户注销时(从 cookie 中删除令牌)如何理解用户注销的其他前端。

我想我必须创建会话来获取用户登录状态。或者我需要集中令牌状态检查。

如何创建这个会话??

我的spring安全码是spring的默认安全码。

最后我为我的英语道歉。 ;)

我的实时项目代码 (UAA)

@Configuration
@EnableAuthorizationServer
public class OAuth2AuthorizationServerConfig extends 
AuthorizationServerConfigurerAdapter 
@Value("$jwt.token.access-token-validity-seconds")
private Integer accessTokenValiditySeconds;
@Value("$jwt.token.support-refresh-token")
private Boolean supportRefreshToken;

private final AuthenticationManager authenticationManager;
private final ClientDetailsServiceImpl clientDetailsService;
private final AccountService userDetailsService;

public OAuth2AuthorizationServerConfig(AuthenticationManager authenticationManager,
        ClientDetailsServiceImpl clientDetailsService, AccountService userDetailsService) 
    super();
    this.authenticationManager = authenticationManager;
    this.clientDetailsService = clientDetailsService;
    this.userDetailsService = userDetailsService;


@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception 
    security.tokenKeyAccess("permitAll()").checkTokenAccess("isAuthenticated()");


@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception 
    clients.withClientDetails(clientDetailsService);


@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception 
    TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
    tokenEnhancerChain.setTokenEnhancers(Arrays.asList(tokenEnhancer(), accessTokenConverter()));

    endpoints.tokenStore(tokenStore()).tokenEnhancer(tokenEnhancerChain)
            .authenticationManager(authenticationManager).userDetailsService(userDetailsService);


@Bean
public TokenStore tokenStore() 
    return new JwtTokenStore(accessTokenConverter());


@Bean
public JwtAccessTokenConverter accessTokenConverter() 
      JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        KeyStoreKeyFactory keyStoreKeyFactory = 
          new KeyStoreKeyFactory(new ClassPathResource("key.jks"), "sayar1234".toCharArray());
        converter.setKeyPair(keyStoreKeyFactory.getKeyPair("key"));

        Resource resource = new ClassPathResource("public-key.txt");
        String publicKey = null;
        try 
            publicKey = IOUtils.toString(resource.getInputStream());
         catch (final IOException e) 
            throw new RuntimeException(e);
        

        converter.setVerifierKey(publicKey);

        return converter;


@Bean
@Primary
public DefaultTokenServices tokenServices() 
    DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
    defaultTokenServices.setTokenStore(tokenStore());
    defaultTokenServices.setSupportRefreshToken(supportRefreshToken);
    defaultTokenServices.setAccessTokenValiditySeconds(accessTokenValiditySeconds);
    return defaultTokenServices;


@Bean
public TokenEnhancer tokenEnhancer() 
    return new CustomTokenEnhancer();


其他资源

@Configuration
@EnableResourceServer
public class OAuth2ResourceServerConfig extends 
ResourceServerConfigurerAdapter 

@Override
public void configure(final HttpSecurity http) throws Exception 
    // @formatter:off
    http.csrf().disable() // csrf
            .antMatcher("/**").authorizeRequests() // /**
            .antMatchers("/actuator/**").permitAll() // Actuator
            .antMatchers("/ws/**").permitAll() // websocket
            .anyRequest().authenticated();
    // @formatter:on


@Override
public void configure(ResourceServerSecurityConfigurer config) 
    config.tokenServices(tokenServices());


@Bean
public TokenStore tokenStore() 
    return new JwtTokenStore(accessTokenConverter());


@Bean
public JwtAccessTokenConverter accessTokenConverter() 
    JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
    Resource resource = new ClassPathResource("public-key.txt");
    String publicKey = null;
    try 
        publicKey = IOUtils.toString(resource.getInputStream());
     catch (final IOException e) 
        throw new RuntimeException(e);
    
    converter.setVerifierKey(publicKey);
    return converter;


@Bean
@Primary
public DefaultTokenServices tokenServices() 
    DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
    defaultTokenServices.setTokenStore(tokenStore());
    return defaultTokenServices;


【问题讨论】:

【参考方案1】:

我们正在使用 Redis 缓存/服务器来存储令牌。您可以在https://redis.io/documentation 阅读有关 Redis 的更多信息

登录后,我们使用此令牌作为键存储用户上下文,并将用户信息作为后端的值存储。您还可以设置此令牌的过期时间。

注销后,您需要从后端删除此令牌和相应的值。

现在每个经过身份验证的请求都将具有此令牌,并将对其进行验证并检查此令牌是否存在于 Redis 中。

2 种情况下会丢失令牌

    用户已注销。 令牌已过期。

在每个有效请求之后,我们都会更新到期时间。因此,只有在系统空闲特定时间的情况下,该令牌才会过期。

这也是有益的分布式后端环境。

【讨论】:

以上是关于如何在 Spring Security 无状态(jwt 令牌)中注销用户?的主要内容,如果未能解决你的问题,请参考以下文章

Spring Security 登录错误:HTTP 状态 404 - /j_spring_security_check

wso2 spring security saml 无状态集成

j_spring_security_check HTTP 状态 404(自定义登录)

Spring Security 和无状态的 Restful 服务

无状态应用程序 Spring Security

您可以在不使其无状态的情况下将 Spring Security 与 REST 服务一起使用吗?