Store url when #Controller is accessed Spring MVC - spring

Is there a way to access the URL resolved from a Spring MVC controller - e.g.
#RequestMapping("/{language}/news/{articleId}")
public String newsPage(...) {
}
Resolves to:
/en/news/63421
I'd like to store this with the session so I can keep a track of last place visited. The motivation here is if the page is secured the login filter will come into play and we have used
SavedRequestAwareAuthenticationSuccessHandler
to route users back to the page they were trying to access.
However if they are viewing unsecured pages and choose to log in using a form that drops down from the top of the screen (the page's menu bar) the 'last page' seems to be the login form so the success handler drops them back to the root context.
I'd like to intercept controller calls and store a single URL with the session, override SavedRequestAwareAuthenticationSuccessHandler to allow us to modify the RequestCache and then let Spring redirect on login success.
Ideally we'd like a generic way to do this across all controllers but not sure if there is a filter we can use to pick this up - filtering requests gets all sorts of noise like css, js, images and html fragment pages so we're hoping someone knows a way to do this just with the controllers themselves.

There are two questions:
1) obtain the url in a controller method
#RequestMapping("/{language}/news/{articleId}")
public String newsPage(..., HttpServletRequest request) {
String uri = request.getRequestUri();
...
}
If you need this very often then you can implement a HandlerMethodArgumentResolver. *See this answer https://stackoverflow.com/a/8769670/280244 for an example (it implements a HandlerMethodArgumentResolver for the current user, but you can easyly adapt it for urls)
2.) store the url for each request in the session
You can implement a Servlet Filter or Spring HandlerInterceptor, both get a HttpServletRequest (In a Servlet Filter you need to cast the ServletRequest to an HttpServletRequest first.
Then you can obtain the url and the Session httpServletRequest.getSession() and then store the url in the session.
public class MyFilter implements Filter {
#Override
public void init(final FilterConfig filterConfig) throws ServletException {
//do nothing
}
#Override
public void doFilter(ServletRequest requ, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
if (requ instanceof HttpServletRequest) {
HttpServletRequest httpServletRequest = (HttpServletRequest) requ;
httpServletRequest.getSession().setAttribute(
"myFilter.LAST_URL",
httpServletRequest .getRequestURI());
}
chain.doFilter(request, response);
}
#Override
public void destroy() {
}
}

To get the URL path you can use the HttpServletRequest - so for example you have:
www.mysite.com/en/news/63421
req.getPathInfo() = /en/news/63421
Storing it in the session though could cause problems if someone is to use your site with multiple tabs open.

Related

Copy RequestParams to RequestHeaders before handling in RestController

To replace a legacy system and not breaking the interface, I'm looking for a way to implement the following scenario:
If a REST client hasn't set a specific HTTP header (applicationId) but sends it as a query-paramter (aka RequestParameter), this value should be taken as a method parameter in a Spring Boot RestController.
The current method looks like this:
#RequestMapping(value = "/something", method = RequestMethod.GET)
public void doSomething(#RequestHeader("applicationId") String applicationId) { }
I think there could be two possible ways:
Annotate the method somehow to map a query-parameter OR a header to a method parameter
Write an Interceptor which reads all query-parameters of a request and set non-existing headers with their values. This way, the method wouldn't have to be touched at all.
In both ways I'm not sure how to implement them (don't know if 1. is even possible). I tried with an own HandlerInterceptor which reads query-params in preHandle (successfully) but isn't able to set headers in the request before it is forwarded to the RestController.
Write a Filter that wraps the incoming request using a HttpServletRequestWrapper. This wrapper should override the getHeader method.
public ParameterToHeaderWrappingRequestFilter extends OncePerRequestFilter {
protected void doFilterInternal(
HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
filterChain.doFilter(new ParameterToHeaderWrappingRequest(request), response, filterChain);
}
}
Register this filter as a #Bean in your Spring Boot application and it will be applied automatically.
public class ParameterToHeaderWrappingRequest extends HttpServletRequestWrapper {
public String getHeader(String name) {
String header = super.getHeader(name);
if (header == null) {
header = getParameter(name);
}
return header;
}
}
Something like that should do the trick. Depending on your needs you might want/need to override some additional header based methods and you probably want to limit the number of headers to override with parameters.
The rest of your code can now be written as is.

Spring Session not working on Tomcat 8 when using Tiles - SESSION Cookie is not set as response is already included

I am using Spring Session 1.2.0.RELEASE on a Spring Boot Project. This is packaged as a war and deployed on Tomcat 8.
I have followed Spring Session documentation and configured it properly. The problem is that the entry point to the application is a controller that sets some value on session but the SESSION cookie is not sent to the browser.
Debugging I see that:
org.springframework.session.web.http.CookieHttpSessionStrategy.onNewSession() tries to write the cookie:
this.cookieSerializer
.writeCookieValue(new CookieValue(request, response, cookieValue));
org.springframework.session.web.http.DefaultCookieSerializer.writeCookieValue() sets the cookie in the response:
response.addCookie(sessionCookie);
The cookie isn't actually written. The underlying response object is org.apache.catalina.core.ApplicationHttpResponse. Its addCookie() method is:
/**
* Disallow <code>addCookie()</code> calls on an included response.
* #param cookie The new cookie
*/
#Override
public void addCookie(Cookie cookie) {
if (!included)
((HttpServletResponse) getResponse()).addCookie(cookie);
}
The problem is that included attribute, which at some point is set true, preventing the cookie from being added.
This happens when the jsp (using tiles) is being serviced:
UPDATE:
This is the moment when the response is being marked as included (when standard.jsp tiles layout is inserting an attribute:
<tiles:insertAttribute name="header" ignore="false"/>
To work around this problem I ended up creating a filter to enforce the creation of the session.
As seen, the first call to the controller didn't add the cookie because during the Tiles-JSP rendering the response was already marked as included. What I do is forcing the creation of the session in the filter and redirecting asking the very same requestURI. This way, since the call doesn't involve a tiles rendering the cookie is created and can be used right away in the next calls.
#Bean
#ConditionalOnExpression("${sessionEnforcerFilter.enabled:true}")
public FilterRegistrationBean sessionEnforcerFilter(){
logger.info("Registering sessionEnforcerFilter");
FilterRegistrationBean frb = new FilterRegistrationBean();
frb.setName("sessionEnforcerFilter");
frb.setFilter(new SessionEnforcerFilter());
frb.setUrlPatterns(Arrays.asList(new String[]{"/*"}));
return frb;
}
public class SessionEnforcerFilter implements Filter{
#Override
public void init(FilterConfig filterConfig) throws ServletException {}
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)throws IOException, ServletException {
HttpServletRequest httpServletRequest = (HttpServletRequest)request;
HttpServletResponse httpServletResponse = (HttpServletResponse)response;
if(httpServletRequest.getSession(false)==null){
logger.debug("sessionEnforcerFilter.doFilter () - Session is null - forcing its creation");
httpServletRequest.getSession();
String requestURI = httpServletRequest.getRequestURI();
logger.debug("sessionEnforcerFilter.doFilter () - Repeating request [{}]", requestURI);
httpServletResponse.sendRedirect(requestURI);
}else{
chain.doFilter(httpServletRequest, response);
}
}
#Override
public void destroy() {}
}
summary
Hold breakPoint in SessionRepositoryResponseWrapper.onResponseCommitted().
Check that the response object inside the SessionRepositoryRequestWrapper is a non-wrapped response. (included = false)
If it is a wrapped response object, make sure that the sessionRepositoryFilter comes first.
================
Spring-session is already handling the problem when 'DispatcherType.INCLUDE (included = true)'.
SessionRepositoryResponseWrapper.onResponseCommitted() is trying to addCookie to the original response object.
The sessionRepositoryFilter must be in the first position to wrap the original applicationHttpResponse passed by tomcat.
Problem Situation
The SessionRepositoryRequestWrapper receives the wrapped response and holds it.
When executing doInclude() in the servlet container, find the original reponse and wrap it with ApplicationHttpResponse (included = true).
Then, SetResponse (new wrapping response) to the innermost wrapper.
http://grepcode.com/file/repo1.maven.org/maven2/org.apache.tomcat.embed/tomcat-embed-core/8.0.24/org/apache/catalina/core/ApplicationDispatcher.java#ApplicationDispatcher.doInclude%28javax.servlet.ServletRequest%2Cjavax.servlet.ServletResponse%29
http://grepcode.com/file/repo1.maven.org/maven2/org.apache.tomcat.embed/tomcat-embed-core/8.0.24/org/apache/catalina/core/ApplicationDispatcher.java#ApplicationDispatcher.wrapResponse%28org.apache.catalina.core.ApplicationDispatcher.State%29
Spring-session does an addCookie on the response (expecting the original response) stored in SessionRepositoryResponseWrapper.onResponseCommitted(), but it can not because it is set to 'included = true'.

Wildfly Database Module Authentication : How to record logins [duplicate]

Given an authentication mechanism of type FORM defined for a Java web app, how do you capture the login performed event before being redirected to requested resource? Is there any kind of listener where I can put my code to be executed when a user logs in?
I feel like defining a filter is not the best solution, as the filter is linked to the resource and would be invoked even when the user is already authenticated and asking for a resource. I'm wondering if there's some class/method triggered only by login event.
There's no such event in Java EE. Yet. As part of JSR375, container managed security will be totally reworked as it's currently scattered across different container implemantations and is not cross-container compatible. This is outlined in this Java EE 8 Security API presentation.
There's already a reference implementation of Security API in progress, Soteria, developed by among others my fellow Arjan Tijms. With the new Security API, CDI will be used to fire authentication events which you can just #Observes. Discussion on the specification took place in this mailing list thread. It's not yet concretely implemented in Soteria.
Until then, assuming FORM based authentication whereby the user principal is internally stored in the session, your best bet is manually checking in a servlet filter if there's an user principal present in the request while your representation of the logged-in user is absent in the HTTP session.
#Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) {
HttpServletRequest request = (HttpServletRequest) req;
String username = request.getRemoteUser();
if (username != null && request.getSession().getAttribute("user") == null) {
// First-time login. You can do your thing here.
User user = yourUserService.find(username);
request.getSession().setAttribute("user", user);
}
chain.doFilter(req, res);
}
Do note that registering a filter on /j_security_check is not guaranteed to work as a decent container will handle it internally before the first filters are hit, for obvious security reasons (user-provided filters could manipulate the request in a bad way, either accidentally or awarely).
If you however happen to use a Java EE server uses the Undertow servletcontainer, such as WildFly, then there's a more clean way to hook on its internal notification events and then fire custom CDI events. This is fleshed out in this blog of Arjan Tijms. As shown in the blog, you can ultimately end up with a CDI bean like this:
#SessionScoped
public class SessionAuthListener implements Serializable {
private static final long serialVersionUID = 1L;
public void onAuthenticated(#Observes AuthenticatedEvent event) {
String username = event.getUserPrincipal().getName();
// Do something with name, e.g. audit,
// load User instance into session, etc
}
public void onLoggedOut(#Observes LoggedOutEvent event) {
// take some action, e.g. audit, null out User, etc
}
}
You can use Servlet filter on the j_security_check URI. This filter will not be invoke on every request, but only on the login request.
Check the following page - Developing servlet filters for form login processing - this works in WebSphere App Server, and WebSphere Liberty profile.
Having such filter:
#WebFilter("/j_security_check")
public class LoginFilter implements Filter {
...
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
System.out.println("Filter called 1: " +((HttpServletRequest)request).getUserPrincipal());
chain.doFilter(request, response);
System.out.println("Filter called 2: " + ((HttpServletRequest)request).getUserPrincipal());
}
gives the following output:
// on incorrect login
Filter called 1: null
[AUDIT ] CWWKS1100A: Authentication did not succeed for user ID user1. An invalid user ID or password was specified.
Filter called 2: null
// on correct login
Filter called 1: null
Filter called 2: WSPrincipal:user1
UPDATE
Other possible way to do it is to use your own servlet for login, change the action in your login page to that servlet and use request.login() method. This is servlet API so should work even in Wildfly and you have full control over login. You just need to find out how wildfly passes the originally requested resource URL (WebSphere does it via cookie).
Servlet pseudo code:
public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String user = request.getParameter("j_username");
String password = request.getParameter("j_password");
try {
request.login(user, password);
// redirect to requested resource
} catch (Exception e) {
// login failed - redirect to error login page
}

