Spring Webflow 2 and bookmarkable URLs - spring

Currently due to the Post/Redirect/Get pattern all flow urls are something like <site_url>/flow_name?execution=? and input GET parameters are not preserved. Thus the users can't copy the url, or bookmark it.
Any suggestions how could this be done neatly ?

We can bookmark a SWF based application's URL by customising FlowHandlerAdapter of SWF API.
Here is a sample:
My SWF configuration file would have:
<bean id="flowController" class="org.springframework.webflow.mvc.servlet.FlowController">
<property name="flowHandlerAdapter" ref="customFlowHandlerAdapter" />
</bean>
<bean id="customFlowHandlerAdapter" class="com.xyz.CustomFlowHandlerAdapter">
<property name="flowExecutor" ref="flowExecutor" />
<property name="flowUrlHandler" >
<bean class="com.xyz.CustomURLFlowHandler" />
</property>
</bean>
My CustomFlowHandlerAdapter would have:
public class CustomFlowHandlerAdapter extends FlowHandlerAdapter {
...
#Override
public ModelAndView handle(HttpServletRequest request,
HttpServletResponse response, Object handler) throws Exception {
FlowHandler flowHandler = (FlowHandler) handler;
checkAndPrepare(request, response, false);
String flowExecutionKey = this.getFlowUrlHandler()
.getFlowExecutionKey(request);
if (flowExecutionKey != null)
try {
ServletExternalContext context = createServletExternalContext(
request, response);
FlowExecutionResult result = this.getFlowExecutor().resumeExecution(
flowExecutionKey, context);
handleFlowExecutionResult(result, context, request, response,
flowHandler);
} catch(org.springframework.webflow.execution.repository.NoSuchFlowExecutionException ex){
response.sendRedirect(request.getRequestURI());
} catch(org.springframework.webflow.execution.repository.BadlyFormattedFlowExecutionKeyException ex){
response.sendRedirect(request.getRequestURI());
} catch (FlowException e) {
handleFlowException(e, request, response, flowHandler);
}
....
Here Iam catching NoSuchFlowExecutionException and am redirecting to the exact flow URL without any parameters. Here you can capture and re-include your parameters
Thus I am able to bookmark my URL from any state(always flow starts from first) also I will be able to send my own parameters if required.

you can always use and bookmark a link to one of your flow's start point.for instance you can do <site_url>/flow_name?personId=123&projectId=456 assuming you have two inputs to your flow personId and projectId. But you need to know the url (you will have to give it to the users), you cannot use the one on your address bar.
even if you want to do that, you won't be able to use and bookmark a link to a specific state in your flow (unless you add some logic to the start of your flow to direct you to a specific event depending on the value of an input).

Related

Spring: Check in code if url has security set to none

It is possible to check in Spring Interceptor preHandle() method if requested URL is secured by Spring Security or not (has set security="none") ?
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if(isSecured(request) && !paymentRegistered())
response.sendRedirect("/payment")
return super.preHandle(request, response, handler);
}
private boolean isSecured(HttpServletRequest request){
//how to check if url has security=none
}
My problem is that after successful login I want to check if user has payed for service. If not I want to redirect to payment page. My idea is to write custom request scope filter or interceptor and check if user has registered payment in database. Problem is that I do not want to filter non secured URLs such as resources, login page, error pages etc. Also payment page (which is secured) should be available.
Maybe better idea is to write custom security filter and add custom flag to Principal object such as servicePayed alongside with other security flags: enabed, accountNonExipired etc.
I would do it writing a custom AuthenticationSuccessHandler, mainly based in the simple implementation SimpleUrlAuthenticationSuccessHandler.
In your implementation, you should overwrite onAuthenticationSuccess method, and there check if you should redirect the user to the payment page or not.
/**
* Calls the parent class {#code handle()} method to forward or redirect to the target
* URL, and then calls {#code clearAuthenticationAttributes()} to remove any leftover
* session data.
*/
public void onAuthenticationSuccess(HttpServletRequest request,
HttpServletResponse response, Authentication authentication)
throws IOException, ServletException {
if(mustCompletePayment(authentication)){
handle(request, response, authentication);
clearAuthenticationAttributes(request);
}
}
Then just write a kind of mustCompletePayment using the authentication object, from which you must be able to check if the user must complete payment or not, or if you already made a custom UserDetailsService to check it during authentication, just check that indicator in your authentication object
EDIT:
If what you really want to do is to avoid any action for the logged user while he does not complete the payment, I would manage with granted authorities.
As I see, the key here is to translate the fact that the user has yet not paid into the authorization layer in a way you could take advantage of it.
You already have implemented the logic to discover if a user has completed payment information or not, so you could write your own UserDetailsService, so in the
UserDetails loadUserByUsername(String username)throws UsernameNotFoundException
you could check that and in case the user has not complete the payment, just erase any returning granthedAuthority from the UserDetails and let only one stating that the user must complete the payment, let's say ROLE_USER_HAS_NOT_PAID.
Then, in security http config (this is xml version but maybe you are using java config), make such a kind of mappings:
<security:http ...>
...
<security:intercept-url pattern="/secured/payment/**" access="ROLE_USER,ROLE_USER_HAS_NOT_PAID" />
<security:intercept-url pattern="/secured/**" access="ROLE_USER_HAS_PAID" />
...
</security:http>
With this config, payment page would be accessible for any user, wherever the user has paid or not, while other pages are available only for users who had already paid. Only, be carefull as you must renew the user's granthed authorities once the user has paid to made him available every page.
This way, the AuthenticationSuccessHandler should not eval other than the user granthed authorities to decide where to redirect the user. I have made this several times by building a AuthenticationSuccessHandler based on a ordered map where I configured a landing page for each of the granthed authorities which need their own landing page.
Now any logged user action is forbidden if he has cont complete payment, so a HTTP 403 would be raised while trying to access any other secured resource. But you want don't want just to block the user from doing anything else, you want to redirect it to the payment page. Here you need an AccessDeniedHandler, where you could do more or less the same check:
public class CustomAuthenticationAccessDeniedHandler extends
AccessDeniedHandlerImpl implements AccessDeniedHandler {
private String errorPage = "/error/403";
private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
#Override
public void handle(HttpServletRequest arg0, HttpServletResponse arg1,
AccessDeniedException arg2) throws IOException, ServletException {
SecurityContext context = SecurityContextHolder.getContext();
if(context.getAuthentication() != null && context.getAuthentication().isAuthenticated()){
if(context.getAuthentication().getAuthorities().contains("ROLE_USER_HAS_NOT_PAID")){
this.redirectStrategy.sendRedirect(arg0, arg1, "/secured/payment/pay");
return;
}
}
this.redirectStrategy.sendRedirect(arg0, arg1, this.getErrorPage());
}
public RedirectStrategy getRedirectStrategy() {
return redirectStrategy;
}
public void setRedirectStrategy(RedirectStrategy redirectStrategy) {
this.redirectStrategy = redirectStrategy;
}
#Override
public void setErrorPage(String errorPage) {
this.errorPage = errorPage;
}
public String getErrorPage() {
return errorPage;
}
}
This way you would redirect users which still must pay to your payment page and in any other case to a default 403 page
Don't know if there's a way to get such information from Spring Security. But maybe if you do not have a lot of urls which are not secured than you can do something like this:
private boolean isSecured(HttpServletRequest request) {
String requestUri = request.getRequestURI();
return !(request.getMethod().equalsIgnoreCase("GET")
&& (requestUri.contains("error/")
|| requestUri.startsWith("resources/"))
);
}
Or move those non-secured resources to some common start path and use the idea described in the code above.
Maybe you will find a way to do that, but IMHO you should not, because it is likely to require to dive in Spring Security internals.
If you want to only use Spring Security the way it was designed for, you could implement a custom AccessDecisionVoter. For example, if could only vote for one single security attributes starting with PAYMENT. You put that security attribute in spring security configuration:
<security:intercept-url pattern="/..." access="PAYMENT,ROLE_ADMIN"/>
to restrict access to user having payed or having the ADMIN role
To declare a custom voter, you must replace the default access decision manager. First you declare it:
<bean id="accessDecisionManager"
class="org.springframework.security.access.vote.AffirmativeBased">
<constructor-arg>
<list>
<bean class="org.springframework.security.access.vote.AuthenticatedVoter"/>
<bean class="org.springframework.security.access.vote.RoleVoter"/>
<bean class="your.package.PaymentVoter"/>
</list>
</constructor-arg>
</bean>
Then you insert it in your <http> element:
<http access-decision-manager-ref="accessDecisionManager"/>

Endless loop in Spring MVC Interceptor

I have written a custom interceptor PreventScreenInterceptor extends HandlerInterceptorAdapter
in preHandle I am checking some conditions, and based on that, I am redirecting using
response.sendRedirect("/myapp/user/noaccess");
Now, whenever I am hitting /myapp/user/noaccess , it is going into endless loop as I am not able to come out of this interceptor. Its getting called again and again.
My Application context has :
<mvc:interceptor>
<mvc:mapping path="/myapp/user/**"/>
<bean class="com.mypackage.interceptors.PreventScreenInterceptor" />
</mvc:interceptor>
You have to use request.getRequestURI() to check that the URI being called is not "/myapp/user/noaccess", before sending your redirect.
You must check that the if the request is not coming with the action where you are redirecting from Interceptor , otherwise it will recall itself again and again.
for reference use this code -
#Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler) throws Exception {
String uri = request.getRequestURI();
logger.debug("inside interceptor and uri = "+uri);
if (!uri.endsWith("/noaccess") ) {
logger.info("request is coming from other than /myapp/user/noaccess");
response.sendRedirect("/myapp/user/noaccess");
}
return true;
}

