dbl001
dbl001

Reputation: 2459

Spring Security: AnonymousAuthenticationToken vs UsernamePasswordAuthenticationToken

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
  1. Does Spring Book try the AnonymousAuthenticationToken code first, because the 'user' field in application.yml is blank?

  2. 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

  1. Why is the app. configured to try both methods of authentication?
  2. How should this be configured?
  3. Is there something in the Spring Boot auto-configuration which controls this?

enter image description here

Upvotes: 2

Views: 23339

Answers (1)

gabrielgiussi
gabrielgiussi

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.

  1. 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.

  1. Request to /securedEndpoint with credentials but whitout Authorization header
  2. The BasicAuthenticationFilter is executed but there is no Authorization header so it can't authenticate the user.
  3. The AnonymousAuthenticationFilter is executed and puts an AnonymousToken in the SecurityContextHolder.
  4. The authorization mechanism throw an Exception because the request tries to get a secured resource.
  5. The ExceptionTranslationFilter catchs this exception and calls the AuthenticationEntryPoint
  6. The AuthenticationEntryPoint makes a redirect to /login
  7. The UsernamePassordAuthenticationFilter is executed and authenticates succesfully your user.
  8. The AnonymousAuthenticationFilter is executed againt but like there is an Authentication in the SecurityContextHolder alredy, it doesn't do anything.
  9. The authorization mechanism allows the user get the resource.

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

Related Questions