Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Spring Security - separate configuration for REST API and other URLs

I have two sets of URLs - one set is REST API, the second one - is the pretty ordinary website. I want to apply different security rules for REST API so that the user/script that occasionally invoked REST API will be answered either with 401 code (basic auth would be fine) - or just 403.

So I want to allow access to REST API for:

  • the user that has been logged in (for javascript on the site page that invokes REST API thus shares the same session).
  • some script that invokes REST API with basic auth credentials in the WWW-Authenticate header.

At the moment I'm trying to figure out what configuration will make spring "understand" what I want. I came up with the following config:

<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" />

    <!-- REST API -->
    <http pattern="/rest/*" use-expressions="true">
        <http-basic />
        <intercept-url pattern="/*" access="isAuthenticated()" />
    </http>

    <!-- Site -->
    <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="/**" 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>

    <authentication-manager>
        <authentication-provider>
            <user-service>
                <user name="admin" password="2" authorities="ROLE_ADMIN,ROLE_USER" />
                <user name="alex" password="1" authorities="ROLE_USER" />
            </user-service>
        </authentication-provider>
    </authentication-manager>
</beans:beans>

Unfortunately not only basic authentication does not work with this config but there is no 403 responses for requests made by anonymous user - application answers with redirect 302 Found which I'd like to disallow for REST API urls.

I tried to add custom entry point for REST API:

<beans:bean id="ep403" class="org.springframework.security.web.authentication.Http403ForbiddenEntryPoint"/>


<!-- REST API -->
<http pattern="/rest/*" entry-point-ref="ep403" use-expressions="true">
    <intercept-url pattern="/*" access="hasRole('ROLE_USER')" />
    <http-basic />
</http>

but this does not work either.

To sum what I do want to get:

  • Allow authorized user to access REST API (authorization facility should take into an account the user session in cookies).
  • Allow script to access REST API if it specifies correct authentication token and does not specifies session in cookies.

UPDATE

After digging into Spring Security internals I found the code that decides whether to deny certain request or not. This is a so called "Access Decision Voters", basically all of them applied to certain request and if one access decision voter in a chain votes to deny access to certain resource the request is ultimately denied.

Hence the original problem might be solved by introducing special access decision voter that behaves in the following way: it tries to extract associated role from the session (if any present in the request) and proceeds to authorization step with this role; if no session present it tries to authenticate the user against the credentials in WWW-Authenticate header and then proceeds to authorization step with the roles associated with the given user.

like image 407
Alex Avatar asked Mar 20 '13 14:03

Alex


People also ask

What happens when we just add the Spring boot starter security dependency to your REST API?

If a Spring Boot Security dependency is added on the classpath, Spring Boot application automatically requires the Basic Authentication for all HTTP Endpoints. The Endpoint “/” and “/home” does not require any authentication. All other Endpoints require authentication.


2 Answers

Reposting as a solution as bluish@ suggested :) Hope this will help someone.

I found a simple workaround for that. I just mapped one rest controller to two distinct sets of URLs and assigned each set to the distinct auth handler in the spring security config:

<!-- 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>

It looks like a good approach to that problem because in future you may want to extend either "stateful"-user or "stateless"-script REST API with the unique URLs specific to either user or script use cases.

In my case this may happen when UX changes will require REST API to provide some new methods designed to implement a particular UI scenario and some script scenario may require some URLs added solely to support certain client script-to-server interaction.

like image 188
Alex Avatar answered Sep 30 '22 17:09

Alex


You can use interceptors to check URLs having /rest/ pattern.

<mvc:interceptors>
        <mvc:interceptor>
            <mvc:mapping path="/rest/**"/>
            <bean class="com.your.class.name.Interceptor></bean>
        </mvc:interceptor>
</mvc:interceptors>

For this add the below line in the header part of the XML, i.e.

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:mvc="http://www.springframework.org/schema/mvc"

Now in the Interceptor class check whatever you need to and this class should implement Spring HandlerInterceptor.

public class Interceptor implements HandlerInterceptor{

    @Override  
    public boolean preHandle(HttpServletRequest request,  
                             HttpServletResponse response,  
                             Object handler) throws Exception {
       //do what you need to check when the request arrives
       //do authentications here
       //return true if success
       //else false
    }

    @Override
    public void postHandle(HttpServletRequest request,
                           HttpServletResponse response, 
                           Object handler,
                           ModelAndView modelAndView) throws Exception {

    }

    @Override
    public void afterCompletion(HttpServletRequest request,
                                HttpServletResponse response, 
                                Object handler, 
                                Exception ex) throws Exception {    
    }

}

Read this for further details about the methods. Also can see this

like image 38
Anubhab Avatar answered Sep 30 '22 17:09

Anubhab