How to Add API Key Access Method for Spring Projects
Most technical SaaS platforms provide APIs for developers or to support clients and SDKs, and our company is no exception. Although saving OAuth2 tokens in browsers is convenient, tokens have expiration times and are issued by third parties, which can be a potential risk for clients and SDKs. Often, issues with third-party authentication services cause users to be unable to log in. Therefore, providing API Key access has become a better choice.
There are two approaches to adding API Key access:
- Reuse the existing Web API, introducing API Key authentication in parallel with the existing OAuth2-based services
- Start a completely separate service, providing consistent services through code sharing.
Both options have their pros and cons. In the early and middle stages of a product, when web and API access methods are roughly the same, the first approach may be better. However, when a project enters the middle and later stages, complete isolation may be a better choice considering the differences between web and API in terms of functionality and security. Since most of the web functionalities are consistent, we decided to add a parallel API Key authentication method on top of the existing OAuth2 authentication in the backend, making it more convenient for users.
This article mainly introduces the best practices for adding API Key authentication to existing Spring projects.
Obtaining the API Key and Generating AuthenticationToken
Get the API Key by checking the HttpRequest header, wrap it in a custom Authentication Token, and place it in the SecurityContext. Note that because Servlets operate in a multi-threaded environment, you should first create a new empty context and then set it back using setContext, rather than directly using
SecurityContextHolder.getContext().setAuthentication(token), which can lead to race conditions.
ProviderManager will match all
AuthenticationProviders based on the Token in the
SecurityContext, and the
supports method is used for matching. Once matched, the
authenticate method can be called for authentication. Here, you can obtain user information by querying the database or calling other services. Note that when handling
exceptions, any requests that fail authentication must be returned as an exception of the
AuthenticationException subclass, so that Spring Filter can capture the exception and return the corresponding error message.
After custom this provider, the next step is register it into filter chain.
Work to be done
After Spring Security 5.7,
WebSecurityConfigurerAdapter has been deprecated. The code modified according to the official documentation does not work properly. After the Token is set in the filter,
ProviderManager did not trigger the call to our custom
APIKeyAuthenticationProvider. This is probably a bug. In the community, there is an issue that combines the
AuthenticationProvider interfaces, so the solution to the problem with version 5.7 has been temporarily postponed.
Compared with other languages, Java has a rich set of components and frameworks, but this is a double-edged sword: having many components allows for faster development speed, but also increases the learning cost. Spring Security can quickly complete 90% of the work, but the remaining 10% of special requirements may take three to five times longer to configure and debug.
I prefer the philosophy of being precise and efficient: if you haven’t started using Java or Spring, I recommend choosing a simpler language and framework, such as Golang or Python; if you have already started using Spring Security, it is best to study it thoroughly and make full use of its features. Of course, as an open-source framework, Spring Security’s design and implementation are worth learning.
Spring Security Custom Authentication Provider Accessed 29 Aug. 2022.
Architecture :: Spring Security. Accessed 29 Aug. 2022.
Servlet Authentication Architecture :: Spring Security Accessed 29 Aug. 2022.
Spring Security without the WebSecurityConfigurerAdapter. 21 Feb. 2022 Accessed 29 Aug. 2022.