返回

Spring Authorization Server 授权服务:扩展授权信息存储方式,解决序列化失败问题

后端

解决Spring Authorization Server中的PhoneCaptchaAuthenticationToken序列化问题

背景介绍

Spring Authorization Server是一个强大的工具,可以简化OAuth 2.0授权的实现。然而,在实际应用中,使用自定义令牌时可能会遇到序列化失败的问题。这通常是由于OAuth 2.0授权服务默认的序列化方式与自定义令牌的序列化形式不兼容造成的。本文将深入探讨如何解决Spring Authorization Server中PhoneCaptchaAuthenticationToken序列化失败的问题,并提供逐步指南和代码示例,以帮助开发者轻松解决此问题。

问题根源

问题根源在于PhoneCaptchaAuthenticationToken是自定义令牌,实现了SerializationAware接口。其序列化形式与OAuth 2.0授权服务默认的序列化方式不兼容,导致在存储和检索时出现问题。要解决此问题,我们需要扩展授权服务中的授权信息存储方式,使其支持PhoneCaptchaAuthenticationToken的序列化。

解决步骤

要解决此问题,我们可以按照以下步骤进行:

  1. 创建自定义授权信息存储类
public class RedisAuthorizationInfoRepository extends JdbcAuthorizationInfoRepository {

    public RedisAuthorizationInfoRepository(DataSource dataSource, RedisConnectionFactory redisConnectionFactory) {
        super(dataSource);
        this.redisConnectionFactory = redisConnectionFactory;
    }

    @Override
    public OAuth2Authorization getAuthorization(OAuth2AuthorizationRequest authorizationRequest) {
        OAuth2Authorization authorization = super.getAuthorization(authorizationRequest);
        if (authorization != null && authorization.getAccessToken() instanceof OAuth2RefreshToken) {
            authorization.setAccessToken(redisConnectionFactory.getConnection().get(authorization.getAccessToken().getTokenValue().getBytes()));
        }
        return authorization;
    }

    @Override
    public void saveAuthorization(OAuth2Authorization authorization) {
        super.saveAuthorization(authorization);
        if (authorization.getAccessToken() instanceof OAuth2RefreshToken) {
            redisConnectionFactory.getConnection().set(authorization.getAccessToken().getTokenValue().getBytes(), authorization.getAccessToken().serialize());
        }
    }

    private final RedisConnectionFactory redisConnectionFactory;
}

此类扩展了默认的JdbcAuthorizationInfoRepository,增加了对Redis的支持,并重写了getAuthorization()和saveAuthorization()方法以支持自定义令牌的序列化。

  1. 配置自定义授权信息存储类
@SpringBootApplication
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

    @Bean
    public AuthorizationServerAuthenticationProvider authorizationServerAuthenticationProvider() {
        AuthorizationServerAuthenticationProvider authorizationServerAuthenticationProvider = new AuthorizationServerAuthenticationProvider();
        authorizationServerAuthenticationProvider.setUserDetailsService(userDetailsService);
        return authorizationServerAuthenticationProvider;
    }

    @Bean
    public AuthorizationServerConfigurerAdapter authorizationServerConfigurerAdapter(AuthorizationServerAuthenticationProvider authorizationServerAuthenticationProvider) {
        AuthorizationServerConfigurerAdapter authorizationServerConfigurerAdapter = new AuthorizationServerConfigurerAdapter() {
            @Override
            public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
                endpoints.authorizationCodeServices(authorizationCodeServices())
                        .authorizationEndpoint()
                        .authenticationProvider(authorizationServerAuthenticationProvider);
            }

            @Override
            public void configure(AuthorizationServerSecurityConfigurer security) {
                security.checkTokenAccess("isAuthenticated()");
            }
        };
        return authorizationServerConfigurerAdapter;
    }

    @Bean
    public AuthorizationCodeServices authorizationCodeServices() {
        JdbcAuthorizationCodeServices authorizationCodeServices = new JdbcAuthorizationCodeServices(dataSource);
        authorizationCodeServices.setAuthorizationInfoRepository(authorizationInfoRepository());
        return authorizationCodeServices;
    }

    @Bean
    public JdbcAuthorizationInfoRepository authorizationInfoRepository() {
        return new RedisAuthorizationInfoRepository(dataSource, redisConnectionFactory());
    }

    @Bean
    public DataSource dataSource() {
        DriverManagerDataSource dataSource = new DriverManagerDataSource();
        dataSource.setDriverClassName("org.h2.Driver");
        dataSource.setUrl("jdbc:h2:mem:oauth2;DB_CLOSE_DELAY=-1");
        dataSource.setUsername("sa");
        dataSource.setPassword("");
        return dataSource;
    }

    @Bean
    public RedisConnectionFactory redisConnectionFactory() {
        RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration("localhost", 6379);
        return new LettuceConnectionFactory(redisStandaloneConfiguration);
    }

    @Bean
    public UserDetailsService userDetailsService() {
        UserDetailsService userDetailsService = new InMemoryUserDetailsManager();
        ((InMemoryUserDetailsManager) userDetailsService).createUser(User.withUsername("user").password("password").roles("USER").build());
        return userDetailsService;
    }
}

