Describe the bug
I migrated to spring boot 3 and changed authorizeRequests to authorizeHttpRequests. But @PreAuthorize is not working in my RestController after migration.
I digged a little about why. When Using authorizeRequests, FilterSecurityInterceptor is configured in filterChain(which is deprecated now). But when using authorizeHttpRequests, AuthorizationFilter is in use on behalf of FilterSecurityInterceptor.
In AuthorizationFilter, AuthorizationDecision's granted field is false. So AccessDeniedException occur and AbstractPreAuthenticatedProcessingFilter's doAuthenticate() does not invoked.
SecurityConfig.java
@EnableMethodSecurity
@EnableWebSecurity(debug = true)
@Configuration
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.headers().frameOptions().disable().and()
.authorizeRequests().requestMatchers("/").permitAll().and() // @PreAuthorize works
// .authorizeHttpRequests().requestMatchers("/").permitAll().and() // @PreAuthorize does not works
.exceptionHandling().authenticationEntryPoint(http403ForbiddenEntryPoint());
return http.build();
}
@Bean
public AdminAuthenticationProvider adminAuthenticationProvider() {
return new AdminAuthenticationProvider();
}
@Bean
public AdminAuthenticationFilter adminAuthenticationFilter() {
return new AdminAuthenticationFilter(adminAuthenticationProvider(), -1);
}
@Bean
public AuthenticationEntryPoint http403ForbiddenEntryPoint() {
return new Http403ForbiddenEntryPoint();
}
}
SampleMethodSecurityController.java
@PreAuthorize("hasRole('ADMIN')")
@RestController
public class SampleMethodSecurityController {
@GetMapping("/health-check")
public String healthCheck(){
return "ok";
}
}
Sample you can see my example code in my github repo Also created pull request to be able to see only related changes.
Comment From: roon-replica
I realized reason of malfunction. Default request with no auth rule is denied as default in spring security 6. So I changed to permit all requests and partially applied @PreAuthorize on resources to protect. This works for me. Close issue myself.
Comment From: AnzhiZhang
I realized reason of malfunction. Default request with no auth rule is denied as default in spring security 6. So I changed to permit all requests and partially applied @PreAuthorize on resources to protect. This works for me. Close issue myself.
But is there a way to set use @PreAuthorize on a controller method, when set anyRequest have to be authenticated? One way is use requestMatchers().permitAll, but I do not want split my controller and permit in two places
Comment From: straurob
So I changed to permit all requests and partially applied @PreAuthorize on resources to protect. This works for me.
I don't have much experience with Spring Security, but this sounds like a flaw. The documentation explicity recommends using a "catch-all" approach with anyRequest().authenticated().
Is there no other way to resolve this problem?
Comment From: falconwoods
So I changed to permit all requests and partially applied @PreAuthorize on resources to protect. This works for me.
I don't have much experience with Spring Security, but this sounds like a flaw. The documentation explicity recommends using a "catch-all" approach with
anyRequest().authenticated().Is there no other way to resolve this problem?
Agreed, should have an approach to set default behavior as authenticated for all APIs and then use @PreAuthorize("permitAll()") to make some APIs public explicitly. Better than explicitly making some APIs protected.
Comment From: AnzhiZhang
Here is my solution:
import java.lang.annotation.Target;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Anonymous {
}
import java.util.function.Function;
import java.util.function.BiFunction;
import java.lang.annotation.Annotation;
import lombok.RequiredArgsConstructor;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.web.method.HandlerMethod;
@RequiredArgsConstructor
public class HandlerMethodAnnotationGetter<A extends Annotation> implements BiFunction<HttpServletRequest, Class<A>, A> {
private final Function<HttpServletRequest, HandlerMethod> handlerMethodGetter;
@Override
public A apply(HttpServletRequest request, Class<A> annotationType) {
// get handler method
HandlerMethod handlerMethod = handlerMethodGetter.apply(request);
// return annotation
if (handlerMethod != null) {
return handlerMethod.getMethodAnnotation(annotationType);
} else {
return null;
}
}
}
import java.util.Collection;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken;
public class AnonymousAuthenticationToken extends PreAuthenticatedAuthenticationToken {
public AnonymousAuthenticationToken(Object aPrincipal, Object aCredentials, Collection<? extends GrantedAuthority> anAuthorities) {
super(aPrincipal, aCredentials, anAuthorities);
}
}
import java.io.IOException;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.lang.NonNull;
import org.springframework.stereotype.Component;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.web.filter.OncePerRequestFilter;
import ***.HandlerMethodAnnotationGetter;
import ***.SecurityUtils;
@Component
@RequiredArgsConstructor
public class AnonymousFilter extends OncePerRequestFilter {
private final HandlerMethodAnnotationGetter<Anonymous> handlerMethodAnnotationGetter;
@Override
protected void doFilterInternal(
@NonNull HttpServletRequest request,
@NonNull HttpServletResponse response,
@NonNull FilterChain filterChain
) throws ServletException, IOException {
if (SecurityUtils.getAuthentication() == null) {
Anonymous anonymous = handlerMethodAnnotationGetter.apply(request, Anonymous.class);
if (anonymous != null) {
AnonymousAuthenticationToken authToken = new AnonymousAuthenticationToken(
"anonymousUser",
"",
AuthorityUtils.createAuthorityList("ROLE_ANONYMOUS")
);
authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityUtils.setAuthentication(authToken);
}
}
// continue to other filters
filterChain.doFilter(request, response);
}
}
Comment From: aminabromand
I do not understand, why this was closed. Ok, the workaround might work, but it is a workaround not a solution, isn't it?
Comment From: dreamstar-enterprises
I have the same problem. @preauthorize does not work on any of my services.