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の行がデータベースにあります。
ヘッダーをAuthorization: Basic xxx
に設定しています。xxx
はaccess_token
を取得するために使用するものと同じなので、正常に機能すると想定しています。
しかし、出力は常にこれです
{"error": "unauthorized"、 "error_description": "User 12345678 was not found in the database"}
パスワードについては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
に入れ、失敗した更新試行に対して呼び出されたかどうかを確認します。
__を使用して_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
_