How to customize 404 error page handling in Tomcat and Spring? - spring-mvc

What I have currently in my web.xml (Tomcat 7)
<error-page>
<error-code>404</error-code>
<location>/member/errors/404.html</location>
</error-page>
My HTTPErrorController
#RequestMapping(value = "/errors/404.html")
public String handle404(HttpServletRequest request,Exception e) {
logger.error(e.getMessage());
return "www.example.com/member/dashboard";
}
This works fine for now, however I'd like to customize it with the following scenario:
If anonymous user access non-existent page www.example.com/home2 it should be redirected to the www.example.com/home
If authorized user access non-existent page www.example.com/member/dashboard2 it should be redirected to the www.example.com/member/dashboard
How can I achieve that ?

I would suggest you are using Spring Security for authentication? In that case, you can use this information and redirect based in the logged in state.
For example
#RequestMapping(value = "/errors/404.html")
public String handle404(HttpServletRequest request, Principal principal, Exception e) {
if (principal != null) {
return "www.example.com/member/dashboard";
} else {
return "www.example.com/home";
}
}
Alternatively, you have an own business session bean holding the user and ask the injected bean in the controller.
And finally you can instrument Spring Security tooling itself, either with custom annotation #Secured applied on the methods or by user's role (given the roles for anonymous or authenticated access) applied of your app's specific urls in the security configuration.
Like always, the best practice depends on the use case.
Update 1:
#RequestMapping(value = "/errors/404.html")
public String handle404(HttpServletRequest request, Principal principal, Exception e) {
if (principal != null && request.getRequestURI().startsWith("/member")) {
return "www.example.com/member/dashboard";
} else {
return "www.example.com/home";
}
}

Related

Spring MVC session resource is getting shared by logged users

