FroquizFroquiz
HomeQuizzesSenior ChallengeGet CertifiedBlogAbout
Sign InStart Quiz
Sign InStart Quiz
Froquiz

The most comprehensive quiz platform for software engineers. Test yourself with 10000+ questions and advance your career.

LinkedIn

Platform

  • Start Quizzes
  • Topics
  • Blog
  • My Profile
  • Sign In

About

  • About Us
  • Contact

Legal

  • Privacy Policy
  • Terms of Service

Β© 2026 Froquiz. All rights reserved.Built with passion for technology
Blog & Articles

Java Spring Security: JWT Authentication, Authorization and Best Practices

Learn Spring Security from the ground up. Covers security filter chain, JWT authentication, role-based authorization, method security, CORS configuration, and testing.

Yusuf SeyitoğluMarch 11, 20263 views11 min read

Java Spring Security: JWT Authentication, Authorization and Best Practices

Spring Security is the standard security framework for Spring Boot applications. It is powerful but complex β€” developers often copy-paste configurations without truly understanding what they do. This guide explains the core concepts and walks through a real JWT authentication setup.

How Spring Security Works

Spring Security operates as a chain of filters that intercept every HTTP request before it reaches your controllers. You configure which URLs require authentication, which roles are needed, and how authentication works.

The key components:

  • SecurityFilterChain β€” the filter chain configuration
  • AuthenticationManager β€” verifies credentials
  • UserDetailsService β€” loads user data for authentication
  • SecurityContext β€” holds the authenticated user for the current request

Basic Configuration

java
@Configuration @EnableWebSecurity public class SecurityConfig { @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http .csrf(csrf -> csrf.disable()) // disable for REST APIs using JWT .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS) ) .authorizeHttpRequests(auth -> auth .requestMatchers("/api/auth/**").permitAll() .requestMatchers("/api/admin/**").hasRole("ADMIN") .requestMatchers(HttpMethod.GET, "/api/products/**").permitAll() .anyRequest().authenticated() ); return http.build(); } @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(12); } }

JWT Authentication Setup

1. JWT Utility

java
@Component public class JwtUtil { @Value("${jwt.secret}") private String secret; @Value("${jwt.expiration:86400000}") private long expiration; // 24 hours in ms public String generateToken(UserDetails userDetails) { return Jwts.builder() .subject(userDetails.getUsername()) .issuedAt(new Date()) .expiration(new Date(System.currentTimeMillis() + expiration)) .signWith(getSigningKey()) .compact(); } public String extractUsername(String token) { return extractClaim(token, Claims::getSubject); } public boolean isTokenValid(String token, UserDetails userDetails) { final String username = extractUsername(token); return username.equals(userDetails.getUsername()) && !isTokenExpired(token); } private boolean isTokenExpired(String token) { return extractClaim(token, Claims::getExpiration).before(new Date()); } private <T> T extractClaim(String token, Function<Claims, T> resolver) { Claims claims = Jwts.parser() .verifyWith(getSigningKey()) .build() .parseSignedClaims(token) .getPayload(); return resolver.apply(claims); } private SecretKey getSigningKey() { byte[] keyBytes = Decoders.BASE64.decode(secret); return Keys.hmacShaKeyFor(keyBytes); } }

2. JWT Authentication Filter

java
@Component @RequiredArgsConstructor public class JwtAuthenticationFilter extends OncePerRequestFilter { private final JwtUtil jwtUtil; private final UserDetailsService userDetailsService; @Override protected void doFilterInternal( HttpServletRequest request, HttpServletResponse response, FilterChain filterChain ) throws ServletException, IOException { final String authHeader = request.getHeader("Authorization"); if (authHeader == null || !authHeader.startsWith("Bearer ")) { filterChain.doFilter(request, response); return; } final String token = authHeader.substring(7); final String username; try { username = jwtUtil.extractUsername(token); } catch (JwtException e) { filterChain.doFilter(request, response); return; } if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) { UserDetails userDetails = userDetailsService.loadUserByUsername(username); if (jwtUtil.isTokenValid(token, userDetails)) { UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken( userDetails, null, userDetails.getAuthorities() ); authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); SecurityContextHolder.getContext().setAuthentication(authToken); } } filterChain.doFilter(request, response); } }

3. Register the Filter in SecurityConfig

java
@Configuration @EnableWebSecurity @RequiredArgsConstructor public class SecurityConfig { private final JwtAuthenticationFilter jwtAuthFilter; private final UserDetailsService userDetailsService; @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http .csrf(csrf -> csrf.disable()) .sessionManagement(s -> s.sessionCreationPolicy(STATELESS)) .authorizeHttpRequests(auth -> auth .requestMatchers("/api/auth/**").permitAll() .anyRequest().authenticated() ) .addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class); return http.build(); } @Bean public AuthenticationManager authenticationManager( AuthenticationConfiguration config ) throws Exception { return config.getAuthenticationManager(); } }

4. UserDetailsService Implementation

java
@Service @RequiredArgsConstructor public class UserDetailsServiceImpl implements UserDetailsService { private final UserRepository userRepository; @Override public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException { return userRepository.findByEmail(email) .orElseThrow(() -> new UsernameNotFoundException("User not found: " + email)); } }

Your User entity should implement UserDetails:

