Reputation: 2459
I am working on a Gradle/ Java 1.8/ Spring Boot, Spring Integration, Spring Batch, Spring Data Rest project (which I inherited).
Here's the dependencies from: build.gradle
// Spring Boot
compile("org.springframework.boot:spring-boot-starter-ws")
compile("org.springframework.boot:spring-boot-starter-web")
compile("org.springframework.boot:spring-boot-starter-integration")
compile("org.springframework.boot:spring-boot-starter-actuator")
compile("org.springframework.boot:spring-boot-starter-security")
compile("org.springframework.boot:spring-boot-starter-data-jpa")
compile("org.springframework.boot:spring-boot-starter-batch")
// Spring integration
compile("org.springframework.integration:spring-integration-core")
compile("org.springframework.integration:spring-integration-ws")
compile("org.springframework.integration:spring-integration-jpa")
compile("org.springframework.integration:spring-integration-sftp")
// Spring batch
compile("org.springframework.batch:spring-batch-core")
compile("org.springframework.batch:spring-batch-integration")
// Spring Data REST
compile("org.springframework.data:spring-data-rest-webmvc")
Looking through the application's DEBUG log, I see two threads:
i. [http-nio-8080-exec-1],
ii [[http-nio-8080-exec-2]
The first one invoking AnonymousAuthenticationToken and is failing @15:02:56.731 in:
org.springframework.security.authentication.AnonymousAuthenticationToken
several milli-seconds later @15:02:56.747 the second one is successful in: org.springframework.security.authentication.UsernamePasswordAuthenticationToken
E.g.
15:02:56.731 [http-nio-8080-exec-1] DEBUG o.s.s.w.a.i.FilterSecurityInterceptor - Previously Authenticated: org.springframework.security.authentication.AnonymousAuthenticationToken@9055e4a6: Principal: anonymousUser; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails@957e: RemoteIpAddress: 127.0.0.1; SessionId: null; Granted Authorities: ROLE_ANONYMOUS
15:02:56.747 [http-nio-8080-exec-2] DEBUG o.s.s.w.a.i.FilterSecurityInterceptor - Previously Authenticated: org.springframework.security.authentication.UsernamePasswordAuthenticationToken@fb774aa3: Principal: org.springframework.security.core.userdetails.User@36ebcb: Username: user; Password: [PROTECTED]; Enabled: true; AccountNonExpired: true; credentialsNonExpired: true; AccountNonLocked: true; Granted Authorities: ROLE_ADMIN,ROLE_USER; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails@957e: RemoteIpAddress: 127.0.0.1; SessionId: null; Granted Authorities: ROLE_ADMIN, ROLE_USER
15:02:56.731 [http-nio-8080-exec-1] DEBUG o.s.s.access.vote.AffirmativeBased - Voter: org.springframework.security.web.access.expression.WebExpressionVoter@9324be9, returned: -1
15:02:56.747 [http-nio-8080-exec-2] DEBUG o.s.s.access.vote.AffirmativeBased - Voter: org.springframework.security.web.access.expression.WebExpressionVoter@9324be9, returned: 1
15:02:56.731 [http-nio-8080-exec-1] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Returning cached instance of singleton bean 'org.springframework.integration.internalMessagingAnnotationPostProcessor'
15:02:56.747 [http-nio-8080-exec-2] DEBUG o.s.s.w.a.i.FilterSecurityInterceptor - Authorization successful
The failed method: AnonymousAuthenticationToken is generating exceptions.
Snippet from application.yml:
# Authentication for "user" to the HTTP endpoints
security:
user:
password: blahblahblah
Does Spring Book try the AnonymousAuthenticationToken code first, because the 'user' field in application.yml is blank?
When I changed the 'user' field to a real value, my gradle build got errors in sftp validation:
11:57:25.869 [DEBUG] [TestEventLogger] Caused by: 11:57:25.869 [DEBUG] [TestEventLogger] mapping values are not allowed here 11:57:25.869 [DEBUG] [TestEventLogger] in 'reader', line 33, column 13: 11:57:25.880 [DEBUG] [TestEventLogger] password: blahblahblah 11:57:24.166 [DEBUG] [TestEventLogger] at org.yaml.snakeyaml.scanner.ScannerImpl.fetchValue(ScannerImpl.java:871) 11:57:24.167 [DEBUG] [TestEventLogger] at org.yaml.snakeyaml.scanner.ScannerImpl.fetchMoreTokens(ScannerImpl.java:360) 11:57:24.167 [DEBUG] [TestEventLogger] at org.yaml.snakeyaml.scanner.ScannerImpl.checkToken(ScannerImpl.java:226) 11:57:24.167 [DEBUG] [TestEventLogger] at org.yaml.snakeyaml.parser.ParserImpl$ParseBlockMappingKey.produce(ParserImpl.java:558) 11:57:24.168 [DEBUG] [TestEventLogger] at org.yaml.snakeyaml.parser.ParserImpl.peekEvent(ParserImpl.java:158) 11:57:24.168 [DEBUG] [TestEventLogger] at org.yaml.snakeyaml.parser.ParserImpl.checkEvent(ParserImpl.java:143) 11:57:24.169 [DEBUG] [TestEventLogger] at org.yaml.snakeyaml.composer.Composer.composeMappingNode(Composer.java:226) 11:57:24.169 [DEBUG] [TestEventLogger] at org.yaml.snakeyaml.composer.Composer.composeNode(Composer.java:155) 11:57:24.169 [DEBUG] [TestEventLogger] at org.yaml.snakeyaml.composer.Composer.composeMappingNode(Composer.java:231) 11:57:24.171 [DEBUG] [TestEventLogger] at org.yaml.snakeyaml.composer.Composer.composeNode(Composer.java:155) 11:57:24.171 [DEBUG] [TestEventLogger] at org.yaml.snakeyaml.composer.Composer.composeDocument(Composer.java:122) 11:57:24.175 [DEBUG] [TestEventLogger] at org.yaml.snakeyaml.composer.Composer.getNode(Composer.java:84) 11:57:24.176 [DEBUG] [TestEventLogger] at org.yaml.snakeyaml.constructor.BaseConstructor.getData(BaseConstructor.java:104) 11:57:24.176 [DEBUG] [TestEventLogger] at org.yaml.snakeyaml.Yaml$1.next(Yaml.java:502) 11:57:24.176 [DEBUG] [TestEventLogger] at org.springframework.beans.factory.config.YamlProcessor.process(YamlProcessor.java:160) 11:57:24.176 [DEBUG] [TestEventLogger] at org.springframework.beans.factory.config.YamlProcessor.process(YamlProcessor.java:138) 11:57:24.176 [DEBUG] [TestEventLogger] at org.springframework.boot.env.YamlPropertySourceLoader$Processor.process(YamlPropertySourceLoader.java:100) 11:57:24.181 [DEBUG] [TestEventLogger] at org.springframework.boot.env.YamlPropertySourceLoader.load(YamlPropertySourceLoader.java:57) 11:57:24.183 [QUIET] [system.out] 11:57:24.183 [DEBUG] [org.gradle.api.internal.tasks.testing.junit.JUnitTestClassProcessor] Executing test class com.distributedfinance.mbi.payment.repository.ExternalAccountTransferRepositorySpecIT 11:57:24.184 [DEBUG] [TestEventLogger] at org.springframework.boot.env.PropertySourcesLoader.load(PropertySourcesLoader.java:126) 11:57:24.184 [DEBUG] [TestEventLogger] at org.springframework.boot.context.config.ConfigFileApplicationListener$Loader.loadIntoGroup(ConfigFileApplicationListener.java:381) 11:57:24.184 [DEBUG] [TestEventLogger] at org.springframework.boot.context.config.ConfigFileApplicationListener$Loader.load(ConfigFileApplicationListener.java:369) 11:57:24.184 [DEBUG] [TestEventLogger] at org.springframework.boot.context.config.ConfigFileApplicationListener$Loader.load(ConfigFileApplicationListener.java:339) 11:57:24.184 [DEBUG] [TestEventLogger] at org.springframework.boot.context.config.ConfigFileApplicationListener.addPropertySources(ConfigFileApplicationListener.java:174) 11:57:24.185 [DEBUG] [TestEventLogger] at org.springframework.boot.context.config.ConfigFileApplicationListener.onApplicationEnvironmentPreparedEvent(ConfigFileApplicationListener.java:144) 11:57:24.185 [DEBUG] [TestEventLogger] at org.springframework.boot.context.config.ConfigFileApplicationListener.onApplicationEnvironmentPreparedEvent(ConfigFileApplicationListener.java:137) 11:57:24.185 [DEBUG] [TestEventLogger] at org.springframework.boot.context.config.ConfigFileApplicationListener.onApplicationEvent(ConfigFileApplicationListener.java:126) 11:57:24.185 [DEBUG] [TestEventLogger] at org.springframework.context.event.SimpleApplicationEventMulticaster.invokeListener(SimpleApplicationEventMulticaster.java:151) 11:57:24.185 [DEBUG] [TestEventLogger] at org.springframework.context.event.SimpleApplicationEventMulticaster.multicastEvent(SimpleApplicationEventMulticaster.java:128) 11:57:24.186 [DEBUG] [TestEventLogger] at org.springframework.boot.context.event.EventPublishingRunListener.publishEvent(EventPublishingRunListener.java:100) 11:57:24.186 [DEBUG] [TestEventLogger] at org.springframework.boot.context.event.EventPublishingRunListener.environmentPrepared(EventPublishingRunListener.java:59) 11:57:24.186 [DEBUG] [TestEventLogger] at org.springframework.boot.SpringApplication.run(SpringApplication.java:285) 11:57:24.186 [DEBUG] [TestEventLogger] at org.springframework.boot.test.SpringApplicationContextLoader.loadContext(SpringApplicationContextLoader.java:103) 11:57:24.186 [DEBUG] [TestEventLogger] at org.springframework.test.context.DefaultCacheAwareContextLoaderDelegate.loadContextInternal(DefaultCacheAwareContextLoaderDelegate.java:68) 11:57:24.187 [DEBUG] [TestEventLogger] at org.springframework.test.context.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:86) 11:57:24.187 [DEBUG] [TestEventLogger] ... 24 more
Upvotes: 2
Views: 23339
Reputation: 9585
Definitely the error is that the AnonymousAuthenticationFilter is being executed before the UsernamePasswordAuthenticationFilter (check the default order here)
I owe you the explanation about why is configured like that in your case but I will answer this one.
- Why is the app. configured to try both methods of authentication?
The anonymous filter must execute after of any attempts of authentication and what it does is put an AuthenticationToken in the SecurityContextHolder if there is no one already (this means that all authentication attemps failed).
Spring does this to have a more consistent way of deal with the Authentication token, otherwise it should ask if SecurityContextHolder.getAuthentication != null and this will complicate the authorization mechanism, for example ask for the role of the user.
From Spring documentation:
Note that there is no real conceptual difference between a user who is “anonymously authenticated” and an unauthenticated user. Spring Security's anonymous authentication just gives you a more convenient way to configure your access-control attributes. Calls to servlet API calls such as getCallerPrincipal, for example, will still return null even though there is actually an anonymous authentication object in the SecurityContextHolder.
Classes can be authored more robustly if they know the SecurityContextHolder always contains an Authentication object, and never null.
What is happening is that you are making a request to an endpoint that requires authentication without being authenticated, but you include your credentials in your request.
If you want a stateless/sessionless backend and you are are going to send your credentials with each request, you have to configure your UsernamePasswordAuthenticationFilter to execute in all the requests (/) or all your secure endpoints (/secure/). The BasicAuthenticationFilter is not needed but you can let the filter.
A tip: you can use JWT for stateless authentication.
UPDATE:
For customize your security configuration in Spring boot
The default security configuration is implemented in SecurityAutoConfiguration and in the classes imported from there (SpringBootWebSecurityConfiguration for web security and AuthenticationManagerConfiguration for authentication configuration which is also relevant in non-web applications). To switch off the Boot default configuration completely in a web application you can add a bean with @EnableWebSecurity. To customize it you normally use external properties and beans of type WebSecurityConfigurerAdapter (e.g. to add form-based login). There are several secure applications in the Spring Boot samples to get you started with common use cases.
Upvotes: 8