This version is still in development and is not considered stable yet. For the latest stable version, please use Spring Security 6.4.2!

Anonymous Authentication

Overview

It is generally considered good security practice to adopt a “deny-by-default” stance, where you explicitly specify what is allowed and disallow everything else. Defining what is accessible to unauthenticated users is a similar situation, particularly for web applications. Many sites require that users must be authenticated for anything other than a few URLs (for example the home and login pages). In that case, it is easiest to define access configuration attributes for these specific URLs rather than for every secured resource. Put differently, sometimes it is nice to say ROLE_SOMETHING is required by default and allow only certain exceptions to this rule, such as for login, logout, and home pages of an application. You could also omit these pages from the filter chain entirely, thus bypassing the access control checks, but this may be undesirable for other reasons, particularly if the pages behave differently for authenticated users.

This is what we mean by anonymous authentication. 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, still return null, even though there is actually an anonymous authentication object in the SecurityContextHolder.

There are other situations where anonymous authentication is useful, such as when an auditing interceptor queries the SecurityContextHolder to identify which principal was responsible for a given operation. Classes can be authored more robustly if they know the SecurityContextHolder always contains an Authentication object and never contains null.

Configuration

Anonymous authentication support is provided automatically when you use the HTTP configuration (introduced in Spring Security 3.0). You can customize (or disable) it by using the <anonymous> element. You need not configure the beans described here unless you are using traditional bean configuration.

Three classes work together to provide the anonymous authentication feature. AnonymousAuthenticationToken is an implementation of Authentication and stores the GrantedAuthority instances that apply to the anonymous principal. There is a corresponding AnonymousAuthenticationProvider, which is chained into the ProviderManager so that AnonymousAuthenticationToken instances are accepted. Finally, an AnonymousAuthenticationFilter is chained after the normal authentication mechanisms and automatically adds an AnonymousAuthenticationToken to the SecurityContextHolder if there is no existing Authentication held there. The filter and authentication provider is defined as follows:

<bean id="anonymousAuthFilter"
	class="org.springframework.security.web.authentication.AnonymousAuthenticationFilter">
<property name="key" value="foobar"/>
<property name="userAttribute" value="anonymousUser,ROLE_ANONYMOUS"/>
</bean>

<bean id="anonymousAuthenticationProvider"
	class="org.springframework.security.authentication.AnonymousAuthenticationProvider">
<property name="key" value="foobar"/>
</bean>

The key is shared between the filter and authentication provider, so that tokens created by the former are accepted by the latter

The use of the key property should not be regarded as providing any real security here. It is merely a book-keeping exercise. If you share a ProviderManager that contains an AnonymousAuthenticationProvider in a scenario where it is possible for an authenticating client to construct the Authentication object (such as with RMI invocations), then a malicious client could submit an AnonymousAuthenticationToken that it had created itself (with the chosen username and authority list). If the key is guessable or can be found out, the token would be accepted by the anonymous provider. This is not a problem with normal usage. However, if you use RMI, you should use a customized ProviderManager that omits the anonymous provider rather than sharing the one you use for your HTTP authentication mechanisms.

The userAttribute is expressed in the form of usernameInTheAuthenticationToken,grantedAuthority[,grantedAuthority]. The same syntax is used after the equals sign for the userMap property of InMemoryDaoImpl.

As explained earlier, the benefit of anonymous authentication is that all URI patterns can have security applied to them, as the following example shows:

<bean id="filterSecurityInterceptor"
	class="org.springframework.security.web.access.intercept.FilterSecurityInterceptor">
<property name="authenticationManager" ref="authenticationManager"/>
<property name="accessDecisionManager" ref="httpRequestAccessDecisionManager"/>
<property name="securityMetadata">
	<security:filter-security-metadata-source>
	<security:intercept-url pattern='/index.jsp' access='ROLE_ANONYMOUS,ROLE_USER'/>
	<security:intercept-url pattern='/hello.htm' access='ROLE_ANONYMOUS,ROLE_USER'/>
	<security:intercept-url pattern='/logoff.jsp' access='ROLE_ANONYMOUS,ROLE_USER'/>
	<security:intercept-url pattern='/login.jsp' access='ROLE_ANONYMOUS,ROLE_USER'/>
	<security:intercept-url pattern='/**' access='ROLE_USER'/>
	</security:filter-security-metadata-source>" +
</property>
</bean>

AuthenticationTrustResolver

Rounding out the anonymous authentication discussion is the AuthenticationTrustResolver interface, with its corresponding AuthenticationTrustResolverImpl implementation. This interface provides an isAnonymous(Authentication) method, which allows interested classes to take into account this special type of authentication status. The ExceptionTranslationFilter uses this interface in processing AccessDeniedException instances. If an AccessDeniedException is thrown and the authentication is of an anonymous type, instead of throwing a 403 (forbidden) response, the filter, instead, commences the AuthenticationEntryPoint so that the principal can authenticate properly. This is a necessary distinction. Otherwise, principals would always be deemed “authenticated” and never be given an opportunity to login through form, basic, digest, or some other normal authentication mechanism.

We often see the ROLE_ANONYMOUS attribute in the earlier interceptor configuration replaced with IS_AUTHENTICATED_ANONYMOUSLY, which is effectively the same thing when defining access controls. This is an example of the use of the AuthenticatedVoter, which we cover in the authorization chapter. It uses an AuthenticationTrustResolver to process this particular configuration attribute and grant access to anonymous users. The AuthenticatedVoter approach is more powerful, since it lets you differentiate between anonymous, remember-me, and fully authenticated users. If you do not need this functionality, though, you can stick with ROLE_ANONYMOUS, which is processed by Spring Security’s standard RoleVoter.

Getting Anonymous Authentications with Spring MVC

Spring MVC resolves parameters of type Principal using its own argument resolver.

This means that a construct like this one:

  • Java

  • Kotlin

@GetMapping("/")
public String method(Authentication authentication) {
	if (authentication instanceof AnonymousAuthenticationToken) {
		return "anonymous";
	} else {
		return "not anonymous";
	}
}
@GetMapping("/")
fun method(authentication: Authentication?): String {
    return if (authentication is AnonymousAuthenticationToken) {
        "anonymous"
    } else {
        "not anonymous"
    }
}

will always return "not anonymous", even for anonymous requests. The reason is that Spring MVC resolves the parameter using HttpServletRequest#getPrincipal, which is null when the request is anonymous.

If you’d like to obtain the Authentication in anonymous requests, use @CurrentSecurityContext instead:

Use CurrentSecurityContext for Anonymous requests
  • Java

  • Kotlin

@GetMapping("/")
public String method(@CurrentSecurityContext SecurityContext context) {
	return context.getAuthentication().getName();
}
@GetMapping("/")
fun method(@CurrentSecurityContext context : SecurityContext) : String =
		context!!.authentication!!.name