Spring security - @PreAuthorize not working

11,706

Solution 1

My collegues found the trick. The @EnableGlobalMethodSecurity(prePostEnabled = true) annotation must not be in the spring-security configuration class but in the Servlet configuration class.

@Configuration
@EnableWebMvc
@EnableSpringDataWebSupport
@EnableJpaRepositories
@EnableGlobalMethodSecurity(prePostEnabled = true)
@ComponentScan(basePackages = { "mypackage.spring.rest" }, excludeFilters = @Filter(type = FilterType.ANNOTATION, value = Configuration.class))
public class SpringRestConfiguration {

}

And it works !

Solution 2

TLDR: @EnableAspectJAutoProxy(proxyTargetClass = true) on WebConfig can be used instead.

The root of the problem may be that Spring doesn't generate controller's proxy classes - by default Spring wraps into proxies only those beans which are defined as interface and Spring IoC find their implementation. If your controllers are classes which don't implement/extend anything, CGLIB proxying has to be used (I recommend reading about Spring Proxying mechanisms), so proxy classes are generated and injected as controllers implementation - that's the place where Spring included an extra logic to respect @PreAuthorize and @PostAuthorize annotations conditions.

On Spring v5 (not Spring Boot), when @EnableGlobalMethodSecurity(prePostEnabled = true) is used on SecurityConfiguration only, it won't be picked up by WebConfig. Moving it to WebConfig will enable handling pre post annotations by Spring Security as well as it will switch on CGLIB proxy mechanism.

Personally, I recommend just adding @EnableAspectJAutoProxy(proxyTargetClass = true) on WebConfig and leaving @EnableGlobalMethodSecurity in SecurityConfig.

I have tested it on Spring v5 only, but due to documentation it should work the same on Spring v4.

Share:
11,706
Truche
Author by

Truche

Developer in Toulouse, France.

Updated on June 20, 2022

