【发布时间】:2018-07-26 04:18:10
【问题描述】:
前言
我正在开发一个OAuth 应用程序以确保两台服务器之间的安全性。我有一个OAuth Server 和一个Resource Server。 Resource Server 部署了一个包含4 APIs 的.war。
单一职责
-
OAuth server必须验证由同一.war的API(4 个中的 1 个)传递的access token。 -
OAuth server必须为特定的accessToken为特定的API保留一个hit count。如果hit count超过配置的hits,OAuth server将抛出 403: Forbidden。 -
.war中的每个API必须首先验证来自OAuth server的accessToken,如果验证通过,则继续提供响应。
我做了什么:
如果.war 有一个API,那么我可以简单地使两个服务器使用webHook 进行通信,下面是执行此操作的代码。
在资源服务器端:
我的不同 API 的网址是:
localhost:8080/API/API1localhost: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 配置是在包级别创建的,因为.war 中的所有APIs 都具有相同的clientID 和clientSecret。
我的 OAuth 服务器如何知道,哪些API 正在被访问,哪些API 需要扣除hitCount。
可能的解决方案?
我正在考虑自定义RemoteTokenService 并在webHoot URL 添加请求参数,然后在OAuth 服务器上使用过滤器来获取传递的tag(如果我可以这样称呼它)
这甚至可能吗?有没有比这更好的方法,不涉及所有这些变通方法?
【问题讨论】:
-
为任何用户管理剩余的 API 命中是授权服务器的工作。身份验证服务器检查剩余的命中,然后授权请求。资源服务器将更多地关注 API,它的工作而不是管理点击。
标签: spring-boot spring-security spring-security-oauth2 spring-oauth2 auth-request