I have set up spring security to authenticate and authorize requests coming into my application. I have set up the configuration as so:
public class OAuth2ServerConfiguration extends ResourceServerConfigurerAdapter {
#Override
public void configure(ResourceServerSecurityConfigurer resources) {
// ...set up token store here
resources.authenticationEntryPoint(new AuthenticationEntryPoint() {
#Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
//QUESTION
// How do I get the destination controller that this request was going to go to?
// Really, I'd like to get some information about the annotations that were on the destination controller.
response.setStatus(401);
}
});
}
I'd like to grab some information about the destination controller that this request was going to go to. The controller isn't actually going to get hit in this scenario because spring security kicked in and threw out the response before it reached the controller.
Any tips?
Thanks!
Assuming that OAuth2ServerConfiguration is a Spring managed bean, this should work for you.
...
#Autowired
private List<HandlerMapping> handlerMappings;
for (HandlerMapping handlerMapping : handlerMappings) {
HandlerExecutionChain handlerExecutionChain = handlerMapping.getHandler(request);
if (handlerExecutionChain != null) {
// handlerExecutionChain.getHandler() is your handler for this request
}
}
If unable to Autowire a List of HandlerMapping, Autowire ApplicationContext and adjust as follows.
for (HandlerMapping handlerMapping : applicationContext.getBeansOfType(HandlerMapping.class).values()) {
HandlerExecutionChain handlerExecutionChain = handlerMapping.getHandler(request);
if (handlerExecutionChain != null) {
// handlerExecutionChain.getHandler() is your handler for this request
}
}
You could try this:
#Configuration
public class WebMvcConfiguration extends WebMvcConfigurerAdapter {
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new HandlerInterceptor() {
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
return true;
}
#Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
}
#Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
// handler is the controller
MyAnnotation annotation = ((HandlerMethod) handler).getMethod().getAnnotation(MyAnnotation.class)
// do stuff with the annotation
}
});
}
}
Related
I have a spring boot app(v2.3.0.RELEASE) and I need to get any request being sent from my restcontroller when there is a bean validation error.
My Request is as follows:
public class PaymentRequest {
#Valid
private PaymentIdentificationRequest paymentIdentification;
#NotBlank(message = "transactionTypeCode.required")
private String transactionTypeCode;
#NotBlank(message = "name.required")
private String name;
}
For instance, if name is null, I need an interceptor to capture values of transactionTypeCode and
paymentIdentification before exception is triggered.
I tried implementing the following interceptor to capture all not null parameters value being sent:
public class MyInterceptor extends HandlerInterceptorAdapter {
#Override
public boolean preHandle(HttpServletRequest requestServlet, HttpServletResponse responseServlet, Object handler) throws Exception {
//capture required valued set it in HttpServletRequest attribute to be used for exception handling
HandlerMethod h1 = (HandlerMethod) handler;
MethodParameter[] param = null;
System.out.println("MINIMAL: INTERCEPTOR PREHANDLE CALLED");
return true;
}
#Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
Enumeration<?> e = request.getParameterNames();
System.out.println("MINIMAL: INTERCEPTOR POSTHANDLE CALLED");
}
#Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception exception) throws Exception {
Enumeration<?> e = request.getParameterNames();
System.out.println("MINIMAL: INTERCEPTOR AFTERCOMPLETION CALLED");
}
When the name is null it enters the method preHandle but I am not able to get the parameters and its corresponding values being sent, any idea how to do it pls?
The reason why I am doing the above changes is because I need to set the value of transactionTypeCode and paymentIdentification in my interceptor above so as to use them below in my exception handler as follows:
#ControllerAdvice
public class RestControllerExceptionHandler extends ResponseEntityExceptionHandler {
private #Autowired
HttpServletRequest httpServletRequest;
#Override
public ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException exception, HttpHeaders headers,
HttpStatus status, WebRequest request) {
log.error(exception.getMessage(), exception);
// mapParam is size zero
Map<String, String[]> mapParam = httpServletRequest.getParameterMap();
if (!ObjectUtils.isEmpty(exception) && !ObjectUtils.isEmpty(this.request1)) {
paymentValidator.onErrorUpdatePayment(this.request1.getAttribute("transactionTypeCode"), this.request1.getAttribute("paymentIdentification "), exception.toString());
}
....
return new ResponseEntity<>(ipsResponse, new HttpHeaders(), ipsResponse.getHttpStatus());
}
This is a typical part of Spring Security configuration:
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().and().cors().disable();
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
http.authorizeRequests().antMatchers("/login", "/api/v1/auth/**").permitAll();
http.authorizeRequests().anyRequest().authenticated();
}
I have a problem with http.authorizeRequests().anyRequest().authenticated().
After adding it, when I call non-existing endpoints, for example: GET: /api/v1/not-existing, I receive 403 instead of expected 404 response.
I want to protect all my resources but I want to get 404 when calling not existing resources.
How can I fix it?
I am okay with this behaviour . If an user is not authenticated , why bother to worry about telling him more information about your system. Just like if an user does not have permission to view your harddisk , why need to let him can discover your harddisk directory tree structure .
If you really want to return 404 , you need to customize AuthenticationEntryPoint and AccessDeniedHandler in the ExceptionTranslationFilter . Both of them will be invoked if an user does not have enough permission to visit an endpoint (i.e. AccessDeniedException happen). The former is for the anonymous user and the latter is for the non-anonymous user (i.e. user that is authenticated successfully but without enough permission)
Both of their default implementation (i.e Http403ForbiddenEntryPoint and AccessDeniedHandlerImpl) simply return 403 now . You have to customize them such that they will first check if there are existing endpoints to serve the current HttpServletRequest and return 404 if no. You can do it by looping through the HandlerMapping inside the DispatcherServlet and check if any of the HandlerMapping can handle the current HttpServletRequest.
First create an object that do this check :
public class HttpRequestEndpointChecker {
private DispatcherServlet servlet;
public HttpRequestEndpointChecker(DispatcherServlet servlet) {
this.servlet = servlet;
}
public boolean isEndpointExist(HttpServletRequest request) {
for (HandlerMapping handlerMapping : servlet.getHandlerMappings()) {
try {
HandlerExecutionChain foundHandler = handlerMapping.getHandler(request);
if (foundHandler != null) {
return true;
}
} catch (Exception e) {
return false;
}
}
return false;
}
}
Then customize AuthenticationEntryPoint and AccessDeniedHandler to use this object for checking :
public class MyAccessDeniedHandler extends AccessDeniedHandlerImpl {
private HttpRequestEndpointChecker endpointChecker;
public MyAccessDeniedHandler(HttpRequestEndpointChecker endpointChecker) {
this.endpointChecker = endpointChecker;
}
#Override
public void handle(HttpServletRequest request, HttpServletResponse response,
AccessDeniedException accessDeniedException) throws IOException, ServletException {
if (!endpointChecker.isEndpointExist(request)) {
response.sendError(HttpServletResponse.SC_NOT_FOUND, "Resource not found");
} else {
super.handle(request, response, accessDeniedException);
}
}
}
public class MyAuthenticationEntryPoint extends Http403ForbiddenEntryPoint {
private HttpRequestEndpointChecker endpointChecker;
public MyAuthenticationEntryPoint(HttpRequestEndpointChecker endpointChecker) {
this.endpointChecker = endpointChecker;
}
#Override
public void commence(HttpServletRequest request, HttpServletResponse response,
AuthenticationException authException) throws IOException {
if (!endpointChecker.isEndpointExist(request)) {
response.sendError(HttpServletResponse.SC_NOT_FOUND, "Resource not found");
} else {
super.commence(request, response, authException);
}
}
}
And configure them :
#Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private DispatcherServlet dispatcherServlet;
#Autowired
private HttpRequestEndpointChecker endpointChecker;
#Override
protected void configure(HttpSecurity http) throws Exception {
..............
..............
http.exceptionHandling()
.authenticationEntryPoint(new MyAuthenticationEntryPoint(endpointChecker))
.accessDeniedHandler(new MyAccessDeniedHandler(endpointChecker));
}
#Bean
public HttpRequestEndpointChecker endpointChecker() {
return new HttpRequestEndpointChecker(dispatcherServlet);
}
}
It seems to me that your only option is the following:
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().and().cors().disable();
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
http.authorizeRequests().antMatchers("/login", "/api/v1/auth/**").permitAll();
http.authorizeRequests().antMatchers(all-your-endpoints).authenticated();
http.authorizeRequests().anyRequest().permitAll();
}
You need to replace all-your-endpoints with a regex or multiple regex that match all your endpoints. In fact, you can even get rid of http.authorizeRequests().antMatchers("/login", "/api/v1/auth/**").permitAll(); unless you really want to be explicit about it.
I'm currently implementing audit trail in my project, I tried using HandlerInterceptor and it seems it won't work in my project, so i looked for another way and I discovered that it's possible with OncePerRequestFilter.
Here's the code of my OncePerRequestFilter class:
#Component
#Order
public class LogFilter extends OncePerRequestFilter {
#Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
String method = request.getMethod();
String username = SecurityContextHolder.getContext().getAuthentication().getName();
String url = request.getRequestURL().toString();
// Log the info you need
// ...
filterChain.doFilter(request, response);
}
}
The only problem so far that I see with my current configuration of OncePerRequestFilter is it also includes the resources such as css / javascripts.
example these links will be also go to the filter:
http://localhost:8000/project/css/style.css
http://localhost:8000/project/3277a64fcca0dbde907d8684aed8f170.png
http://localhost:8000/project/js/script.js.map
What i want is to filter only the controller request mappings, and ignore the resources
example:
http://localhost:8000/project/accounts/client-users
http://localhost:8000/project/accounts
This code is a workaround to ignore resource file. not sure if it's the best practice tho.
#Component
#Order
public class LogFilter extends OncePerRequestFilter {
#Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
String method = request.getMethod();
String username = SecurityContextHolder.getContext().getAuthentication().getName();
String url = request.getRequestURL().toString();
filterChain.doFilter(request, response);
}
protected boolean shouldNotFilter(HttpServletRequest request)
throws ServletException {
String url = request.getRequestURL().toString();
return isResourceUrl(url);
}
private boolean isResourceUrl(String url) {
boolean isResourceUrl = false;
List<String> resourceRequests = Arrays.asList(
"/css/", "/js/", "/scss/", "/fonts/", "/emails/",
".css", ".js", ".scss", ".eot", ".svg", ".ttf", ".woff", ".otf", ".ico", ".png");
for (String resourceRequest : resourceRequests) {
if (url.contains(resourceRequest)) {
isResourceUrl = true;
}
}
return isResourceUrl;
}
}
Use something like this:
#Override
public void configure(final WebSecurity web) throws Exception {
web.ignoring()
.antMatchers(
"/example/docs",
"/swagger-resources/**",
"/swagger-ui.html");
}
I'm implementing a (sort of) load balancing HandlerInterceptor using Spring Boot.
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String uri = request.getRequestURI();
if (shouldUseServer1(uri)) {
response.sendRedirect(server1Uri);
} else {
response.sendRedirect(server2Uri);
}
}
The idea is, that based on the url, we either redirect to one service or another. The application doesn't have any explicit RequestMappings (yet).
Now the problem is, when the interceptor is called, the request is redirected to the default Spring error handler. As a result the URI stored in the HttpServletRequest is replaced by /error (effectively denying the access to the original URI).
Is there any way to intercept a request before it is rerouted to the error handler (or to get the original uri)?
EDIT:
Because of the way Spring MVC handles requests with no mapping, you'll either need a filter:
#Component
public class CustomFilter implements Filter {
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
request.getSession().setAttribute("ORIGINAL_REQUEST_URI", request.getRequestURI());
chain.doFilter(request, response);
// alternatively, ignore the last 2 lines
// and just do your redirects from here
// and don't continue the filter chain
}
#Override
public void destroy() {}
#Override
public void init(FilterConfig arg0) throws ServletException {}
}
Otherwise, if you'd rather not rely on the session, you'll need to make the DispatcherServlet throw an exception in case no handler mapping is found, and then send the redirect from a #ControllerAdvice error handler:
#ControllerAdvice
class NoHandlerFoundExceptionExceptionHandler {
#ExceptionHandler(value = NoHandlerFoundException.class)
public ModelAndView
defaultErrorHandler(HttpServletRequest req, NoHandlerFoundException e) throws Exception {
String uri = // resolve the URI
return new ModelAndView("redirect:" + uri);
}
}
To avoid duplication, you may want to have a common class that you'll call from both the interceptor and the error handler.
I have a customized spring AuthenticationProvider class but try to intercept the HTTPServletRequest and HTTPServletResponse within the loadUserDetails method.
#Component("darnGoodAuthenticaionProvider")
public class DarnGoodAuthenticaionProvider
extends HandlerInterceptorAdapter
implements AuthenticationUserDetailsService {
private HttpServletRequest request;
private HttpServletResponse response;
#Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler)
throws Exception {
this.request = request;
this.response = response;
// we don't want anything falling here
return true;
}
#Override
public UserDetails loadUserDetails(Authentication token)throws
UsernameNotFoundException{
.......
}
}
I know the preHandler method from HandlerIntercepterAdapter is capable to the job but how can I be sure that the preHandler method is called prior to loadUserDetails, so that I can get the request and response prepared?
Thanks
On a servlet container, each request will be handled from the moment the request is received until the response is returned by only one thread (request == current thread).
So it's a matter of putting a servlet filter BEFORE the spring security filter chain (with the filter-mapping element above the filter-mapping of spring security), and storing the request and response in the thread using a ThreadLocal variable - see also this answer.
Then on the DarnGoodAuthenticaionProvider access the request using a static method RequestResponseHolder.getRequest().
web.xml config:
<filter>
<filter-name>saveRequestResponseFilter</filter-name>
<filter-class>sample.save.request.filter.SaveRequestResponseFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>saveRequestResponseFilter</filter-name>
<url-pattern>/mobilews/*</url-pattern>
</filter-mapping>
Filter to save the request response in the thread:
public class SaveRequestResponseFilter implements Filter {
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse resp = (HttpServletResponse) response;
RequestResponseHolder.setRequestResponse(req,resp);
try {
chain.doFilter(request, response);
}
finally {
RequestResponseHolder.clear();
}
}
#Override
public void init(FilterConfig filterConfig) throws ServletException {
...
}
#Override
public void destroy() {
...
}
}
Request/Response holder:
public class RequestResponseHolder {
private static ThreadLocal<HttpServletRequest> requestHolder = new ThreadLocal<HttpServletRequest>();
private static ThreadLocal<HttpServletResponse> responseHolder = new ThreadLocal<HttpServletResponse>();
public static void setRequestResponse(HttpServletRequest request, HttpServletResponse response) {
requestHolder.set(request);
responseHolder.set(response);
}
public static HttpServletRequest getServletRequest(){
return requestHolder.get();
}
public static HttpServletResponse getServletResponse() {
return responseHolder.get();
}
public static void clear() {
requestHolder.remove();
responseHolder.remove();
}
}
Obtaining the request from DarnGoodAuthenticaionProvider:
HttpServletRequest req = RequestResponseHolder.getServletRequest();