Announcement Announcement Module
Collapse
No announcement yet.
Apply distinct authorization schemes to REST API URLs Page Title Module
Move Remove Collapse
X
Conversation Detail Module
Collapse
  • Filter
  • Time
  • Show
Clear All
new posts

  • Apply distinct authorization schemes to REST API URLs

    This is a duplicate of my question posted to stackoverflow a day ago.

    Basically the question is what is the elegant configuration solution (if any) to
    * Allow authorized user to access REST API (authorization facility should take into an account existing user session in cookies).
    * Allow unauthorized user to access REST API if the user specifies correct basic authentication token in WWW-Authenticate header and does not specifies session in cookies.

    I wouldn't write custom decision voter and looks like it is the only way to solve the problem.

    Ultimately I came up with the following config, but it certainly does not work:
    <!-- Defines custom security policy for REST API -->
    <beans:bean id="nonRedirectingAccessDeniedHandler" class="org.springframework.security.web.access.Acc essDeniedHandlerImpl"/>
    <beans:bean id="forbiddenEntryPoint" class="org.springframework.security.web.authentica tion.Http403ForbiddenEntryPoint"/>

    <!-- REST API -->
    <http pattern="/rest/**" use-expressions="true" entry-point-ref="forbiddenEntryPoint">
    <http-basic/>
    <access-denied-handler ref="nonRedirectingAccessDeniedHandler"/>
    <intercept-url pattern="/**" access="isAuthenticated()" />
    </http>

  • #2
    If I understand correctly the problem you are trying to solve is to ensure that a redirect happens when the user is not logged in within the browser and return a 401 or 403 as a response when acting as a rest service? If so, you can remove the pattern="/rest/**" and take a look at the DelegatingAuthenticationEntryPoint.

    Code:
    <http use-expressions="true" entry-point-ref="entryPoint">
        <http-basic/>
        <intercept-url pattern="/**" access="isAuthenticated()" />
    </http>
    
    <beans:bean id="redirectingEntryPoint"
          class="org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint">
       <!-- this is where it sends for the login page...you must create this page -->
       <beans:property name="loginFormUrl" value="/login"/> 
    <beans:bean>
    <beans:bean id="forbiddenEntryPoint"
          class="org.springframework.security.web.authentication.Http403ForbiddenEntryPoint"/>
    
    <beans:bean id="entryPoint"
          class="org.springframework.security.web.authentication.DelegatingAuthenticationEntryPoint">
        <beans:constructor-arg>
            <beans:map>
                <beans:entry key="/rest/**" value-ref="forbiddenEntryPoint" />
            </beans:map>
        </beans:constructor-arg>
        <beans:property name="defaultEntryPoint" ref="redirectingEntryPoint"/>
     </bean>
    Last edited by Rob Winch; Mar 21st, 2013, 12:05 PM. Reason: formatting

    Comment


    • #3
      Originally posted by Rob Winch View Post
      If I understand correctly the problem you are trying to solve is to ensure that a redirect happens when the user is not logged in within the browser and return a 401 or 403 as a response when acting as a rest service? If so, you can remove the pattern="/rest/**" and take a look at the DelegatingAuthenticationEntryPoint.
      Thank you for your answer.
      I tried to use the proposed approach and end up with the following config:

      Code:
      <beans:beans xmlns="http://www.springframework.org/schema/security"
                   xmlns:beans="http://www.springframework.org/schema/beans"
                   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 pattern="/" security="none" />
          <http pattern="/static/**" security="none" />
      
          <!-- Defines custom security policy for REST API -->
          <beans:bean id="nonRedirectingAccessDeniedHandler" class="org.springframework.security.web.access.AccessDeniedHandlerImpl"/>
          <beans:bean id="forbiddenEntryPoint" class="org.springframework.security.web.authentication.Http403ForbiddenEntryPoint"/>
      
          <beans:bean id="redirectingEntryPoint"
                      class="org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint">
              <beans:property name="loginFormUrl" value="/login.html"/>
          </beans:bean>
      
          <beans:bean id="entryPoint"
                      class="org.springframework.security.web.authentication.DelegatingAuthenticationEntryPoint">
              <beans:constructor-arg>
                  <beans:map>
                      <beans:entry key="/rest/**" value-ref="forbiddenEntryPoint" />
                  </beans:map>
              </beans:constructor-arg>
              <beans:property name="defaultEntryPoint" ref="redirectingEntryPoint"/>
          </beans:bean>
      
          <http access-denied-page="/WEB-INF/views/errors/403.jsp" use-expressions="true">
              <intercept-url pattern="/index.html" access="hasRole('ROLE_USER') or hasRole('ROLE_ANONYMOUS')" />
              <intercept-url pattern="/login.html" access="hasRole('ROLE_USER') or hasRole('ROLE_ANONYMOUS')" />
              <intercept-url pattern="/admin/*" access="hasRole('ROLE_ADMIN')" />
              <intercept-url pattern="/profile/**" access="hasRole('ROLE_USER')" />
              <!--<intercept-url pattern="/**" access="hasRole('ROLE_USER')" />-->
              <form-login login-page="/login.html"
                          default-target-url="/index.html"
                          authentication-failure-url="/login.html?error=1" />
      
              <logout logout-url="/logout.do" logout-success-url="/index.html" />
      
              <anonymous username="guest" granted-authority="ROLE_ANONYMOUS" />
              <remember-me />
      
              <http-basic/>
              <intercept-url pattern="/rest/**" access="isAuthenticated()" />
          </http>
      
          <authentication-manager>
              <authentication-provider>
                  <user-service>
                      <user name="admin" password="1" authorities="ROLE_ADMIN,ROLE_USER" />
                      <user name="alex" password="2" authorities="ROLE_USER" />
                  </user-service>
              </authentication-provider>
          </authentication-manager>
      </beans:beans>
      I highly doubt that I got your idea quite right because I believe that http-basic tag can not be mixed with the others.

      Nevertheless I tried to test an application with this config and got the following misterious exception:

      Code:
      Caused by: 
      java.lang.NullPointerException
      	at org.springframework.expression.spel.ast.SpelNodeImpl.<init>(SpelNodeImpl.java:50)
      	at org.springframework.expression.spel.ast.Operator.<init>(Operator.java:32)
      	at org.springframework.expression.spel.ast.OpDivide.<init>(OpDivide.java:34)
      	at org.springframework.expression.spel.standard.InternalSpelExpressionParser.eatProductExpression(InternalSpelExpressionParser.java:258)

      Comment


      • #4
        When did you get this error? Can you post the logs just prior to the error? Can you please post the entire stacktrace?

        Comment


        • #5
          Sure.

          I've created gist with full stacktrace and some logs: https://gist.github.com/avshabanov/ed88130bdaf940563f72
          Unfortunately this forum does not support messages that exceed 10K symbols limit.
          Last edited by alex.j; Mar 22nd, 2013, 04:03 AM.

          Comment


          • #6
            I guess the problem is how key for forbidden entry point is defined. Try to make explicit initialization:

            Code:
            <beans:bean id="entryPoint" class="org.springframework.security.web.authentication.DelegatingAuthenticationEntryPoint">
                <beans:constructor-arg>
                    <beans:map>
                        <beans:entry value-ref="forbiddenEntryPoint">
                            <beans:key>
            
                                <beans:bean class="org.springframework.security.web.util.AntPathRequestMatcher">
                                    <beans:constructor-arg value="/rest/**"/>
                                </beans:bean>
                            </beans:key>
                        </beans:entry>
                    </beans:map>
                </beans:constructor-arg>
                <beans:property name="defaultEntryPoint" ref="redirectingEntryPoint"/>
            </beans:bean>

            Comment


            • #7
              Thank you green_trutle....I think you are correct. I mitakenly thought that RequestMatcherEditor would convert the request. Sorry for the poor memory and not double checking on this.

              Comment


              • #8
                Thanks for pointing to the error in configuration. Unfortunately basic auth does not work with the proposed config (unauthorized users receive 302 Redirect).

                Comment


                • #9
                  Originally posted by alex.j View Post
                  Thanks for pointing to the error in configuration. Unfortunately basic auth does not work with the proposed config (unauthorized users receive 302 Redirect).
                  I'm not sure but maybe you need to specify your delegating entry point on http element?
                  Code:
                  <http entry-point-ref="entryPoint" ...
                  Otherwise i don't know what entry point will be picked up by <http> since you have several of them in config.

                  Comment


                  • #10
                    Actually i'm solving similar problem so maybe someone can advise how to solve it (i'm sorry for posting the question in this thread).

                    So i have
                    1) /rest/* - Rest api, Roles: ROLE_USER, ROLE_ADMIN
                    2) /admin/* - admin pages, Roles: ROLE_ADMIN
                    3) /login - unsecured form login

                    There are some REST calls on admin pages (GET,POST). It means if user enter to admin's area through login page he needs to enter credentials for REST Basic auth every time he query REST resources. I would like to avoid it.

                    Here is my thoughts about it:

                    1) put both config in one <http> element with pattern "/**" (now they have several <http> sections)
                    2) disable "stateless" sessions ???
                    3) create delegating entry point:
                    /rest/* -> basic auth entry point
                    /admin/* -> login form entry point
                    default -> login form entry point

                    Are my expectations correct?

                    Comment


                    • #11
                      Originally posted by green_trutle View Post
                      Actually i'm solving similar problem so maybe someone can advise how to solve it ...
                      As for me I come to believe there is no simple solution for my problem and I found a very simple workaround:
                      Let me remind that basically I have two distinct kind of users - scripts (or say external application) that may access REST API and I'd like to authenticate these users via basic auth. The other users are just ordinary site users that surf site via browser and use REST API indirectly via jscript embedded into the site pages. For these users I want REST API security to authorize the users via session like "ordinary" site pages do.

                      So I just mapped my REST API controller to two distinct sets of urls:
                      /rest/stateless/** - for scripts, and I protected this with basic auth.
                      /rest/stateful/** - for ordinary users.
                      Code:
                      <!-- Defines custom security policy for Stateful REST API -->
                      <beans:bean id="nonRedirectingAccessDeniedHandler" class="org.springframework.security.web.access.AccessDeniedHandlerImpl"/>
                      <beans:bean id="forbiddenEntryPoint" class="org.springframework.security.web.authentication.Http403ForbiddenEntryPoint"/>
                      
                      <!-- Stateful REST API -->
                      <http pattern="/rest/stateful/**" use-expressions="true" entry-point-ref="forbiddenEntryPoint">
                          <access-denied-handler ref="nonRedirectingAccessDeniedHandler"/>
                          <intercept-url pattern="/rest/stateful/**" access="isAuthenticated()" />
                      </http>
                      
                      <!-- Stateless REST API -->
                      <http pattern="/rest/stateless/**" use-expressions="true" create-session="stateless">
                          <http-basic/>
                          <intercept-url pattern="/rest/stateless/**" access="isAuthenticated()" />
                      </http>
                      See update 2 to my question on stackoverflow: http://stackoverflow.com/questions/1...and-other-urls

                      Comment


                      • #12
                        I still think you was very close with your previous configuration: keep one <http> and use delegating entry point, you just forgot to specify entry point on http

                        Comment


                        • #13
                          Originally posted by green_trutle View Post
                          I still think you was very close with your previous configuration: keep one <http> and use delegating entry point, you just forgot to specify entry point on http
                          I tried to add entry-point-ref to http block, and, well, despite some minor issues it works almost as expected.
                          Thank you so much!

                          Comment


                          • #14
                            Originally posted by alex.j View Post
                            I tried to add entry-point-ref to http block, and, well, despite some minor issues it works almost as expected.
                            Thank you so much!
                            Great! I'm glad it helped.

                            Comment


                            • #15
                              There are some REST calls on admin pages (GET,POST). It means if user enter to admin's area through login page he needs to enter credentials for REST Basic auth every time he query REST resources. I would like to avoid it.
                              I may guess that you need to have only isAuthenticated() configuration in the http section of your security config, so on this layer all the authorized users will have access to REST API, and then you can annotate your REST controller's method with the security annotations. If my understanding is correct each unauthorized call will result in 403 in such a case.

                              I didn't tried this yet and I'm only guessing.

                              Comment

                              Working...
                              X