我可以在oauth / check_token端点附加一些信息并在授权服务器上检索它吗?

时间:2018-02-15 11:57:04

标签: spring-boot spring-security spring-security-oauth2 spring-oauth2 auth-request

前言

我正在开发一个OAuth应用程序,以确保两台服务器之间的安全性。我有OAuth ServerResource ServerResource Server部署了一个.war,其中包含4 APIs

单一责任

  1. OAuth server必须验证同一access token API(4个中的1个)传递的.war
  2. OAuth server必须为特定hit count的特定accessToken保留API。如果hit count超出配置的hitsOAuth server会抛出 403:禁止
  3. API中的每个.war必须首先验证accessToken中的OAuth server,如果已经过验证,则继续提供回复。
  4. 我做了什么:

    如果.war只有一个API,那么我可以简单地让两个服务器使用webHook进行通信,下面是执行此操作的代码。

    在资源服务器端:

    我对不同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>,则代码会路由这些请求

    &#13;
    &#13;
    webHook
    &#13;
    &#13;
    &#13;

    我使用了远程令牌服务并定义了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 APIAPIs。问题是spring配置是在包级别创建的,因此.war中的所有clientID都具有相同的clientSecretAPI

    我的OAuth服务器如何知道,正在访问哪个特定API以及hitCount需要扣除RemoteTokenService

    可能的解决方案? 我想到自定义webHoot URL并在OAuth添加请求参数,然后使用tag服务器上的过滤器来获取传递的module.exports = { doSomething: () => { let something = this.something.title; } }; (如果我可以称之为)

    这甚至可能吗?有没有比这更好的方法,这并不涉及所有这些工作?

1 个答案:

答案 0 :(得分:6)

Eureka !! 我终于找到了解决这个问题的方法。

您所要做的就是:

资源服务器上的配置

而不是使用RemoteTokenService使custom remote token service在生成的请求中附加一些数据(查询参数)。

public class CustomRemoteTokenService implements ResourceServerTokenServices {

protected final Log logger = LogFactory.getLog(getClass());

private RestOperations restTemplate;

private String checkTokenEndpointUrl;

private String clientId;

private String clientSecret;

private String tokenName = "token";

private AccessTokenConverter tokenConverter = new DefaultAccessTokenConverter();

@Autowired
public CustomRemoteTokenService() {
    restTemplate = new RestTemplate();
    ((RestTemplate) restTemplate).setErrorHandler(new DefaultResponseErrorHandler() {
        @Override
        // Ignore 400
        public void handleError(ClientHttpResponse response) throws IOException {
            if (response.getRawStatusCode() != 400) {
                super.handleError(response);
            }
        }
    });
}

public void setRestTemplate(RestOperations restTemplate) {
    this.restTemplate = restTemplate;
}

public void setCheckTokenEndpointUrl(String checkTokenEndpointUrl) {
    this.checkTokenEndpointUrl = checkTokenEndpointUrl;
}

public void setClientId(String clientId) {
    this.clientId = clientId;
}

public void setClientSecret(String clientSecret) {
    this.clientSecret = clientSecret;
}

public void setAccessTokenConverter(AccessTokenConverter accessTokenConverter) {
    this.tokenConverter = accessTokenConverter;
}

public void setTokenName(String tokenName) {
    this.tokenName = tokenName;
}

@Override
public OAuth2Authentication loadAuthentication(String accessToken) throws AuthenticationException, InvalidTokenException {

    /*
     * This code needs to be more dynamic. Every time an API is added we have to add its entry in the if check for now.
     * Should be changed later.
     */
    HttpServletRequest request = Context.getCurrentInstance().getRequest();         
    MultiValueMap<String, String> formData = new LinkedMultiValueMap<String, String>();
    String uri = request.getRequestURI();

    formData.add(tokenName, accessToken);       

    if(request != null) {
        if(uri.contains("API1")) {
            formData.add("api", "1");
        }else if(uri.contains("API2")) {
            formData.add("api", "2");
        } 
    }

    HttpHeaders headers = new HttpHeaders();
    headers.set("Authorization", getAuthorizationHeader(clientId, clientSecret));
    Map<String, Object> map = postForMap(checkTokenEndpointUrl, formData, headers);

    if (map.containsKey("error")) {
        logger.debug("check_token returned error: " + map.get("error"));
        throw new InvalidTokenException(accessToken);
    }



    Assert.state(map.containsKey("client_id"), "Client id must be present in response from auth server");
    return tokenConverter.extractAuthentication(map);
}

@Override
public OAuth2AccessToken readAccessToken(String accessToken) {
    throw new UnsupportedOperationException("Not supported: read access token");
}

private String getAuthorizationHeader(String clientId, String clientSecret) {
    String creds = String.format("%s:%s", clientId, clientSecret);
    try {
        return "Basic " + new String(Base64.encode(creds.getBytes("UTF-8")));
    }
    catch (UnsupportedEncodingException e) {
        throw new IllegalStateException("Could not convert String");
    }
}

private Map<String, Object> postForMap(String path, MultiValueMap<String, String> formData, HttpHeaders headers) {
    if (headers.getContentType() == null) {
        headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
    }
    @SuppressWarnings("rawtypes")
    Map map = restTemplate.exchange(path, HttpMethod.POST,
            new HttpEntity<MultiValueMap<String, String>>(formData, headers), Map.class).getBody();
    @SuppressWarnings("unchecked")
    Map<String, Object> result = map;
    return result;
}

}

