(From SpringSource forum.)
When the HttpSession has expired and the user re-submits a page in the flow, he/she is sent back to the beginning of the flow. All I want to add to this behavior is a message explaining why it occurred. "You were inactive, so you have been restarted..."
What's the easiest/best-practice way to do this?
The default behavior, in FlowHandlerAdapter.defaultHandleException(), "attempts to start a new execution of the ended or expired flow".
It looks like a WebFlow way to handle this would be to provide a FlowHandler with a handleException() method that checks for an instanceof NoSuchFlowExecutionException, then do something like construct a redirect URL or place something on Session scope that can later be removed once utilized.
Due to the way WebFlow uses redirects, I don't think any other scopes would allow such a flag or message to be used later when the new flow's view renders.
However, simply detecting a new Session in an Interceptor or even a Filter would seem to be just as effective. Which is what I ended up doing in my previous investigation of this, as documented in the referenced forum thread. I was just hoping for something prettier.
Also, by the time the new flow begins, a new Session ID has already been created, so there's no way to initially detect this condition from within the flow.xml.
Sample filter logic:
if (request.getRequestedSessionId() != null && !request.isRequestedSessionIdValid()) {
log.info("Expired Session ID: " + request.getRequestedSessionId());
response.sendRedirect("sessionExpired");
}
else {
chain.doFilter(request, response);
}
Sample Interceptor:
public class SessionExpiredInterceptor extends HandlerInterceptorAdapter
{
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
Object handler) throws Exception {
if (request.getRequestedSessionId() != null && !request.isRequestedSessionIdValid()) {
response.sendRedirect("sessionExpired");
return false;
}
return true;
}
}
Step 1:
FlowController has a default handlerAdapter. To customize session exceptions you are required to write your own custom handler adapter and register it with the flow controller bean as below:
<bean id="flowController" class="org.springframework.webflow.mvc.servlet.FlowController">
.
.<property name="flowHandlerAdapter" ref="customFlowHandlerAdapter"/>
.
</bean>
<bean id="customFlowHandlerAdapter" class="gov.mo.courts.pbw.adapters.CustomFlowHandlerAdapter"
p:flowExecutor-ref="flowExecutor"/>
Step 2:
CustomFlowHandlerAdapter
In this class override defaultHandleException method. this is the method that webflow invokes in case of exceptions and re-initializes session. please note, new session has already been created till this point. Only the exception type will tell you at this point that the previous session timed out.
public class PbwFlowHandlerAdapter extends FlowHandlerAdapter{
protected void defaultHandleException(String flowId, FlowException e,
HttpServletRequest request, HttpServletResponse response)
throws IOException {
if(e instanceof NoSuchFlowExecutionException){
if(e.getCause() instanceof NoSuchConversationException){
//"use newly created session object within request object to save your customized message."
}
}
super.defaultHandleException(flowId, e, request, response);
}
The first view page of your app should be able to show this message.
<%
if (session.getAttribute(YOUR_CUSTOM_MSG_KEY) != null) {
%>
<p class="errormessage">
<%=session.getAttribute(YOUR_CUSTOM_MSG_KEY)%>
</p>
<%
//once the message has been shown, remove it from the session
//as a new session has already been started at this time
session.removeAttribute(YOUR_CUSTOM_MSG_KEY);
}
%>
Hope this helps.
From the Jira request I had opened, WebFlow developer says,
Custom FlowHandler is the extension point for an expired session. You
can redirect to the flow and append a query param, something like
restarted=true, and then in the flow declare an <input
name="restarted" type="boolean" value="flashScope.restarted" />.
I haven't been able to confirm, but wanted to share the information.
Related
I am developing a standalone .Net Core API targeting framework .Net Core 2.2.The authentication scheme is JWTBearerTokens connecting to our ADFS Identify server.
When I call an API endpoing decorated with the [Authorize] attribute I am getting a 401 Unauthorized response, which is expected and default behaviour.
What I want to do next is instead of having that same call return a 401, I would like to return the status code to be 404. (I don't want to get into great details of why 404. Simply, I do not want to expose that the endpoint exists if a valid token is not included in request)
In previous .Net Framework WebAPI you could create your own attribute and override the HandleUnauthorizedRequest method and return the status code you want.
I have reviewed the documentation on policy-based authorization, but have not tried the sample or tried implementing it. The policy handler looks more to do with handling (return success or fail) if a policy is not fulfilled. I do not see anywhere where you can return a different status code on failure. So that only would make sense if I start checking against actual Policies.
Any insights?
Returning 404 instead of 401 is bad practice(as mentioned in the comments by #Chris Pratt) and must be avoided. Consider these cases,
You're leaving the project to someone else and they can't figure why 404 is returned
A 404 is returned when you call the homepage/Index page. Poor ideology.
Later on in the project, you decide to allow post requests without authentication. So on and so forth.
Anyways, as part of the community, I'll give you the answer...
Add this to your global.asax
void Application_EndRequest(object source, System.EventArgs args)
{
if (Response.StatusCode == 401)
{
Response.ClearContent();
Response.RedirectToRoute("ErrorH", (RouteTable.Routes["ErrorH"] as Route).Defaults);
}
}
And in routeConfig, create a route for your errorHandler :
routes.MapRoute(
"ErrorH",
"Error/{action}/{errMsg}",
new { controller = "CustomController", action = "Change401To404", errMsg = UrlParameter.Optional }
);
And in your custom controller :
public class CustomController : Controller //or Base
{
public ActionResult Change401To404(){
//Do whatever you want
}
}
PS: This is not the only way, there are many other ways to do it. But at least in this method, you can differentiate real 404 responses from 401 responses.
I'm trying to do URL authorization using a custom AccessDecisionVoter. I don't get any errors and debugging shows that my voter is picked up at start up. However, at runtime, the vote method is not called, thus allowing every authenticated user full access.
Note that, I don't need method security. I'm also not using XML config. That rules out every example ever posted on the internet regarding this topic.
#Configuration
#EnableWebSecurity
#EnableWebMvc
#ComponentScan
#Order(-10)
public class HttpSecurityConfig extends WebSecurityConfigurerAdapter {
#Value("${trusted_ports}")
private List<Integer> trustedPorts;
#Autowired
private UserDetailsService userDetailsService;
#Autowired
private ServiceIdAwareVoter serviceIdAwareVoter;
RequestMatcher requestMatcher = new OrRequestMatcher(
// #formatter:off
new AntPathRequestMatcher("/**", GET.name()),
new AntPathRequestMatcher("/**", POST.name()),
new AntPathRequestMatcher("/**", DELETE.name()),
new AntPathRequestMatcher("/**", PATCH.name()),
new AntPathRequestMatcher("/**", PUT.name())
// #formatter:on
);
#Override
protected UserDetailsService userDetailsService() {
return userDetailsService;
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(preAuthProvider());
auth.authenticationProvider(authProvider());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
// #formatter:off
http.
httpBasic().and().
authorizeRequests().anyRequest().fullyAuthenticated().
accessDecisionManager(accessDecisionManager()).and().
csrf().disable().
logout().disable().
exceptionHandling().and().
sessionManagement().sessionCreationPolicy(STATELESS).and().
anonymous().disable().
addFilterAfter(preAuthFilter(), X509AuthenticationFilter.class).
addFilter(authFilter());
// #formatter:on
}
AccessDecisionManager accessDecisionManager() {
return new UnanimousBased(ImmutableList.of(serviceIdAwareVoter));
}
Filter preAuthFilter() throws Exception {
PreAuthenticationFilter preAuthFilter = new PreAuthenticationFilter(trustedPorts);
preAuthFilter.setAuthenticationManager(super.authenticationManager());
return preAuthFilter;
}
PreAuthenticatedAuthenticationProvider preAuthProvider() {
PreAuthenticatedAuthenticationProvider preAuthProvider = new PreAuthenticatedAuthenticationProvider();
UserDetailsByNameServiceWrapper<PreAuthenticatedAuthenticationToken> userDetailsServiceWrapper = new UserDetailsByNameServiceWrapper<>();
userDetailsServiceWrapper.setUserDetailsService(userDetailsService());
preAuthProvider.setPreAuthenticatedUserDetailsService(userDetailsServiceWrapper);
return preAuthProvider;
}
Filter authFilter() throws Exception {
AppIdAppKeyAuthenticationFilter authFilter = new AppIdAppKeyAuthenticationFilter(requestMatcher);
authFilter.setAuthenticationFailureHandler(new ExceptionStoringAuthenticationFailureHandler());
authFilter.setAuthenticationSuccessHandler(new UrlForwardingAuthenticationSuccessHandler());
authFilter.setAuthenticationManager(authenticationManagerBean());
return authFilter;
}
AuthenticationProvider authProvider() {
AppIdAppKeyAuthenticationProvider authProvider = new AppIdAppKeyAuthenticationProvider();
authProvider.setUserDetailsService(userDetailsService());
return authProvider;
}
Background:
After hours of debugging, I found out the root cause of the problem, which is really deep. Part of it is due to the fact that the Spring Security Java config is very poorly documented (for which I've opened a JIRA ticket). Theirs, as well as most online, examples are copy-pasted from XML config whereas the world has stopped using Spring XML config since probably 2010. Another part is due to the fact that REST service security is an afterthought in the Spring Security design and they don't have first-class support for protecting applications that don't have a login page, error page and the usual view layer. Last but not the least is that there were several (mis)configurations in my app which all came together and created a perfect storm of mind-boggling complexity.
Technical Context:
Using the authorizeRequests() configures a ExpressionUrlAuthorizationConfigurer which ultimately sets up a UnanimousBased AccessDecisionManager with a WebExpressionVoter. This AccessDecisionManager is called from the FilterSecurityInterceptor if the authentication succeeds (obviously there's no point in authorization if the user fails authentication in the first place).
Issues:
In my AbstractAnnotationConfigDispatcherServletInitializer subclass, which is basically the Java version of the web.xml, I'd configured filters not to intercept forward requests. I'm not going to go into the why here. For the interested, here's an example of how it's done:
private Dynamic registerCorsFilter(ServletContext ctx) {
Dynamic registration = ctx.addFilter("CorsFilter", CorsFilter.class);
registration.addMappingForUrlPatterns(getDispatcherTypes(), false, "/*");
return registration;
}
private EnumSet<DispatcherType> getDispatcherTypes() {
return (isAsyncSupported() ? EnumSet.of(DispatcherType.REQUEST, DispatcherType.FORWARD, DispatcherType.INCLUDE, DispatcherType.ASYNC)
: EnumSet.of(DispatcherType.REQUEST, DispatcherType.FORWARD, DispatcherType.INCLUDE));
}
If you take the DispatcherType.FORWARD out of the dispatcher types set, the registered filter doesn't kick in for that kind of request.
The authFilter shown in my question extended from UsernamePasswordAuthenticationFilter and had an AuthenticationSuccessHandler which forwarded the request to the destination URL after successful authentication. The default Spring implementation uses a SavedRequestAwareAuthenticationSuccessHandler which does a redirect to a webpage, which is unwanted in the context of a REST app.
Due to the above 2 reasons, the FilterSecurityInterceptor was not invoked after successful authentication which in turn, skipped the authorization chain causing the issue in my original post.
Fix:
Get rid of custom dispatcher configuration from web app initializer.
Don't do forward, or redirect, from AuthenticationSuccessHandler. Just let the request take it's natural course.
The custom voter has a vote method that looks as follows:
public int vote(Authentication authentication, FilterInvocation fi,
Collection<ConfigAttribute> attributes) {
}
The attributes in my case, as shown in my original post, is the string expression fullyAuthenticated. I didn't use it for authorization as I already knew the user to have been authenticated through the various filters in the authentication flow.
I hope this serves as documentation for all those souls who're suffering from the lack of documentation in Spring Security Java config.
Your config is saying that you are allowing access to fully authenticated users right here:
authorizeRequests().anyRequest().fullyAuthenticated().
You are telling Spring Security to grant access to any request as long as they are fully authenticated. What's you're goal? How are you trying to restrict access, by a role/permission? I'm guessing it's something that you are dictating inside your custom voter bean?
Usually the voter bean comes into play when you have conflicting security levels, for example, here you say that that all requests have full access but if your code hits a method with method level security like this (not a very real-world example):
#PreAuthrorize("permitNone")
public void someMethod{
...
}
You're going to have voters come into play because your java security config is saying "grant access to everyone" (voting yes to access) but this method annotation is "grant access to no one" (voting no to access).
In your case, there's nothing to vote on, you are granting everyone access.
I use interceptor to check if a user is logged in every controller call like this :
public boolean preHandle(HttpServletRequest request,HttpServletResponse response, Object handler) {
if(request.getSession().getAttribute("user") == null) {
response.sendRedirect("redirect:/login?next="+
URLEncoder.encode(
request.getRequestURL().toString() + "" +
(request.getQueryString() != null ? "?" + request.getQueryString() : "")
,"utf-8");
return false;
}
return true;
}
It work fine for normal request but for ajax request i can't make a response.sendRedirect(..).
How to know if it's a ajax or normal request ?
How can i do it like if i got a ajax error ?
$.ajax({
.....
success : function(data) { ...... },
error : function(){
alert("login error"); // or
document.location = '/path/login' // or something else
}
});
There a other way to handle it rather than using interceptor ?
1. How to know if it's a ajax or normal request ?
You can check inside your interceptor for the existence of the X-Requested-With header. This header is always added to the ajax request by the jQuery library (to my knowing almost all major js libraries add it as well) with the purpose of preventing the Cross-Site request forgery. To figure out if the request is ajax, you can write your preHandle method like
public boolean preHandle(HttpServletRequest request,HttpServletResponse response, Object handler) {
String requestedWith = request.getHeader("X-Requested-With");
Boolean isAjax = requestedWith != null ? "XMLHttpRequest".equals(requestedWith) : false;
...
}
2. How can i do it like if i got a ajax error ?
As you've already noticed, ajax request don't recognize server side redirects, as the intention of the server side redirects is to be transparent to the client. In the case of an ajax request, don't do redirect rather set some status code to the response e.g. response.setStatus(respCode) or add a custom header e.g. response.setHeader("Location", "/path/login"), and read it through in the jQuery's complete method which is a callback that follows after either success or error, e.g.
$.ajax({
//...
complete: function(xhr, textStatus) {
console.log(xhr.status);
console.log(xhr.getResponseHeader('Location'));
// do something e.g. redirect
}
});
3. There a other way to handle it rather than using interceptor ?
Definitely. Checkout Spring Security. Its a framework, and adds a bit to the learning curve, but its well worth it. It will add much more than a custom solution, e.g. you'll get authorization mechanism on top of the authentication. When your application matures, you'll notice that the straigthforward implementation that you're on to now, has quite a few security flaws that are not hard to exploit e.g. session fixation, where spring security can easily protect you. There's plenty of examples online, and you'll get better support here on the SO in comparison to any custom solution. You can unit test it, an asset I personally value very much
You could simply:
Refuse ajax requests before the user is properly logged in
once the user logs in, set a security token in the session or somewhere
pass that token in the ajax request and use that token to validate on the server side prehandle
in your case you would check the existence of the token before running into the code
Also, the preHandle does not have to apply to every routes, you could also have different routes each with different authorisation, prehandle, code.
I'm writing a MessageHandler to authenticate a user.
If a request is not containing a special header , I want to block it at the MessageHandler stage.
But if the user wants to go to the Users/Login method, he will probably have no header (because he is not Login yet ).
The problem is that I don't want to block him at the [authorize] controller level.
It's pretty simple :
If he doesn't have the header and he is not on the way to login — BLOCK
If he doesn't have the header and he is on the way to login — only then - ALLOW
Question
1) At the MessaageHandler stage , how can I know that he is on a way to do login ? ( NB : I don't mention the {action} in the route. e.g. :
--
public class User :ApiController
{
[HttpPost]
public bool CheckLogin (....) //i'm not specifying action in the route
{
}
}
2) Looking at the command to read the header :
AuthenticationHeaderValue auth = actionContext.Request.Headers.Authorization;
But - Authorization != Authentication.
So why does web api reference the authorization header as an Authentication ?
The MessageHandler executes before routing has occurred. So at this stage you don't know yet which controller action will be executed.
One possibility would be to check the verb and the path being requested and perform the custom verification based on that:
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
string path = request.RequestUri.PathAndQuery;
if (request.Method == HttpMethod.Post && path.StartsWith("/api/checklogin", StringComparison.InvariantCultureIgnoreCase))
{
// Do not enforce the presence of the custom header
return base.SendAsync(request, cancellationToken);
}
// Check for the presence of your custom header
}
So why does web api reference the authorization header as an Authentication ?
At HTTP level, the header is called Authorization.
I believe you are trying to reinvent the wheel while it is already there. You have Autorize and AllowAnonymous (for your Login action) and then you could have a custom authentication filter to read the header and set up the Principal for the request lifetime.
The reason for that is that the term authorization header has been always used in the context of HTTP header-based authentication. Someone who used the tern for the first time was probably not aware that authentication header would probably be slightly more appropriate.
All, I'm trying to implement an HttpModule (IHttpModule) to catch pages request and redirect to a new page. Unfortunately, it seems I can't use the Session in the new page. because the Session is null.
Here is my code looks like. please review it .
public class MyModule : IHttpModule
{
public void Init(HttpApplication context)
{
context.BeginRequest += new EventHandler(context_BeginRequest);
}
void context_BeginRequest(object sender, EventArgs e)
{
....
HttpContext.Current.Server.Transfer("newpage.aspx");//redirect to new page.
}
}
In the newpage.aspx, There is an exception says Object reference not set to an instance of an object for the code HttpContext.Current.Session[xxx], Because the HttpContext.Current.Session is null .
Could someone tell me what happen to it ?
Thanks.
Update
All, I found If I use the HttpContext.Current.Response.Redirect to redirect url . Everything is ok. I mean the Session object is initiated before being used.But that doesn't work for Server.Transfer.
I already knew what is the difference of these two.
The normal aspx runtime pipeline with 2 modules is:
--> HttpModule_1.BeginRequest(); --> HttpModule_2.BeginRequest(); --> HttpHandler(Page)
<-- HttpModule_1.EndRequest(); <-- HttpModule_2.EndRequest(); <-- HttpHandler(Page)
Imagine HttpModule_1 as your custom module and HttpModule_2 as the aspx session module.
Your custom module is running first, so no session is filled in when your module is running.
When HttpModule_1 is running BeginRequest you add a Server.Trasfer(). Now, the server transfer will execute the HttpHandler of the request page immediately, it will be called without leaving the BeginRequest and after the handler is done the transfer method will run the Request.End() and terminate the processing.
So, the handler for the "newpage.aspx" will also run before the session module. (actually, the session module will never run because Server.Transfer() will end the request).
If you guaranty that the session module is processes before your module you should resolve the problem (see https://stackoverflow.com/a/2427632/953144).