Spring Web Api, parse URLS

Hello I'm new in Spring.
I' m developing a Spring Web Api and I have problems parsing the URLs with regular expressions. I have already looked at the following posts:
http://stackoverflow.com/questions/7841770/optional-path-variables-in-spring-mvc-requestmapping-uritemplate
http://stackoverflow.com/questions/12516969/spring-mvc-getting-pathvariables-containing-dots-and-slashes
http://stackoverflow.com/questions/8998419/requestmapping-annotation-in-spring-mvc
but I haven't found yet a solution to my problem.
I want that all my requests get mapped to a single method, the length of the URL can be variable and the number of parameters can be also variable. I would like to capture the whole url with the variable pathValue and not until the slash /:
#RequestMapping(value = "{pathValue}", method = RequestMethod.GET)
All regular expressions that I have tested in Spring capture content between slashes (/......./) and don't consider de remaining URL.
The main point is, that I want to parse the url in a single method and that implies that all requests get mapped to that method.
Is there any way to achieve this in Spring?
Many thanks for your help and advice.
If you really want to dispatch ALL request to one handler then you do not need to have the spring method dispatcher at all.
Instead you can have your own Request handler
<bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
<property name="urlMap">
<map>
<entry key="/**" value="myCatchAllResourceHandler" />
</map>
</property>
<property name="order" value="100000" />
</bean>
<bean id="myCatchAllResourceHandler" name="myCatchAllResourceHandler"
class="MyCatchAllResourceHandler">
</bean>
You have to implement your own Request handler
public class MyCatchAllResourceHandler extends HttpRequestHandler() {
/**
* Process the given request, generating a response.
* #param request current HTTP request
* #param response current HTTP response
* #throws ServletException in case of general errors
* #throws IOException in case of I/O errors
*/
void handleRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException;
System.out.println("I get invoked");
}
}
But to be honest, this is almost like throwing all Spring MVC away!

