Announcement Announcement Module
Collapse
No announcement yet.
Converting security exceptions to API responses Page Title Module
Move Remove Collapse
X
Conversation Detail Module
Collapse
  • Filter
  • Time
  • Show
Clear All
new posts

  • Converting security exceptions to API responses

    Hello:

    I'm trying to figure out how best to handle token related exceptions, both those possible within the framework itself (token not found, has expired), as well as others I'd like to add (source IP address changed, token has been used too many times, token usage suspicious and captcha is required etc.).

    In addition, my RESTful APIs are being invoked cross-domain via JSONP. Rather than making use of Ajax and being able to return appropriate HTTP status codes, JSONP performed within JavaScript simply times out if the callback function is not invoked. This is the case when the API fails for any reason.

    It seems I must always return a response, and indicate the status within, good or bad. So, I need to deal with the bad so I can return the callback function wrapped status so the client will know what's up.

    I think this crosses the boundary between Spring Security and the OAuth extension. First off, my application has no end user data. I'm using client credentials so that a tenant application can acquire a bearer token on behalf of a user agent (browser for now), which then provides the token to access the APIs, and identify the tenant. There is tenant specific content, as well as rules applied to the shared content.

    In terms of dealing with Spring Security exceptions, I see ExceptionMappingAuthenticationFailureHandler comes up. This looks very useful; I could forward to a JSP that will return the response needed. But, I've only seen a custom implementation being wired in via authentication-failure-handler-ref attribute of the form-login element. I have no end users, and thus no form-login:

    Code:
    <beans:beans xmlns="http://www.springframework.org/schema/security"
    	xmlns:beans="http://www.springframework.org/schema/beans" 	xmlns:oauth="http://www.springframework.org/schema/security/oauth2"
    	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
                  http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.1.xsd
                  http://www.springframework.org/schema/security/oauth2 http://www.springframework.org/schema/security/spring-security-oauth2-1.0.xsd">
    		
    	<!-- Note that ISEC is both an OAuth authorization server as well as the 
    		resource server.
    	-->
    	<http auto-config="true" create-session="never" access-decision-manager-ref="accessDecisionManager">
    		<intercept-url pattern="/api/rest/v1/client/**" access="ROLE_CLIENT,SCOPE_READ" />
    		<intercept-url pattern="/api/rest/v1/partner/**" access="ROLE_PARTNER,SCOPE_WRITE" />
    		<intercept-url pattern="/oauth/token" access="IS_AUTHENTICATED_ANONYMOUSLY" />
    		<intercept-url pattern="/oauth/authorize" access="IS_AUTHENTICATED_ANONYMOUSLY" />
    		<intercept-url pattern="/oauth/**" access="ROLE_CLIENT,ROLE_PARTNER" />
    			
    		<custom-filter ref="resourceServerFilter" after="EXCEPTION_TRANSLATION_FILTER"/>
    	</http>
    	
    	<oauth:resource-server id="resourceServerFilter" resource-id="isecApi" token-services-ref="tokenServices"/>
    	
    	<oauth:authorization-server client-details-service-ref="clientDetailsService" token-services-ref="tokenServices">
    		<oauth:client-credentials />
    	</oauth:authorization-server>
    	
    	<authentication-manager/>
    	
    	<beans:bean id="tokenServices" class="org.springframework.security.oauth2.provider.token.RandomValueTokenServices">
    		<beans:property name="tokenStore" ref="tokenStore"/>
    		<beans:property name="supportRefreshToken" value="false" />
    	</beans:bean>
    
    	<beans:bean id="accessDecisionManager" class="org.springframework.security.access.vote.UnanimousBased">
    		<beans:constructor-arg>
    			<beans:list>
    				<beans:bean class="org.springframework.security.oauth2.provider.vote.ScopeVoter" />
    				<beans:bean class="org.springframework.security.access.vote.RoleVoter" />
    				<beans:bean class="org.springframework.security.access.vote.AuthenticatedVoter" />
    			</beans:list>
    		</beans:constructor-arg>
    	</beans:bean>
    	
    	<bean id="clientDetailsService" class="com.mycompany.isec.service.cassandra.CassandraClientDetailsService">
    		<constructor-arg ref="isecTenantEm"/>
    	</bean>
    	
    	<bean id="tokenStore" class="com.mycompany.isec.service.cassandra.CassandraTokenStore">
    		<constructor-arg ref="isecSecurityEm"/>
    		<constructor-arg ref="clientDetailsService"/>
    	</bean>
    		
    </beans:beans>
    Note my application is both an authorization server and a resource (API) server. I added the tokenStore and clientDetailsService beans from a different context file just so everything is listed above. Can resource-server be configured instead of login-form? Do I have to create my own subclass of the resource server filter to get the hooks I need?

    I am using 1.0.0.M5.

    Thanks,

    Jeff

  • #2
    If you look at the latest sparklr2 sample code you will see a custom authentication-entry-point and a custom access-denied-handler. Those, plus the existing filters, should give you json reponses to API clients if that's what you want (there's no form login for the OAuth2 resourecs in sparklr2 either).

    Comment


    • #3
      Hi Dave:

      I got the latest from github, and I can see what you're referring to.

      Code:
      <http pattern="/photos/**" entry-point-ref="oauthAuthenticationEntryPoint" access-decision-manager-ref="accessDecisionManager"...
      ...
      <bean id="oauthAuthenticationEntryPoint" class="org.springframework.security.oauth2.provider.error.MediaTypeAwareAuthenticationEntryPoint">
      	<property name="realmName" value="sparklr2" />
      </bean>
      This looks very promising. I assume this is not 1.0.0.M5 functionality? Is M6 or RC1 due out soon? I guess I'll have to review how to configure Maven to look for the SpringSource nightly builds etc.

      With just a quick look at MediaTypeAwareAuthenticationEntryPoint, it looks like the media types and response format strings are configurable, though the actual message is limited to the AuthenticationException message string. It looks like the top of the food chain exception class, OAuth2Exception, hands off the message string to its superclass, and that is eventually what generateResponseBody() will put in the error response.

      Is the proper approach to override generateResponseBody() if I want to, say, include the OAuth2 error code and additional information that may be present in the exception? This includes the OAauth2 exceptions as well as any application specific ones. I just want to make sure I'm going about this the right way.

      Thanks!

      Jeff

      Comment


      • #4
        I would go with generateResponseBody() as a first step. If the OAuth2Exceptions are not rendered correctly we could fix that in the framework. Don't forget the access-denied-handler as well (for the other half of the Spring Security hierarchy).

        For application exceptions I would expect you would handle them in application code (e.g. in a Spring MVC HandlerExceptionResolver). The Spring Security exceptions have to be handled in the filter chain because the application code might not have been touched, but you ought to be able to get at any other exception from your code directly (e.g. with @ExceptionHandler).

        I was thinking we might have an M6 soon because the spec is still in draft status, even though it seems quite stable now. Snapshots come from a repository with the same name as the milestones but using the word "snapshot" instead of "milestone".

        Comment


        • #5
          Thanks Dave. I'm not using Spring MVC, but I can do the equivalent using the JAX-RS (Jersey) ExceptionMapper provider.

          Have a good weekend,

          Jeff

          Comment


          • #6
            Hi Dave:

            I hate to ask, but is there any word on the M7 release? I'm getting a lot of pushback in deploying our beta app with a nightly build, or my own build. Using a milestone release is passable though...

            Thanks,

            Jeff

            Comment


            • #7
              I'm open to it, and I'm using Spring Security OAuth in production myself, even though I generally recommend waiting for a full release. I hadn't planned on an M7, but there are only 9 issues resolved since M6. What is it that you need from M7, specifically?

              Comment


              • #8
                M6 jar is missing classes from M6 examples

                Hi Dave:

                The problem I'm having with M6 is that apparently the jar brought down via Maven is missing some of the newer classes I need in order to address Spring Security's handling of error responses. For example, within the M6 code from github, these classes exists, but not in the jar I get from the repo. For example, in the OAuth 2 sparklr sample app config:

                Code:
                	<bean id="oauthAuthenticationEntryPoint" class="org.springframework.security.oauth2.provider.error.OAuth2AuthenticationEntryPoint">
                		<property name="realmName" value="sparklr2" />
                	</bean>
                
                	<bean id="oauthAccessDeniedHandler" class="org.springframework.security.oauth2.provider.error.OAuth2AccessDeniedHandler" />
                ...
                
                	<bean id="clientCredentialsTokenEndpointFilter" class="org.springframework.security.oauth2.provider.client.ClientCredentialsTokenEndpointFilter">
                		<property name="authenticationManager" ref="clientAuthenticationManager" />
                	</bean>
                In the jar I see only:

                Code:
                org/springframework/security/oauth2/provider/error/MediaTypeAwareAccessDeniedHandler.class
                org/springframework/security/oauth2/provider/error/DefaultProviderExceptionHandler.class
                org/springframework/security/oauth2/provider/error/ProviderExceptionHandler.class
                org/springframework/security/oauth2/provider/error/MediaTypeAwareAuthenticationEntryPoint.class
                org/springframework/security/oauth2/provider/client/ClientDetailsUserDetailsService.class
                org/springframework/security/oauth2/provider/client/ClientCredentialsTokenGranter.class
                So, I was hoping an M7 or RC1 Maven artifact might contain those classes. For the moment, I did a very kludgy thing and created my own package and copied the required class source code and went with that. So far so good, but I don't like it.

                Take it easy,

                Jeff

                Comment


                • #9
                  Those classes are simple enough that forking them is probably fine as an interim measure. I'm not really sure how github can give you a different download than Maven, unless you are downloading the master zip (point and click error?), but if you can shed any light on that it would make me feel better.

                  Comment


                  • #10
                    Hi Dave:

                    I just removed 1.0.0.M6 from ~/.m2/repository/org/springframework/security/oauth/spring-security-oauth2 to see if I had something corrupt in there. But, the newly downloaded jar is the same.

                    I have the following defined in my POM:

                    Code:
                        <spring.security.oauth.version>1.0.0.M6</spring.security.oauth.version>
                        ...
                    		<dependency>
                    			<groupId>org.springframework.security.oauth</groupId>
                    			<artifactId>spring-security-oauth2</artifactId>
                    			<version>${spring.security.oauth.version}</version>
                    		</dependency>
                    The sources I downloaded from github came in a tar.gz file named: SpringSource-spring-security-oauth-1.0.0.M6-11-gcad240a.tar.gz

                    Perhaps M6-11 is some time significantly past the M6 release, and what I'm getting via Maven? It could be a point and click error. On the other hand, it looks I need something beyond M6 then.

                    Thanks!

                    Jeff

                    Comment


                    • #11
                      I can see that simply adding those additional classes is not sufficient. I plumbed together some things and submitted a request with a long ago expired access token and got:

                      Code:
                      2012-03-20 15:50:12 DEBUG filter.OAuth2ProtectedResourceFilter - Token not found in headers. Trying request parameters.
                      2012-03-20 15:50:12 DEBUG cassandra.CassandraTokenStore - readAccessToken - For tokenValue: ad41945d-9bfa-4ee8-a564-d28ee468e1e9, returning token: null
                      2012-03-20 15:50:12 DEBUG error.DefaultProviderExceptionHandler - OAuth error.
                      error="invalid_token", error_description="Invalid access token: ad41945d-9bfa-4ee8-a564-d28ee468e1e9"
                      	at org.springframework.security.oauth2.provider.token.RandomValueTokenServices.loadAuthentication(RandomValueTokenServices.java:158)
                      	at org.springframework.security.oauth2.provider.filter.OAuth2ProtectedResourceFilter.doFilter(OAuth2ProtectedResourceFilter.java:47)
                      	at org.springframework.security.oauth2.provider.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:79)
                      	at org.springframework.security.oauth2.provider.filter.OAuth2ExceptionHandlerFilter.doFilter(OAuth2ExceptionHandlerFilter.java:57)
                      	at org.springframework.security.oauth2.provider.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:79)
                      	at org.springframework.security.oauth2.provider.filter.CompositeFilter.doFilter(CompositeFilter.java:59)
                      	at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:346)
                      	at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:259)
                      	at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1322)
                              ...
                      The class DefaultProviderExceptionHandler exists in the jar from Maven, but not in the M6-ish source I have. So, there must be some deletions and other things done post M6 that I need to incorporate.

                      An M7, RC1 or whatever would be helpful.

                      Cheers,

                      Jeff

                      Comment


                      • #12
                        The log extract looks clean. What's the problem, exactly? You didn't get a JSON response?

                        Comment


                        • #13
                          I got a JSON response, but my code based on OAuth2AccessDeniedHandler for serializing the response did not get invoked. I'm wondering if my kludged implementation has all of its wires connected.

                          Jeff

                          Comment


                          • #14
                            AccessDeiniedHandler is called by the ExceptionTranslationFilter if it catches an AccessDeniedException. Your exception looks like an OAuth2Exception, which is not an AccessDeniedException, but is an AuthenticationException. Where it is handled depends on where it is thrown, but I would expect it would be picked up by an AuthenticationEntryPoint. Does that make sense?

                            Comment


                            • #15
                              Hi Dave:

                              That does make sense. Back to the code. Thanks for the pointer!

                              Jeff

                              Comment

                              Working...
                              X