Предисловие
Я работаю над приложением OAuth
для обеспечения безопасности между двумя серверами. У меня есть OAuth Server
и Resource Server
. В Resource Server
развернут один .war
, содержащий 4 APIs
.
Единая ответственность
OAuth server
должен проверитьaccess token
, который был переданAPI
(1 из 4) из того же.war
.OAuth server
должен хранитьhit count
для конкретногоaccessToken
для конкретногоAPI
. Еслиhit count
превысит настроенноеhits
,OAuth server
выдаст ошибку 403: Forbidden.- Каждый
API
в.war
должен сначала проверитьaccessToken
изOAuth server
и, если он подтвержден, затем приступить к предоставлению ответа.
Что я наделал:
Если .war
имеет один API
, то я могу просто заставить два сервера обмениваться данными, используя webHook
, ниже приведен код, который это делает.
На стороне сервера ресурсов:
Мои URL-адреса для разных API:
localhost:8080/API/API1
localhost:8080/API/API2
Код ниже направляет любой запрос, если у них есть /API/anything
к spring security filters
<http pattern="/API/**" create-session="never" authentication-manager-ref="authenticationManager" entry-point-ref="oauthAuthenticationEntryPoint" xmlns="http://www.springframework.org/schema/security">
<anonymous enabled="false" />
<intercept-url pattern="/places/**" method="GET" access="IS_AUTHENTICATED_FULLY" />
<custom-filter ref="resourceServerFilter" before="PRE_AUTH_FILTER" />
<access-denied-handler ref="oauthAccessDeniedHandler" />
</http>
Я использовал службы удаленных токенов и определил webHook
для маршрутизации запроса к OAuth server
.
<bean id="tokenServices" class="org.springframework.security.oauth2.provider.token.RemoteTokenServices">
<property name="checkTokenEndpointUrl" value="http://localhost:8181/OUTPOST/oauth/check_token"/>
<property name="clientId" value="atlas"/>
<property name="clientSecret" value="atlas"/>
</bean>
Конфигурация сервера аутентификации
@EnableAuthorizationServer
@Configuration
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
private static String REALM="OUTPOST_API";
@Autowired
private ClientDetailsService clientService;
@Autowired
public AuthorizationServerConfig(AuthenticationManager authenticationManager,RedisConnectionFactory redisConnectionFactory) {
this.authenticationManager = authenticationManager;
this.redisTokenStore = new RedisTokenStore(redisConnectionFactory);
}
// @Autowired
// @Qualifier("authenticationManagerBean")
private AuthenticationManager authenticationManager;
private TokenStore redisTokenStore;
@Autowired
private UserApprovalHandler userApprovalHandler;
@Autowired
private RedisConnectionFactory redisConnectionFactory;
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security.tokenKeyAccess("isAuthenticated()")
.checkTokenAccess("isAuthenticated()").
realm(REALM+"/client");
}
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients
.inMemory()
.withClient("cl1")
.secret("pwd")
.authorizedGrantTypes("password", "client_credentials", "refresh_token")
.authorities("ROLE_CLIENT", "ROLE_ADMIN")
.scopes("read", "write", "trust")/*
.resourceIds("sample-oauth")*/
.accessTokenValiditySeconds(1000)
.refreshTokenValiditySeconds(5000)
.and()
.withClient("atlas")
.secret("atlas");
}
@Bean
@Autowired
public TokenStore tokenStore(RedisConnectionFactory redisConnectionFactory) {
this.redisTokenStore = new RedisTokenStore(redisConnectionFactory);
return this.redisTokenStore;
}
@Bean
public WebResponseExceptionTranslator loggingExceptionTranslator() {
return new DefaultWebResponseExceptionTranslator() {
@Override
public ResponseEntity<OAuth2Exception> translate(Exception e) throws Exception {
// This is the line that prints the stack trace to the log. You can customise this to format the trace etc if you like
e.printStackTrace();
// Carry on handling the exception
ResponseEntity<OAuth2Exception> responseEntity = super.translate(e);
HttpHeaders headers = new HttpHeaders();
headers.setAll(responseEntity.getHeaders().toSingleValueMap());
OAuth2Exception excBody = responseEntity.getBody();
return new ResponseEntity<>(excBody, headers, responseEntity.getStatusCode());
}
};
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.tokenStore(redisTokenStore).userApprovalHandler(userApprovalHandler)
.authenticationManager(authenticationManager)
.exceptionTranslator(loggingExceptionTranslator());
}
public void setRedisConnectionFactory(RedisConnectionFactory redisConnectionFactory) {
this.redisConnectionFactory = redisConnectionFactory;
}
@Bean
public TokenStoreUserApprovalHandler userApprovalHandler(){
TokenStoreUserApprovalHandler handler = new TokenStoreUserApprovalHandler();
handler.setTokenStore(redisTokenStore);
handler.setRequestFactory(new DefaultOAuth2RequestFactory(clientService));
handler.setClientDetailsService(clientService);
return handler;
}
@Bean
@Autowired
public ApprovalStore approvalStore() throws Exception {
TokenApprovalStore store = new TokenApprovalStore();
store.setTokenStore(redisTokenStore);
return store;
}
@Bean
@Primary
@Autowired
public DefaultTokenServices tokenServices() {
DefaultTokenServices tokenServices = new DefaultTokenServices();
tokenServices.setSupportRefreshToken(true);
tokenServices.setTokenStore(redisTokenStore);
return tokenServices;
}
}
@Component
class MyOAuth2AuthenticationEntryPoint extends OAuth2AuthenticationEntryPoint{}
В чем мне нужна помощь:
Проблема в поддержке одиночных .war
и multiple API
. Проблема в том, что конфигурация spring создается на уровне пакета, из-за чего все APIs
в .war
имеют одинаковые clientID
и clientSecret
.
Как мой сервер OAuth узнает, к каким конкретным API
осуществляется доступ и из каких API
необходимо вычесть hitCount
.
Возможное решение? Я думал о настройке RemoteTokenService
и добавлении параметра запроса на webHoot URL
, а затем использовании фильтра на сервере OAuth
для получения переданного tag
(если можно так назвать)
Это вообще возможно? Есть ли лучший подход, чем этот, который не включает все эти обходные пути?