web-dev-qa-db-ja.com

OAuth2-Spring Boot-更新トークン

Spring Bootアプリケーションを構成して、oauth2認証を提供しました。

@Configuration
public class OAuth2Configuration {

    @Configuration
    @EnableResourceServer
    protected static class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {

       @Autowired
       private CustomAuthenticationEntryPoint customAuthenticationEntryPoint;

       @Autowired
       private CustomLogoutSuccessHandler customLogoutSuccessHandler;

       @Override
       public void configure(HttpSecurity http) throws Exception {
           http.exceptionHandling()
               .authenticationEntryPoint(customAuthenticationEntryPoint)
                        .and()
                        .logout()
                        .logoutUrl("/oauth/logout")
                        .logoutSuccessHandler(customLogoutSuccessHandler)
                        .and()
                        .csrf()
                        .disable()
                        .headers()
                        .frameOptions().disable()
                        .exceptionHandling().and()
                        .sessionManagement()
                        .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                        .and()
                        .authorizeRequests()
                        .antMatchers(HttpMethod.OPTIONS, "/**").permitAll()
                        .antMatchers("/api/v1/login/**").permitAll()
                        .antMatchers("/api/v1/admin/**").permitAll()
                        .antMatchers("/api/v1/test/**").permitAll()
                        .antMatchers("/oauth/token").permitAll()
                        .antMatchers("/api/**").authenticated();
            }
        }

    @Configuration
    @EnableAuthorizationServer
    protected static class AuthorizationServerConfiguration 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 DataSource dataSource;

        @Bean
        public TokenStore tokenStore() {
            return new JdbcTokenStore(dataSource);
        }

        @Autowired
        @Qualifier("authenticationManagerBean")
        private AuthenticationManager authenticationManager;

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

        @Override
        public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
            clients.inMemory()
                   .withClient(propertyResolver.getProperty(PROP_CLIENTID))
                   .scopes("read", "write")
                   .authorities(Authorities.ROLE_USER.name())
                   .authorizedGrantTypes("password", "refresh_token", "authorization_code", "implicit")
                   .secret(propertyResolver.getProperty(PROP_SECRET))
                   .accessTokenValiditySeconds(
                    propertyResolver.getProperty(PROP_TOKEN_VALIDITY_SECONDS, Integer.class, 1800))
                   .refreshTokenValiditySeconds(100000);
            }

        @Override
        public void setEnvironment(Environment environment) {
            this.propertyResolver = new RelaxedPropertyResolver(environment, ENV_OAUTH);
        }
    }
}

@Configuration
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
    @Autowired
    private UserDetailsService userDetailsService;

    @Bean
    public CustomPasswordEncoder passwordEncoder() {
        return new CustomPasswordEncoder();
    }

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService)
            .passwordEncoder(passwordEncoder());
    }

    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring()
           .antMatchers(HttpMethod.OPTIONS, "/**").antMatchers("/api/login/**");
    }

    @Override
    public void configure(HttpSecurity http) throws Exception {      
        http.httpBasic().realmName("WebServices").and().sessionManagement()
            .sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
            .requestMatchers().antMatchers("/oauth/authorize").and()
            .authorizeRequests().antMatchers("/oauth/authorize")
            .authenticated();
    }

    @Override
    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    @EnableGlobalMethodSecurity(prePostEnabled = true, jsr250Enabled = true)
    private static class GlobalSecurityConfiguration extends GlobalMethodSecurityConfiguration {
        @Override
        protected MethodSecurityExpressionHandler createExpressionHandler() {
            return new OAuth2MethodSecurityExpressionHandler();
        }
    }
}

public class UserDetailsServiceImpl implements UserDetailsService {
    @Inject
    private AccountDao accountDao;

    @Override
    @Transactional
    public UserDetails loadUserByUsername(final String login) {
        Account userFromDatabase = null;
        String lowercaseLogin = login.toLowerCase();
        if (lowercaseLogin.contains("@")) {
            userFromDatabase = accountDao.getByEmailId(lowercaseLogin);
        } else {
            userFromDatabase = accountDao.getByPhoneNumber(lowercaseLogin);
        }

        if (userFromDatabase != null) {
            if (!userFromDatabase.getActivated()) {
                throw new UserNotActivatedException("User " + lowercaseLogin + " was not activated");
            }
            List<GrantedAuthority> grantedAuthorities = userFromDatabase.getRoles().stream()
                    .map(authority -> new SimpleGrantedAuthority(authority.getRoleName())).collect(Collectors.toList());
            return new org.springframework.security.core.userdetails.User(userFromDatabase.getAccountName(),
                    userFromDatabase.getAccountPassword(), grantedAuthorities);
        } else {
            throw new UsernameNotFoundException("User " + lowercaseLogin + " was not found in the " + "database");
        }
    }
}