在此类中,我们配置了自定义授权信息存储类,包括数据源和Redis连接工厂。

  1. 使用自定义授权信息存储类
@RunWith(SpringRunner.class)
@SpringBootTest
public class ApplicationTests {

    @Autowired
    private AuthorizationServerEndpointsConfigurer authorizationServerEndpointsConfigurer;

    @Test
    public void testAuthorizationInfoRepository() {
        OAuth2AuthorizationRequest authorizationRequest = new OAuth2AuthorizationRequest(
                ClientRegistration.withRegistrationId("client").clientId("client").build(),
                Collections.singleton(Scope.of("read")),
                Collections.singletonList(ResponseType.CODE),
                "https://example.com/redirect_uri");
        OAuth2Authorization authorization = authorizationServerEndpointsConfigurer.authorizationCodeServices().createAuthorizationCode(authorizationRequest);
        OAuth2Authorization authorization2 = authorizationServerEndpointsConfigurer.authorizationCodeServices().loadAuthorization(authorization.getAuthorizationCode().getTokenValue());
        assertNotNull(authorization2);
    }
}

在测试类中,我们使用自定义授权信息存储类创建和加载授权信息。

常见问题解答

  1. 为什么PhoneCaptchaAuthenticationToken无法使用默认的序列化方式序列化?

PhoneCaptchaAuthenticationToken实现了SerializationAware接口,其序列化形式与默认的序列化方式不兼容。

  1. 如何创建自定义授权信息存储类?

自定义授权信息存储类需要扩展OAuth2AuthorizationService的授权信息存储方式,并重写getAuthorization()和saveAuthorization()方法以支持自定义令牌的序列化。

  1. 如何配置自定义授权信息存储类?

在Spring Boot应用程序中,通过配置数据源和Redis连接工厂来配置自定义授权信息存储类。

  1. 如何使用自定义授权信息存储类?

在授权服务中,使用authorizationCodeServices().setAuthorizationInfoRepository()方法将自定义授权信息存储类设置为授权代码服务的授权信息存储库。

  1. 解决了PhoneCaptchaAuthenticationToken序列化失败的问题后,还需要做其他事情吗?

解决此问题后,还需要根据需要调整应用程序代码,以利用PhoneCaptchaAuthenticationToken的附加功能和特性。

结论

通过扩展授权信息存储方式并使用自定义授权信息存储类,我们可以解决Spring Authorization Server中的PhoneCaptchaAuthenticationToken序列化失败问题。本文提供了详细的分步指南和代码示例,使开发者能够轻松实现此解决方案。通过遵循这些步骤,开发者可以确保授权服务支持自定义令牌,并避免序列化失败问题。