Preface
I am working on an OAuth
application for security between two servers. I have an OAuth Server
and a Resource Server
. The Resource Server
has a single .war
deployed that contains 4 APIs
.
Single Responsibility
- The
OAuth server
has to validate a theaccess token
that was passed by anAPI
(1 of the 4) from that same.war
. - The
OAuth server
has to keep ahit count
for a particularaccessToken
for a particularAPI
. If thehit count
exceeds the configuredhits
theOAuth server
would throw a 403: Forbidden. - Every
API
in the.war
must first validate theaccessToken
from theOAuth server
and if it's validated, then proceed to provide the response.
What I've done:
If a .war
has a single API
then I can simply make the two servers communicate using a webHook
, below is the code that does it.
On the Resource Server Side:
My urls for different APIs are:
localhost:8080/API/API1
localhost:8080/API/API2
Below code routes any request if they have /API/anything
towards the 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>
I have used remote token services and defined the webHook
to route the request to the 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>
Configuration for Auth server
@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{}
What I need help with:
The issue is with the support for single .war
and multiple API
. The issue is the spring config is created at a package level because of which all the APIs
in the .war
have the same clientID
and clientSecret
.
How would my OAuth server know, which specific API
is being accessed and of which API
the hitCount
needs to be deducted.
Possible Solution?
I was thinks of customizing RemoteTokenService
and adding a request parameter at the webHoot URL
and then using a filter at OAuth
server to get the passed tag
(if I may call it that)
Is this even possible? Is there any better approch than this, that doesn't involve all these work arounds?
Eureka !! I finally found a way out to resolve this problem.
All you have to do is :
Configuration at Resource server
Instead of using
RemoteTokenService
make acustom remote token service
which appends some data (query parameter) in the generated request.By implementing
ResourceServerTokenServices
you can modify the request that is sent by theresource server
to theauth server
for authentication and authorization.configuration at Auth Server
Override the spring security controller. What i mean by overring is make a
custom controller
so that the request foroauth/check_token
is handled by your custom controller and not the spring defined controller.