How to pass request parameter to 'default-target-url'

I am setting 'cat=1' in the hidden field in login.jsp page and was expecting it to be available on the default-target-url. Entry in spring-security.xml is,
<form-login login-page="/login.html" default-target-url="/index.html"
authentication-failure-url="/loginfailed.html" />
and in the controller,
#RequestMapping(value="/index", method = RequestMethod.GET)
public String index(HttpServletRequest request) {
String cat = request.getParameter("cat");
if (cat != null && cat.equalsIgnoreCase("1")) {
return "add";
}
return "redirect:/index.jsp";
}
but cant get request parameter value (cat is null) so I believe it is because 'default-target-url' redirects the request (and does not forward it?). Is it the case?
If yes then is there any way I can pass parameter to the 'default-target-url'?
I have changed implementation approach a bit. Details give below,
spring-security.xml
<form-login login-page="/login.html" authentication-success-handler-ref="feedSuccessHandler"
authentication-failure-url="/loginfailed.html" />
<logout logout-success-url="/loggedout.html"/>
<beans:bean id="feedSuccessHandler"
class="main.java.com.sp.utilities.FeedSuccessHandler">
</beans:bean>
FeedSuccessHandler.java
public class FeedSuccessHandler implements AuthenticationSuccessHandler {
public void onAuthenticationSuccess(HttpServletRequest request,
HttpServletResponse response, Authentication authentication)
throws IOException, ServletException {
String cat = request.getParameter("cat");
if (cat != null && cat.equalsIgnoreCase("1")) {
response.sendRedirect(request.getContextPath()+"/add.html");
}else{
SavedRequest savedRequest = new HttpSessionRequestCache().getRequest(request, response);
if(savedRequest != null) {
response.sendRedirect(savedRequest.getRedirectUrl());
}else{
response.sendRedirect(request.getContextPath()+"/");
}
}
}
}
Application is working as desired also in future if I want to customize redirection based on roles, I can use same class.
It does redirect by defult, but there are a couple configuration options you can use to change this behavior. Both of them is defined on the AbstractAuthenticationTargetUrlRequestHandler which is the parent class of the two existing authentication success handler implementations (by default SavedRequestAwareAuthenticationSuccessHandler is used by the namespace configuration).
Set its targetUrlParameter property, so that it will check if the HTTP request has a parameter with that name. If so, it will redirect to the URL given in that request parameter.
Or set a custom redirectStrategy. The default implementation calls response.sendRedirect(), but you can change that as you like in your custom implementation.
You will have some difficulty though, because neither of these configuration points are exposed through the namespace configuration, so you will need to go a level deeper, and write the bean definitions manually.
The redirect is controlled by the Redirect Strategy definined in the redirectStrategy property of SimpleUrlAuthenticationSuccessHandler.
The Default for redirectStrategy is an instance of DefaultRedirectStrategy.
What you need to do is to implement you own redirectStrategy (implements RedirectStrategy).
And then configure it:
...
<bean id="usernamePasswordAuthenticationFilter">
...
<property name="authenticationSuccessHandler">
<bean
class="org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler">
<property name="redirectStrategy">
<bean class="yourRedirectStrategy"/>
<property>
</bean>
</property>
</bean>

