48

I'm trying to define access rules at method-level but it's not working what so ever.

SecurityConfiguration

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
    @Override
    public void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication().
                withUser("user").password("user").roles("USER").and().
                withUser("admin").password("admin").roles("ADMIN");
    }
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .authorizeRequests()
                .antMatchers("/v2/**").authenticated()
                .and()
                .httpBasic()
                .realmName("Secure api")
                .and()
                .csrf()
                .disable();
    }
}

ExampleController

@EnableAutoConfiguration
@RestController
@RequestMapping({"/v2/"})
public class ExampleController {
    @PreAuthorize("hasAuthority('ROLE_ADMIN')")
    @RequestMapping(value = "/home", method = RequestMethod.GET)
    String home() {
        return "Hello World";
    }
}

Whenever I try to access /v2/home using user:user it executes just fine, shouldn't it give me an Access Denied error due to 'user' not having ROLE_ADMIN?

I'm actually thinking of ditching access rules at method-level and stick to http() ant rules, but I have to know why it's not working for me.

prettyvoid
  • 3,184
  • 6
  • 31
  • 54

11 Answers11

79

You have to add @EnableGlobalMethodSecurity(prePostEnabled = true) in your WebSecurityConfig.

You can find it here: http://www.baeldung.com/spring-security-expressions-basic

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
rollstuhlfahrer
  • 3,888
  • 9
  • 23
  • 37
Eric Zürcher
  • 809
  • 1
  • 6
  • 3
  • 4
    Also per previous commenter if putting pre/post on a controller method that does not implement an interface also add annotation property `proxyTargetClass = true` to `EnableGlobalMethodSecurity` – GameSalutes Jul 17 '18 at 03:24
  • 1
    Does not work for me unfortunately (see https://stackoverflow.com/questions/53125388/preauthorize-not-working-with-prepostenabled-true). – Stefan Falk Nov 02 '18 at 20:31
38

A common problem with using PrePost annotations on controllers is that Spring method security is based on Spring AOP, which is by default implemented with JDK proxies.

That means that it works fine on the service layer which is injected in controller layer as interfaces, but it is ignored on controller layer because controller generally do not implement interfaces.

The following is just my opinion:

  • prefered way: move the pre post annotation on service layer
  • if you cannot (or do not want to), try to have your controller implement an interface containing all the annotated methods
  • as a last way, use proxy-target-class=true
Serge Ballesta
  • 136,215
  • 10
  • 111
  • 230
  • 4
    Thanks for the answer. I've tried using the annotation on a `CrudRepository` interface and it worked fine. Having an interface to each controller is kinda silly in my opinion as an interface isn't really necessary. `proxy-target-class=true` didn't make a difference, the annotations still doesn't work on controllers, however it caused a crash (while set to true) when having the annotation inside the repository interface (Cannot subclass com.sun.proxy). Anyway I think I'm going to stick to rules in the security config, and maybe use PreAuth on some of my repositories. – prettyvoid Sep 08 '15 at 07:45
  • 2
    One other thing that I noticed, I've seen examples where the @PreAuthorize was used in a `class` instead of an `interface` and it's supposed to be working.. I wonder why it works for them but not for me. – prettyvoid Sep 08 '15 at 09:29
  • 1
    Enable Aop in application context – Mradul Pandey Jun 02 '16 at 14:11
  • Adding the interface fixed it for me, but another project doesn't have interfaces, and the controller methods are protected. There must be some other configuration that they're using that I'm not. – Kieveli Jun 02 '16 at 17:29
  • I am facing the same issue but opposite. @PreAuthorize works normal on controller layer but not on service layer. Do you know why? – An Nguyen Jul 14 '16 at 14:54
  • 1
    I had weird problems using @PreAuthorize on Controller: some applications works well and some applications don't. – Dherik Jan 26 '18 at 14:58
  • 2
    has this been changed by now? Because the prePost annotations work absolutely fine on my controllers without implementing any interface or allowing target class proxying – Wecherowski Aug 29 '20 at 15:20
17

put @EnableGlobalMethodSecurity(prePostEnabled = true) into MvcConfig class (extends WebMvcConfigurerAdapter) instead of (extends WebSecurityConfigurerAdapter).

Like below example:-

@EnableGlobalMethodSecurity(prePostEnabled = true)
public class MvcConfiguration extends WebMvcConfigurerAdapter {
stanicmail
  • 723
  • 6
  • 18
14

I had a similar problem and the following solved it:

1) I had to make my method public (i.e. make your method home() public)

2) I have to use hasRole instead of hasAuthority

Horowitzathome
  • 319
  • 3
  • 11
7

There are two different ways to use this, one is to prefix and one is not. And you maybe change @PreAuthorize("hasAuthority('ROLE_ADMIN')") to @PreAuthorize("hasAuthority('ADMIN')") will be ok.

next is @PreAuthorize source code.

private String defaultRolePrefix = "ROLE_";
public final boolean hasAuthority(String authority) {
    return hasAnyAuthority(authority);
}

public final boolean hasAnyAuthority(String... authorities) {
    return hasAnyAuthorityName(null, authorities);
}

public final boolean hasRole(String role) {
    return hasAnyRole(role);
}

public final boolean hasAnyRole(String... roles) {
    return hasAnyAuthorityName(defaultRolePrefix, roles);
}
beautifulcode
  • 344
  • 3
  • 5
3

For making it working on controller layer. I had to put @EnableAspectJAutoProxy on my configuration class. Example :

@Configuration
@EnableWebMvc
@EnableAspectJAutoProxy
@ComponentScan(basePackages = { "com.abc.fraud.ts.userservices.web.controller" })
public class WebConfig extends WebMvcConfigurerAdapter{

}
3

My Controller`s methods were private they need to be public.

Georgi Peev
  • 642
  • 1
  • 11
  • 19
  • i 'm trying this for past one month , everything is correct , expect this public one ..hell of a learning. Finally found the problem. thanks @georgi Peev – anavaras lamurep Jun 24 '21 at 21:31
3

I was facing the same issue while trying to authorize AD Group of user these steps worked for me

  1. @EnableGlobalMethodSecurity(prePostEnabled = true) on class which is annotated with @EnableWebSecurity

  2. Created custom(GroupUserDetails) UserDetails which will have userName and authorities and return the instance of GroupUserDetails from custome UserDetailsService

  3. Annotate controller method with @PreAuthorize("hasAuthority('Group-Name')")

1

If you have a xml context file for your security beans and a separate one for your web/servlet context, than you als need to add:

<security:global-method-security pre-post-annotations="enabled"/>

to your web-context.xml / servlet context. Its not enough to just add it in the security context xml.

Its not inherited in child contexts.

HTH

Chris
  • 14,919
  • 18
  • 71
  • 74
1

your request method is protected by default. So they can scan it. Set it public!!!

1

I change the controller menthod from private to public .it's work .you can try it .