Monday, November 20, 2023

How to secure an URL using hasRole() in Spring Security? Is it enough to hide sections of JSP to protect URL? Example?

One of the Spring security question asked to me on a recent interview was about is it enough to hide portions of JSP to protect a URL from unauthorized access in Spring security? First of all I didn't understand the question, so I ask him to clarify a bit more. He explained to me that there are different roles in his application e.g. DEVELOPER, ADMIN, TRADER, OPERATION and each role can only see the functionalities based upon their role. For example, an ADMIN has a right to add or remove new users into the system, while DEVELOPER can only see test order, but OPERATION can see all orders. 

Similarly, only users with role TRADER can place orders. Since all functionalities were exposed using links and buttons on same JSP page, his question was that is it enough to just hide portions of JSP?

Well, the answer is No. Even though Spring Security provides a good tag library which allow you to conditionally render view based upon user role, it just not enough to protect the URL. 

For example, suppose administration functionalities are provided by /admin URL but a person with DEVELOPER access cannot see it but nothing stops it from directly going to /admin URL in his browser's address bar and accessing admin related functionality. 

In order to protect admin functionality we need to first secure /admin URL in spring security configuration and then we need to hide them as well in JSP page. 

How to secure an URL using hasRole() in Spring Security? 

You can secure a URL based upon a role in Spring security configuration using Spring Expression language or SpEL. It provides several useful methods e.g. hasRole() or isAuthenticated() to protect an URL based upon conditions.

For example, to secure /admin URL in your Java web application, you can declare following line in your Spring security configuration file:

<http use-expressions = "true">
<intercept-url pattern="/admin/**" access="hasRole('ROLE_ADMIN')" />
...
</http>

similarly to protect /order URL you can restrict access to that to traders by adding following expression:

<http use-expressions = "true">
<intercept-url pattern="/admin/**" access="hasRole('ROLE_ADMIN')" />
<intercept-url pattern="/order/**" access="hasRole('ROLE_TRADER')" />
...
</http>

The hasRole() method will return true if current user has granted authorities ROLE_ADMIN, otherwise it will return false, which means no one other than user with correct authorization or authorities can access the protected URL. 

Btw, if you are using Java configuration to configure Spring security in your web application, then you can use the antMathchers() method to specify conditions as shown below:

@Configuration
@EnableWebSecurity
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
            http
                .authorizeRequests()
                .antMatchers("/admin/**").hasRole("ADMIN")
                .antMatchers("/order/**").hasRole("TRADER");
        }
        ...
}


Once you have this configuration in place, no one can access the protected URL without proper access. By the way, there is still one more thing you need to sort out, the condition specified in the access attribute of <authorize> tag in your JSP page. 

Btw, if you notice we haven't specified ROLE_ADMIN or ROLE_TRADER, instead just ADMIN or TRADER, this is because from Spring Security Spring 4 onwards any role is automatically prefixes with ROLE_ string.

Anyway, If you remember, I have told you that you can use access attribute of <security:authorize> tag of Spring security tag library to show or hide portion of JSP. For example

<security:authorize access="hasRole('ROLE_ADMIN')>
.. admin links only visible to user with role = ROLE_ADMIN
</security:authorize>

Now, if you realize, the condition to access /admin URLs are specified in both JSP pages and Spring security configuration file, which is not ideal. The conditions should just be in one place and preferably in the security configuration file. 

Fortunately, the authorize tag has another url attribute which you can use to leverage the security constraint declare for an URL. Unlike the access attribute where the security constraint is explicitly declare the url attribute indirectly refers to the security constraint for a given URL pattern. 

Since you have already declared security conditions for /admin in the configuration e.g. on XML file or on Java code, you can use the url attribute like below to hide the admin links for users which doesn't have ROLE_ADMIN authorities:

<security:authorize url="/admin">
.. admin links only visible to user with role = ROLE_ADMIN
</security:authorize>

Now, the expression is only configured in one place on spring security configuration file, its easy to change the condition and there is no duplication as well. 

How to secure an URL using hasRole() in Spring Security?



That's all about how to protect an URL in Spring security. As I have said, it's not enough to just hide the respective URL using authorize tag, you also need to secure it in the spring security configuration to protect it from unauthorized access. 

Though, you should also hide it in JSP and use url attribute instead of access attribute to keep the security constraint in one place. Unlike access attribute, the url attribute refers to security constraint of URL indirectly. 

You are also free to use powerful Spring expression language to specify sophisticated conditions using hasRole(), isAuthenticated() and other methods.

No comments:

Post a Comment