Spring security JWT filter throws 500 and HTML instead of 401 and json - spring

I've been having trouble getting security working correctly, half of the problem was fixed with this
Spring Boot Security wont ignore certain paths that dont need to be secured
Second problem is spring is ignoring the HTTP status code on failure and always throws a 500.
When the JWT token is invalid I want to return a 401 and a json response. I keep getting a 500 and the white label html page.
JwtFilter
class JwtFilter(private val tokenService: TokenService) : GenericFilterBean() {
override fun doFilter(req: ServletRequest, res: ServletResponse, chain: FilterChain) {
val request = req as HttpServletRequest
val response = res as HttpServletResponse
val httpRequest = request as HttpServletRequest
val path = httpRequest.servletPath.toString().substring(0, 12)
if (path == "/api/v1/auth") {
chain.doFilter(req, res)
return
} else {
val token = TokenUtil.extractToken(request as HttpServletRequest)
if (token != null && token.isNotEmpty()) {
try {
tokenService.getClaims(token)
} catch (e: SignatureException) {
throw ResponseStatusException(HttpStatus.UNAUTHORIZED, "Invalid JWT Signature")
} catch (e: MalformedJwtException) {
throw ResponseStatusException(HttpStatus.UNAUTHORIZED, "Invalid JWT token")
} catch (e: ExpiredJwtException) {
throw ResponseStatusException(HttpStatus.UNAUTHORIZED, "Expired JWT token")
} catch (e: UnsupportedJwtException) {
throw ResponseStatusException(HttpStatus.UNAUTHORIZED, "Unsupported JWT exception")
} catch (e: IllegalArgumentException) {
throw ResponseStatusException(HttpStatus.UNAUTHORIZED, "Jwt claims string is empty")
}
} else {
throw ResponseStatusException(HttpStatus.UNAUTHORIZED, "Missing auth token")
}
chain.doFilter(req, res)
}
}
}
In my application class too I also have
#SpringBootApplication(exclude = [ErrorMvcAutoConfiguration::class])
Everywhere else in the application ResponseStatusException throws an error with the correct code and in JSON format, here for example when I throw an exception the response will be HTML like
<!doctype html>
HTTP Status 500 – Internal Server Error
body {
font-family: Tahoma, Arial, sans-serif;
}
h1,
h2,
h3,
b {
color: white;
background-color: #525D76;
}
h1 {
font-size: 22px;
}
h2 {
font-size: 16px;
}
h3 {
font-size: 14px;
}
p {
font-size: 12px;
}
a {
color: black;
}
.line {
height: 1px;
background-color: #525D76;
border: none;
}
</style>
HTTP Status 500 – Internal Server Error
Type Exception Report
Message 401 UNAUTHORIZED "Expired JWT token"
Description The server encountered an unexpected condition that prevented it from fulfilling the request.
Exception
org.springframework.web.server.ResponseStatusException: 401 UNAUTHORIZED "Expired JWT token"
events.slap.app.web.security.JwtFilter.doFilter(JwtFilter.kt:40)
org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:116)
org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
org.springframework.web.filter.CorsFilter.doFilterInternal(CorsFilter.java:92)
org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
org.springframework.security.web.header.HeaderWriterFilter.doHeadersAfter(HeaderWriterFilter.java:92)
org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:77)
org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:105)
org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:56)
org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:215)
org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:178)
org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:358)
org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:271)
org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)
org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)
org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)
org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
Note The full stack trace of the root cause is available in the server logs.
Apache Tomcat/9.0.35

Instead of throwing exceptions in the filter, do this
response.sendsetStatus(HttpServletResponse.SC_UNAUTHORIZED);
return;
or if you want message as well
StringBuilder sb = new StringBuilder();
sb.append("{ ");
sb.append("\"error\": \"Unauthorized\" ");
sb.append("\"message\": \"Unauthorized\"");<--- your message here
sb.append("\"path\": \"")
.append(request.getRequestURL())
.append("\"");
sb.append("} ");
response.setContentType("application/json");
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.getWriter().write(sb.toString());
return;

