Pitel
Pitel

Reputation: 5393

@PreAuthorize(permitAll) still requires authentication

I have the following example method in my Repository (with @RepositoryRestResource annotation):

@Override
@PreAuthorize("permitAll")
@PostAuthorize("permitAll")
public Iterable<User> findAll();

But I'm still getting 401 Unauthorized, event when I add those permitAll annotation to whole Repository interface.

I got this as my WebSecurityConfigurerAdapter:

@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
@Configuration
class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(final HttpSecurity http) throws Exception {
        http.authorizeRequests().anyRequest().fullyAuthenticated().and().httpBasic().and().csrf().disable();
    }
}

I suppose this takes precedence over those method annotations, bu I don't know how to fix this.

Upvotes: 28

Views: 14848

Answers (2)

LagSeeing
LagSeeing

Reputation: 73

I think you can use custom annotation to achieve it.

Just like this

a custom annotation:

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface PermitAllAndUnauthenticated {
}

a SecurityChain to permitAll:

@Configuration
@Slf4j
public class PermitAllAndUnauthenticatedAnnotationWebSecurityAutoConfiguration {

    @Bean
    @Order(SecurityProperties.BASIC_AUTH_ORDER - 10)
    SecurityFilterChain permitAllAndUnauthenticatedAnnotationSecurityFilterChain(
        final HttpSecurity http, final RequestMappingHandlerMapping requestMappingHandlerMapping) throws Exception {
        http.securityMatcher(request -> {
            try {
                final var handlerExecutionChain = requestMappingHandlerMapping.getHandler(request);
                if (handlerExecutionChain == null) {
                    return false;
                }
                final var handler = handlerExecutionChain.getHandler();
                if (!(handler instanceof final HandlerMethod handlerMethod)) {
                    return false;
                }
                final var annotation =
                    AnnotationUtils.findMergedAnnotationOnMethodOrClass(handlerMethod.getMethod(), null,
                        PermitAllAndUnauthenticated.class);
                return annotation != null;
            } catch (final Exception e) {
                throw new RuntimeException(e);
            }
        });
        http.authorizeHttpRequests((requests) -> requests.anyRequest()
            .permitAll());
        http.csrf(AbstractHttpConfigurer::disable);
        return http.build();
    }
}

the AnnotationUtils is just a method to find annotation on both method and class (you can use your own implement)

mine is like this

@Slf4j
public final class AnnotationUtils {

    private static final Cache<AnnotationKey, Annotation> ANNOTATION_CACHE = Caffeine.newBuilder()
        .build();
    private static final Annotation NO_ANNOTATION = () -> Annotation.class;

    @Nullable
    public static <A extends Annotation> A findMergedAnnotationOnMethodOrClass(
        final Method method, @Nullable final Class<?> targetClass, final Class<A> annotationClass) {
        if (method.getDeclaringClass() == Object.class) {
            return null;
        }

        final var cacheKey = new AnnotationKey(new MethodClassKey(method, targetClass), annotationClass);
        final var annotation = ANNOTATION_CACHE.get(cacheKey, k -> {
            final var specificMethod = AopUtils.getMostSpecificMethod(method, targetClass);
            var annotationDef = AnnotatedElementUtils.findMergedAnnotation(specificMethod, annotationClass);
            if (annotationDef != null) {
                return annotationDef;
            }

            annotationDef =
                AnnotatedElementUtils.findMergedAnnotation(specificMethod.getDeclaringClass(), annotationClass);
            return annotationDef != null
                ? annotationDef
                : NO_ANNOTATION;

        });
        //noinspection unchecked
        return annotation == NO_ANNOTATION
            ? null
            : (A)annotation;
    }

    private record AnnotationKey(MethodClassKey methodClassKey, Class<? extends Annotation> annotationType) {
    }

}

Then you can use @PermitAllAndUnauthenticated annotate the class or method you need to be public.

Upvotes: 0

Leon
Leon

Reputation: 12481

Method security is applied after the web security filter.

Since you have anyRequest().fullyAuthenticated() in your configuration, your findAll method will never be hit. anyRequest().fullyAuthenticated() means that all attempts to access a web endpoint that does no have have some from of full user authentication on it will fail.

From the JavaDoc

Specify that URLs are allowed by users who have authenticated and were not "remembered".

You will need to add an additional path in your web security, some like.

protected void configure(final HttpSecurity http) throws Exception {
    http.authorizeRequests()
            .anyRequest().fullyAuthenticated()
            .antMatchers(HttpMethod.GET, '/somePath').permitAll()
         .and()
            .httpBasic()
         .and()
            .csrf().disable();
}

Upvotes: 20

Related Questions