I have deployed a web application using Apache tomcat 8, java 8 and centos server in production.
When i tested the system by 5-6 users concurrently in office network everything seemed ok. But in client network, one users info is getting by another user(session attributes are shared/mixup). For example, if user A logs in, after a while his name is showing user B, who is logged in from different computer. If user presses Ctrl+R then his/her previous session restores for a while.
N.B. this scenario never happens other than that client network. They are using specific proxy. Other than proxy, this scenario does not happen.
I have a simple LoginController class without defining any scope. Some code snippets are below:
1. Login GET method:
#RequestMapping(value = "/login", method = RequestMethod.GET)
public String getLogin(#ModelAttribute LoginForm loginForm)
{
return "login";
}
2. Login POST method:
#RequestMapping("/login", RequestMethod.POST)
public String Login(#ModelAttribute LoginForm loginForm, HttpSession session)
{
User dbUser = this.userService.getUser(loginForm.getUserID());
if (dbUser != null)
{
if(passwordCheckedSuccess(dbUser.getPassword(), loginForm.getPassword()))
{
session.setAttribute("userName", dbUser.getUserName());
session.setAttribute("userId", dbUser.getUserId()); // primary key of user class
return "dashboard";
}
else
{
return "login";
}
}
}
3. I have created a loginIntercptor class to filter secured pages:
public class LoginInterceptor extends HandlerInterceptorAdapter
{
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception
{
if (!request.getRequestURI().endsWith("/login"))
{
if (request.getSession().getAttribute("userId") == null)
{
response.sendRedirect(request.getContextPath() + "/login");
return false;
}
}
return true;
}
}
I am not using spring security.
Please suggest a way to get rid of it. Thanks.

ASP.NET allow anonymous access to OData $metadata when site has global AuthorizeAttribute

I have an ASP.NET OData site that has the following in the WebApiConfig file:
config.Filters.Add(new AuthorizeAttribute())
This forces all callers to authenticate before calling any of the controllers.
Unfortunately, this also forces user authentication to access the "$metadata" url.
I need to globally force authentication for all controller access while also allowing anonymous access the the "$metadata" url.
I realize this question has already been answered, but I have a couple concerns with the accepted answer:
Assumes the metadata endpoint will not change
Requires updating the code if an endpoint is added/moved
Does not handle the root endpoint (without /$meatdata)
I agree with creating your own AuthorizeAttribute, but I would implement the method a little differently.
protected override bool IsAuthorized(HttpActionContext actionContext)
{
if (actionContext.ControllerContext.Controller is System.Web.OData.MetadataController)
return true;
return base.IsAuthorized(actionContext);
}
My solution simply checks to see if the controller being accessed is OData's MetadataController. If it is, allow anyone access, otherwise, go through the normal authorization checks.
Create a custom filter that derives from AuthorizeAttribute and override the IsAuthorized method as follows:
public class CustomAuthorizationFilter : AuthorizeAttribute
{
protected override bool IsAuthorized(HttpActionContext actionContext)
{
if (actionContext.Request.RequestUri.AbsolutePath == "/$metadata" ||
actionContext.Request.RequestUri.AbsolutePath == "/%24metadata")
{
return true;
}
return base.IsAuthorized(actionContext);
}
}
Register the filter:
config.Filters.Add(new CustomAuthorizationFilter());
I wanted to add one more option. If you replace the default Web API dependency resolver (HttpConfiguration.DependencyResolver = YourIDependencyResolver) you can intercept the request for the metadata controller (ODataMetadataController or MetadataController, depending on the version of the OData library) and replace it with your own implementation, like below:
[AllowAnonymous, OverrideAuthorization]
public class AnonymousODataMetadataController : ODataMetadataController
{
protected override void Initialize(HttpControllerContext controllerContext)
{
// You must replace the controller descriptor because it appears
// that the AuthorizeAttribute is pulled from the
// controllerContext.ControllerDescriptor.ControllerType (which
// is the original type) instead of from controlContext.Controller
// (which is the type we injected).
controllerContext.ControllerDescriptor = new HttpControllerDescriptor
{
Configuration = controllerContext.Configuration,
ControllerName = GetType().Name,
ControllerType = GetType()
};
base.Initialize(controllerContext);
}
}
See Dependency Injection in ASP.NET Web API 2 for info about the Web API dependency injection system.

Re-route all requests dynamically based on some condition

I have an ASP.Net MVC 5 web site and need to dynamically re-route all incoming requests to a specific Controller and Action under certain circumstances.
For example, if the database does not exist, I would like to re-route all incoming requests to a specific Action, such as SetupController.MissingDatabase:
public class SetupController : Controller
{
public ActionResult MissingDatabase()
{
return View();
}
}
I would like to check for this condition (database existence) for each and every request. It would be best to perform the check early on in the pipeline, rather than at the top of each Action, or in each Contoller. Of course, if the incoming request is being routed to SetupController.MissingDatabase I don't need to perform the check or re-route the request.
What is the best way to accomplish this?
Specifically, where in the ASP.Net MVC 5 pipeline is the best place to perform such a check, and how would I re-route the incoming request?
You can create an action filter to do that and register it in the application level so that it will be executed for all the incoming requests.
public class VerifySetupIsGood : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext context)
{
var dbExists=VerifyDataBaseExists();
if ( ! dbExists)
{
var values = new Dictionary<string, string> {{"action", "MissingDatabase"},
{"controller", "Setup"}};
var routeValDict = new RouteValueDictionary(values);
//redirect the request to MissingDatabase action method.
context.Result = new RedirectToRouteResult(routeValDict);
}
}
}
Assuming VerifyDataBaseExists() executes your code to check the db exists or not and return a boolean value. Now register this action filter in the Application_Start event in global.asax.cs
protected void Application_Start()
{
//Existing code goes here
GlobalFilters.Filters.Add(new VerifySetupIsGood());
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
//Existing code goes here
}
You can update the action filter to check for a subset of requests as well if needed. You may get the request url from the object of ActionExecutingContext and use it to do your checks.

Securing SignalR Calls

