Spring OAuthアプリケーションで更新トークンを使用しようとしていますが、成功しません。システムは、パスワード付与時に更新トークンを発行します。
{
"access_token": "xxxxx",
"token_type": "bearer",
"refresh_token": "xxxxxx",
"expires_in": 21599,
"scope": "read write"
}
ただし、更新トークンを使用しようとすると、次のエラーが発生します。
curl -u acme -d "grant_type = refresh_token&refresh_token = xxxxxx" http:// localhost:9999/uaa/oauth/token
{
"error": "invalid_token",
"error_description": "Cannot convert access token to JSON"
}
私の認証サーバーの設定は次のとおりです:
@Controller
@SessionAttributes("authorizationRequest")
@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)
@EnableResourceServer
@ImportResource("classpath:/spring/application-context.xml")
@Configuration
public class ApplicationConfiguration extends WebMvcConfigurerAdapter {
@RequestMapping("/user")
@ResponseBody
public Principal user(Principal user) {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
System.out.println(auth.toString());
return user;
}
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/login").setViewName("login");
registry.addViewController("/oauth/confirm_access").setViewName("authorize");
}
@Configuration
@Order(-20)
protected static class LoginConfig extends WebSecurityConfigurerAdapter {
@Autowired
private AuthenticationManager authenticationManager;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
// @formatter:off
http
.formLogin().loginPage("/login").permitAll()
.and()
.requestMatchers().antMatchers("/login", "/oauth/authorize", "/oauth/confirm_access")
.and()
.authorizeRequests().anyRequest().authenticated();
// @formatter:on
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
@Configuration
public static class JwtConfiguration {
@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
KeyPair keyPair = new KeyStoreKeyFactory(new ClassPathResource("keystore.jks"), "foobar".toCharArray())
.getKeyPair("test");
converter.setKeyPair(keyPair);
return converter;
}
@Bean
public JwtTokenStore jwtTokenStore() {
return new JwtTokenStore(jwtAccessTokenConverter());
}
}
@Configuration
@EnableAuthorizationServer
protected static class OAuth2AuthorizationConfig extends AuthorizationServerConfigurerAdapter implements
EnvironmentAware {
private static final String ENV_OAUTH = "authentication.oauth.";
private static final String PROP_CLIENTID = "clientid";
private static final String PROP_SECRET = "secret";
private static final String PROP_TOKEN_VALIDITY_SECONDS = "tokenValidityInSeconds";
private RelaxedPropertyResolver propertyResolver;
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private JwtAccessTokenConverter jwtAccessTokenConverter;
@Autowired
private JwtTokenStore jwtTokenStore;
@Autowired
@Qualifier("myUserDetailsService")
private UserDetailsService userDetailsService;
@Autowired
private DataSource dataSource;
@Override
public void setEnvironment(Environment environment) {
this.propertyResolver = new RelaxedPropertyResolver(environment, ENV_OAUTH);
}
@Bean
public TokenEnhancer tokenEnhancer() {
return new CustomTokenEnhancer();
}
@Bean
@Primary
public DefaultTokenServices tokenServices() {
DefaultTokenServices tokenServices = new DefaultTokenServices();
tokenServices.setSupportRefreshToken(true);
tokenServices.setTokenStore(jwtTokenStore);
tokenServices.setAuthenticationManager(authenticationManager);
tokenServices.setTokenEnhancer(tokenEnhancer());
return tokenServices;
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
// The order is important here - the custom enhancer must come before the jwtAccessTokenConverter.
tokenEnhancerChain.setTokenEnhancers(Arrays.asList(tokenEnhancer(), jwtAccessTokenConverter));
endpoints
.authenticationManager(authenticationManager)
.tokenEnhancer(tokenEnhancerChain)
.tokenStore(jwtTokenStore)
.userDetailsService(userDetailsService);
}
@Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer)
throws Exception {
oauthServer.tokenKeyAccess("permitAll()").checkTokenAccess("isAuthenticated()");
}
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.jdbc(dataSource);
/*.withClient(propertyResolver.getProperty(PROP_CLIENTID))
.scopes("read", "write")
.autoApprove(true)
.authorities(ClientAuthoritiesConstants.CLIENT)
.authorizedGrantTypes("authorization_code", "refresh_token", "password")
.secret(propertyResolver.getProperty(PROP_SECRET))
.accessTokenValiditySeconds(propertyResolver.getProperty(PROP_TOKEN_VALIDITY_SECONDS, Integer
.class, 1800));*/
}
}
/**
* Configures the global LDAP authentication
*/
@Configuration
protected static class AuthenticationConfiguration extends GlobalAuthenticationConfigurerAdapter implements EnvironmentAware {
private static final String ENV_LDAP = "authentication.ldap.";
private static final String PROP_SEARCH_BASE = "userSearchBase";
private static final String PROP_SEARCH_FILTER = "userSearchFilter";
private static final String PROP_GROUP_SEARCH_FILTER = "groupSearchFilter";
private static final String PROP_LDAP_URL = "url";
private static final String PROP_LDAP_USER = "userDn";
private static final String PROP_LDAP_PASS = "password";
private RelaxedPropertyResolver propertyResolver;
/**
* Maps the LDAP user to the Principle that we'll be using in the app
*/
public UserDetailsContextMapper userDetailsContextMapper() {
return new UserDetailsContextMapper() {
@Override
public UserDetails mapUserFromContext(DirContextOperations ctx, String username,
Collection<? extends GrantedAuthority> authorities) {
// Get the common name of the user
String commonName = ctx.getStringAttribute("cn");
// Get the users email address
String email = ctx.getStringAttribute("mail");
// Get the domino user UNID
String uId = ctx.getStringAttribute("uid");
return new CustomUserDetails(email, "", commonName, authorities);
}
@Override
public void mapUserToContext(UserDetails user, DirContextAdapter ctx) {
throw new IllegalStateException("Only retrieving data from LDAP is currently supported");
}
};
}
@Override
public void setEnvironment(Environment environment) {
this.propertyResolver = new RelaxedPropertyResolver(environment, ENV_LDAP);
}
@Override
public void init(AuthenticationManagerBuilder auth) throws Exception {
auth
.ldapAuthentication()
.userSearchBase(propertyResolver.getProperty(PROP_SEARCH_BASE))
.groupSearchBase(propertyResolver.getProperty(PROP_SEARCH_BASE))
.userSearchFilter(propertyResolver.getProperty(PROP_SEARCH_FILTER))
.groupSearchFilter(propertyResolver.getProperty(PROP_GROUP_SEARCH_FILTER))
.userDetailsContextMapper(userDetailsContextMapper())
.contextSource()
.url(propertyResolver.getProperty(PROP_LDAP_URL))
.managerDn(propertyResolver.getProperty(PROP_LDAP_USER))
.managerPassword(propertyResolver.getProperty(PROP_LDAP_PASS));
}
}
}
有効な更新トークンが与えられたときに認証サーバーが新しいトークンを発行しない理由は誰にもありますか?
そのため、問題は無効なrefresh_token形式であったようです。私の設定により、認証サーバーが期待していたのは有効なJWTでしたが、プレーンなベアラートークンを送信していました。したがって、「トークンをJSONに変換できません」というエラーメッセージが表示されます。
ちなみに、このドキュメントは、Springのすべての部分OAuthがどのように組み合わされるかを理解するのに役立つと思いました。
https://github.com/spring-projects/spring-security-oauth/blob/master/docs/oauth2.md
この問題がありました。 「Bearer xxxxxx ...」を送信していて、TokenEnhancerは「Bearer」接頭辞なしの「xxxxx ...」だけを期待していました
同じ問題がありました。デバッグの結果、署名が一致しないことがわかりました。
私の場合、キーの設定が少し異なり、署名と検証でキーが一致しないというバグがあります。
https://github.com/spring-projects/spring-security-oauth/issues/1144
誰かに役立つかどうかは2年間ではありませんが、同じ問題は、トークンサービスプロバイダーDefaultTokenServicesのJwtTokenStoreで使用したtokenEnhancerを使用していなかったことが原因でした。
<!-- Access token converter -->
<bean id="jwtAccessTokenConverter"
class="org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter">
<property name="signingKey" value="${security.jwt.signing-key}"/>
</bean>
<!-- Token store -->
<bean id="jwtTokenStore"
class="org.springframework.security.oauth2.provider.token.store.JwtTokenStore">
<constructor-arg name="jwtTokenEnhancer" ref="jwtAccessTokenConverter"/>
</bean>
<!-- Creates token store services provider -->
<bean id="tokenServiceProvider"
class="org.springframework.security.oauth2.provider.token.DefaultTokenServices">
<property name="tokenStore"
ref="jwtTokenStore"/>
<!--This must be set according to z docs -->
<property name="tokenEnhancer"
ref="jwtAccessTokenConverter"/>
<property name="supportRefreshToken"
value="true"/>
<property name="accessTokenValiditySeconds"
value="${security.jwt.access-token-validity-seconds}"/>
<property name="refreshTokenValiditySeconds"
value="${security.jwt.refresh-token-validity-seconds}"/>
</bean>
Spring Boot 1.5.4にも同じ問題があります
jwtAccessTokenConverter.setVerifierKey(publicKey);
が実際に使用されるベリファイア(デバッグ値はnull)を設定していないのは実際です-
JwtAccessTokenConverter
...protected Map<String, Object> decode(String token) {
try {
Jwt jwt = JwtHelper.decodeAndVerify(token, verifier);
回避策が役に立ったとして:
private JwtAccessTokenConverter jwtAccessTokenConverter() {
JwtAccessTokenConverter jwtAccessTokenConverter = new CustomTokenEnhancer();
jwtAccessTokenConverter.setSigningKey(jwtSigningKey);
jwtAccessTokenConverter.setVerifier(new RsaVerifier(jwtPublicKey));
log.info("Set JWT signing key to: {}", jwtAccessTokenConverter.getKey());
return jwtAccessTokenConverter;
}