I am implementing Spring security with JWT in my application and when ever an unauthorized call is made it returns the following response
#Override
public void commence(HttpServletRequest request,
HttpServletResponse response,
AuthenticationException authException) throws IOException {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized");
}
The response json look like below
{
"timestamp": 1497832267379,
"status": 401,
"error": "Unauthorized",
"message": "Unauthorized",
"path": "/path"
}
Instead of this can I sent my own custom response something like:
{
"code":401,
"message":"The request is unauthorized"
}
Any help is appreciated
EDIT
I updated the code to below format:
#Override
public void commence(HttpServletRequest request,
HttpServletResponse response,
AuthenticationException authException) throws IOException {
//response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized");
Status unauthorizedEntry = new Status();
unauthorizedEntry.setCode(401);
unauthorizedEntry.setMessage("Unauthorized Entry");
Map<String, Object> unauthorizedEntryResponse = new HashMap<>();
unauthorizedEntryResponse.put("status", unauthorizedEntry);
objectMapper.writeValue(response.getOutputStream(), unauthorizedEntry);
response.flushBuffer();
}
My Status class is below:
public class Status {
int code;
String message;
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
Now I am getting a 200 response but nothing is shown in the screen. It is fully blank. Any help is appreciated!
You can try to add a controller advice
#RestController
#ControllerAdvice
public class ExceptionHandlerController {
#ExceptionHandler(UsernameNotFoundException.class, DataAccessException.class)
#ResponseStatus(HttpStatus.SC_UNAUTHORIZED)
#ResponseBody ErrorInfo
UnauthorizeExceptionInfo(HttpServletRequest req, Exception ex) {
return new ErrorInfo(req.getRequestURL(), ex);
}
}
and ErrorInfo.class
#JsonIgnore
public final StringBuffer url;
public final String ex;
public ErrorInfo(StringBuffer stringBuffer, Exception ex) {
this.url = stringBuffer;
this.ex = ex.getLocalizedMessage();
}
and when you will throw a new UsernameNotFoundException the controller will handle the response.
And I suppose that the exceptions are throw in your #Override public loadUserByUsername from CustomUserDetailsService if the password/email don't match.
More details here: https://spring.io/blog/2013/11/01/exception-handling-in-spring-mvc
This ought to work for you:
#Autowired
private ObjectMapper objectMapper;
#Override
public void commence(HttpServletRequest request,
HttpServletResponse response,
AuthenticationException authException) throws IOException {
// notify client of response body content type
response.addHeader("Content-Type", "application/json;charset=UTF-8");
// set the response status code
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
// set up the response body
Status unauthorized = new Status(HttpServletResponse.SC_UNAUTHORIZED,
"The request is unauthorized");
// write the response body
objectMapper.writeValue(response.getOutputStream(), unauthorized);
// commit the response
response.flushBuffer();
}
public class Status {
private int code;
private String message;
public Status(int code, String message) {
this.code = code;
this.message = message;
}
public int getCode() {
return code;
}
public String getMessage() {
return message;
}
}
Note that you need
Related
I have ProxyAuthenticator class that implement Authenticator interface.
public class ProxyAuthenticator implements Authenticator {
private String proxyUser;
private String proxyPassword;
public ProxyTessiAuthenticator(String proxyUser, String proxyPassword) {
this.proxyUser = proxyUser;
this.proxyPassword = proxyPassword;
}
#Override
public Request authenticateProxy(Proxy proxy, Response response) throws IOException {
return authenticate(proxy, response);
}
#Override
public Request authenticate(Proxy proxy, Response response) throws IOException {
String credential = Credentials.basic(proxyUser, proxyPassword);
return response.request().newBuilder()
.header("Proxy-Authorization", credential)
.build();
}
}
I get this exception with com.squareup.okhttp:
java.net.ProtocolException: Too many follow-up requests: 21
You will need to short circuit additional authentication requests if you have already tried.
https://github.com/square/okhttp/blob/3a6646dfd61cf55e3db38ebd08c7e504dde7a4bd/docs/recipes.md#handling-authentication-kt-java
https://howtoprogram.xyz/2016/11/03/basic-authentication-okhttp-example/
public Request authenticate(Route route, Response response) throws IOException {
String credential = ...
if (responseCount(response) >= 3) {
return null;
}
return response.request().newBuilder().header("Authorization", credential).build();
}
I'm trying to catch an exception thrown in my implementation of StreamingResponseBody, I can see the exception being thrown inside the class however the thrown exception isn't visible to the method body or my Controller Advice. So none of my handling seems to work, just interested to know which is the correct way to handle exceptions in this case.
#GetMapping(path = "/test", produces = "application/json")
public StreamingResponseBody test(#RequestParam(value = "var1") final String test)
throws IOException{
return new StreamingResponseBody() {
#Override
public void writeTo(final OutputStream outputStream) throws IOException{
try {
// Some operations..
} catch (final SomeCustomException e) {
throw new IOException(e);
}
}
};
}
I would expect my ControllerAdvice to return an ResponseEntity with a Http Status of 500.
The best way I discovered to handle errors/exceptions in the web environment is to create your custom exception with the disabled stack trace, and handle it with #ControllerAdvice.
import lombok.Getter;
import org.springframework.http.HttpStatus;
public class MyException extends RuntimeException {
#Getter private HttpStatus httpStatus;
public MyException(String message) {
this(message, HttpStatus.INTERNAL_SERVER_ERROR);
}
public MyException(String message, HttpStatus status) {
super(message, null, false, false);
this.httpStatus = status;
}
}
And then handle it in #ControllerAdvice like this:
#ExceptionHandler(MyException.class)
public ResponseEntity handleMyException(MyException exception) {
return ResponseEntity.status(exception.getHttpStatus()).body(
ErrorDTO.builder()
.message(exception.getMessage())
.description(exception.getHttpStatus().getReasonPhrase())
.build());
}
where ErrorDTO is just a simple DTO with two fields:
#Value
#Builder
public class ErrorDTO {
private final String message;
private final String description;
}
I have code with a custom exception:
#ResponseStatus(value = BAD_REQUEST, reason = "Login is busy")
public class LoginIsBusyException extends RuntimeException{
}
And a method that can throw it:
#RequestMapping(method = POST)
public void registration(#RequestBody UserRest user) throws
LoginIsBusyException{
userService.checkAlreadyExist(user.getLogin(), user.getMail());
user.setActive(false);
UserRest userRest = userService.addUser(user);
Integer randomToken = randomTokenService.getRandomToken(userRest.getMail());
mailService.sendMail(randomToken, userRest.getLogin(), userRest.getMail());
}
The problem is that the client receives only the error code but does not receive the statusText "Login is busy", Already tried to add a method catching this exception
#ExceptionHandler(LoginIsBusyException.class)
public void handleException(HttpServletResponse response) throws IOException
{
response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Login is busy");
}
But, the message is lost somewhere and the customer gets this response:
You have missed #ResponseBody for your handleException method and also it returns void with your current code i.e., you are NOT passing the response body, as shown below:
#ResponseBody
#ExceptionHandler(LoginIsBusyException.class)
public String handleException(HttpServletResponse response) throws IOException
{
response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Login is busy");
}
or else you use ResponseEntity to produce both header and body as shown below
#ExceptionHandler(LoginIsBusyException.class)
public ResponseEntity<String>
handleException(LoginIsBusyException exe) {
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("Login is busy");
}
How to write ResponseEntity to HttpServletResponse (as it makes #ResponseBody)?
For example I have authentication success handler:
#Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
Map responseMap = new HashMap();
responseMap.put("user", "my_user_name");
ResponseEntity responseEntity = new ResponseEntity(response, HttpStatus.OK);
}
If use MappingJackson2HttpMessageConverter I have error: "Could not write content: not in non blocking mode."
Code:
HttpOutputMessage outputMessage = new ServletServerHttpResponse(response);
messageConverter.write(responseEntity, null, outputMessage);
What are the best practices of implementation handlers with HttpServletResponse?
You can use a custom response object, convert it to a JSON string using the Jackson's ObjectMapper and write the result into the request.
Example
MyResponseObject.java
private String user;
public String getUser() {
return user;
}
public void setUser(String user) {
this.user = user;
}
MyAuthenticationSuccessHandler.java
#Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
MyResponseObject responseObj = new MyResponseObject();
responseObj.setUser("my_user_name");
String json = new ObjectMapper().writeValueAsString(responseObj);
httpServletResponse.setStatus(HttpStatus.OK.value());
httpServletResponse.getWriter().write(json);
httpServletResponse.flushBuffer();
}
Based on andrearro88's answer, I have made this generic function to copy a ResponseEntity to a HttpServletResponse:
public static void populateResponse(ResponseEntity<String> responseEntity, HttpServletResponse servletResponse)
throws IOException {
for (Map.Entry<String, List<String>> header : responseEntity.getHeaders().entrySet()) {
String chave = header.getKey();
for (String valor : header.getValue()) {
servletResponse.addHeader(chave, valor);
}
}
servletResponse.setStatus(responseEntity.getStatusCodeValue());
servletResponse.getWriter().write(responseEntity.getBody());
}
I'm using Spring Boot + Spring Security (java config).
My question is the old one, but all info which I've found is partially outdated and mostly contains xml-config (which difficult or even impossible to adapt some time)
I'm trying to do stateless authentication with a token (which doesn't stored on server side). Long story short - it is a simple analogue for JSON Web Tokens authentication format.
I'm using two custom filters before default one:
TokenizedUsernamePasswordAuthenticationFilter which creates token after
successful authentication on entry point ("/myApp/login")
TokenAuthenticationFilter which tries to authenticate the user using token (if provided) for all restricted URLs.
I do not understand how properly handle custom exceptions(with custom message or redirect) if I want some...
Exceptions in filters are not relevant to exceptions in controllers, so they will not be handled by same handlers...
If I've understood right, I can not use
.formLogin()
.defaultSuccessUrl("...")
.failureUrl("...")
.successHandler(myAuthenticationSuccessHandler)
.failureHandler(myAthenticationFailureHandler)
to customize exceptions, because I use custom filters...
So what the way to do it?
My config:
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and() .anonymous()
.and() .authorizeRequests()
.antMatchers("/").permitAll()
...
.antMatchers(HttpMethod.POST, "/login").permitAll()
.and()
.addFilterBefore(new TokenizedUsernamePasswordAuthenticationFilter("/login",...), UsernamePasswordAuthenticationFilter.class)
.addFilterBefore(new TokenAuthenticationFilter(...), UsernamePasswordAuthenticationFilter.class)
}
We can set AuthenticationSuccessHandler and AuthenticationFailureHandler in your custom filter as well.
Well in your case,
// Constructor of TokenizedUsernamePasswordAuthenticationFilter class
public TokenizedUsernamePasswordAuthenticationFilter(String path, AuthenticationSuccessHandler successHandler, AuthenticationFailureHandler failureHandler) {
setAuthenticationSuccessHandler(successHandler);
setAuthenticationFailureHandler(failureHandler);
}
Now to use these handlers just invoke onAuthenticationSuccess() or onAuthenticationFailure() methods.
#Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response,
FilterChain chain, Authentication authentication) throws IOException, ServletException {
getSuccessHandler().onAuthenticationSuccess(request, response, authentication);
}
#Override
protected void unsuccessfulAuthentication(HttpServletRequest request,
HttpServletResponse response,
AuthenticationException failed)
throws IOException, ServletException {
getFailureHandler().onAuthenticationFailure(request, response, failed);
}
You can create your custom authentication handler classes to handle the success or failure cases. For example,
public class LoginSuccessHandler implements AuthenticationSuccessHandler {
#Override
public void onAuthenticationSuccess(HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse,
Authentication authentication)
throws IOException, ServletException {
SecurityContextHolder.getContext().setAuthentication(authentication);
// Do your stuff, eg. Set token in response header, etc.
}
}
Now for handling the exception,
public class LoginFailureHandler implements AuthenticationFailureHandler {
#Override
public void onAuthenticationFailure(HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse,
AuthenticationException e)
throws IOException, ServletException {
String errorMessage = ExceptionUtils.getMessage(e);
sendError(httpServletResponse, HttpServletResponse.SC_UNAUTHORIZED, errorMessage, e);
}
private void sendError(HttpServletResponse response, int code, String message, Exception e) throws IOException {
SecurityContextHolder.clearContext();
Response<String> exceptionResponse =
new Response<>(Response.STATUES_FAILURE, message, ExceptionUtils.getStackTrace(e));
exceptionResponse.send(response, code);
}
}
My custom response class to generate desired JSON response,
public class Response<T> {
public static final String STATUES_SUCCESS = "success";
public static final String STATUES_FAILURE = "failure";
private String status;
private String message;
private T data;
private static final Logger logger = Logger.getLogger(Response.class);
public Response(String status, String message, T data) {
this.status = status;
this.message = message;
this.data = data;
}
public String getStatus() {
return status;
}
public String getMessage() {
return message;
}
public T getData() {
return data;
}
public String toJson() throws JsonProcessingException {
ObjectWriter ow = new ObjectMapper().writer().withDefaultPrettyPrinter();
try {
return ow.writeValueAsString(this);
} catch (JsonProcessingException e) {
logger.error(e.getLocalizedMessage());
throw e;
}
}
public void send(HttpServletResponse response, int code) throws IOException {
response.setStatus(code);
response.setContentType("application/json");
String errorMessage;
errorMessage = toJson();
response.getWriter().println(errorMessage);
response.getWriter().flush();
}
}
I hope this helps.