Grails 8 returning 500 error instead of 401 for an unauthenticated request
Maintainer thường phản hồi trong vòng 1 ngày
Chưa có ai nhận issue này.
Đánh giá
- Độ khó
- 3/5
- Thời gian dự kiến
- 1-2 ngày
- Mức phù hợp với người mới
- 55/100
- Loại issue
- Lỗi
- Độ rõ ràng
- Đặc tả rõ ràng
- Mức độ hoạt động
- Sôi nổi
- Công nghệ
- groovy
- Lĩnh vực
- authentication, security
Hướng nghiên cứu
Start with the reproduction repo linked in the issue (tprebs/grails-8-security-401-regression) and run runGrails8.sh to see the 500. The relevant code is the re-implemented FilterSecurityInterceptor and AccessDecisionVoter path in grails-spring-security-compat, where WebExpressionVoter.vote asserts on a null Authentication. Done means an unauthenticated request to a @Secured action returns 401 with no AssertionError, and the Grails 7 behaviour is matched.
Do mô hình lập chỉ mục viết ra từ nội dung của issue.
Mô tả
Expected Behavior
An unauthenticated request to a @Secured controller should be answered with a 401 Unauthorized. When no authentication is provided grails 8 is responding with a 500 error and an page. Previous versions of grails returned 401 Unauthorized
Actual Behaviour
On 8.0.0 the same request is answered with 500 Internal Server Error and an HTML error page, and the server log carries an AssertionError.
This happens when the filter chain legitimately contains no anonymous authentication filter, so the request reaches the authorisation interceptor with a null Authentication. That is ordinary, documented
configuration, the Spring Security Core plugin's filters.adoc describes composing a chain with
JOINED_FILTERS and filter names prefixed with -, and removing anonymousAuthenticationFilter is a
deliberate choice for a stateless API that must not grant anonymous identities:
grails.plugin.springsecurity.filterChain.chainMap = [
[
pattern: '/**',
filters: 'JOINED_FILTERS,-anonymousAuthenticationFilter,-exceptionTranslationFilter,' +
'-authenticationProcessingFilter,-securityContextPersistenceFilter,' +
'-rememberMeAuthenticationFilter'
],
]
Grails builds with no anonymous filter, as configured:
Creating filter chain: Ant [pattern='/**'],
[SecurityRequestHolderFilter, MutableLogoutFilter, RestAuthenticationFilter,
SecurityContextHolderAwareRequestFilter, RestTokenValidationFilter,
ExceptionTranslationFilter, FilterSecurityInterceptor]
An unauthenticated GET /secret then produces:
ERROR o.a.c.c.C.[.[.[.[dispatcherServletRegistration] : Servlet.service() for servlet
[dispatcherServletRegistration] threw exception [Filter execution threw an exception] with root cause
java.lang.AssertionError: authentication cannot be null. Expression: authentication. Values: authentication = null
at grails.plugin.springsecurity.web.access.expression.WebExpressionVoter.vote(WebExpressionVoter.groovy:47)
at grails.plugin.springsecurity.access.vote.AuthenticatedVetoableDecisionManager.decide(...)
at org.springframework.security.web.access.intercept.FilterSecurityInterceptor.doFilter(FilterSecurityInterceptor.groovy:54)
ERROR o.a.c.c.C.[Tomcat].[localhost] : Exception Processing [ErrorPage[errorCode=500, location=/error]]
Cause
Spring Security 7.1.1 removed FilterSecurityInterceptor and the AccessDecisionManager/AccessDecisionVoter
API. Grails 8 ships org.apache.grails:grails-spring-security-compat to re-implement those classes in Spring
Security's own package namespace, which is why the stack frame above reads FilterSecurityInterceptor.groovy
rather than Spring's Java original.
The re-implementation omits the null-Authentication guard. Spring Security's
AbstractSecurityInterceptor.authenticateIfRequired() begins:
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication == null) {
throw new AuthenticationCredentialsNotFoundException(
"An Authentication object was not found in the SecurityContext");
}
AuthenticationCredentialsNotFoundException extends AuthenticationException, so
ExceptionTranslationFilter catches it and the entry point returns 401. That is the path that works on
Grails 7.
In grails-spring-security-compat:8.0.0 there is no null guard, and the null Authentication is passed to the decision manager and reaches WebExpressionVoter, whose Groovy assert throws an AssertionError. That is an Error, not an AuthenticationException, so ExceptionTranslationFilter does not catch it and the request fails as a 500.
Steps To Reproduce
- Create a Grails 8.0.0 application with
grails-spring-securityandgrails-spring-security-rest. - Add one controller with
@Secured(['ROLE_USER']). - Set the catch-all chain in
grails-app/conf/application.groovy:
[pattern: '/**', filters: 'JOINED_FILTERS,-anonymousAuthenticationFilter'](the other exclusions above
are not required to trigger it; removing the anonymous filter is). - Request the secured action with no
Authorizationheader:curl -i http://localhost:8080/secret. - Observe
HTTP 500and an HTML error page. Expected401. - Check the server log for
java.lang.AssertionError: authentication cannot be nulloriginating in
WebExpressionVoter.vote. - Run the same application on Grails 7.2.3 and the request is answered with
401.
recreation available here https://github.com/tprebs/grails-8-security-401-regression
A minimal Grails application with one @Secured controller and two scripts:
./runGrails7.sh # passes - answered with 401
./runGrails8.sh # fails - answered with 500
Environment Information
- Grails 8.0.0
- Gradle 9.8.0,
- Java 21
- macOS
Example Application
https://github.com/tprebs/grails-8-security-401-regression
Version
8.0.0
- Ngôn ngữ chính
- Groovy
- Star
- 2.9k
- Fork
- 976
- Merge trung bình
- 21 giờ 14 phút
- Pull request đã merge (30 ngày)
- 174
Chuẩn bị môi trường
- Không có Dockerfile hay tệp Docker Compose
- Có mẫu pull request
- Đọc hướng dẫn đóng góp
Bắt đầu từ đâu
- Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
- Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
- Fork repository và làm thay đổi trên một nhánh.
- Mở pull request có tham chiếu số hiệu của issue.
Issue khác của apache/grails-core
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 74/100
apache/grails-core#16635 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 78/100
apache/grails-core#16289 · 5 bình luận ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 82/100
apache/grails-core#15764 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Update documentation for Java 25Có thể đã có người làm @ruthst00 đã nhận 8 ngày trước. Đang mởrelates-to: documentation weekly-action-item
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 68/100
apache/grails-core#15725 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Độ khó 4/5 3-5 ngày Mức phù hợp với người mới 18/100
apache/grails-core#16634 ·
Maintainer thường phản hồi trong vòng 1 ngày
Tất cả issue của apache/grails-core
Issue tương tự
-
area: network audit bug good first issue priority: P3 security severity: low type: denial-of-service
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 66/100
pierruno-labs/prowl#29 ·
-
RUSTSEC-2026-0348: Brotli certificate decompression can allocate up to 1 GiB before authenticationĐang mở
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 62/100
-
bug
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 72/100
Maintainer thường phản hồi trong vòng 1 ngày
-
Redact user-identity passwords in standard association logsCó thể đã có người làm @jdotc1 đã nhận hôm nay. Đang mở
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 70/100
pydicom/pynetdicom#1098 ·
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 78/100