Thowing custom AuthenticationException inside UsernamePasswordAuthenticationFilter's attemptAuthentication not working when reading in AuthEntryPoint - spring

Custom AuthenticationException:
public class UnauthorizedException extends AuthenticationException {
public UnauthorizedException(final String message) {
super(message);
}
public UnauthorizedException(final String message, final Throwable cause) {
super(message, cause);
}
}
Custom UsernamePasswordAuthenticationFilter's attemptFunction():
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) {
try {
ObjectMapper mapper = new ObjectMapper();
UserRequestDto userRequestDto = mapper.readValue(request.getInputStream(), UserRequestDto.class);
UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(
userRequestDto.getUsername(), userRequestDto.getPassword()
);
return this.authenticationManager.authenticate(authToken);
} catch (IOException ioException) {
throw new ServerErrorException(this.accessor.getMessage("server.error"));
} catch (DisabledException disabledException) {
throw new UnauthorizedException(this.accessor.getMessage("user.disabledAccount"));
} catch (LockedException lockedException) {
throw new UnauthorizedException(this.accessor.getMessage("user.lockedAccount"));
} catch (BadCredentialsException | EntryNotFoundException badCredentialsException) {
throw new UnauthorizedException(this.accessor.getMessage("user.incorrectCredentials"));
} catch (AuthenticationException entryNotFoundException) {
throw new UnauthorizedException(this.accessor.getMessage("user.incorrectCredentials"));
}
}
Custom AuthenticationEntryPoint:
public void commence(HttpServletRequest request, HttpServletResponse response,
AuthenticationException authException) throws IOException, ServletException {
System.out.println(authException.getMessage());
int UNAUTHORIZED = HttpStatus.UNAUTHORIZED.value();
response.setStatus(UNAUTHORIZED);
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
Response<Integer> responseBuild = Response.build(String.valueOf(UNAUTHORIZED), true);
new ObjectMapper().writeValue(response.getOutputStream(), responseBuild);
}
The last catch block inside attemptAuthentication() method throws custom AuthenticationException with custom message ("Incorrect username or password"), but when printing AuthenticationException's message in AuthenticationEntryPoint's commence() method, it prints out "InsufficientAuthenticationException: Full authentication is required to access this resource" (both when given username does and does not exist in the database).
What am I missing here?

Related

Customizing NoHandlerException response with ControllerAdvice

