Announcement Announcement Module
Collapse
No announcement yet.
Securing REST with OAuth2: Authentication object was not found in the SecurityContext Page Title Module
Move Remove Collapse
X
Conversation Detail Module
Collapse
  • Filter
  • Time
  • Show
Clear All
new posts

  • Securing REST with OAuth2: Authentication object was not found in the SecurityContext

    I have an existing REST API (Apache CXF), which I am trying to secure using oAuth2. I'm going to have to re-write the API in Spring anyway (I need ACL and new endpoints), but initially I just want to secure one endpoint: /services/rest/version

    I've read the wiki, and the oAuth2 spec, and I'm trying to adapt the sparklr2 example to what I want.

    I've got a form-based login.jsp page, and a AccountController linked to a /register endpoint, where I can create new users and log them in and out. It's backed by a jdbc store.

    This is how I've secured my REST endpoint:

    Code:
    <http pattern="/services/rest/**" create-session="never"
    		entry-point-ref="oauthAuthenticationEntryPoint"
    		access-decision-manager-ref="accessDecisionManager"
    		xmlns="http://www.springframework.org/schema/security">
    		<anonymous enabled="false" />
    		<intercept-url pattern="/services/rest/version" access="ROLE_USER,SCOPE_READ" 
    			/>
    		<custom-filter ref="resourceServerFilter" before="PRE_AUTH_FILTER" />
    		<access-denied-handler ref="oauthAccessDeniedHandler" />
    	</http>
    I noticed some odd behavior: I expected that /services/rest/version would give me an error if I viewed it in my browser, but if I first login using the form, it doesn't give me an error, it gives me the version number. If I'm not loged in, it does give me an error:

    Code:
    <oauth>
    <error_description>
    An Authentication object was not found in the SecurityContext
    </error_description>
    <error>unauthorized</error>
    </oauth>
    I wasn't sure if this was right or not, because of course my browser isn't a oAuth client. So I tried to adapt some of the tests in tonr. I copied ServerRunning wholesale, and for TestAuthorizationCodeGrant I made some modifications. When I tried the tests,
    Code:
    testCannotConnectWithoutToken()
    and
    Code:
    testAttemptedTokenAcquisitionWithNoRedirect()
    both pass. The third test fails. My version is:

    Code:
    @Test
        public void testTokenAcquisitionWithCorrectContext() throws Exception {
    
            MultiValueMap<String, String> form = new LinkedMultiValueMap<String, String>();
            form.add("j_username", "[email protected]");
            form.add("j_password", "password");
            HttpHeaders response = serverRunning.postForHeaders("/TSSKeyManagementService/login.do", form);
            String cookie = response.getFirst("Set-Cookie");
    
            HttpHeaders headers = new HttpHeaders();
            headers.set("Cookie", cookie);
             headers.setAccept(Collections.singletonList(MediaType.ALL));
    //        headers.setAccept(MediaType.parseMediaTypes("image/png,image/*;q=0.8,*/*;q=0.5"));
    
            String location = serverRunning.getForRedirect("/TSSKeyManagementService/services/rest/version", headers);
            location = authenticateAndApprove(location);
    
            assertTrue("Redirect location should be to the original photo URL: " + location, location.contains("version"));
            HttpStatus status = serverRunning.getStatusCode(location, headers);
            assertEquals((double)HttpStatus.OK.value(), (double)status.value(), (double)0);
        }
    It fails at the line
    Code:
    String location = serverRunning.getForRedirect("/TSSKeyManagementService/services/rest/version", headers);
    because the headers of the response it's getting are just
    Code:
    {Server=[Apache-Coyote/1.1], Date=[Thu, 29 Nov 2012 05:22:04 GMT], Content-Type=[application/xml], Content-Length=[20]}
    There's no location in that. The body is also null. So the test expects to be redirected to the login page, but it isn't. Just like in the browser? I suppose that the server should request un-authenticated requests to /services/rest/version to /oauth/authorize?

    My point is: What have I done wrong ? Where might I have done something wrong? Please guide me o wise members of the forum.

    I've tried to avoid spamming this post with all my source files. If you know of something helpful, please tell me, I'm happy to add it, I'm really new to SpringSecurity, so I'm not quite sure what's relevent.

  • #2
    If your UI and API endpoints are in the same Spring Security filter chain they will pick up each others session-based authentication by default. I think you need create-session="stateless" in your API chain. In case you actually meant to use "never" you can define an independent security-context-repository and inject that into the chain as well (top level in the <http/> element).

    Comment


    • #3
      Ah ha! That makes perfect sense. Thanks for explaining that. I switched to stateless for the API and now I'm getting a proper 401 Unauthorized error when I run the test, and the same message as I see in the browser when I'm logged out.

      But I'm still not getting a redirect. I did a diff to double-check my spring-servelet.xml was the same, and it is, excluding where I've switched to using JDBC stores. Really! Attachment

      My understanding of the flow (informed by the test case) was:
      • Request for (protected) /services/rest/version
      • If not oAuth autheticated, redirect to /oauth/authorize
      • /oauth/authorize protected by BASIC or form Spring Security, redirects to /login.jsp
      • Correct login forwards to /oauth/authorize again
      • Correct authorization gives client oAuth auth, redirects to /oauth/token
      • With token, client can then access /services/rest/version

      Is that not the case? Will unauthorised access simply result in an error, and it's up to the client to start the request to /oauth/authorize?

      Thanks!
      Attached Files

      Comment


      • #4
        From the tonr/sparklr example, I would assume that "/services" in your post is similar to the resource server sparklr. In your client, you would essentially define a resource for your corresponding resource server. Here is the corresponding example from tonr:

        <oauth:resource id="sparklr" type="authorization_code" client-id="tonr" client-secret="secret"
        access-token-uri="${accessTokenUri}" user-authorization-uri="${userAuthorizationUri}" scope="read" />

        Then you would associate an OAuthRestTemplate with this resource like so:
        <property name="sparklrRestTemplate">
        <oauth:rest-template resource="sparklr" />
        </property>

        Internally, the oauth rest template should see that when you try to access the resource server, you dont have a valid token and would initiate the authorization flow to /oauth/authorize.

        Comment


        • #5
          Thanks palapura for the explanation (Yes, /services is where my "photo" equivalent API lives). As I understand now, if I'm not using SpringSecuirty oAuth client-side, I shouldn't expect to get redirects. I should get Authentication errors from the server, which the client-side library should handle. Some configuration options I've set should tell the client library where to find the token and authorization endpoints.

          However, my clients probably can't use SpringSecurity, not being Java. Maybe the desktop client could (it is currently Java), although I'd be welcome to advice from anyone who has tried.

          Comment


          • #6
            Originally posted by eve View Post
            Will unauthorised access simply result in an error, and it's up to the client to start the request to /oauth/authorize?
            Correct. Even if you're not using Spring on the client it should still handle that.

            Comment

            Working...
            X