通过实施ResourceServerTokenServices,您可以修改resource server发送给auth server的请求,以进行身份​​验证和授权。

<\ n> 在Auth服务器上配置

覆盖弹簧安全控制器。我所说的overring是指custom controller,以便oauth/check_token的请求由您的自定义控制器而不是弹簧定义的控制器处理。

@RestController
public class CustomCheckTokenEndpoint {

private ResourceServerTokenServices resourceServerTokenServices;

private AccessTokenConverter accessTokenConverter = new DefaultAccessTokenConverter();

protected final Log logger = LogFactory.getLog(getClass());

private WebResponseExceptionTranslator exceptionTranslator = new DefaultWebResponseExceptionTranslator();

@Autowired
KeyHitManager keyHitManager;

public CustomCheckTokenEndpoint(ResourceServerTokenServices resourceServerTokenServices) {
    this.resourceServerTokenServices = resourceServerTokenServices;
}

/**
 * @param exceptionTranslator
 *            the exception translator to set
 */
public void setExceptionTranslator(WebResponseExceptionTranslator exceptionTranslator) {
    this.exceptionTranslator = exceptionTranslator;
}

/**
 * @param accessTokenConverter
 *            the accessTokenConverter to set
 */
public void setAccessTokenConverter(AccessTokenConverter accessTokenConverter) {
    this.accessTokenConverter = accessTokenConverter;
}

@RequestMapping(value = "/oauth/check_token")
@ResponseBody
public Map<String, ?> customCheckToken(@RequestParam("token") String value, @RequestParam("api") int api) {

    OAuth2AccessToken token = resourceServerTokenServices.readAccessToken(value);
    if (token == null) {
        throw new InvalidTokenException("Token was not recognised");
    }

    if (token.isExpired()) {
        throw new InvalidTokenException("Token has expired");
    }

    OAuth2Authentication authentication = resourceServerTokenServices.loadAuthentication(token.getValue());

    Map<String, ?> response = accessTokenConverter.convertAccessToken(token, authentication);

    String clientId = (String) response.get("client_id");
    if (!keyHitManager.isHitAvailble(api,clientId)) {
        throw new InvalidTokenException(
                "Services for this key has been suspended due to daily/hourly transactions limit");
    }

    return response;
}

@ExceptionHandler(InvalidTokenException.class)
public ResponseEntity<OAuth2Exception> handleException(Exception e) throws Exception {
    logger.info("Handling error: " + e.getClass().getSimpleName() + ", " + e.getMessage());
    // This isn't an oauth resource, so we don't want to send an
    // unauthorized code here. The client has already authenticated
    // successfully with basic auth and should just
    // get back the invalid token error.
    @SuppressWarnings("serial")
    InvalidTokenException e400 = new InvalidTokenException(e.getMessage()) {
        @Override
        public int getHttpErrorCode() {
            return 400;
        }
    };
    return exceptionTranslator.translate(e400);
}
}