spring authentication entry point

I have controller method, which annotated with
#RequestMapping(value = "/someting")
#PreAuthorize("hasAnyRole('ROLE_ACTIVE')")
...
When users without it role transit on this mapping I want to make the users without the appropriate role of the redirect to the home page and displays an alert, the fact that access is denied.
To solve this problem I make custom AccessDeniedHandler, which works perfectly, but only for authenticated users
For users without authentication I found AuthenticationEntryPoint
It looks like
public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint {
#Override
public void commence(HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse,
AuthenticationException e) throws IOException, ServletException {
FlashMap flashMap = RequestContextUtils.getOutputFlashMap(httpServletRequest);
if(flashMap != null) {
Alerts.addWarningAlert(flashMap, "access denied");
}
httpServletResponse.sendRedirect("/");
}
}
My alert can be added only to flash attributes or model of my main page, but flash map in this method always have null value
How I can solve it without redirecting to other controller, which then redirects to main page and add value to model? Or can I add my flash attributes to http servlet response?
It was possible using Session attributes. I added attribute and then take this attribute from Session in alerts handler.

Alter request header before controller method using #RequestHeader is called

I have a few controllers that use the #RequestHeader annotation to get the logged in user. I cannot seem to find the correct way to alter the header before the method is called.
I already tried to wrap the request and using an interceptor (in the prehandle method, if I'm not mistaken) and pass along the request,but it seems like the headers are not being queried. An exception is thrown by the servlet dispatcher that the username is missing in the header.
Therefore I'm wondering whether someone knows how and when spring handles this annotation, so I can write the proper interceptor.
Don't have the code at hand, but if needed, I will post fragments later on. But the question is simple: how to inject a param into the request header when #RequestHeader is used on a controller method?
Kind regards,
Tom
First edit:
#Sotirios
I tried using the Filter, which works. But this is less convenient for me than the handlerinterceptor. Since I only need the filter for debugging. So again the better question: why isn't this not working with an interceptor?
public void doFilter(final ServletRequest request, final ServletResponse response, final FilterChain chain) throws IOException, ServletException {
final HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletRequestWrapper wrapper = new HttpServletRequestWrapper(httpRequest) {
#Override
public Enumeration getHeaders(String name) {
Enumeration headers = super.getHeaders(name);
if( isUseFilter() && Constants.REMOTE_USER.equalsIgnoreCase(name) ){
String user = super.getHeader(name);
headers = enumeration(asList(isEmpty(user)? getDebuggingUserId() :user));
}
return headers;
}
};
chain.doFilter(wrapper, response);
}

Resources