【问题标题】:Spring-boot JWT logoutSpring-boot JWT 注销
【发布时间】:2016-04-01 06:22:12
【问题描述】:

我使用此代码https://github.com/gdongus/spring-boot-oauth-jwt-example 并且一切正常,但我不知道如何实现注销功能。有人可以给我建议吗?谢谢。

【问题讨论】:

    标签: authentication spring-boot logout jwt


    【解决方案1】:

    客户端注销很简单,只需丢弃您拥有的令牌即可。要提供服务器端注销功能,您的应用程序必须了解当前经过身份验证的客户端,即现有令牌。基于令牌的身份验证的“内置”问题是,如果发布了令牌,它在到期之前一直有效,并且没有“远程失效”解决方案。您唯一的机会是避免使用您不再信任的令牌访问请求。

    因此,您必须记住在名为 token store 的容器中发布的每个令牌。

    TokenStore 接口的一些实现可以在内存中工作,或者可能与数据库 (JdbcTokenStore) 一起工作。举个简单的例子,InMemoryTokenStore 就足够了。

    要使用它,必须按如下方式创建和配置令牌存储。

    将此添加到您的AuthorizationServerConfiguration

    @Bean
    public InMemoryTokenStore tokenStore() {
        return new InMemoryTokenStore();
    }
    

    并在AuthorizationServerEndpointsConfigurer中使用:

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer configurer) throws Exception {
        configurer.authenticationManager(authenticationManager);
        configurer.userDetailsService(userDetailsService);
        configurer.accessTokenConverter(accessTokenConverter());
        configurer.tokenStore(tokenStore());
    }
    

    也将其添加到您的ResourceServerConfiguration

    @Autowired
    private InMemoryTokenStore inMemoryTokenStore;
    ...
    @Override
    public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
        resources.resourceId("resource").tokenStore(inMemoryTokenStore);
    }
    

    几乎就是这样。现在您可以根据需要实现您的注销功能,也许使用一个特殊的端点,您只需要获取令牌并将其从令牌存储中删除:

    inMemoryTokenStore.removeAccessToken(accessToken);
    inMemoryTokenStore.removeRefreshToken(refreshToken);
    

    注意也要删除刷新令牌,否则(如果仅删除访问令牌)客户端可以使用刷新令牌获得新的令牌。

    这是一个测试用例,根据您的测试来验证它是否正常工作:

    @Test
    public void getUserWithValidAuth() throws Exception {
        final HttpHeaders headers = getHttpHeader(CLIENT_USER, CLIENT_SECRET);
        final HttpEntity<String> request = new HttpEntity<>(headers);
    
        final String tokenUrl = getOAuthTokenUrl(OAUTH_TOKEN_USERNAME, OAUTH_TOKEN_PASSWORD);
        final ResponseEntity<Object> response = restTemplate.exchange(tokenUrl, HttpMethod.POST, request, Object.class);
        assertTrue("Did not get auth tokens!", response.getStatusCode().is2xxSuccessful());
    
        final Map result = (Map) response.getBody();
        final String accessTokenAsString = (String) result.get(ACCESS_TOKEN);
        final String refreshTokenAsString = (String) result.get(REFRESH_TOKEN);
    
        final String resourceUrlWithToken = "http://localhost:" + port + "/users?access_token=" + accessTokenAsString;
    
        final ResponseEntity<String> userResponse = restTemplate.exchange(resourceUrlWithToken, HttpMethod.GET, null,
                String.class);
        assertTrue("Could not request user data!", userResponse.getStatusCode().is2xxSuccessful());
    
        final OAuth2AccessToken accessToken = inMemoryTokenStore.readAccessToken(accessTokenAsString);
        final OAuth2RefreshToken refreshToken = inMemoryTokenStore.readRefreshToken(refreshTokenAsString);
        inMemoryTokenStore.removeAccessToken(accessToken);
        inMemoryTokenStore.removeRefreshToken(refreshToken);
    
        try {
            restTemplate.exchange(resourceUrlWithToken, HttpMethod.GET, null, String.class);
            fail("Should not get here, expected 401 for request with access token!");
        } catch (HttpClientErrorException e) {
            // would not be needed with MockMvc
        }
    
        final String refreshTokenUrl = REFRESH_TOKEN_URL + refreshTokenAsString;
        try {
            restTemplate.exchange(refreshTokenUrl, HttpMethod.POST, request, Object.class);
            fail("Should not get here, expected 401 for request with refresh token!");
        } catch (HttpClientErrorException e) {
            // would not be needed with MockMvc
        }
    }
    

    至少只是一个建议,使用MockMvc 是一个很棒的测试框架,它可以轻松测试休息调用,并且您可以在使用 RestTemplate 时摆脱障碍和样板代码。也许你想试一试。

    【讨论】:

    • 这个答案与JWT无关。 JWT 是自包含的令牌。如果令牌存储在 authserver 和资源服务器之间共享,那么 JWT 的整个点就被打败了
    • 这很重要。所以 Ashok 是对的,这可能不是“生产就绪”示例,只是一个提示。
    【解决方案2】:

    由于注销完成后,访问令牌和刷新令牌都会从身份验证服务器的底层存储中删除,我们只需要担心资源服务器中的访问令牌失效,直到它过期

    为了完成此操作,您需要在通过 Spring Stream/Integration 完成注销后立即从 auth-server 发布事件,并使所有令牌受众实例订阅注销事件

    您可以添加自己的LogoutHandler,从身份验证服务器发布此事件。来自 Spring 云流的@StreamListner 可用于在每个资源服务器上侦听此事件

    此注销事件必须包含已删除的访问令牌和到期前的剩余时间。此事件的所有接收者必须将这些访问令牌存储在内存中的黑名单中,并确保他们拒绝访问资源,如果接收到的访问令牌与任何现有的黑名单令牌匹配。访问令牌过期后,只需将其从内存中删除即可。要自动使密钥过期,您可以使用来自 guava 的 CacheBuilder 之类的东西

    因此,总的来说,AFAIK,由于 JWT 的性质,没有现成的访问令牌过期的解决方案

    【讨论】:

      【解决方案3】:

      您可以创建一个USER_TOKEN表并保留用户生成的所有令牌,[有助于多设备登录],并且当从前端调用注销时,删除或销毁令牌前端本地存储并调用具有相同令牌的 /logout api,然后验证令牌并输入服务器代码并从 USER_TOKEN 中删除该特定令牌强>表。

      所以下次如果有人想使用相同的令牌访问 API,该令牌将不会被验证,因为它不存在于 DB 中。

      但如果有人关闭浏览器选项卡,而不是注销,那么令牌将一直有效,直到它过期。

      【讨论】:

        猜你喜欢
        • 2020-08-11
        • 2017-08-21
        • 2018-09-23
        • 2019-07-20
        • 2018-02-19
        • 1970-01-01
        • 2021-03-11
        • 2019-07-19
        • 2017-03-26
        相关资源
        最近更新 更多