How to Test Spring Boot APIs with Spring Security Enabled During Development
I was building a multi-tenant RBAC application with Spring Boot, and everything was going smoothly until I tried to test my APIs. Every request returned 401 Unauthorized. I knew Spring Security was doing its job, but I needed a way to test different user roles and scenarios without constantly logging in and managing tokens manually.
The Problem
Here’s what I was dealing with:
- Every API endpoint required authentication
- I needed to test different roles (USER, ADMIN, TENANT_ADMIN)
- JWT tokens kept expiring during manual testing
- I couldn’t easily test unauthorized access scenarios
I wanted to keep security enabled but make my development workflow efficient. After all, disabling security entirely defeats the purpose of testing the actual behavior.
Approach 1: Spring Security Test Annotations (For Unit Tests)
The cleanest solution for automated tests is using Spring Security’s built-in test annotations. These let you simulate authenticated users without dealing with actual tokens.
@SpringBootTest@AutoConfigureMockMvcclass ApiSecurityTests {
@Autowired private MockMvc mockMvc;
@Test @WithMockUser(username = "user", roles = {"USER"}) void testUserEndpoint() throws Exception { mockMvc.perform(get("/api/user/profile")) .andExpect(status().isOk()); }
@Test @WithMockUser(username = "admin", roles = {"ADMIN"}) void testAdminEndpoint() throws Exception { mockMvc.perform(get("/api/admin/users")) .andExpect(status().isOk()); }
@Test @WithMockUser(roles = "USER") void userCannotAccessAdminEndpoints() throws Exception { mockMvc.perform(get("/api/admin/users")) .andExpect(status().isForbidden()); }
@Test void unauthenticatedUserIsRejected() throws Exception { mockMvc.perform(get("/api/user/profile")) .andExpect(status().isUnauthorized()); }}This approach works great for automated tests, but what about manual testing with Postman or curl?
Approach 2: Development Profile with Relaxed Security
For manual testing during development, I created a separate security configuration that only activates in the dev profile.
@Configuration@EnableWebSecurity@Profile("dev")public class DevSecurityConfig {
@Bean public SecurityFilterChain devFilterChain(HttpSecurity http) throws Exception { http.authorizeHttpRequests(auth -> auth.anyRequest().permitAll()) .csrf(csrf -> csrf.disable()); return http.build(); }}This works, but I quickly realized it wasn’t ideal for testing role-based access control. I was bypassing the actual security logic, which meant I wasn’t testing what would happen in production.
Approach 3: Dev-Only Token Generation Endpoint
The best middle ground I found was creating a development-only endpoint that generates test tokens with specific roles. This keeps security enabled while making manual testing much easier.
@RestController@RequestMapping("/api/dev")@Profile({"dev", "test"})public class DevTokenController {
@Autowired private JwtService jwtService;
@GetMapping("/test-users") public List<Map<String, Object>> getTestUsers() { return List.of( Map.of("username", "user", "password", "pass", "roles", List.of("USER")), Map.of("username", "admin", "password", "pass", "roles", List.of("ADMIN", "USER")), Map.of("username", "tenant-admin", "password", "pass", "roles", List.of("TENANT_ADMIN", "USER")) ); }
@PostMapping("/generate-token") public Map<String, String> generateToken(@RequestParam String username, @RequestParam String[] roles) { String token = jwtService.generateToken( username, Arrays.asList(roles), Duration.ofHours(24) ); return Map.of( "token", token, "Authorization", "Bearer " + token ); }}Now I can quickly generate tokens for different roles:
# Get a regular user tokencurl -X POST "http://localhost:8080/api/dev/generate-token?username=user&roles=USER"
# Get an admin tokencurl -X POST "http://localhost:8080/api/dev/generate-token?username=admin&roles=ADMIN,USER"
# Use the tokencurl -H "Authorization: Bearer <token>" http://localhost:8080/api/admin/usersApproach 4: Using @WithUserDetails for Realistic Tests
When I needed to test with actual UserDetails implementation (for custom user properties or authorities), I used @WithUserDetails instead of @WithMockUser.
@TestConfigurationpublic class TestSecurityConfig {
@Bean @Primary public UserDetailsService testUserDetailsService() { return new InMemoryUserDetailsManager( User.withUsername("user") .password("{noop}password") .roles("USER") .build(), User.withUsername("admin") .password("{noop}password") .roles("ADMIN", "USER") .build(), User.withUsername("tenant-admin") .password("{noop}password") .roles("TENANT_ADMIN", "USER") .build() ); }}@SpringBootTest@AutoConfigureMockMvc@Import(TestSecurityConfig.class)class RoleBasedApiTests {
@Autowired private MockMvc mockMvc;
@Test @WithUserDetails(value = "admin", userDetailsServiceBeanName = "testUserDetailsService") void adminCanAccessAllEndpoints() throws Exception { mockMvc.perform(get("/api/admin/users")) .andExpect(status().isOk()); }}What I Learned
+------------------------+------------------+----------------------+| Scenario | Recommended | Why |+------------------------+------------------+----------------------+| Unit/Integration Tests | @WithMockUser | Simple, no setup || Custom UserDetails | @WithUserDetails | Tests actual impl || Manual API Testing | Dev token | Keeps security on || Quick Dev Iteration | Permit all | Fastest but risky |+------------------------+------------------+----------------------+Common mistakes I made:
-
Disabling security entirely - I initially turned off security for all dev profiles, which meant my tests weren’t catching real security issues.
-
Not testing all role permutations - I only tested happy paths, missing cases where users shouldn’t have access.
-
Hardcoded tokens - I copied tokens from login responses, but they kept expiring mid-testing.
-
Forgetting unauthorized scenarios - I only tested what authenticated users could do, not what happened without authentication.
Security Considerations
The dev token endpoint is a powerful tool but requires safeguards:
@RestController@RequestMapping("/api/dev")@Profile({"dev", "test"}) // NEVER activate in production@ConditionalOnProperty(name = "app.dev.testing.enabled", havingValue = "true")public class DevTokenController { // Only runs when explicitly enabled}I also added a startup warning:
@Component@Profile("dev")public class DevSecurityWarning implements ApplicationListener<ContextRefreshedEvent> {
private static final Logger log = LoggerFactory.getLogger(DevSecurityWarning.class);
@Override public void onApplicationEvent(ContextRefreshedEvent event) { log.warn("========================================"); log.warn("DEV PROFILE ACTIVE - Security relaxed"); log.warn("NEVER use this profile in production!"); log.warn("========================================"); }}Final Thoughts
Testing Spring Boot APIs with security enabled doesn’t have to be painful. The key is choosing the right tool for each scenario:
- Use
@WithMockUserfor quick unit tests - Use
@WithUserDetailswhen you need realistic user details - Create dev-only token endpoints for manual testing
- Keep security enabled in all environments, just with different configurations
The dev token endpoint approach gave me the best balance: I could test with real authentication logic while avoiding the friction of managing tokens manually. And by restricting it to dev/test profiles with explicit warnings, I ensured it would never leak into production.
Final Words + More Resources
My intention with this article was to help others share my knowledge and experience. If you want to contact me, you can contact by email: Email me
Here are also the most important links from this article along with some further resources that will help you in this scope:
- 👨💻 Spring Security Testing Documentation
- 👨💻 Spring Boot Testing Guide
- 👨💻 Spring Security Authentication Deep Dive
- 👨💻 Spring Security Conference Talk
Oh, and if you found these resources useful, don’t forget to support me by starring the repo on GitHub!
Comments