java
@Entity @Table(name = "users") public class User implements UserDetails { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String email; private String password; @Enumerated(EnumType.STRING) private Role role; @Override public Collection<? extends GrantedAuthority> getAuthorities() { return List.of(new SimpleGrantedAuthority("ROLE_" + role.name())); } @Override public String getUsername() { return email; } @Override public String getPassword() { return password; } @Override public boolean isAccountNonExpired() { return true; } @Override public boolean isAccountNonLocked() { return true; } @Override public boolean isCredentialsNonExpired(){ return true; } @Override public boolean isEnabled() { return true; } }

5. Authentication Controller

java
@RestController @RequestMapping("/api/auth") @RequiredArgsConstructor public class AuthController { private final AuthenticationManager authManager; private final UserDetailsService userDetailsService; private final JwtUtil jwtUtil; private final UserRepository userRepository; private final PasswordEncoder passwordEncoder; @PostMapping("/login") public ResponseEntity<AuthResponse> login(@RequestBody @Valid LoginRequest req) { authManager.authenticate( new UsernamePasswordAuthenticationToken(req.email(), req.password()) ); UserDetails user = userDetailsService.loadUserByUsername(req.email()); String token = jwtUtil.generateToken(user); return ResponseEntity.ok(new AuthResponse(token)); } @PostMapping("/register") public ResponseEntity<AuthResponse> register(@RequestBody @Valid RegisterRequest req) { if (userRepository.existsByEmail(req.email())) { throw new ResponseStatusException(HttpStatus.CONFLICT, "Email already in use"); } User user = new User(); user.setEmail(req.email()); user.setPassword(passwordEncoder.encode(req.password())); user.setRole(Role.USER); userRepository.save(user); String token = jwtUtil.generateToken(user); return ResponseEntity.status(HttpStatus.CREATED).body(new AuthResponse(token)); } }

Role-Based Authorization

URL-level authorization

java
.authorizeHttpRequests(auth -> auth .requestMatchers("/api/admin/**").hasRole("ADMIN") .requestMatchers("/api/manager/**").hasAnyRole("ADMIN", "MANAGER") .requestMatchers(HttpMethod.DELETE, "/api/**").hasRole("ADMIN") .anyRequest().authenticated() )

Method-level security

java
@Configuration @EnableMethodSecurity public class SecurityConfig { ... } @Service public class UserService { @PreAuthorize("hasRole('ADMIN')") public List<User> getAllUsers() { ... } @PreAuthorize("hasRole('ADMIN') or #userId == authentication.principal.id") public User getUser(Long userId) { ... } @PostAuthorize("returnObject.email == authentication.name") public User getCurrentUserProfile() { ... } }

@PreAuthorize evaluates before the method runs. @PostAuthorize evaluates after, with access to the return value.

CORS Configuration

java
@Bean public CorsConfigurationSource corsConfigurationSource() { CorsConfiguration config = new CorsConfiguration(); config.setAllowedOrigins(List.of("https://myapp.com", "http://localhost:3000")); config.setAllowedMethods(List.of("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS")); config.setAllowedHeaders(List.of("Authorization", "Content-Type")); config.setAllowCredentials(true); config.setMaxAge(3600L); UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/api/**", config); return source; }

Testing Secured Endpoints

java
@SpringBootTest @AutoConfigureMockMvc class UserControllerTest { @Autowired MockMvc mockMvc; @Test @WithMockUser(roles = "ADMIN") void adminCanGetAllUsers() throws Exception { mockMvc.perform(get("/api/admin/users")) .andExpect(status().isOk()); } @Test @WithMockUser(roles = "USER") void userCannotAccessAdminEndpoint() throws Exception { mockMvc.perform(get("/api/admin/users")) .andExpect(status().isForbidden()); } @Test void unauthenticatedCannotAccessProtectedEndpoint() throws Exception { mockMvc.perform(get("/api/profile")) .andExpect(status().isUnauthorized()); } }

Common Interview Questions

Q: What is the difference between authentication and authorization?

Authentication verifies who you are (login with credentials). Authorization determines what you are allowed to do (access control based on roles/permissions). Spring Security handles both: UserDetailsService for authentication, authorizeHttpRequests and @PreAuthorize for authorization.

Q: Why do you disable CSRF for REST APIs?

CSRF attacks work by tricking a browser into making requests using existing cookies. Since REST APIs authenticate with JWT tokens in the Authorization header (not cookies), the browser cannot automatically include them in cross-site requests β€” so CSRF protection is unnecessary and just adds overhead.

Q: What is the SecurityContext?

The SecurityContext holds the Authentication object for the current request thread. Spring uses ThreadLocal storage so each request thread has its own security context. You access it via

code
SecurityContextHolder.getContext().getAuthentication()
.

Practice Java on Froquiz

Spring Security questions are common in Java backend interviews at intermediate and senior levels. Test your Java and Spring Boot knowledge on Froquiz across all difficulty levels.

Summary

  • Spring Security intercepts requests through a filter chain before they reach controllers
  • SecurityFilterChain configures URL authorization, session policy, and custom filters
  • JWT authentication: validate token in a filter, set authentication in SecurityContext
  • UserDetailsService loads user data; the User entity implements UserDetails
  • Use @PreAuthorize for method-level security with Spring Expression Language
  • Configure CORS via CorsConfigurationSource registered in the security config
  • Disable CSRF for stateless REST APIs using JWT Bearer tokens

About Author

Yusuf Seyitoğlu

Author β†’

Other Posts

  • System Design Fundamentals: Scalability, Load Balancing, Caching and DatabasesMar 12
  • CSS Advanced Techniques: Custom Properties, Container Queries, Grid Masonry and Modern LayoutsMar 12
  • GraphQL Schema Design: Types, Resolvers, Mutations and Best PracticesMar 12
All Blogs