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.
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 !
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With