Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Spring security - @PreAuthorize not working

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.

like image 995
Truche Avatar asked Apr 15 '15 06:04

Truche


2 Answers

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 !

like image 153
Truche Avatar answered Oct 13 '22 20:10

Truche


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.

like image 30
Krzysztof Skrzynecki Avatar answered Oct 13 '22 21:10

Krzysztof Skrzynecki