11

I am adding Spring Security to one Spring project. The architecture of the system is REST and user can access to different resources.

I would like to give access to personal information to administrators and users that are owners of this information. I have started simple: filtering user profile like this:

In my service layer I wanted to use method annotations and include method parameters..

@PreAuthorize("hasRole('ROLE_ADMIN') or principal.userId == #id")
public Usuario getUser(int id) throws DAOException {
    ...
}

But this is not working at all. Any user can see all profiles (admins and all users also) when this URL is requested (Web layer):

@RequestMapping(value="/user/{uid}", method=RequestMethod.GET)
    public ModelAndView getUser(@PathVariable int uid) throws DAOException {
        userDAO = new UsuarioJPADAO();
        userService.setUsuarioDAO(userDAO);

    return new ModelAndView("user", "user", userService.getUser(uid));
}

Here is my security.xml

<?xml version="1.0" encoding="UTF-8"?>
<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">

<!-- Security Annotations -->
    <global-method-security 
        pre-post-annotations="enabled"/>

<http auto-config="true" use-expressions="true">
    <intercept-url pattern="/css/**" access="permitAll" />
    <intercept-url pattern="/images/**" access="permitAll" />
    <intercept-url pattern="/js/**" access="permitAll" />
    <intercept-url pattern="/favicon.ico" access="permitAll" />
    <intercept-url pattern="/login" access="permitAll" />

    <intercept-url pattern="/users" access="hasRole('ROLE_ADMIN')" />
    <intercept-url pattern="/users/page/*" access="hasRole('ROLE_ADMIN')" />
    <intercept-url pattern="/customers" access="hasRole('ROLE_ADMIN')" />
    <intercept-url pattern="/employees" access="hasRole('ROLE_ADMIN')" />

    <intercept-url pattern="/search/*" access="hasRole('ROLE_ADMIN')" />

    <intercept-url pattern="/*" access="hasAnyRole('ROLE_ADMIN, ROLE_EMPLOYEE, ROLE_PARTNER, ROLE_USER')" />
    <intercept-url pattern="/*/*" access="hasAnyRole('ROLE_USER, ROLE_ADMIN')" />
    <intercept-url pattern="/*/*/*" access="hasAnyRole('ROLE_USER, ROLE_ADMIN')" />
    <intercept-url pattern="/*/*/*/*" access="hasAnyRole('ROLE_USER, ROLE_ADMIN')" />
    <intercept-url pattern="/*/*/*/*/*" access="hasAnyRole('ROLE_USER, ROLE_ADMIN')" />
    <intercept-url pattern="/*/*/*/*/*/*" access="hasAnyRole('ROLE_USER, ROLE_ADMIN')" />
    <intercept-url pattern="/*/*/*/*/*/*/*" access="hasAnyRole('ROLE_USER, ROLE_ADMIN')" />
    <form-login login-page="/login" login-processing-url="/doLogin" 
                authentication-failure-url="/login?error"
                username-parameter="username" password-parameter="password"
                default-target-url="/default" />

    <logout invalidate-session="true" logout-success-url="/login?logout" logout-url="/logout"/>
</http>
<authentication-manager>
    <authentication-provider user-service-ref="UsuarioService">
    </authentication-provider>
</authentication-manager>    

I have checked Spring Security 3.1 book and apparently my configuration is as book suggests. I have read other Stack Overflow posts (here and here) but I had no luck.

Update: Added application-context.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:p="http://www.springframework.org/schema/p"       
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xmlns:security="http://www.springframework.org/schema/security"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:jee="http://www.springframework.org/schema/jee"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
       http://www.springframework.org/schema/aop 
       http://www.springframework.org/schema/aop/spring-aop-3.1.xsd
       http://www.springframework.org/schema/tx 
       http://www.springframework.org/schema/tx/spring-tx-3.1.xsd
       http://www.springframework.org/schema/mvc
       http://www.springframework.org/schema/mvc/spring-mvc-3.1.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context-3.1.xsd
       http://www.springframework.org/schema/security
       http://www.springframework.org/schema/security/spring-security-3.1.xsd">

    <context:annotation-config />

<context:component-scan base-package="com.pe.fs" />

<mvc:annotation-driven />

<mvc:resources mapping="/**" location="/" />   

<mvc:interceptors>
<bean class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor">
    <property name="paramName" value="lang" />
</bean>
</mvc:interceptors>

<!-- DataSource -->
<bean id="jpaDataSource" class="oracle.jdbc.pool.OracleDataSource"
    destroy-method="close" 
    p:driverType="oracle.jdbc.OracleDriver" 
    p:user="**********"
    p:password="**********"
    p:uRL="jdbc:oracle:thin:@localhost:1521:XE"
/>

<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
    <property name="persistenceXmlLocation" value="classpath*:META-INF/persistence.xml"></property>
    <property name="persistenceUnitName" value="freesunPU" />
    <property name="dataSource" ref="jpaDataSource" />
    <property name="jpaVendorAdapter">
        <bean class="org.springframework.orm.jpa.vendor.EclipseLinkJpaVendorAdapter">
            <property name="showSql" value="false" />
        </bean>
    </property>
    <property name="loadTimeWeaver">
        <bean class="org.springframework.instrument.classloading.InstrumentationLoadTimeWeaver"/>
    </property>
</bean>

