Spring Security: AnonymousAuthenticationToken vs UsernamePasswordAuthenticationToken

13,907

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.

Share:
13,907
dbl001
Author by

dbl001

Founded Software Intelligence, Inc. in 1992 with the mission of integrating the latest Artificial Intelligence technologies (AI) to augment users’ performance, accuracy and productivity. Designer/team lead of our first product Parser/Loader, an Oracle /C++ ETL tool used by a major brokerage firm for intraday and overnight trade processing. Designer/team leader of our second product TIE, an AI tool used by a major investment bank in 14 countries to process institutional trades. My current interest is in applying machine learning, deep belief neural networks , Spark into network analysis, trading, finance, genomics. Prior to my work at Software Intelligence, Inc., I worked on the team that invented the first PC / Unix file server. My current interest is in applying machine learning, deep belief neural networks at scale with Spark + GPUs, in areas which generate growth, value and productivity. A recent contract was to build in Java 1.8, the merchant banking interface (MBI) to a crowd-funding website accessing Wells Fargo's Payment Manager and Payment Gateway systems. MBI was written in Java, Groovy, Gradle, Spring Boot, Spring Batch, Spring Rest, Spring Data, Spring Security, Postgres 9.4 and Hibernate. We run machine learning and deep learning in the cloud with Spark on Amazon AWS as well as, Microsoft’s Azure and Google’s Cloud Platform. We use Databricks, Zeppelin, Python, R, Scala, Mathematica and Matlab. Software Intelligence, Inc. provides contract programming, consulting and does R&D.

Updated on June 05, 2022

Comments

  • dbl001
    dbl001 almost 2 years

    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