Spring security - @PreAuthorize not working
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.
Comments
-
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 no503 ERROR
is fired.Thanks for help.