ここで、アクセストークンの有効期限が切れた後に更新トークンを取得しようとすると、常に

2017-07-10 00:57:40.797 INFO 68115 --- [nio-9090-exec-4] o.s.s.o.provider.endpoint.TokenEndpoint:処理エラー:NoSuchClientException、要求されたIDのクライアントなし:12345678

ただし、列番号が電話番号12345678、アカウント名が12345678の行がデータベースにあります。

https:// myTestWebServices/oauth/token?grant_type = refresh_token&refresh_token = f4cc8213-3f2b-4a30-965b-6feca898479e

ヘッダーをAuthorization: Basic xxxに設定しています。xxxaccess_tokenを取得するために使用するものと同じなので、正常に機能すると想定しています。

しかし、出力は常にこれです

{"error": "unauthorized"、 "error_description": "User 12345678 was not found in the database"}

10
i_raqz

パスワードについてはgrant_type、clientIdおよびclientSecretが必要です。 Authorizationヘッダーで、アクセストークンの代わりにBase64でエンコードされたclientIdおよびclientSecretを渡します。そのようです:

curl -H "Authorization: Bearer [base64encode(clientId:clientSecret)]" "https://yourdomain.com/oauth/token?grant_type=refresh_token&refresh_token=[yourRefreshToken]"

私はあなたが最初にこのようなトークンを手に入れていると仮定しています(私が尋ねたにもかかわらずあなたは言わなかった):

curl --data "grant_type=password&username=user&password=pass&client_id=my_client" http://localhost:8080/oauth/token"

また、ブレークポイントをloadUserByUsernameに入れ、失敗した更新試行に対して呼び出されたかどうかを確認します。

2
Abhijit Sarkar

__を使用して_access token_をフェッチするときは、clientIdおよびclient secret(これらはserIdおよびpasswordとは異なります)を渡す必要があります_refresh token_。 authorisationヘッダーで何が渡されているかわかりません。

2つの異なる問題が発生しているようです。以下のエラーが発生した場合:

{"error": "unauthorized"、 "error_description": "User 12345678 was not found in the database"}

ユーザーが正常に認証済みであり、サービスがアクセストークンと更新トークンを返したかどうかを確認できますか? UserDetailsServiceにデバッグポインタを配置して、フローを確認できます。

以下の手順に従って、構成の検証を試みます。

使用していると仮定して、更新トークンを取得する

_curl -vu clientId:clientSecret 'http://your_domain_url/api/oauth/token?username=userName&password=password&grant_type=password'
_

ここでユーザー名およびパスワードクライアントIDおよびクライアントシークレットとは異なりますこれにより、更新トークンとアクセスが返されます応答のトークン

_{"access_token":"d5deb98a-75fc-4f3a-bbfd-e5c87ca2ca6f","token_type":"bearer","refresh_token":"b2be4291-57e9-4b28-b114-feb3406e030d","expires_in":2,"scope":"read write"}
_

上記の応答には、アクセストークンと更新トークンがあります。アクセストークンの有効期限が切れると、更新トークンを使用して、次のようにアクセストークンを取得できます。

_curl -vu clientId:clientSecret 'http://your_domain_url/api/oauth/token?grant_type=refresh_token&refresh_token=refresh_token_value'
_

応答:

_{"access_token":"13fd30f9-f0c5-414e-9fbd-a5e2f9f3e4a7","token_type":"bearer","refresh_token":"b2be4291-57e9-4b28-b114-feb3406e030d","expires_in":2,"scope":"read write"}
_

これで、アクセストークンを使用してサービスを呼び出すことができます

_curl -i -H "Authorization: Bearer 13fd30f9-f0c5-414e-9fbd-a5e2f9f3e4a7" http://your_domain_url/api/mySecureApi
_
1