Home >Java >javaTutorial >How SpringBoot Security implements single sign-out and clears all tokens

How SpringBoot Security implements single sign-out and clears all tokens

WBOY
WBOYforward
2023-05-14 13:43:112085browse

Requirements

  • Systems A, B, and C implement login through the sso service

  • Systems A, B, and C obtain Atoken and Btoken respectively , Ctoken three tokens

  • After one of the systems actively logs out, the other two systems also log out

  • So far all Atoken and Btoken , Ctoken invalid

Record token

pom file introduces dependency

  • Redis database dependency

  • hutool: used to parse token

<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
   <groupId>cn.hutool</groupId>
   <artifactId>hutool-all</artifactId>
   <version>5.7.13</version>
</dependency>

token storage class implements AuthJdbcTokenStore

  • TokenStore inherits JdbcTokenStore

  • Use the username of the logged-in user username as the Redis key

  • Because there will be multiple systems where the user logs in, the value uses the Redis list type to store the token

  • Set the validity time to ensure that it is no less than the maximum validity time of the token in the list

@Component
public class AuthJdbcTokenStore extends JdbcTokenStore {
    public static final String USER_HAVE_TOKEN = "user-tokens:";
    @Resource
    RedisTemplate redisTemplate;
    public AuthJdbcTokenStore(DataSource connectionFactory) {
        super(connectionFactory);
    }
    @Override
    public void storeAccessToken(OAuth3AccessToken token, OAuth3Authentication authentication) {
        super.storeAccessToken(token, authentication);
        if (Optional.ofNullable(authentication.getUserAuthentication()).isPresent()) {
            User user = (User) authentication.getUserAuthentication().getPrincipal();
            String userTokensKey = USER_HAVE_TOKEN + user.getUsername();
            String tokenValue = token.getValue();
            redisTemplate.opsForList().leftPush(userTokensKey, tokenValue);
            Long seconds = redisTemplate.opsForValue().getOperations().getExpire(userTokensKey);
            Long tokenExpTime = getExpTime(tokenValue);
            Long expTime = seconds < tokenExpTime ? tokenExpTime : seconds;
            redisTemplate.expire(userTokensKey, expTime, TimeUnit.SECONDS);
        }
    }
    private long getExpTime(String accessToken) {
        JWT jwt = JWTUtil.parseToken(accessToken);
        cn.hutool.json.JSONObject jsonObject = jwt.getPayload().getClaimsJson();
        long nowTime = Instant.now().getEpochSecond();
        long expEndTime = jsonObject.getLong("exp");
        long expTime = (expEndTime - nowTime);
        return expTime;
    }
}

oauth_access_token Using JdbcTokenStore to store tokens requires a new table

CREATE TABLE `oauth_access_token` (
  `create_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
  `token_id` varchar(255) DEFAULT NULL,
  `token` blob,
  `authentication_id` varchar(255) DEFAULT NULL,
  `user_name` varchar(255) DEFAULT NULL,
  `client_id` varchar(255) DEFAULT NULL,
  `authentication` blob,
  `refresh_token` varchar(255) DEFAULT NULL,
  UNIQUE KEY `authentication_id` (`authentication_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3;

AuthorizationServerConfigurerAdapter uses AuthJdbcTokenStore for token storage

  • Introducing DataSource, because the construction method of JdbcTokenStore must pass in DataSource

  • Create according to TokenStore, Use AuthJdbcTokenStore to implement

  • ##tokenServices Add TokenStore

  • endpoints Add tokenServices

  • @Configuration
    @EnableAuthorizationServer
    public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
        @Autowired
        private DataSource dataSource;
    	...
        @Bean
        public TokenStore tokenStore() {
            JdbcTokenStore tokenStore = new AuthJdbcTokenStore(dataSource);
            return tokenStore;
        }
        @Override
        public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
            DefaultTokenServices tokenServices = new DefaultTokenServices();
            tokenServices.setTokenStore(tokenStore());
            endpoints
                    .authenticationManager(authenticationManager)
                    .tokenServices(tokenServices)
                    .accessTokenConverter(converter)
            ;
        }
    	...
    }
Clear token

  • Inherit SimpleUrlLogoutSuccessHandler

  • Get the username userName

  • Get the token list stored in Redis during login

  • Token string is converted into OAuth3AccessToken

  • Use tokenStore to delete token

  • @Component
    public class AuthLogoutSuccessHandler1 extends SimpleUrlLogoutSuccessHandler {
        String USER_HAVE_TOKEN = AuthJdbcTokenStore.USER_HAVE_TOKEN;
        @Resource
        RedisTemplate redisTemplate;
        @Resource
        TokenStore tokenStore;
        @Override
        public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
            if (!Objects.isNull(authentication)) {
                String userName = authentication.getName();
                String userTokensKey = USER_HAVE_TOKEN + userName;
                Long size = redisTemplate.opsForList().size(userTokensKey);
                List<String> list = redisTemplate.opsForList().range(userTokensKey, 0, size);
                for (String tokenValue : list) {
                    OAuth3AccessToken token = tokenStore.readAccessToken(tokenValue);
                    if (Objects.nonNull(token)) {
                        tokenStore.removeAccessToken(token);
                    }
                }
                redisTemplate.delete(userTokensKey);
                super.handle(request, response, authentication);
            }
        }
    }
Solve the logout time Too long

Scenario: After the project has been running for a period of time, it is found that the logout time is getting slower and slower

Problem: Through debugging, it is found that the time consuming is mainly in the period of deleting the token

tokenStore.removeAccessToken(token);

Reason: As time goes by, there are more and more tokens, and the token storage table oauth_access_token becomes abnormally large, so the deletion efficiency is very poor

Solution: Use other TokenStore, or clear the table data of oauth_access_token

The above is the detailed content of How SpringBoot Security implements single sign-out and clears all tokens. For more information, please follow other related articles on the PHP Chinese website!

Statement:
This article is reproduced at:yisu.com. If there is any infringement, please contact admin@php.cn delete