Implementing JWT‑Based Authentication and RBAC with Spring Security in a Spring Boot Application
This article demonstrates how to integrate JWT token authentication and role‑based access control (RBAC) into a Spring Boot backend using Spring Security, covering model classification, dependency setup, user details implementation, token utilities, filters, configuration, and login handling with code examples.
The article introduces RBAC concepts, compares Shiro and Spring Security, and explains how to integrate JWT authentication into a Spring Boot backend.
Model classification: it describes the RBAC0‑RBAC3 models, role hierarchy, constraints such as role mutual exclusion, cardinality, prerequisites, and runtime mutual exclusion.
Dependency setup: Maven dependencies for spring-boot-starter-security and jjwt are added.
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
<version>2.3.1.RELEASE</version>
</dependency>UserDetails implementation: a JwtUser class implements UserDetails to hold username, password, state and authorities.
public class JwtUser implements UserDetails {
private String username;
private String password;
private Integer state;
private Collection<? extends GrantedAuthority> authorities;
// getters, setters and overridden methods omitted for brevity
}Token utility: JwtTokenUtil provides methods to generate, parse, refresh and validate JWT tokens.
public class JwtTokenUtil implements Serializable {
private String secret;
private Long expiration;
private String header;
private String generateToken(Map<String, Object> claims) { /* ... */ }
private Claims getClaimsFromToken(String token) { /* ... */ }
public String generateToken(UserDetails userDetails) { /* ... */ }
public String getUsernameFromToken(String token) { /* ... */ }
public Boolean isTokenExpired(String token) { /* ... */ }
public String refreshToken(String token) { /* ... */ }
public Boolean validateToken(String token, UserDetails userDetails) { /* ... */ }
}Authentication filter: JwtAuthenticationTokenFilter extends OncePerRequestFilter to extract the JWT from the request header, validate it and set the security context.
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
@Autowired private UserDetailsService userDetailsService;
@Autowired private JwtTokenUtil jwtTokenUtil;
@Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
String authHeader = request.getHeader(jwtTokenUtil.getHeader());
if (authHeader != null && StringUtils.isNotEmpty(authHeader)) {
String username = jwtTokenUtil.getUsernameFromToken(authHeader);
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
if (jwtTokenUtil.validateToken(authHeader, userDetails)) {
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authentication);
}
}
}
chain.doFilter(request, response);
}
}UserDetailsService implementation: JwtUserDetailsServiceImpl loads a user from the database and converts roles to SimpleGrantedAuthority objects.
@Service
public class JwtUserDetailsServiceImpl implements UserDetailsService {
@Autowired private UserMapper userMapper;
@Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userMapper.selectByUserName(username);
if (user == null) throw new UsernameNotFoundException(String.format("'%s' not found", username));
List<SimpleGrantedAuthority> authorities = user.getRoles().stream()
.map(Role::getRolename)
.map(SimpleGrantedAuthority::new)
.collect(Collectors.toList());
return new JwtUser(user.getUsername(), user.getPassword(), user.getState(), authorities);
}
}Login service: UserServiceImpl authenticates credentials via AuthenticationManager, generates a JWT with JwtTokenUtil and returns it.
@Service
public class UserServiceImpl implements UserService {
@Autowired private AuthenticationManager authenticationManager;
@Autowired private UserDetailsService userDetailsService;
@Autowired private JwtTokenUtil jwtTokenUtil;
public RetResult login(String username, String password) throws AuthenticationException {
UsernamePasswordAuthenticationToken upToken = new UsernamePasswordAuthenticationToken(username, password);
Authentication authentication = authenticationManager.authenticate(upToken);
SecurityContextHolder.getContext().setAuthentication(authentication);
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
return new RetResult(RetCode.SUCCESS.getCode(), jwtTokenUtil.generateToken(userDetails));
}
}Security configuration: a WebSecurity (or WebSecurityConfigurerAdapter) disables CSRF, sets the session to stateless, permits /auth/** and OPTIONS requests, adds the JWT filter, and configures CORS.
@EnableWebSecurity
public class WebSecurity extends WebSecurityConfigurerAdapter {
@Autowired private UserDetailsService userDetailsService;
@Autowired private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;
@Bean(name = BeanIds.AUTHENTICATION_MANAGER)
@Override public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); }
@Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); }
@Override protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and().authorizeRequests()
.antMatchers(HttpMethod.OPTIONS, "/**").permitAll()
.antMatchers("/auth/**").permitAll()
.anyRequest().authenticated()
.and().headers().cacheControl();
http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
http.authorizeRequests().requestMatchers(CorsUtils::isPreFlightRequest).permitAll();
}
@Bean public CorsFilter corsFilter() { /* CORS configuration omitted for brevity */ }
}Custom JSON login filter: CustomAuthenticationFilter overrides attemptAuthentication to read username and password from a JSON request body.
public class CustomAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
@Override public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
if (MediaType.APPLICATION_JSON_VALUE.equals(request.getContentType())) {
try (InputStream is = request.getInputStream()) {
AuthenticationBean bean = new ObjectMapper().readValue(is, AuthenticationBean.class);
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(bean.getUsername(), bean.getPassword());
setDetails(request, authRequest);
return this.getAuthenticationManager().authenticate(authRequest);
} catch (IOException e) { throw new AuthenticationServiceException(e.getMessage(), e); }
}
return super.attemptAuthentication(request, response);
}
}The article finishes with a runnable demo that returns a JWT token after successful login, and advises using BCryptPasswordEncoder for password hashing.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Java Architect Essentials
Committed to sharing quality articles and tutorials to help Java programmers progress from junior to mid-level to senior architect. We curate high-quality learning resources, interview questions, videos, and projects from across the internet to help you systematically improve your Java architecture skills. Follow and reply '1024' to get Java programming resources. Learn together, grow together.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
