I am using Spring Boot 3.1.4
Requirements:
- Requests to auth/registration do not need any authorization or roles.
- Requests anywhere else need basic auth.
@Configuration
public class AuthConfig {
@Autowired
private BasicAuthFilter basicAuthFilter;
@Bean
public SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception {
return httpSecurity
.authorizeHttpRequests(authorizationManagerRequestMatcherRegistry -> {
authorizationManagerRequestMatcherRegistry.requestMatchers("/auth/registration").permitAll();
authorizationManagerRequestMatcherRegistry.anyRequest().authenticated();
})
.csrf(AbstractHttpConfigurer::disable)
.addFilterBefore(basicAuthFilter, UsernamePasswordAuthenticationFilter.class)
.build();
}
}
@Component
public class BasicAuthFilter extends OncePerRequestFilter {
@Autowired
private PersonService personService;
private final BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
String requestURI = request.getRequestURI();
if (requestURI.equals("/auth/registration")) {
// TODO: remove
Collection<GrantedAuthority> grantedAuthorities = new ArrayList<>();
GrantedAuthority grantedAuthority = new GrantedAuthority() {
@Override
public String getAuthority() {
return "admin";
}
};
grantedAuthorities.add(grantedAuthority);
Authentication authentication = new UsernamePasswordAuthenticationToken(new Person(), new Person(), grantedAuthorities);
SecurityContextHolder.getContext().setAuthentication(authentication);
filterChain.doFilter(request, response);
return;
}
String authorizationHeader = request.getHeader("Authorization");
if (authorizationHeader == null) {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
return;
}
if (!authorizationHeader.startsWith("Basic ")) {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
return;
}
String base64Credentials = authorizationHeader.substring("Basic ".length());
String decodedCredentials = new String(Base64.getDecoder().decode(base64Credentials), StandardCharsets.UTF_8);
String[] credentials = decodedCredentials.split(":");
String username = credentials[0];
String password = credentials[1];
Person person = personService.getPerson(username);
if (bCryptPasswordEncoder.matches(password, person.getPassword())) {
Collection<GrantedAuthority> grantedAuthorities = new ArrayList<>();
GrantedAuthority grantedAuthority = new GrantedAuthority() {
@Override
public String getAuthority() {
return "admin";
}
};
grantedAuthorities.add(grantedAuthority);
Authentication authentication = new UsernamePasswordAuthenticationToken(person, person.getPassword(), grantedAuthorities);
SecurityContextHolder.getContext().setAuthentication(authentication);
filterChain.doFilter(request, response);
} else {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
}
}
}
When I make request to e.g. /persons without authorization I want to send an 401 back to the client. And that's actually what I do
if (authorizationHeader == null) {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
return;
}
Unfortunately the FilterChain doesn't stop there and continues to run for every other filter too. Ultimately the AuthorizationFilter fails too and sends an 403.
IMO when the auth is missing, I want to return 401 and not 403.
How do I return 401? What is the best practice?