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

时间:2018-08-28 04:44:38

标签: spring-security jwt token logout stateless

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

我的用户登录方案: 当用户想要从前端登录时,我重定向到我的实时项目(另一个前端),用户在实时前端登录并重定向到前端,并在查询参数中传递令牌,因此前端可以使用此令牌获取资源。 当用户从前端两个登录名重定向到实时状态时,当用户之前在存储的实时令牌的cookie中登录并且没有获取用户名和密码时,使用存在令牌重定向到前端两个。 这是我的单曲:)。

但是我无法注销用户,因为当用户注销(从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 个答案:

答案 0 :(得分:1)

我们使用 Redis 缓存/服务器来存储令牌。您可以在 https://redis.io/documentation

阅读有关 Redis 的更多信息

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

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

现在每个经过身份验证的请求都会有这个令牌来验证它并检查这个令牌是否存在于 Redis 中。

Token 会在 2 种情况下丢失

  1. 用户已退出。
  2. 令牌已过期。

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

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