Comments

  • Truche
    Truche almost 2 years

    I am facing an issue in using the @PreAuthorize annotation. Even if my user does not own the asked roles, my secured methods are executed.

    My controller :

    @Controller
    @RequestMapping("/stats/distributions")
    public class DistributionStatsController {
    
        @PreAuthorize("hasAnyAuthority('AK_LOCAL_DIST_INT', 'AK_ADMIN')")
        @RequestMapping(method = RequestMethod.POST, consumes = "application/json; charset=utf-8", 
            produces = "application/json; charset=utf-8")
        public @ResponseBody List<DistributionStatsResource> filter(@RequestBody DistributionStatsResource resource,  
               @RequestParam(required = false, value = "documentId") Long documentId, 
               @RequestParam(required = false, value = "distStatus") EnumDistributionStatus distributionStatus, 
               Pageable pageable, HttpServletRequest request) {
        }
    }
    

    Here is my spring security configuration :

    @Configuration
    @EnableWebSecurity
    @EnableGlobalMethodSecurity(prePostEnabled = true)
    public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
    
        /** Defines the AuthenticationManager/providers. */
        @Override
        protected void configure(AuthenticationManagerBuilder auth) throws Exception {
            auth.authenticationProvider(preAuthenticatedAuthenticationProvider());
        }
    
        @Override
        public void configure(WebSecurity web) throws Exception {
            web.ignoring().antMatchers("/css/**", "/font/**", "/icones/**", "/img/**");
        }
    
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            // TODO Configure HTTP URLs and filters.
            http.authorizeRequests().antMatchers("/views/access401.html").permitAll().antMatchers("/views/admin/agent.html").hasAuthority("AK_ADMIN")
            .antMatchers("/views/admin/agentDetail.html").hasAuthority("AK_ADMIN").antMatchers("/views/admin/businesses.html")
            .hasAuthority("AK_ADMIN").antMatchers("/views/admin/distributors.html").hasAuthority("AK_ADMIN")
            .antMatchers("/views/admin/distributionReportList.html").hasAuthority("AK_ADMIN")
            .antMatchers("/views/documentEdition/documentDetail.html").hasAnyAuthority("AK_CENTRAL_DIST", "AK_LOCAL_DIST_INT", "AK_ADMIN")
    
            .antMatchers("/views/home/home.html").fullyAuthenticated().antMatchers("/views/distribution/distribution.html")
            .hasAnyAuthority("AK_LOCAL_DIST_INT", "AK_ADMIN").antMatchers("/views/distribution/distributionEdit.html")
            .hasAnyAuthority("AK_LOCAL_DIST_INT", "AK_ADMIN").antMatchers("/views/admin/types.html").hasAuthority("AK_ADMIN").and()
                .exceptionHandling().authenticationEntryPoint(unauthorizedEntryPoint()).and().addFilter(habileFilter()).csrf().disable(); // Disable CSRF
            // protection.
        }
    
        /** Gives an alias to the authenticationManager. */
        @Override
        @Bean(name = "authenticationManager")
        public AuthenticationManager authenticationManagerBean() throws Exception {
            return super.authenticationManagerBean();
        }
    
        /** A unauthorized entry point. */
        @Bean
        public AuthenticationEntryPoint unauthorizedEntryPoint() {
            return new ForbiddenEntryPoint();
        }
    
        /** The user details service used by the PreAuthenticatedAuthenticationProvider. */
        @Bean
        public AuthenticationUserDetailsService<PreAuthenticatedAuthenticationToken> myAuthenticationUserDetailsService() {
            return new NgwisAuthenticationUserDetailsService();
        }
    
        /** The PreAuthenticatedAuthenticationProvider. */
        @Bean
        public PreAuthenticatedAuthenticationProvider preAuthenticatedAuthenticationProvider() {
            PreAuthenticatedAuthenticationProvider pro = new PreAuthenticatedAuthenticationProvider();
            pro.setPreAuthenticatedUserDetailsService(myAuthenticationUserDetailsService());
            return pro;
        }
    
        // ---- Filters.
    
        /** Builds an Habile filter.
         *
         * @return the habile filter. */
        @Bean
        public RequestHeaderAuthenticationFilter habileFilter() throws Exception {
            NgwisRequestHeaderAuthenticationFilter filter = new NgwisRequestHeaderAuthenticationFilter();
            filter.setPrincipalRequestHeader("SM_USER");
            filter.setCredentialsRequestHeader(NgwisRequestHeaderAuthenticationFilter.HABILE_FILTER_NAME);
            filter.setAuthenticationManager(authenticationManager());
            return filter;
        }
    }
    

    (This class is referenced in my base configuration class)

    My RequestHeaderAuthenticationFilter class :

    public class NgwisRequestHeaderAuthenticationFilter extends RequestHeaderAuthenticationFilter {
    
        public static final String HABILE_FILTER_NAME = "HABILE";
    
        /** Pour mise à disposition des informations de sécurité */
        public static final String BEAN_SECURITIES = "com.airfrance.springsecurity.securities";
    
        private static final org.slf4j.Logger logger = LoggerFactory.getLogger(NgwisRequestHeaderAuthenticationFilter.class);
    
        // AK de l'utilisateur en fonction de ses profils
        private UserAccessKeys userAccessKeys = null;
    
        // Pour passer l'info au niveau de la config de spring security
        private String credentialsRequestHeader;
    
        @Inject
        private IAgentService agentService;
    
        @Inject
        private DozerBeanMapper mapper;
    
        /** Credentials aren't usually applicable, but if a {@code credentialsRequestHeader} is set, this will be read and used as
         * the credentials value. Otherwise a dummy value will be used. */
        @Override
        protected Object getPreAuthenticatedCredentials(HttpServletRequest request) {
            Collection<GrantedAuthority> tmp = new ArrayList<GrantedAuthority>();
            User user = new User(request.getRemoteUser().toUpperCase(), "none", false, false, false, false, tmp);
            if (credentialsRequestHeader != null) {
                if (credentialsRequestHeader.equalsIgnoreCase("HABILE")) {
                    try {
                        LdapBean ldBean = LdapBeanAccessor.getLdapBean(request);
                        if (ldBean != null) {
                            userAccessKeys = new UserAccessKeys(request, ldBean, agentService, mapper);
                            request.getSession().setAttribute(BEAN_SECURITIES, userAccessKeys);
                            List<String> auths = new ArrayList<String>();
                            for (GrantedAuthority auth : userAccessKeys.getAuthorities()) {
                                auths.add(auth.getAuthority());
                            }
                            logger.debug("User {} connected with authorities {}", userAccessKeys.getLogin(), StringUtils.join(auths, ", "));
                            user = new User(request.getRemoteUser().toUpperCase(), "none", true, true, true, true, userAccessKeys.getAuthorities());
                        }
                    } catch (NoLdapBeanInSessionException e) {
                        logger.error("Erreur lors de la connexion de {}", request.getRemoteUser().toUpperCase(), e);
                    } catch (NotProtectedGetLdapException e) {
                        logger.error("Erreur technique ", e);
                    }
                    if (userAccessKeys.getAgent() != null) {
                        return user;
                    } else {
                        return null;
                    }
                } else {
                    return request.getHeader(credentialsRequestHeader);
                }
            }
    
            return "N/A";
        }
    
        @Override
        public void setCredentialsRequestHeader(String credentialsRequestHeader) {
            Assert.hasText(credentialsRequestHeader, "credentialsRequestHeader must not be empty or null");
            this.credentialsRequestHeader = credentialsRequestHeader;
        }
    }
    

    I checked in this class we get the authorities of the logged user. Everything seems to be alright.

    When I run this code with a user with just a AK_CONSULT role, the method is executed and no 503 ERROR is fired.

    Thanks for help.