error-handling-spring-boot-starter icon indicating copy to clipboard operation
error-handling-spring-boot-starter copied to clipboard

Add 401 support for when not authenticated (spring security)

Open Caceresenzo opened this issue 2 years ago • 0 comments

Hello

Could you handle the case where the requester hasn't been authenticated? For the moment, Spring Security return only the 403 status.

A way that could be done is by using an AuthenticationEntryPoint (reference):

@Component
public class UnauthorizedEntryPoint implements AuthenticationEntryPoint {
	
	protected final HttpStatusMapper httpStatusMapper;
	protected final ErrorCodeMapper errorCodeMapper;
	protected final ErrorMessageMapper errorMessageMapper;
	protected final ObjectMapper objectMapper;
	
	@Autowired
	public UnauthorizedEntryPoint(HttpStatusMapper httpStatusMapper, ErrorCodeMapper errorCodeMapper, ErrorMessageMapper errorMessageMapper) {
		this(httpStatusMapper, errorCodeMapper, errorMessageMapper, new ObjectMapper());
	}
	
	public UnauthorizedEntryPoint(HttpStatusMapper httpStatusMapper, ErrorCodeMapper errorCodeMapper, ErrorMessageMapper errorMessageMapper, ObjectMapper objectMapper) {
		this.httpStatusMapper = httpStatusMapper;
		this.errorCodeMapper = errorCodeMapper;
		this.errorMessageMapper = errorMessageMapper;
		this.objectMapper = objectMapper;
	}
	
	@Override
	public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws JsonProcessingException, IOException {
		ApiErrorResponse errorResponse = createResponse(authException);
		
		response.setStatus(errorResponse.getHttpStatus().value());
		response.getWriter().write(objectMapper.writeValueAsString(errorResponse));
	}
	
	public ApiErrorResponse createResponse(AuthenticationException exception) {
		HttpStatus httpStatus = httpStatusMapper.getHttpStatus(exception, HttpStatus.UNAUTHORIZED);
		String code = errorCodeMapper.getErrorCode(exception);
		String message = errorMessageMapper.getErrorMessage(exception);
		
		return new ApiErrorResponse(httpStatus, code, message);
	}
	
}

Though, I didn't managed to make it auto-configurable.... The only way is to use http.exceptionHandling().authenticationEntryPoint(...):

@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class ApplicationSecurity extends WebSecurityConfigurerAdapter {
	
	@Autowired
	private UnauthorizedEntryPoint unauthorizedEntryPoint;
	
	@Override
	protected void configure(HttpSecurity http) throws Exception {
		http.httpBasic().disable();
		
		/* used for the next example */
		http.authorizeHttpRequests()
			.antMatchers("/insecure", "/secure").permitAll()
			.anyRequest().authenticated();
		
		http.exceptionHandling().authenticationEntryPoint(unauthorizedEntryPoint);
	}
	
}

And testing this method using a basic controller give the expected results:

@RestController
public class IndexController {
	
	@GetMapping("/insecure")
	public String insecure() {
		return "should be ok";
	}
	
	@PreAuthorize("authenticated")
	@GetMapping("/secure")
	public String secure() {
		return "should raise an access denied exception";
	}
	
	@GetMapping("/authenticated")
	public String authenticated() {
		return "should raise an insufficient authentication exception";
	}
	
}

What do you think?

Caceresenzo avatar Jul 30 '22 21:07 Caceresenzo