This problem has been bothered me around three-day and thanks #Kavithakaran Kanapathippillai 's comment.
Following is the way I did
if (token != null && token.isNotEmpty()) {
String msg = new String();
try {
tokenService.getClaims(token)
} catch (SignatureException ex) {
msg = "Invalid JWT signature";
} catch (MalformedJwtException ex) {
msg = "Invalid JWT token";
} catch (ExpiredJwtException ex) {
msg = "Expired JWT token";
} catch (UnsupportedJwtException ex) {
msg = "Unsupported JWT token";
} catch (IllegalArgumentException ex) {
msg = "JWT claims string is empty.";
}
if (msg.isNotEmpty()) {
StringBuilder sb = new StringBuilder();
sb.append("{ ");
sb.append("\"error\": \"Unauthorized\",");
sb.append("\"message\": \"Invalid Token.\",");
sb.append("\"path\": \"")
.append(request.getRequestURL())
.append("\"");
sb.append("} ");
response.setContentType("application/json");
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.getWriter().write(sb.toString());
return;
}
chain.doFilter(req, res)

If you are setting up an ServerAuthenticationEntryPoint that is used by SecurityWebFilterChain (comes with #EnableWebFluxSecurity) you can use this
class HttpBasicServerAuthenticationEntryPoint implements ServerAuthenticationEntryPoint
For some reason, I got 500 thrown instead of 401 when I tried to implement my own EntryPoint. It however works as expected and throws 401 now.

Related

Migrating from Jersey client to RestTemplate, but it catch 401 error as HttpClientErrorException but Jersey client was not throwing this error why?

In my Service there was Jersey client implementation to call a rest API now I was migrating this code to RestTemplate.
In old code when there was a 401 error that comes as a response from the backend and I store the response in an object.
But when I migrated the code to RestTeplate the 401 is caught by HttpClientErrorException class so I am not able to get the response since the code flow goes to the catch block.
Jersey Client code
public Employees getEmployees1() throws MyException {
Employee employee=new Employee(23, "Test", "Test", "Test#test.com");
ClientResponse response=null;
try {
Client client = Client.create();
WebResource webResource = client.resource("http://localhost:8080/employees/");
response = webResource.accept("application/json")
.type("application/json").header("Authorization", "invalid Data").post(ClientResponse.class, employee);
}catch (RuntimeException e) {
logger.error("Runtime Error Occured -{} Response - {} ",e.getMessage(),response.getStatus());
throw new MyException("Unexpected Error Occured",e);
}catch (Exception e) {
logger.error("Some Error Occured -{} Response - {} ",e.getMessage(),response.getStatus());
throw new MyException("Unexpected Error Occured",e);
}
return response.readEntity(Employees.class);
}
RestTemplate Code
public Employees getEmployees() throws MyException {
Employee employee=new Employee(23, "Test", "Test", "Test#test.com");
HttpHeaders headers=new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON));
headers.add(HttpHeaders.AUTHORIZATION, "invalid Data");
ResponseEntity<Employees> response=null;
try {
response = this.restTemplate.exchange("http://localhost:8080/employees/", HttpMethod.POST, new HttpEntity<Employee>(employee,headers), Employees.class);
}catch (RuntimeException e) {
logger.error("Runtime Error Occured -{} Response - {} ",e.getMessage(),response.getStatusCode());
throw new MyException("Unexpected Error Occured",e);
}catch (Exception e) {
logger.error("Some Error Occured -{} Response - {} ",e.getMessage(),response.getStatusCode());
throw new MyException("Unexpected Error Occured",e);
}
return response.getBody();
}
By default RestTemplate throws an HttpStatusCodeException (a child of it) for all 400+ status codes - see DefaultResponseErrorHandler. You can change this behavior by setting your own implementation of ResponseErrorHandler to RestTemplate using setErrorHandler or if RestTemplate is constructed using RestTemplateBuilder - using errorHandler method of the builder.
I used the default ResponseErrorHandler, by using this it will bypass all the ResponseError exception
RestTemplate rt = restTemplateBuilder.errorHandler(new ResponseErrorHandler(){
#Override
public boolean hasError(ClientHttpResponse response) throws IOException {
return false;
}
#Override
public void handleError(ClientHttpResponse response) throws IOException {
}})
.build();

Okhttp3.14 Stream closed

I have some usage issue about okhttp in 3.14.9 release
if i want add LoggingInterceptor for each request, how can i get response body, which can only consume once?
And follow is my attemp
public class LoggingRequestInterceptor implements Interceptor {
#Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
Response response = chain.proceed(request);
log.debug(
"{}, {}, {}, {}, {}, {}, {}",
request.url(),
request.method(),
JSONUtil.toJsonStr(request.body()),
request.headers(),
dup.body() == null ? null : dup.body().string());
return response;
}
}
It will throw exception of stream closed, how to fix it?
Use peekBody for this
val client = OkHttpClient.Builder()
.addInterceptor {
val response = it.proceed(it.request())
println(response.peekBody(1000000).string())
response
}
.build()
I do research for this issue. We can use buffer, which is in RequestBody-source-getBuffer.
Working code is below:
public String getResponseBody(Response response) {
try {
ResponseBody responseBody = response.body();
if (!ObjectUtil.isNull(responseBody)) {
BufferedSource source = responseBody.source();
source.request(Long.MAX_VALUE);
Buffer buffer = source.getBuffer();
return buffer.clone().readString(UTF8);
}
} catch (IOException e) {
log.error("get response body failed: ", e);
}
return null;
}
From ernest-kiwele:
Using a try with a resources block with the response causes this "closed" problem when the response body is read outside of the try block:
try (Response response = client.newCall(request.build()).execute()) {
return response;
} //response.close() called implicitly by the JVM
The fix is to restructure the code to only use the response within the try block.

sun.security.validator.ValidatorException while invoking graph API