I try to implement a custom error response in a spring boot rest application for 404 errors.
I read many of the solutions presented in stackoverflow, without success.
When I call an invalid entry point I obtain this result:
{
"timestamp": "2022-06-22T10:38:41.114+00:00",
"status": 404,
"error": "Not Found",
"path": "/ws-facturx/fx2"
}
But i'd like to have a response that should look like this:
{
"operationId": "u044eZg2gHwtadqxB5CVv6aeMBjj0w",
"status": "ERROR",
"operation": "webserviceName",
"clientName": "ACME Inc",
"errorMessage": "Error message from Server",
"createdAt": "2022-06-22T09:15:04.844+00:00"
}
I first tried to use #RestControllerAdvice to intercept the exception when they are thrown.
#ExceptionHandler(value = {AppServiceException.class, NoHandlerFoundException.class, ServletServiceException.class })
public ResponseEntity<Object> handleAppServiceException(Exception ex,
WebRequest req) throws JsonProcessingException {
FacturxDto request = context.getFacturxDtoContext();
ErrorMessage errorMessage = errorMessageBuilder(request, ex);
return new ResponseEntity<>(errorMessage, new HttpHeaders(), HttpStatus.INTERNAL_SERVER_ERROR);
}
I also modified my application.properties :
spring.mvc.throw-exception-if-no-handler-found=true
spring.web.resources.add-mappings=false
If i call a non defined entry point I do not reach this method. I tried to use an interceptor.
I firs added a class for adding interceptor to InterceptorRegistry:
#Configuration
public class WebMvcConfig implements WebMvcConfigurer {
private final ApplicationExchangeContext context;
public WebMvcConfig(ApplicationExchangeContext context) {
this.context = context;
}
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new ApplicationInterceptor(context)).addPathPatterns("/**");
}
}
My ApplicationInterception looks like this:
#Component
public class ApplicationInterceptor implements HandlerInterceptor {
private final ApplicationExchangeContext context;
#Autowired
public ApplicationInterceptor(ApplicationExchangeContext context) {
this.context = context;
}
//unimplemented methods comes here. Define the following method so that it
//will handle the request before it is passed to the controller.
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if (response.getStatus() == HttpStatus.NOT_FOUND.value()) {
// si on a un 404
System.out.println(handler);
String requestData = request.getReader().lines().collect(Collectors.joining());
System.out.println(requestData);
Gson gson = new Gson();
FacturxDto facturxDto = gson.fromJson(requestData, FacturxDto.class);
context.setFacturxDtoContext(facturxDto);
throw new ServletServiceException("404...");
}
System.out.println("Done in preHandle");
return true;
// return HandlerInterceptor.super.preHandle(request, response, handler);
}
#Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
// HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
System.out.println(request);
System.out.println(response);
if (response.getStatus() == HttpStatus.NOT_FOUND.value()) {
// si on a un 404
System.out.println(handler);
String requestData = request.getReader().lines().collect(Collectors.joining());
System.out.println(requestData);
Gson gson = new Gson();
FacturxDto facturxDto = gson.fromJson(requestData, FacturxDto.class);
context.setFacturxDtoContext(facturxDto);
throw new ServletServiceException("404...");
}
System.out.println("Done in afterCompletion");
}
}
On the preHandle, i do reach the catch part of the code block but i do not access the RestControllerAdvice method that should handle this exception and build my expected object.
The exception is thrown. But i do not return it to user. Instead I do have an HTML page.

Spring boot application filter response body

