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

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>

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"/>

Camel & CXF & REST: ERROR No message body writer has been found for class java.util.ArrayList, ContentType: application/json

In my Spring configuration file:
<bean id="jacksonJsonProvider" class="org.codehaus.jackson.jaxrs.JacksonJsonProvider" />
<bean id="restJacksonProviderList" class="java.util.ArrayList">
<constructor-arg>
<list>
<ref bean="jacksonJsonProvider"/>
</list>
</constructor-arg>
</bean>
//......
<route id="RestMyRoute">
<from id="RestRequest" uri="cxfrs:/rest/MyService?resourceClasses=com.myself.services.MyService&bindingStyle=SimpleConsumer&providers=#restJacksonProviderList" />
<to uri="direct:doRoute" />
</route>
The Service interface:
#GET
#Path("/my/something/{id}")
#Consumes(MediaType.APPLICATION_JSON)
#Produces(MediaType.APPLICATION_JSON)
#WebMethod
#WebResult(name = "getSomethingResponse")
public List<MySomething> getSomething(
#PathParam("id") #WebParam(name = "id") String id);
The code above works! I can send the get request to the URl and I get a JSON response.
Now, I do a small change: Instead of defining the web service's URL (and the route) by XML configuration, I define them by Java code:
public class MyRoute extends RouteBuilder {
private String uriRest = "cxfrs:/rest/MyService?resourceClasses=com.myself.services.MyService&bindingStyle=SimpleConsumer&providers=#restJacksonProviderList";
#Override
public void configure() throws Exception {
from(uriRest).
to("log:input").
to("direct:doRoute").
to("log:output");
}
}
When I hit the web service URL, I am getting 500 Internal Server Error and in the logs (Tomcat) I see JAXRSUtils ERROR No message body writer has been found for class java.util.ArrayList, ContentType: application/json
Actually the debugger tells me that defining the URI by Java code is recognized, since I do hit the code inside the route.
I saw this error in many answers here, basically they say to add a Json provider and assign it to the CXF endpoint.
Seems to me like it is what I have done. But it does not work.
Any idea what I am doing wrong here?
As peeskillet said, it's because there isn't a list of providers registered under the name restJacksonProviderList. You can get the JndiContext like this and bind a list to it in the configure method of your routebuilder:
JndiContext registry = (JndiRegistry) context.getRegistry();
registry.bind("restJacksonProviderList", Arrays.asList(new JacksonJsonProvider()));
Edit after comments:
Change & for & in your cxfrs uri definition, & is only needed in xml.

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;
}

How to obtain a referrer URL with Spring MVC 3.1

I am building a website that has 2 pages. A recipe list page, a recipe detail page, and a sign in page. A user can sign in to the website by clicking on a sign in button on the recipe list page or recipe detail page. When a user click the sign in button, the user will be brought to the sign in page. I would like to redirect the user back again to the recipe detail page if they click the sign in button from the recipe detail page, or to the recipe list page if they click the sign in button from the recipe list page.
I wrote this method in a Controller class. This method will be called whenever user sign in to the website. I stored the referer URL into the session. The intention of saving this URL into a session is to keep track the page where user click on the sign in button. And also to redirect user to that page in the authentication handler that I wrote.
#RequestMapping(value = "/login", method = RequestMethod.GET)
public String openLoginPage(Model uiModel, HttpServletRequest request) {
String referrer = request.getHeader("Referer");
request.getSession().setAttribute("url_prior_login", referrer);
return RECIPE_LOGIN_PAGE;
}
I also created an authentication handler class called SuccessHandler.
public class SuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {
/*
* (non-Javadoc)
*
* #see org.springframework.security.web.authentication.
* SavedRequestAwareAuthenticationSuccessHandler
* #onAuthenticationSuccess(javax.servlet.http.HttpServletRequest,
* javax.servlet.http.HttpServletResponse,
* org.springframework.security.core.Authentication)
*/
#Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws ServletException, IOException {
String url = (String) request.getSession().getAttribute("url_prior_login");
Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
if (principal != null && principal instanceof RecipeUser) {
request.getSession().setAttribute("RecipeUser", (RecipeUser) principal);
}
getRedirectStrategy().sendRedirect(request, response, url);
}
}
This class will redirect user to either the recipe list page or the recipe detail page when they sign in to the website. I register this class inside a security-context.xml file:
<http use-expressions="true" auto-config="false" entry-point-ref="authenticationEntryPoint">
<intercept-url pattern="/login" access="permitAll" />
<form-login login-page="/login" authentication-failure-url="/loginfail"
default-target-url="/login"
authentication-success-handler-ref="successHandler" />
<logout logout-success-url="/" />
</http>
<authentication-manager alias="authManager">
<authentication-provider user-service-ref='myUserDetailsService' />
</authentication-manager>
<beans:bean id="myUserDetailsService" class="com.safe.stack.service.security.UserService">
<beans:property name="dataSource" ref="dataSource" />
</beans:bean>
<beans:bean id="successHandler"
class="com.safe.stack.service.security.SuccessHandler" />
<beans:bean id="authenticationEntryPoint"
class="org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint">
<beans:property name="loginFormUrl" value="/login"/>
</beans:bean>
Is this a good way to do what I want to achieve ? Is there a better way to do this ? I could not find any example on how to this using Spring MVC.
Thank you
This is how I do it in Spring 3.1.4
public void onAuthenticationSuccess(HttpServletRequest request,
HttpServletResponse response, Authentication authentication)
throws IOException, ServletException {
// Supplies the default target Url that will be used if
// no saved request is found in the session
setDefaultTargetUrl("/member/dashboard");
super.onAuthenticationSuccess(request, response, authentication);
}
Extending SavedRequestAwareAuthenticationSuccessHandler is correct since the redirection is done automatically.
Just add new attribute "always-use-default-target" to "form-login" tag and set it to "false" (by default it is set to "true", even if don't specify it) like this:
<form-login
login-page="/login" authentication-failure-url="/loginfail"
default-target-url="/login"
authentication-success-handler-ref="successHandler"
always-use-default-target="false"
/>
You can read more about it in Spring Security documentation here, on end of section: 3.2.3.
If you add this, you won't need custom onAuthenticationSuccess anymore and you won't need to store anything in session on login page.

Spring Webflow 2 and bookmarkable URLs

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).

Resources