GWT does not work with Spring REST service

I wrote a Spring REST application. I tested it with curl command and it worked truly.
In another GWT Ajax application I have an RequestBuilder object that it does not work with my Spring Rest: after calling sendRequest method, the event onResponseReceived is fired but the getText method returns an empty string.
this is a part of my spring servlet.xml configuration file
<bean id="jsonmembertemplate"
class="org.springframework.web.servlet.view.json.MappingJacksonJsonView" >
<property name="contentType" value="text/javascript;charset=UTF-8"/>
<property name="disableCaching" value="false"/>
</bean>
<bean class="org.springframework.web.servlet.view.BeanNameViewResolver" />
and a part of my controller class
#RequestMapping(method = RequestMethod.GET, value="/member/get/{memberid}")
public String getMember(Model model, #PathVariable("memberid") int id, HttpServletResponse response) {
model.addAttribute("member", memberDao.get(id));
return "jsonmembertemplate";
}
and gwt code
private RequestBuilder rb = new RequestBuilder(RequestBuilder.GET, "http://localhost:8080/depna-nat-server/member/get/1?");
try {
rb.setHeader("Content-Type", "text/javascript;charset=UTF-8");
rb.sendRequest(null, new RequestCallback() {
#Override
public void onResponseReceived(Request request, Response response) {
Window.alert(Integer.toString(response.getStatusCode()));
Window.alert(response.getStatusText());
Window.alert(Integer.toString(response.getText().length()));
area.setText(response.getText());
}
#Override
public void onError(Request request, Throwable exception) {
Window.alert("fail");
}
});
} catch (RequestException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
I have a couple of gwt applications working with json and xml rest services offered by spring, we use spring instead of gwt (rpc or rf) because these services are offered to 3party apps as well.
I started my first project with RequestBuilder and we dind't have any problem, so maybe you have some issue in your code, could you inspect the traffic and post the errors you have?
If you are running a cross-domain issue (statusCode=0 normally means it) , add a filter to your servlet container, take a look to this document.
I finally opted to use gwtquery-ajax and gquery-data-binding because it made really easy to consume these services and to map them to java objects.

Resources