Grails 8 returning 500 error instead of 401 for an unauthenticated request
Mantenedores costumam responder em até 1 dia
Avaliação
- Dificuldade
- 3/5
- Tempo estimado
- 1-2 dias
- Facilidade para iniciantes
- 55/100
- Tipo de issue
- Bug
- Clareza
- Claramente especificada
- Status de atividade
- Ativa
- Stack de tecnologia
- groovy
- Domínio
- authentication, security
Direção de pesquisa
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.
Escrita pelo modelo de indexação a partir do texto da issue.
Descrição
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
- Linguagem predominante
- Groovy
- Estrelas
- 2.9k
- Forks
- 978
- Merge médio
- 1d 2h
- PRs com merge (30d)
- 183
Preparar o ambiente
- Sem Dockerfile nem arquivo Docker Compose
- Tem um modelo de pull request
- Ler o guia de contribuição
Primeiros passos
- Leia a issue inteira e depois o guia de contribuição do projeto.
- Comente na issue dizendo que vai assumir — evita que duas pessoas façam o mesmo trabalho.
- Faça um fork do repositório e trabalhe em uma branch.
- Abra um pull request que referencie o número da issue.
Mais de apache/grails-core
-
Dificuldade 2/5 1-3 horas Facilidade para iniciantes 74/100
apache/grails-core#16635 ·
Mantenedores costumam responder em até 1 dia
-
Dificuldade 2/5 1-3 horas Facilidade para iniciantes 78/100
apache/grails-core#16289 · 5 comentários ·
Mantenedores costumam responder em até 1 dia
-
Dificuldade 2/5 1-3 horas Facilidade para iniciantes 82/100
apache/grails-core#15764 ·
Mantenedores costumam responder em até 1 dia
-
Update documentation for Java 25Talvez já em andamento @ruthst00 assumiu há 10 dias. Abertarelates-to: documentation weekly-action-item
Dificuldade 2/5 1-3 horas Facilidade para iniciantes 68/100
apache/grails-core#15725 ·
Mantenedores costumam responder em até 1 dia
-
Dificuldade 3/5 1-2 dias Facilidade para iniciantes 40/100
apache/grails-core#16660 ·
Mantenedores costumam responder em até 1 dia
Todas as issues de apache/grails-core
Issues semelhantes
-
enhancement frontend
Dificuldade 2/5 1-3 horas Facilidade para iniciantes 83/100
Albertinesilva/backend-engineering-journey-java-springboot#46 ·
-
Snyk: vulnerabilities in jackson-databind 2.21.5Talvez já em andamento @harrywu23 assumiu hoje. Abertabug
Dificuldade 1/5 1-3 horas Facilidade para iniciantes 80/100
SOFTENG-310-Group-4/Project-A1-310-Group-4#115 ·
Mantenedores costumam responder em até 1 dia
-
Dificuldade 2/5 1-3 horas Facilidade para iniciantes 73/100
Mantenedores costumam responder em até 1 dia
-
bug domain: backend priority: p1 Stellar Wave
Dificuldade 2/5 1-3 horas Facilidade para iniciantes 78/100
StellarState/SS-backend#626 · 2 comentários ·
Mantenedores costumam responder em até 1 dia
-
A2A audit timestamps end in +00:00Z (invalid ISO 8601)Talvez já em andamento @O1sumitkumar assumiu hoje. Abertabug good first issue help wanted python
Dificuldade 2/5 1-3 horas Facilidade para iniciantes 78/100
Mantenedores costumam responder em até 1 dia