ifferent types of users, but I don’t want to create different roles for them. Instead, I will have two different tables: one for staff and one for users. These two types of users will be authenticated with a username and password. Authentication will be handled by separate APIs: one for staff (/api/v1/auth/staff
) and one for users (/api/v1/auth/user
). After successful authentication, the APIs will be structured as /api/v1/staff/* and /api/v1/students/*.
To achieve this, I have created two different services: UserService and StaffService, both of which extend UserDetailsService from Spring Security. Below is the security configuration I have created for this setup.
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Autowired
private JwtAuthenticationEntryPoint point;
@Autowired
private JwtAuthenticationFilter filter;
@Autowired
private PasswordEncoder passwordEncoder;
@Autowired
private CustomAuthenticationProvider customAuthenticationProvider;
@Bean
public AuthenticationManager authenticationManager(HttpSecurity http) throws Exception {
AuthenticationManagerBuilder authenticationManagerBuilder = http.getSharedObject(AuthenticationManagerBuilder.class);
authenticationManagerBuilder.authenticationProvider(customAuthenticationProvider);
return authenticationManagerBuilder.build();
}
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.csrf(AbstractHttpConfigurer::disable)
.cors(AbstractHttpConfigurer::disable)
.authorizeHttpRequests(request -> request
.requestMatchers(
new AntPathRequestMatcher("/api/v1/auth/**")
).permitAll()
)
.authorizeHttpRequests(request -> request
.requestMatchers(
new AntPathRequestMatcher("/api/v1/staffs")
).hasRole("ADMIN").anyRequest().authenticated()
)
.httpBasic(Customizer.withDefaults())
.exceptionHandling(ex -> ex.authenticationEntryPoint(point))
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS));
http.addFilterBefore(filter, UsernamePasswordAuthenticationFilter.class);
return http.build();
}
}
Then the JWT authentication filter will be like below
@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {
private final Logger logger = Logger.getLogger(JwtAuthenticationFilter.class.getName());
@Autowired
private JwtHelper jwtHelper;
@Autowired
private UserService userService;
@Autowired
private StaffService staffService;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException, java.io.IOException {
String authHeader = request.getHeader("Authorization");
String username = null;
String token = null;
if (authHeader != null && authHeader.startsWith("Bearer ")) {
token = authHeader.substring(7);
try {
username = this.jwtHelper.getUsernameFromToken(token);
} catch (IllegalArgumentException e) {
logger.info("Illegal Argument while fetching the username");
e.printStackTrace();
} catch (ExpiredJwtException e) {
logger.info("Given jwt token is expired ");
e.printStackTrace();
} catch (MalformedJwtException e) {
logger.info("Some changed has done in token !! Invalid Token");
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
} else {
logger.info("Invalid JWT token");
}
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = null;
if (request.getRequestURI().contains("/api/v1/auth/user") || request.getRequestURI().contains("/api/v1/user")) {
userDetails = this.userService.loadUserByUsername(username);
} else if (request.getRequestURI().contains("/api/v1/auth/staff") || request.getRequestURI().contains("/api/v1/staff")) {
userDetails = this.staffService.loadUserByUsername(username);
} else {
logger.info("User not found ot unauthorized access");
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized Access");
}
Boolean validToken = this.jwtHelper.validateToken(token, userDetails);
if (validToken) {
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authentication);
} else {
logger.info("Validation Fails");
}
}
try {
filterChain.doFilter(request, response);
} catch (java.io.IOException e) {
throw new RuntimeException(e);
}
}
}
The problem is, I can’t use multiple UserDetailsService in the application. So, I tried to create Authentication Provider like below.
@Component
public class CustomAuthenticationProvider implements AuthenticationProvider {
private static final Logger logger = Logger.getLogger(CustomAuthenticationProvider.class.getName());
private final StaffService staffService;
private final UserService userService;
CustomAuthenticationProvider(StaffService staffService, UserService userService) {
this.staffService = staffService;
this.userService = userService;
}
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String username = authentication.getName();
String password = authentication.getCredentials().toString();
UserDetails userDetails = null;
if (authentication instanceof UsernamePasswordAuthenticationToken) {
UsernamePasswordAuthenticationToken token = (UsernamePasswordAuthenticationToken) authentication;
if (token.getPrincipal() != null && token.getPrincipal() instanceof String) {
String principal = (String) token.getPrincipal();
logger.info("principal is " + principal);
if (principal.startsWith("/auth/staff")) {
userDetails = staffService.loadUserByUsername(username);
} else if (principal.startsWith("/auth/student")) {
userDetails = userService.loadUserByUsername(username);
}
}
}
if (userDetails == null || !password.equals(userDetails.getPassword())) {
throw new BadCredentialsException("Bad credentials");
}
return new UsernamePasswordAuthenticationToken(userDetails, password, userDetails.getAuthorities());
}
@Override
public boolean supports(Class<?> authentication) {
return authentication.equals(UsernamePasswordAuthenticationToken.class);
}
}
But this authentication provider is also not working. If anybody knows a solution for this, please help me.