OAuth2 シングル サインオンを備えたスプリング ブート アプリケーションを使用しています (開発中ですが、既に動作しています)。
セッションストアを Tomcat から Redis に変更しました。
問題
ClassCastException
から User オブジェクトを取得しようとしたときに取得しましたSecurityContextHolder
。
User オブジェクトの保存先UsernamePasswordAuthenticationToken
@Service
@Transactional
public class MyTokenService implements ResourceServerTokenServices {
@Setter
private OAuth2RestOperations restTemplate;
@Autowired
ResourceServerProperties sso;
@Override
public OAuth2Authentication loadAuthentication(String accessToken)
throws AuthenticationException, InvalidTokenException {
// get from http://some-auth-provider/me
MyUser myUser = getMyInfo();
// persist to mysql
UserEntity user = save(myUser);
OAuth2Request request =
new OAuth2Request(null, sso.getClientId(), null, true, null, null, null, null, null);
// org.springframework.boot.devtools.restart.classloader.RestartClassLoader@3d66ad6e
logger.info(user.getClass().getClassLoader().toString());
UsernamePasswordAuthenticationToken token =
new UsernamePasswordAuthenticationToken(user.getId(), "N/A", user.getAuthorities());
token.setDetails(user);
return new OAuth2Authentication(request, token);
}
@Override
public OAuth2AccessToken readAccessToken(String accessToken) {
throw new UnsupportedOperationException("Not supported: read access token");
}
}
認証後のセッション
127.0.0.1:6379> keys *
1) "spring:session:expirations:1445309400000"
2) "spring:session:sessions:3447d160-5f41-49c9-abf9-2bf0ef2e6bc2"
127.0.0.1:6379> hkeys spring:session:sessions:3447d160-5f41-49c9-abf9-2bf0ef2e6bc2
1) "sessionAttr:SPRING_SECURITY_SAVED_REQUEST"
2) "sessionAttr:org.springframework.security.web.csrf.HttpSessionCsrfTokenRepository.CSRF_TOKEN"
3) "lastAccessedTime"
4) "maxInactiveInterval"
5) "sessionAttr:SPRING_SECURITY_LAST_EXCEPTION"
6) "sessionAttr:SPRING_SECURITY_CONTEXT"
7) "creationTime"
127.0.0.1:6379> type spring:session:sessions:3447d160-5f41-49c9-abf9-2bf0ef2e6bc2:sessionAttr:SPRING_SECURITY_CONTEXT
none
User オブジェクトの取得 (認証後)
public UserEntity getMyInfo() {
OAuth2Authentication oAuth2Authentication =
(OAuth2Authentication) SecurityContextHolder.getContext().getAuthentication();
Long userId = (Long) oAuth2Authentication.getPrincipal();
UsernamePasswordAuthenticationToken userAuthentication =
(UsernamePasswordAuthenticationToken) oAuth2Authentication.getUserAuthentication();
try {
// sun.misc.Launcher$AppClassLoader@14dad5dc
logger.info(userAuthentication.getDetails().getClass().getClassLoader().toString());
return (UserEntity) userAuthentication.getDetails();
} catch (Exception e) {
// fixme ClassCastException
// workaround
return userRepository.accountDetail(userId);
}
}
オブジェクトをデバッグし、MyUser オブジェクトがあるuserAuthentication
ことを確認します。userAuthentication.details
UserEntityClassCastException
をUserEntityにキャストすると発生しますが、なぜですか?
プリンシパル (=UserEntity.id) を正しく取得できます。
誰にも解決策はありますか?
以下は現在の構成の一部です。
依存関係
springBootVersion = '1.3.0.RC1'
compile("org.springframework.boot:spring-boot-starter-redis")
compile("org.springframework.session:spring-session")
compile("com.github.kstyrc:embedded-redis:0.6")
Redis 構成
@Configuration
@EnableRedisHttpSession
public class RedisConfig {
private static RedisServer redisServer;
@Profile({"local", "unit"})
@Bean
public RedisConnectionFactory connectionFactory() throws IOException {
RedisExecProvider customProvider = RedisExecProvider.defaultProvider()
.override(OS.MAC_OS_X, Architecture.x86_64, "/path/to/redis/3.0.2/redis-server");
redisServer = new RedisServerBuilder()
.redisExecProvider(customProvider)
.port(Protocol.DEFAULT_PORT)
.build();
redisServer.start();
return new JedisConnectionFactory();
}
@Profile({"local", "unit"})
@PreDestroy
public void destroy() {
if (redisServer != null) {
redisServer.stop();
}
}
@Bean
@ConditionalOnMissingBean(RequestContextFilter.class)
public RequestContextFilter requestContextFilter() {
return new RequestContextFilter();
}
@Bean
public FilterRegistrationBean requestContextFilterChainRegistration(
@Qualifier("requestContextFilter") Filter securityFilter) {
FilterRegistrationBean registration = new FilterRegistrationBean(securityFilter);
registration.setOrder(SessionRepositoryFilter.DEFAULT_ORDER + 1);
registration.setName("requestContextFilter");
return registration;
}
OAuth2 SSO 構成
@Configuration
@EnableOAuth2Sso
@EnableWebSecurity
public class OAuth2Config extends WebSecurityConfigurerAdapter {
@Autowired
ResourceServerProperties sso;
@Autowired
@Qualifier("userInfoRestTemplate")
private OAuth2RestOperations restTemplate;
@Primary
@Bean
public ResourceServerTokenServices userInfoTokenServices() {
MyTokenService tokenService = new MyTokenService();
tokenService.setRestTemplate(restTemplate);
return tokenService;
}
// ...
}
同様の問題
上記の回答のように (RedisConfig.java で) フィルターの順序を変更しましたが、機能しません。
私の現在のフィルターの順序は以下です。
o.s.b.c.embedded.FilterRegistrationBean : Mapping filter: 'metricFilter' to: [/*]
o.s.b.c.embedded.FilterRegistrationBean : Mapping filter: 'springSessionRepositoryFilter' to: [/*]
o.s.b.c.embedded.FilterRegistrationBean : Mapping filter: 'requestContextFilter' to: [/*]
o.s.b.c.embedded.FilterRegistrationBean : Mapping filter: 'characterEncodingFilter' to: [/*]
o.s.b.c.embedded.FilterRegistrationBean : Mapping filter: 'OAuth2ClientContextFilter' to: [/*]
o.s.b.c.embedded.FilterRegistrationBean : Mapping filter: 'springSecurityFilterChain' to: [/*]
o.s.b.c.embedded.FilterRegistrationBean : Mapping filter: 'webRequestLoggingFilter' to: [/*]
o.s.b.c.embedded.FilterRegistrationBean : Mapping filter: 'applicationContextIdFilter' to: [/*]
コードを更新する
ClassCastException の原因は ClassLoader の不一致です。
org.springframework.boot.devtools.restart.classloader.RestartClassLoader
とsun.misc.Launcher$AppClassLoader
しかし、相変わらず、この問題を解決する方法はわかりません。
解決しました!
以下のフィルターの順序を変更すると、機能します。
- requestContextFilter
- oAuth2ClientContextFilter
- springSessionRepositoryFilter