I am working on a spring boot application. I want to modify the response of the request by request body field "Id".
I have implemented below, but still getting just the name in the output while implementing.Any suggestions on implementing below would be helpful:
Below is the requestBody:
{
"id" : "123"
}
In response, I want to append that field to response id(fieldname from request body).
responseBody:
{
"name" : "foo123" //name + id from request
}
MyCustomFilter:
public class TestFilter implements Filter {
#Override
public void doFilter(
ServletRequest request,
ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletResponse res = (HttpServletResponse) response;
ByteArrayOutputStream baos = new ByteArrayOutputStream();
final PrintStream ps = new PrintStream(baos);
MultiReadHttpServletRequest wrapper = new MultiReadHttpServletRequest((HttpServletRequest) request);
MyRequestWrapper req = new MyRequestWrapper(wrapper);
String userId = req.getId();
chain.doFilter(wrapper, new HttpServletResponseWrapper(res) {
#Override
public ServletOutputStream getOutputStream() throws IOException {
return new DelegatingServletOutputStream(new TeeOutputStream(super.getOutputStream(), ps)
);
}
#Override
public PrintWriter getWriter() throws IOException {
return new PrintWriter(new DelegatingServletOutputStream(new TeeOutputStream(super.getOutputStream(), ps))
);
}
});
String responseBody = baos.toString();
ObjectMapper mapper = new ObjectMapper();
JsonNode node = mapper.readTree(responseBody);
String name = node.get("name").astext();
((ObjectNode) node1).put("name", name + userId);
chain.doFilter(wrapper, res);
}
MyRequestWrapper:
public class MyRequestWrapper extends HttpServletRequestWrapper {
private ServletInputStream input;
public MyRequestWrapper(ServletRequest request) {
super((HttpServletRequest)request);
}
public String getId() throws IOException {
if (input == null) {
try {
JSONObject jsonObject = new JSONObject(IOUtils.toString(super.getInputStream()));
String userId = jsonObject.getString("id");
userId = userId.replaceAll("\\D+","");
return userId;
} catch (JSONException e) {
e.printStackTrace();
}
}
return null;
}
}
MultiReadHttpServletRequest.java
public class MultiReadHttpServletRequest extends HttpServletRequestWrapper {
private byte[] body;
public MultiReadHttpServletRequest(HttpServletRequest request) {
super(request);
try {
body = IOUtils.toByteArray(request.getInputStream());
} catch (IOException ex) {
body = new byte[0];
}
}
#Override
public BufferedReader getReader() throws IOException {
return new BufferedReader(new InputStreamReader(getInputStream(), getCharacterEncoding()));
}
#Override
public ServletInputStream getInputStream() throws IOException {
return new ServletInputStream() {
ByteArrayInputStream wrapperStream = new ByteArrayInputStream(body);
#Override
public boolean isFinished() {
return false;
}
#Override
public boolean isReady() {
return false;
}
#Override
public void setReadListener(ReadListener readListener) {
}
#Override
public int read() throws IOException {
return wrapperStream.read();
}
};
}
}
Any suggestions are appreciated. TIA.
Nte: After update i am not able to see the updated response as output. I am still seeing just the name but not id appended to it.
The one issue I see with your own implementation of ServletRequest is that you call super.getInputStream() instead of request.getInputStream(). Your own request is empty by default, that's why you're getting time out exception. You have to delegate a call to the actual request:
public class MyRequestWrapper extends HttpServletRequestWrapper {
private ServletInputStream input;
public MyRequestWrapper(ServletRequest request) {
super((HttpServletRequest)request);
}
public String getId() throws IOException {
if (input == null) {
try {
JSONObject jsonObject = new JSONObject(IOUtils.toString(/*DELETEGATE TO ACTUAL REQUEST*/request.getInputStream()));
String userId = jsonObject.getString("id");
userId = userId.replaceAll("\\D+","");
return userId;
} catch (JSONException e) {
e.printStackTrace();
}
}
return null;
}
}

How to set implementation of OncePerRequestFilter to filter only one endpoint

Im using this Filter to log my requests and responses, it works very well, but I noticed that I didnt need actualy this filter for all my endpoints - it will be more efficient and enough to filtering and logging requests from only one endpoint.
Is it possible without making if statement in afterRequest method?
Im searching this a lot, but almost every example is with spring security :(
#Slf4j
#Component
public class RequestAndResponseLoggingFilter extends OncePerRequestFilter {
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
if (isAsyncDispatch(request)) {
filterChain.doFilter(request, response);
} else {
doFilterWrapped(wrapRequest(request), wrapResponse(response), filterChain);
}
}
protected void doFilterWrapped(ContentCachingRequestWrapper request, ContentCachingResponseWrapper response, FilterChain filterChain) throws ServletException, IOException {
beforeRequest(request, response);
try {
filterChain.doFilter(request, response);
}
finally {
afterRequest(request, response);
response.copyBodyToResponse();
}
}
protected void beforeRequest(ContentCachingRequestWrapper request, ContentCachingResponseWrapper response) {
if (log.isInfoEnabled()) {
val address = request.getRemoteAddr();
val queryString = request.getQueryString();
if (queryString == null) {
log.info("{}> {} {}", address, request.getMethod(), request.getRequestURI());
} else {
log.info("{}> {} {}?{}", address, request.getMethod(), request.getRequestURI(), queryString);
}
Collections.list(request.getHeaderNames()).forEach(headerName ->
Collections.list(request.getHeaders(headerName)).forEach(headerValue ->
log.info("{}> {}: {}", address, headerName, headerValue)));
val content = request.getContentAsByteArray();
if (content.length > 0) {
log.info("{}>", address);
try {
val contentString = new String(content, request.getCharacterEncoding());
Stream.of(contentString.split("\r\n|\r|\n")).forEach(line -> log.info("{}> {}", address, line));
} catch (UnsupportedEncodingException e) {
log.info("{}> [{} bytes body]", address, content.length);
}
}
log.info("{}>", address);
}
}
protected void afterRequest(ContentCachingRequestWrapper request, ContentCachingResponseWrapper response) {
if (log.isInfoEnabled()) {
val address = request.getRemoteAddr();
val status = response.getStatus();
log.info("{}< {} {}", address, status, HttpStatus.valueOf(status).getReasonPhrase());
response.getHeaderNames().forEach(headerName ->
response.getHeaders(headerName).forEach(headerValue ->
log.info("{}< {}: {}", address, headerName, headerValue)));
val content = response.getContentAsByteArray();
if (content.length > 0) {
log.info("{}<", address);
try {
val contentString = new String(content, request.getCharacterEncoding());
Stream.of(contentString.split("\r\n|\r|\n")).forEach(line -> log.info("{}< {}", address, line));
} catch (UnsupportedEncodingException e) {
log.info("{}< [{} bytes body]", address, content.length);
}
}
}
}
private static ContentCachingRequestWrapper wrapRequest(HttpServletRequest request) {
if (request instanceof ContentCachingRequestWrapper) {
return (ContentCachingRequestWrapper) request;
} else {
return new ContentCachingRequestWrapper(request);
}
}
private static ContentCachingResponseWrapper wrapResponse(HttpServletResponse response) {
if (response instanceof ContentCachingResponseWrapper) {
return (ContentCachingResponseWrapper) response;
} else {
return new ContentCachingResponseWrapper(response);
}
}
}

How to read httpServletResponse in the interceptor?

I have a spring boot application. And now I need to read request and response in interceptor.I use a HttpServletRequestWrapper replace the request in DispatcherServlet
#Component("dispatcherServlet")
public class FofDisPatcherServlet extends DispatcherServlet {
#Override
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
MultiReadHttpServletRequest requestWrapper = null;
try {
requestWrapper = new MultiReadHttpServletRequest(request);
super.doDispatch(requestWrapper, response);
} catch (Exception e) {
super.doDispatch(request,response);
}
}
}
And in my interceptor , I can read the request body. But when I want to read the response body, it doesn't works.when I replace the response in the CustomerDispatcherServlet I got nothing response.I have tried ContentCachingResponseWrapper , but I got the payload with "".
It's a old question.and I have search some questions but didn't find a suitable solution.
I know I can solve the problem with AOP.But I want to know how can I do it in the interceptor?
here is my interceptor code
public class CustomerInterceptor extends HandlerInterceptorAdapter{
#Override
public void postHandle(...){
MultiReadHttpServletRequest req = (MultiReadHttpServletRequest) request;
ContentCachingResponseWrapper res = new ContentCachingResponseWrapper(response);
Byte[] body = res. getContentAsByteArray();
...
}
}
the body I got is [].
After few days .I find the answer.In the CustomerDispatcherServlet I should add responseWrapper.copyBodyToResponse()
the CustomerDIspatcherServlet like this:
#Component("dispatcherServlet")
public class FofDisPatcherServlet extends DispatcherServlet {
#Override
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
MultiReadHttpServletRequest requestWrapper = null;
try {
requestWrapper = new MultiReadHttpServletRequest(request);
if (!(response instanceof ContentCachingResponseWrapper)) {
ContentCachingResponseWrapper responseWrapper = new ContentCachingResponseWrapper(response);
super.doDispatch(requestWrapper, responseWrapper);
responseWrapper.copyBodyToResponse();
}else {
super.doDispatch(requestWrapper, response);
}
} catch (Exception e) {
super.doDispatch(request, response);
}
}
}
Try this:
#Component("dispatcherServlet")
public class FofDisPatcherServlet extends DispatcherServlet {
#Override
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
MultiReadHttpServletRequest requestWrapper = null;
try {
requestWrapper = new MultiReadHttpServletRequest(request);
super.doDispatch(requestWrapper, new ContentCachingResponseWrapper(request));
} catch (Exception e) {
super.doDispatch(request,response);
}
}
}
.
public class CustomerInterceptor extends HandlerInterceptorAdapter{
#Override
public void postHandle(..., HttpServletResponse response){
if (response instanceof ContentCachingResponseWrapper) {
Byte[] body = ((ContentCachingResponseWrapper)response). getContentAsByteArray();
}
...
}
}
The error is in your code
public class CustomerInterceptor extends HandlerInterceptorAdapter{
#Override
public void postHandle((HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView){
MultiReadHttpServletRequest req = (MultiReadHttpServletRequest) request;
ContentCachingResponseWrapper res = new ContentCachingResponseWrapper(response);
Byte[] body = res. getContentAsByteArray();
...
}
}
You are passing request in ContentCachingResponseWrapper.
See this question very similar problem .

LDAP custom authentication filter

I have a custom authentication CustomAuthenticationProvider class which does a authenticate the user by hitting LDAP remote server. I managed to create and configure the custom authentication provider but having trouble to call doAuthentication method which is defined in SecurityConfiguration.
#Component
public class CustomAuthenticationProvider implements AuthenticationProvider {
private final Logger log=LoggerFactory.getLogger(CustomAuthenticationProvider.class);
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String username = authentication.getName();
String password = (String) authentication.getCredentials();
if (username == null) {
throw new BadCredentialsException("User is not found");
}
if (password == null) {
throw new BadCredentialsException("Password is not found");
}
try {
LdapContextSource ldapContextSource = new LdapContextSource();
ldapContextSource.setUrl("ldap://jnj.com:3268");
ldapContextSource.setBase("dc=jnj,dc=com");
ldapContextSource.setUserDn(username);
ldapContextSource.setPassword(password);
try {
// initialize the context
ldapContextSource.afterPropertiesSet();
} catch (Exception e) {
e.printStackTrace();
}
LdapTemplate ldapTemplate = new LdapTemplate(ldapContextSource);
ldapTemplate.afterPropertiesSet();
// ldapTemplate.setIgnorePartialResultException(true); // Active Directory doesn’t transparently handle referrals. This fixes that.
AndFilter filter = new AndFilter();
filter.and(new EqualsFilter("sAMAccountName", username));
try {
boolean authed = ldapTemplate.authenticate("", filter.toString(), password);
log.debug("Auuthenticated : "+authed);
} catch (org.springframework.ldap.AuthenticationException ee) {
//userDisplay.setText(“Invalid Username/Password”);
}
} catch (Exception e) {
e.printStackTrace();
}
Collection<? extends GrantedAuthority> authorities = Collections.singleton(new SimpleGrantedAuthority("ROLE_USER"));
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username, password, authorities);
return authenticationToken;
// return new UsernamePasswordAuthenticationToken(username,password);
}
#Override
public boolean supports(Class<?> authentication) {
return authentication.equals(UsernamePasswordAuthenticationToken.class);
}
}
This method is called in SecurityConfiguration,I need to bind this method to mine CustomAuthenticationProvider class
#Inject
public void configureGlobal(AuthenticationManagerBuilder auth) {
try {
auth.authenticationProvider(authProvider)
.ldapAuthentication().userDetailsContextMapper(userDetailsContextMapper())
.ldapAuthoritiesPopulator(ldapAuthoritiesPopulator()).userSearchBase(USER_SEARCH_BASE)
.userSearchFilter(USER_SEARCH_FILTER).groupSearchBase(GROUP_SEARCH_BASE)
.groupSearchFilter(GROUP_SEARCH_FILTER).contextSource(contextSource());
} catch (Exception e) {
throw new BeanInitializationException("Security configuration failed", e);
}
}
This method I need to call for LDAP
protected DirContextOperations doAuthentication(UsernamePasswordAuthenticationToken auth) {
String username = auth.getName();
String password = (String) auth.getCredentials();
DirContext ctx = bindAsUser(username, password);
try {
return searchForUser(ctx, username);
} catch (NamingException e) {
log.error("Failed to locate directory entry for authenticated user: " + username, e);
throw badCredentials(e);
} finally {
LdapUtils.closeContext(ctx);
}
}

Resources