I am getting below exception while invoking the Microsoft graph API by using swagger in the local.
Getting below exception:
feign.RetryableException: sun.security.validator.ValidatorException: PKIX path building failed:
sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path
#Override
public int addUser(String token, AddUserRequest request) {
Response response = null;
int status = 0;
try {
long time1 = System.currentTimeMillis();
response = graphAPIFeignClient.addUser("Bearer " + token, request);
log.info("{}:: Time taken to call Graph Api is {}", loggingComponentName,
(System.currentTimeMillis() - time1));
if (response.status() == 200) {
status = response.status();
} else if (response.status() == 404) {
throw new SearchResultNotFoundException("get users not found");
} else {
log.error("{}:: Graph Api failed:: status code {}",
loggingComponentName, 500);
throw new AccessManagementException(HttpStatus.valueOf(500), "Graph Api failed");
}
} catch (FeignException ex) {
log.error("{}:: Graph Api failed:: status code {} & message {}",
loggingComponentName, ex.status(), ex.getMessage());
throw new AccessManagementException(HttpStatus.valueOf(ex.status()), "Graph Api failed");
}
return status;
}
//feign client declaration
#PostMapping(value = "/v1.0/users")
#RequestLine("POST v1.0/users")
//#Headers({"Authorization: {authorization}","Content-Type: application/json"})
Response addUser(#RequestHeader(HttpHeaders.AUTHORIZATION) String authorisation,
#RequestBody AddUserRequest request);
Same is working fine with the postman call.enter image description here

Can't open zip archive after upgrading to Spring boot 1.5.1

I switched from spring boot 1.4.3.RELEASE to 1.5.1.RELEASE. I have an HttpServletResponse to which I write the content of the archive, which is downloadable via a rest-endpoint. The file gets downloaded, but I can't open it anymore with the zip unarchiver, which is not the case when using spring boot 1.4.3.
The response headers look like this
X-Frame-Options:DENY
Cache-Control:no-cache, no-store, max-age=0, must-revalidate
X-Content-Type-Options:nosniff
Content-Disposition:attachment; filename="myfile.zip"
Connection:close
Pragma:no-cache
Expires:0
Content-Transfer-Encoding:binary
X-XSS-Protection:1; mode=block
Content-Length:1054691
Date:Tue, 28 Feb 2017 05:39:32 GMT
Content-Type:application/zip
Those are the methods responsible with writing the file to the response:
public void writeZipToResponse(HttpServletResponse response) throws IOException {
Optional<MyObject> myObject= findMyObject();
if (myObject.isPresent()) {
response.addHeader("Content-type", AdditionalMediaTypes.APPLICATION_ZIP);
response.addHeader("Content-Transfer-Encoding", "binary");
response.addHeader("Content-Disposition", "attachment; filename=\"" + myObject.get().getName() + ".zip\"");
response.setStatus(HttpStatus.OK.value());
int lengthOfFile = writeObjectAsArchive(myObject.get(), response.getOutputStream());
response.addHeader("Content-Length", String.valueOf(lengthOfFile));
}
else {
response.setStatus(HttpStatus.NOT_FOUND.value());
}
}
and this:
int writeObjectAsArchive(Collection<Dummy> dummies, OutputStream out) {
try {
ZipOutputStream zipArchive = new ZipOutputStream(out);
int length = 0;
for (Dummy dummy: dummies) {
ZipEntry entry = new ZipEntry(dummy.getFileName());
zipArchive.putNextEntry(entry);
byte[] fileAsByteArray = dummy.getFileAsByteArray();
zipArchive.write(fileAsByteArray);
zipArchive.closeEntry();
length += fileAsByteArray.length;
}
zipArchive.finish();
return length;
}
catch (IOException e) {
throw new RuntimeException(e);
}
}
You must close the output stream.
int writeObjectAsArchive(Collection<Dummy> dummies, OutputStream out) {
try {
ZipOutputStream zipArchive = new ZipOutputStream(out);
...
zipArchive.finish();
zipArchive.close();
return length;
}
catch (IOException e) {
throw new RuntimeException(e);
}
}

How get the bad request message in retrofit 2

My service return Status:
400 Bad Request
with a message:
Invalid credentials(wrong Password)
I can see this in Postman.
The problem is that i don't know how get the
"400 Bad Request message Invalid credentials(wrong Password)" from my android code.
Here is my code:
call.enqueue(new Callback<JWToken>() {
#Override
public void onResponse(Call<JWToken> call, Response<JWToken> response) {
String strMsg = response.message(); // I get message "Bad Request" without the string "Invalid credentials(wrong Password)"
}
How can I get the message "Invalid credentials(wrong Password)" that my web api returns and I can see in Postman?
Thanks a lot!
Try looking in the errorBody() of the Response. Note that errorBody will only be non-null if isSuccessful is false.
call.enqueue(new Callback<JWToken>() {
#Override
public void onResponse(Call<JWToken> call, Response<JWToken> response) {
if (!response.isSuccessful()) {
String strMsg = response.message();
try {
String errorContent = response.errorBody().string();
} catch (IOException e) {
e.printStackTrace();
}
}
}

Resources