54

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,446
  • 6
  • 36
  • 60

14 Answers14

93

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,988
  • 9
  • 25
  • 38
Eric Zürcher
  • 949
  • 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
  • Worked for me, although the annotation has since become deprecated, this is the currently non-deprecated one: @EnableMethodSecurity(prePostEnabled = true) – Niels Van Steen Apr 25 '23 at 10:53
  • it works for me, with GraphQL, thanks – Henry Xiloj Herrera Jul 14 '23 at 18:49
39

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
  • 143,923
  • 11
  • 122
  • 252
  • 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 {
Apostolos
  • 10,033
  • 5
  • 24
  • 39
stanicmail
  • 743
  • 7
  • 19
15

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
  • 359
  • 3
  • 13
8

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
  • 364
  • 3
  • 6
7

For those coming here in 2023 and beyond, you must add the @EnableMethodSecurity annotation to your configuration class. It's also worth noting that Spring Boot also uses Auto Configuration as of 2023. An example security configuration is included below.

@Configuration
@EnableWebSecurity
@EnableMethodSecurity // Add this
public class SecurityConfig {
    // Add your configuration Beans here
}

And your controller should look something like this:

@RestController
@RequestMapping("/users")
public class UserController
{
    // Replace this with your service or other means to fetch resources
    private final UserRepository userRepository;

    public UserController(UserRepository userRepository)
    {
        this.userRepository = userRepository;
    }

    // Note: I am using a scope called "users.read" below, but you can use
    // ROLE_<role_name> if you are using roles instead.
    @GetMapping(value = "/list")
    @PreAuthorize("hasAuthority('users.read')")
    public List<UserDto> listUsers()
    {
        return userRepository.findAllBy();
    }
}
Keenan
  • 179
  • 1
  • 9
4

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')")

Apostolos
  • 10,033
  • 5
  • 24
  • 39
Minakshi Jha
  • 101
  • 1
  • 2
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{

}
Apostolos
  • 10,033
  • 5
  • 24
  • 39
3

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

Georgi Peev
  • 912
  • 1
  • 14
  • 25
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
  • 15,429
  • 19
  • 72
  • 74
1

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

Apostolos
  • 10,033
  • 5
  • 24
  • 39
1

I changed the controller menthod from private to public and it is working.

Apostolos
  • 10,033
  • 5
  • 24
  • 39
0

June 2023 : For those who are still struggling:
SecurityConfig

 @Bean
   public UserDetailsService userDetailsService(PasswordEncoder passwordEncoder){
           
           UserDetails user = User.withUsername("user")
                .password(passwordEncoder.encode("user123"))
                .roles("USER")
                .build();

           return new InMemoryUserDetailsManager(admin,user);
    }

Your Controller should be like:

@GetMapping("/getAll")
    @PreAuthorize("hasAuthority('ROLE_USER')")
    public List<Student> findAllStudents() {
        return studentServiceI.findAll();
    }
ViKi
  • 1
0

I had a static keyword in the method, by removing this, the @Preauthorize worked.

Jek
  • 1
  • Your answer could be improved with additional supporting information. Please [edit] to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Jun 26 '23 at 12:01