I'm using the SignalR Javascript client and ASP.NET ServiceHost. I need the SignalR hubs and callbacks to only be accessible to logged in users. I also need to be able to get the identity of the currently logged in user from the Hub using the FormsIdentity from HttpContext.Current.User.
How do I secure the hub's so that only authenticated users can use SignalR?
How do I get the identity of the currently logged in user from the Hub?
You should use the this.Context.User.Identity that is available from the Hub. See a related question
EDIT: To stop unauthenticated users:
public void ThisMethodRequiresAuthentication()
{
if(!this.Context.User.Identity.IsAuthenticated)
{
// possible send a message back to the client (and show the result to the user)
this.Clients.SendUnauthenticatedMessage("You don't have the correct permissions for this action.");
return;
}
// user is authenticated continue
}
EDIT #2:
This might be better, just return a message
public string ThisMethodRequiresAuthentication()
{
if(!this.Context.User.Identity.IsAuthenticated)
{
// possible send a message back to the client (and show the result to the user)
return "You don't have the correct permissions for this action.");
// EDIT: or throw the 403 exception (like in the answer from Jared Kells (+1 from me for his answer), which I actually like better than the string)
throw new HttpException(403, "Forbidden");
}
// user is authenticated continue
return "success";
}
You can lock down the SignalR URL's using the PostAuthenticateRequest event on your HttpApplication. Add the following to your Global.asax.cs
This will block requests that don't use "https" or aren't authenticated.
public override void Init()
{
PostAuthenticateRequest += OnPostAuthenticateRequest;
}
private void OnPostAuthenticateRequest(object sender, EventArgs eventArgs)
{
if (Context.Request.Path.StartsWith("/signalr", StringComparison.OrdinalIgnoreCase))
{
if(Context.Request.Url.Scheme != "https")
{
throw new HttpException(403, "Forbidden");
}
if (!Context.User.Identity.IsAuthenticated)
{
throw new HttpException(403, "Forbidden");
}
}
}
Inside your hub you can access the current user through the Context object.
Context.User.Identity.Name
For part 1. of your question you could use annotations like below (This worked with SignalR 1.1):
[Authorize]
public class MyHub : Hub
{
public void MarkFilled(int id)
{
Clients.All.Filled(id);
}
public void MarkUnFilled(int id)
{
Clients.All.UnFilled(id);
}
}
Something missing from the other answers is the ability to use SignalR's built in custom auth classes. The actual SignalR documentation on the topic is terrible, but I left a comment at the bottom of the page detailing how to actually do it (Authentication and Authorization for SignalR Hubs).
Basically you override the Provided SignalR AuthorizeAttribute class
[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
public class CustomAuthAttribute : AuthorizeAttribute
Then you decorate your hubs with [CustomAuth] above the class declaration. You can then override the following methods to handle auth:
bool AuthorizeHubConnection(HubDescriptor hubDesc, IRequest request);
bool AuthorizeHubMethodInvocation(IHubIncomingInvokerContext hubContext, bool appliesToMethod);
Since I'm on IIS servers and have a custom auth scheme, I simply return true from the AuthorizeHubConnection method, because in my Auth HttpModule I already authenicate the /signalr/connect and /signalr/reconnect calls and save user data in an HttpContext item. So the module handles authenticating on the initial SignalR connection call (a standard HTTP call that initiates the web socket connection).
To authorize calls on specific hub methods I check method names against permissions saved in the HttpContext (it is the same HttpContext saved from the initial connect request) and return true or false based on whether the user has permission to call a certain method.
In your case you might be able to actually use the AuthorizeHubConnection method and decorate your hub methods with specific roles, because it looks like you are using a standardized identity system, but if something isn't working right you can always revert to brute force with HttpModule (or OWIN) middle-ware and looking up context data in on subsequent websocket calls with AuthorizeHubMethodInvocation.

What is the correct way to implement login with redirect using JSF 2.0?

Part of my site should be accessible only to authorized users. Let's assume user would enter page a.html which belongs to the authorized-only part.
If I was to use servlets/JSP I could write a filter that checked whether user is logged in and if not - redirected him to login page. After a successful login user would be redirected to the page he wanted to reach initially, in this case a.html. (Page address could be stored in request).
What is a proper way to implement such scenario in JSF 2.0?
Just do it the same way, with a Filter. It's good to know that JSF session scoped managed beans are under the covers stored as a HttpSession attribute with the managed bean name as key.
Assuming that you've a managed bean like this:
#ManagedBean
#SessionScoped
public class UserManager {
private User user;
// ...
public boolean isLoggedIn() {
return (user != null);
}
}
Then you can check it in Filter#doFilter() as follows:
UserManager userManager = (UserManager) ((HttpServletRequest) request).getSession().getAttribute("userManager");
if (userManager != null && userManager.isLoggedIn()) {
chain.doFilter(request, response);
} else {
((HttpServletResponse) response).sendRedirect("login.xhtml");
}

Resources