Announcement Announcement Module
Collapse
No announcement yet.
Using extension points to dealt specially with InvalidTokenException? Page Title Module
Move Remove Collapse
X
Conversation Detail Module
Collapse
  • Filter
  • Time
  • Show
Clear All
new posts

  • Using extension points to dealt specially with InvalidTokenException?

    Hello:

    I'm trying to customize the response processing for InvalidTokenException. For my app, a number of APIs are invoked using JSONP (application/x-javascript). Plain JSON and XML are also supported, but with JSONP the goal is to always return a response code of 200 so the user agent callback function will get invoked. When an error is detected by the server, a special JSON response is returned to indicate the error, but with a 200 status.

    There are a number of API specific errors I handle that way, but I also want to do so if the provided OAuth token has issues. In my case, the reason for this would be token expiration, receiving a token I had forcefully invalidated due to abuse, or if the user agent provides garbage or no token whatsoever. The goal here is to allow the user agent to display an appropriate message, or make a request to the tenant application for another token. There is no end user data here, we're just talking about a tenant application acquiring bearer tokens on behalf of user agents using its client credentials.

    I'm using 1.0.0.RC3, moving up from M6. I was hoping to utilize some of the newer extension points etc. to get what I need. But, I'm having a problem. I can see there is org.springframework.security.oauth2.provider.error .OAuth2AccessDeniedHandler, which is instantiated in spring-servlet.xml of the sample apps, with the id of oauthAccessDeniedHandler. That extends AbstractOAuth2SecurityExceptionHandler, which allows for the injection of an OAuth2ExceptionRenderer via the exceptionRenderer property.

    I figured I could extend DefaultOAuth2ExceptionRenderer and override handleHttpEntityResponse(), and deal with InvalidTokenException to my liking, and then pass everything else off to the superclass method. This would be handy since I can get the Accept header value, as well as look for the callback request parameter via the provided ServletWebRequest instance.

    I created this little class to check out this theory:

    Code:
    package com.company.isec.security.handler;
    
    import org.apache.log4j.Logger;
    import org.springframework.http.HttpEntity;
    import org.springframework.security.oauth2.provider.error.DefaultOAuth2ExceptionRenderer;
    import org.springframework.web.context.request.ServletWebRequest;
    
    public class IsecOAuth2ExceptionRenderer extends DefaultOAuth2ExceptionRenderer {
    	
    	private static final Logger LOG = Logger.getLogger(IsecOAuth2ExceptionRenderer.class);
    
    	@Override
    	public void handleHttpEntityResponse(
    			final HttpEntity<?> responseEntity,
    			final ServletWebRequest webRequest) throws Exception {
    		
    		LOG.info(String.format("handleHttpEntityResponse - exception: %s", responseEntity.getBody()));		
    		//super.handleHttpEntityResponse(responseEntity, webRequest);
    	}
    }
    However, I never see that log message, and even after commenting out the invocation of the superclass method, I still get a rendered response to a bogus token:

    Code:
    imac:Downloads jas$ curl --header "Authorization: bearer xxxxxx-14a4-473e-81a7-c28da60b0de0" --header "Accept: application/json" "http://localhost:9090/isec/api/rest/v1/client/articles/PM/16176148" -w "\nhttp code: %{http_code}\n"
    {"error":"invalid_token","error_description":"Invalid access token: xxxxxx-14a4-473e-81a7-c28da60b0de0"}
    http code: 401
    imac:Downloads
    So, my code is not being invoked. In my security-servlet.xml, among any other things:

    Code:
    <http pattern="/api/rest/**" create-session="never" entry-point-ref="oauthAuthenticationEntryPoint" access-decision-manager-ref="accessDecisionManager">		
    	<anonymous enabled="false" />
    	<intercept-url pattern="/api/rest/v1/client/**" access="ROLE_CLIENT,SCOPE_READ" />
    	<intercept-url pattern="/api/rest/v1/partner/**" access="ROLE_PARTNER,SCOPE_WRITE" />
    	<custom-filter ref="resourceServerFilter" before="PRE_AUTH_FILTER" />
    	<access-denied-handler ref="oauthAccessDeniedHandler" />
    </http>
    
    <beans:bean id="oauthAccessDeniedHandler" class="org.springframework.security.oauth2.provider.error.OAuth2AccessDeniedHandler">
    	<beans:property name="exceptionRenderer">
    		<beans:bean class="com.company.isec.security.handler.IsecOAuth2ExceptionRenderer"/>
    	</beans:property>
    </beans:bean>
    I've been doing way to much work this weekend, so perhaps I'm just burned out. But, what am I missing here? Is there a more appropriate way to get the access I need to get the job done?

    Thanks!

    Jeff

  • #2
    An AccessDeniedHandler is mainly there to handle AccessDeniedException. Authentication errors are usually handled by an AuthenticationEntryPoint, so probably your code is just in the wrong strategy implementation. It gets quite tricky to analyse where an exception is going to be handled, since it depends a lot on the precise point in the filter chain that it is thrown, but that woul dbe my initial guess.

    Comment


    • #3
      Thanks for the pointer Dave. Perhaps was too wrapped around this being an authorization issue. I see OAuth2AuthenticationEntryPoint also extends AbstractOAuth2SecurityExceptionHandler, so I adjusted my security config a bit:
      Code:
      	<beans:bean id="oauthAuthenticationEntryPoint" class="org.springframework.security.oauth2.provider.error.OAuth2AuthenticationEntryPoint">
      		<beans:property name="realmName" value="ISEC" />
      		<beans:property name="exceptionRenderer">
      			<beans:bean class="com.company.isec.security.handler.IsecOAuth2ExceptionRenderer"/>
      		</beans:property>
      	</beans:bean>
      
      	<beans:bean id="oauthAccessDeniedHandler" class="org.springframework.security.oauth2.provider.error.OAuth2AccessDeniedHandler"/>
      For the case where no token is provided, my renderer is being invoked:
      Code:
      2012-10-15 06:29:41 INFO  handler.IsecOAuth2ExceptionRenderer - handleHttpEntityResponse - exception: error="unauthorized", error_description="An Authentication object was not found in the SecurityContext"
      Since my code just invokes the super class method, I see the response:

      Code:
      imac:Downloads jas$ curl --header "Accept: application/json" "http://localhost:9090/isec/api/rest/v1/client/articles/PM/16176148?tenantId=sigmaLifeScience" -w "\nhttp code: %{http_code}\n"
      {"error":"unauthorized","error_description":"An Authentication object was not found in the SecurityContext"}
      http code: 401
      imac:Downloads jas$
      But, when I provide an expired token (can see TokenStore.removeAccessToken() invoked), or a bogus token, my renderer code does not get invoked., and I see the standard responses:

      Code:
      imac:Downloads jas$ curl --header "Authorization: bearer 5ccc7bbe-07f4-4788-8eb0-07e79b8bb94c" --header "Accept: application/json" "http://localhost:9090/isec/api/rest/v1/client/articles/PM/16176148" -w "\nhttp code: %{http_code}\n"
      {"error":"invalid_token","error_description":"Access token expired: 5ccc7bbe-07f4-4788-8eb0-07e79b8bb94c"}
      http code: 401
      imac:Downloads jas$ curl --header "Authorization: bearer xxxxxx-14a4-473e-81a7-c28da60b0de0" --header "Accept: application/json" "http://localhost:9090/isec/api/rest/v1/client/articles/PM/16176148?" -w "\nhttp code: %{http_code}\n"
      {"error":"invalid_token","error_description":"Invalid access token: xxxxxx-14a4-473e-81a7-c28da60b0de0"}
      http code: 401
      imac:Downloads jas$
      These last two scenarios are actually more interesting to me.

      I'll keep looking, but any other pointers would be appreciated.

      Cheers,

      Jeff

      Comment


      • #4
        It looks like I've already touched on the only two concrete classes extending AbstractOAuth2SecurityExceptionHandler. Likewise, OAuth2ExceptionRenderer is only referenced within AbstractOAuth2SecurityExceptionHandler and DefaultOAuth2ExceptionRenderer.

        Perhaps this rendering cannot be altered?

        Cheers,

        Jeff

        Comment


        • #5
          I'm not sure yet that I understand what you expected to see and didn't. I am pretty sure that if you send an invalid token to an OAuth2 protected resource you will see an InvalidTokenException (not AccessDeniedException) and it will lead to the authentication entry point being called, and in turn that will call the exception renderer. So what did your custom renderer not do that it was supposed to?

          Comment


          • #6
            Hi Dave:

            I REALLY appreciate you helping out with this.

            My goal is to handle invalid token exceptions myself. Ideally, I'd also like to participate on the authorization server side as well to process optional (non-OAuth) parameters passed to the token endpoint. But, that's for later.

            When Spring Security/OAuth resource server functionality decides a token is invalid; expired, missing, total junk, etc. I would like to know about it and render my own response. This is largely to support API access via JSONP where I need to return a 200 status code and indicate the error situation in an API defined format. As far as a tenant application, or malicious actor etc., providing incorrect client credentials to the token endpoint etc., having that handled in the OAuth 2 standard manner is the way to go.

            In my case, there is no end user data and authorization etc., I'm working strictly with a tenant obtaining a bearer token on behalf of the user agent and handing it over. The user agent then invokes the APIs (usually via provided JavaScript 'widgets') and supplies that token. The token (via the authentication in the TokenStore) allows me to determine the tenant that issued the token, record usage, access tenant specific content etc.

            What I was attempting to do, looking at some of the Spring Security OAuth code from github, and refreshing some of my Spring Security knowledge, was to determine just where my app could get involved as described for handling token issues for the resource server. You've seen my code and configuration attempts above in this thread, but there is something I'm apparently just not getting conceptually or missing some configuration detail.

            So first off, am I dealing with an authentication or authorization issue at the time my resource server is being accessed with a token provided? The tenant has authenticated already to obtain the token in the first place, and now the user agent is using the token, and is it now authenticating to the resource server? If the token is found to be invalid for some reason at this point, is it an authentication error?

            Going from there, I can see where authorization now comes in:

            Code:
            <http pattern="/api/rest/**" create-session="stateless" entry-point-ref="oauthAuthenticationEntryPoint" use-expressions="true">		
            	<anonymous enabled="false" />
            	<intercept-url pattern="/api/rest/v1/client/articles/**" 
            		access="#oauth2.clientHasRole('ROLE_CONTENT_ARTICLES') and #oauth2.clientHasRole('ROLE_CLIENT') and #oauth2.hasScope('read')" />
            	<intercept-url pattern="/api/rest/v1/client/**" 
            		access="#oauth2.clientHasRole('ROLE_CLIENT') and #oauth2.hasScope('read')" />
            	<intercept-url pattern="/api/rest/v1/partner/**" 
            		method="GET"
            		access="#oauth2.clientHasRole('ROLE_PARTNER') and #oauth2.hasScope('read')" />
            	<intercept-url pattern="/api/rest/v1/partner/**" 
            		method="POST" 
            		access="#oauth2.clientHasRole('ROLE_PARTNER') and #oauth2.hasScope('write')" />
            	
            	<custom-filter ref="resourceServerFilter" before="PRE_AUTH_FILTER" />
            	<access-denied-handler ref="oauthAccessDeniedHandler" />
            	<expression-handler ref="oauthWebExpressionHandler" />
            </http>
            So here, I'd also like to render those authorization exceptions when generated.

            In my earlier post, I can see my code (renderer) getting invoked only in the case where there is no OAuth token provided to the resource server, via the AuthenticationEntryPoint. If an expired or bogus token is an authentication issue, then I'd expect to see my code invoked for them too, but I do not. With my renderer configured in the AccessDeniedHandler, I never see it get invoked for any of these scenarios.

            I guess I don't understand the right way to get hooked into handling these exceptions in my own way. Perhaps it's less of a Spring Security OAuth issue, and more a Spring Security detail. But, since I saw you exception hierarchy and response rendering code, I figured I'd start there.

            Thanks!

            Jeff

            Comment


            • #7
              The invalid token is handled by the OAuth2AuthenticationProcessingFilter, which you would have set up using <oauth:resource-server/>. In Spring Security terms it is neither an AccessDeniedException nor an AuthenticationException and it is not handled by the normal ExceptionTranslationFilter. I think the problem might just be that you haven't injected your custom AuthenticationEntryPoint into the OAuth2 filter (you need an extra attribute on the <oauth:resource-server/>).

              BTW, your filter chain has no fallback intercept URL, so there is no access check for /api/rest/foo (for example). Maybe that's OK if you don't expose any endpoints that you don't refer to, but if I were you I'd add a fallback denyAll pattern.

              Comment


              • #8
                Excellent!

                Thanks again Dave. I got pulled away on something, and now I can try to wrap this up. It looks like also setting the authentication entry point on the resource server was the trick:

                Code:
                <oauth:resource-server id="resourceServerFilter" resource-id="isecApi" token-services-ref="tokenServices" entry-point-ref="oauthAuthenticationEntryPoint" />
                With that, my exception render gets invoked as needed:

                Code:
                2012-11-05 16:02:42 INFO  handler.IsecOAuth2ExceptionRenderer - handleHttpEntityResponse - exception: error="invalid_token", error_description="Invalid access token: 5887acc0-89af-4318-b384-7c8985dfb09e"
                2012-11-05 16:03:16 INFO  handler.IsecOAuth2ExceptionRenderer - handleHttpEntityResponse - exception: error="unauthorized", error_description="An Authentication object was not found in the SecurityContext"
                2012-11-05 16:13:34 INFO  cassandra.CassandraTokenStore - removeAccessToken - Removed access token: 57cd4b3c-02b0-4397-bdc9-e2a535cebe98
                2012-11-05 16:13:34 INFO  handler.IsecOAuth2ExceptionRenderer - handleHttpEntityResponse - exception: error="invalid_token", error_description="Access token expired: 57cd4b3c-02b0-4397-bdc9-e2a535cebe98"
                And, thanks for pointing out the lack of a fallback URL. In that <http> configuration, I added after all of the more specific intercept URLs:

                Code:
                <intercept-url pattern="/api/rest/**" access="denyAll" />
                Thank you very much for all your help. Now I have to figure out how to customize the render as I need it. At least it's getting called!

                Cheers,

                Jeff

                Comment

                Working...
                X