<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager"
    p:entityManagerFactory-ref="entityManagerFactory" />

<tx:annotation-driven mode="aspectj"/>

<context:load-time-weaver aspectj-weaving="autodetect" />

Update: I have added spring-security-aspects to POM and no changes. Other changes suggested in answers have been tested with but annotations such @PreAuthorize are still not working. Cna this be a problem between contexts? Can be the usage of aspectJ the reason?

What am I doing wrong?

Community
  • 1
  • 1
Spacemonkey
  • 1,725
  • 3
  • 20
  • 44
  • Your `Usuario getUser(int id)` method is defined in some interface? By default this kind of annotations may not work (JDK proxieas are used to add authorization checks in runtime and they can target only interface methods) – Maksym Demidas Mar 12 '13 at 12:29
  • @MaksymDemidas No. My UsuarioService implements UserDetailsService. I am also working with my own UsuarioDetails class that extends my domain Usuario and implements UserDetails – Spacemonkey Mar 12 '13 at 12:31
  • wft man? :) change ""/*/*/*/*/*"" to "/**" – Yura Oct 19 '16 at 12:56
  • @Yura I don't maintain this application anymore but thanks for the advice! – Spacemonkey Oct 19 '16 at 12:59

3 Answers3

9

Finally I found solution. In SO I found some usefull answers. See here and here.

I moved global-method-security to application-context.xml which is the context of my services.

<security:global-method-security 
    mode="aspectj"
    secured-annotations="enabled"
    jsr250-annotations="disabled"
    pre-post-annotations="enabled"/>

Where mode="aspectj" as Javadoc says:

...can be used to specify that AspectJ should be used instead of the default Spring AOP. If set, secured classes must be woven with the AnnotationSecurityAspect from the spring-security-aspects module.

Of course, I have added to POM spring-security-aspects:

<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-aspects</artifactId>
    <version>3.1.3.RELEASE</version>
</dependency>
Community
  • 1
  • 1
Spacemonkey
  • 1,725
  • 3
  • 20
  • 44
0

Add new interface:

public interface UserService extends UserDetailsService {
    Usuario getUser(int id) throws DAOException
}

Implement it in your user service and try again. Spring will be able add requested authorization checks using JDK proxies.

As another option you can configure Spring to use some more heavyweight libraries like Javassist or even AspectJ.In this case interface will be not necessary.

EDIT. Make sure that global-method-security is declared in the same spring context with your user service bean.

Maksym Demidas
  • 7,707
  • 1
  • 29
  • 36
  • Is not working when adding the interface.. Should I annotate the method as I did in my post, not in the interface, isn't? – Spacemonkey Mar 12 '13 at 12:52
  • Yes, annotations normally are applied on implementation side – Maksym Demidas Mar 12 '13 at 13:09
  • 1
    Please show your spring xml. You can have two spring contexts (for beans and for Spring MVC). Are you sure that your global-method-security elemnt is defined in the same context with your user service? – Maksym Demidas Mar 12 '13 at 13:13
  • I have added spring xml. The fact is that I am not declaring beans here. It is true that I have another context: Spring MVC. And global-method-security is declared there. Maybe I should move the global annotation to spring xml. – Spacemonkey Mar 12 '13 at 14:22
  • You can declare it in both contexts to be sure that your annotations will work everywhere – Maksym Demidas Mar 12 '13 at 14:40
  • Not working either.. I declared in both xml files but no changes. Where can be the problem? – Spacemonkey Mar 12 '13 at 14:55
  • I see that you use aspectj. I do not have any experiance with it so I cann't help here. Can you successfully apply some another annotation to your service layer (for example @Transactional or @Cacheable)? – Maksym Demidas Mar 12 '13 at 15:36
  • I use `@Transactional` in my data access layer (I just read that it is better to annotate service layer and not DAO layer with `@Transactional`). I don't know if is working because I don't know how to monitorize the effects. Of course my application is getting data properly from database, so I suppose is working. I am not using `@Cacheable` – Spacemonkey Mar 12 '13 at 15:46
  • About `@Cacheable` annotations, I don't cache my DAO objects because I need to inject my PersistenceUnit and I am not working with a Java EE 5 compatible server. As I use JPA, I cannot inject PersistenceUnit and I cannot declare my beans in configuration files. – Spacemonkey Mar 12 '13 at 15:51
  • I mean you can't use any annotations at all? Or just @PreAuthorize? – Maksym Demidas Mar 12 '13 at 16:01
  • I really don't know. I would like to make work `@PreAuthorize`. Then, if necessary I would use `@Transactional` or others.. – Spacemonkey Mar 12 '13 at 16:03
  • Maybe you can disable aspectj and try default JDK proxies? – Maksym Demidas Mar 12 '13 at 16:14
  • The point is that I am using JPA and I need LAZY weaving in order to avoid memory overflows when loading in memory linked objects from database. I just checked and does not work. I get this error `Must start with Java agent to use InstrumentationLoadTimeWeaver.` because I need aspectj-weaving. – Spacemonkey Mar 12 '13 at 16:25
0

The alternative way to make it work is to add the following code in your security.xml

<intercept-url pattern="/user/**" access="hasRole('ROLE_ADMIN')" />

It will ensure that only admin can access the resources starting with pattern /user/.

Arjun Thakur
  • 351
  • 5
  • 13