Issue with mvc:annotation-driven and #PathVariable - spring-mvc

Not sure what I am doing wrong here but when I DO NOT define the mvc:annotation-driven in my servlet, the value returned from the #PathVariable is not getting displayed in my jsp page and when I do define the annotation-driven, all other links e.g. home gets broken and I get the The specified HTTP method is not allowed for the requested resource (Request method GET not supported).
#Controller("HealthCheckController")
#RequestMapping("/healthCheckSummary")
public class HealthCheckController {
private Log log = LogFactory.getLog(GolSimpleMappingExceptionResolver.class);
private HealthCheckService healthCheckService = null;
private IEappDataAccessFacade dataAccessFacade;
#RequestMapping(value = "/{username}/{password}", method = RequestMethod.GET)
public String getEappStatus(#PathVariable String username, #PathVariable String password, Model model){
String dbConnectivityStatus = getDatabaseConnectivityStatus() ? "online" : "offline";
if (!username.equals("lola") || !password.equals("123")) {
// wrong credentials were provided
log.error("The login credentials in the header are not valid." + username + password);
throw new RuntimeException( "Unable to continue, the login credentials in the header are not valid." + username + password);
}
model.addAttribute("healthCheckSummary", dbConnectivityStatus);
return "healthCheckSummary";
}
public HealthCheckService getHealthCheckService()
{
return healthCheckService;
}
public boolean getDatabaseConnectivityStatus() {
String result = “OK”;
if (result != null) {
return true;
}
return false;
}
}
Oh and in the application context we have defined the
<tx:annotation-driven />
JSP page
<%# page language="java"%>
Welcome ${username} - ${password}
Eapp is currently ${healthCheckSummary}

Two things:
You never put your username and password #PathVariables in the model. Your jsp page has no way of knowing that they even existed as you lose any reference to them after the code leaves getEappStatus. Add the following to your handler method:
model.addAttribute("username", username);
model.addAttribute("password", password);
and see if that works.
You might want to add <mvc:annotation-driven/> just to avoid some surprises when you use some spring-mvc annotations. This isn't strictly necessary, but might save you some head scratching when some #RequestBody doesn't work.

Related

Submit on create user page doesn't go to createuser method in spring-mvc controller

I normally work on JAX-RS REST services, and I have to make some changes to a webapp using Spring MVC, which I'm only superficially familiar with (the MVC part, not Spring).
I have a view that lets the user create new users. When the form is submitted, instead of hitting the appropriate action in the Spring MVC controller, the page just "blanks out", as if it forwarded to a view that doesn't exist.
It's not clear to me what to look for here. Perhaps there's some logging I can enable that will show what request mapping decision Spring makes?
I'll try to show the relevant pieces of code here.
Inside the jsp page that has the form for the user/password/name fields, there's something like this:
Create User
I set a breakpoint in "validateUserCredentials()", which does some trivial validation of the entry fields:
function validateUserCredentials(){
var inputPassword=/[a-zA-Z0-9_-]{4,14}/ ;
var inputUsername=/[a-zA-Z0-9_-]{4,14}/ ;
var error ="false";
if (document.f.password.value.search(inputPassword)==-1)
{
error="true";
}else if (document.f.username.value.search(inputUsername)==-1){
error="true";
}else if (document.f.fullname.value.length==0){
error="true";
}else if (document.f.displayName.value.length==0){
error="true";
}
if(error =="true"){
document.f.action="/account/createuser?error=true";
document.f.submit();
}else{
document.f.action="/account/createuser";
document.f.submit();
}
}
Here is an excerpt of the controller:
#Controller
#SessionAttributes(value= {"userRoleMap", "userStatusMap"})
public class AccountController {
private static Logger logger = LogManager.getLogger(AccountController.class);
#Autowired
private UserAccountService accountService;
#RequestMapping(value = "/account/getCurrentUsers", method = RequestMethod.GET)
public ModelAndView getCurrentUsers(ModelMap model) {
logger.debug("In getCurrentUsers.");
List<UserInfo> userInfoList =accountService.getCurrentUserList();
Users users = new Users();
users.setUserInfoList(userInfoList);
HashMap<String,String> userRoleMap=populateAndGetUseridRoleMap(userInfoList);
HashMap<String,Boolean> userStatusMap=populateAndGetUseridStatusMap(userInfoList);
model.addAttribute("userRoleMap",userRoleMap);
model.addAttribute("userStatusMap",userStatusMap);
return new ModelAndView("userRole","users",users);
}
#RequestMapping(value = "/account/createuser", method = RequestMethod.GET)
public String getCreateUserPage() {
logger.debug("In getCreateUserPage.");
return "createUser";
}
#RequestMapping(value = "/account/createuser", method = RequestMethod.POST)
public String createUser(#RequestParam("username") String newUser,#RequestParam("password") String password,
#RequestParam("role") String role,
#RequestParam("fullname") String fullname,
#RequestParam("displayName") String displayName,
#RequestParam(value="error", required=false) boolean error,ModelMap model) {
logger.debug("In createUser. newUser[" + newUser + "] password[" + password + "] role[" + role +
"] fullname[" + fullname + "] displayname[" + displayName + "] error[" + error +
"] model[" + model + "]");
When I click the submit button, the page just "blanks out". The url was at "/account/createuser" before the submit, and it didn't change after the submit.
When I looked at the log, I only saw this:
controllers.AccountController (AccountController.java:73) - In getCurrentUsers.
controllers.AccountController (AccountController.java:104) - In getCreateUserPage.
It didn't appear to get into the "createUser" method.
Update:
I also noticed earlier in the log the following line:
org.springframework.web.servlet.handler.AbstractHandlerMethodMapping.registerHandlerMethod Mapped "{[/account/createuser],methods=[POST],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto public java.lang.String ...AccountController.createUser(java.lang.String,java.lang.String,java.lang.String,java.lang.String,java.lang.String,boolean,org.springframework.ui.ModelMap)
This was only due to the fact that some of the application links in the jsp pages were "raw" relative references, instead of using "

Mock Custom User in Spring Security Test

We are using Spring 4.3.9.RELEASE and Spring Security 4.2.3.RELEASE, so these are some of the latest versions we have seen. We have a RESTful (spring-mvc) backend where we are using Spring Web Security for roles-based access to the API's.
We have a controller that looks like this:
#RequestMapping(value = "/create", method = RequestMethod.POST, produces = "application/json", headers = "content-type=application/json")
public #ResponseBody MyObjectEntity createMyObject(#RequestBody MyObjectEntity myObj) throws MyObjectException
{
UserDetails userDetails = (UserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
CustomUser user = null;
if (userDetails instanceof CustomUser)
{
user = ((CustomUser) userDetails);
}
String email = user.getEmail();
MyObjectEntity myObj = MyObjectService.createMyObject(myObj, email);
if (SecurityContextHolder.getContext().getAuthentication() != null)
{
SecurityContextHolder.getContext().setAuthentication(null);
}
return myObj;
}
We know a user has logged in from the web-site with a username and password. We know the UI has a token, and they pass it along in the header. Our security uses the SiteMinder example, which means we have a UserDetailsService that goes to a third-party, passes along the token, and we now have the username, password, and the roles the user has. This is normally working well.
We did create a CustomUserDetailsService as follows:
public class CustomUserDetailsService implements UserDetailsService
{
#Override
public UserDetails loadUserByUsername(String accessToken) throws
UsernameNotFoundException,
PreAuthenticatedCredentialsNotFoundException
{
// goto to third-party service to verify token
// get the Custom User and the user roles
// also get some extra data, so a custom user
}
}
So, once we established the token is valid, and we have gotten additional user information from that third-party, and we have the valid role that is authorized for this API ... then we can execute the controller itself. And we see this code is traditional for getting an existing user out of the Spring Security Context.
UserDetails userDetails = (UserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
CustomUser user = null;
if (userDetails instanceof CustomUser)
{
user = ((CustomUser) userDetails);
}
Actually, from what we have read, this is the way to do it when you have a custom user and CustomUserDetails. With this code, we want to get the email of this user. And this all works when we actually test the API with Advanced REST Client. Our QA has to authenticate against the web-site, and they get tokens passed back to the UI, they get those access tokens, and put those in the headers of the Advanced REST Client (or Postman) and this all works.
We even have code to invalidate the security context when the API is over.
if (SecurityContextHolder.getContext().getAuthentication() != null)
{
SecurityContextHolder.getContext().setAuthentication(null);
}
Against, the real API, with the real progress, this works great.
Now, when it comes to testing, some of the tests work against our secured controllers and some do not. So, here we have a controller to test:
#RequestMapping(value = "/{productId}", method = RequestMethod.GET, headers = "Accept=application/json")
public #ResponseBody ProductEntity getProductById(#PathVariable("productId") long productId)
{
logger.debug("ProductController: getProductById: productId=" + productId);
CustomUser user = authenticate();
ProductEntity productEntity = service.getById(productId);
logger.debug("ProductController: getProductById: productEntity=" + productEntity);
invalidateUser();
return productEntity;
}
And here is the test:
#Test
public void testMockGetProductByProductId() throws Exception
{
MockHttpServletRequestBuilder requestBuilder = MockMvcRequestBuilders.get(BASE_URL + "/1").with(user("testuser").roles("REGULAR_USER"));
this.mockMvc.perform(requestBuilder).andDo(print()).andExpect(status().isOk());
}
This works because even when we get to the controller, we don't need the CustomerUser set, so it works. If the role is the correct role ("REGULAR_USER"), then it works, if the role is not correct, we get a 403 error which are expecting.
But if you look at the Controller I first posted at the top, we NEED the CustomUser to be set, and if it isn't set, then when we try to get that email, we fail. So, we have been looking at multiple ways of setting up a mock user in authentication, so when we get to the Controller we can get that CustomUser already in security context.
I've actually done this before, but that was when we were using the standard spring security user, and not a custom user.
We can definitely establish a CustomUser in the security context, but when it gets to the controller, and this code is run ....
// THIS WORKS
UserDetails userDetails = (UserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
CustomUser user = null;
// This IF fails because;
// userDetails is of instance User (Spring Security User)
// and not CustomUser.
if (userDetails instanceof CustomUser)
{
user = ((CustomUser) userDetails);
}
Let me add the code we have for our CustomUser:
public class CustomUser implements UserDetails
{
private static final long serialVersionUID = -6650061185298405641L;
private String userName;
private ArrayList<GrantedAuthority> authorities;
private String firstName;
private String middleName;
private String lastName;
private String email;
private String phone;
private String externalUserId;
// getters/setters
// toString
}
I hope I put enough information here that someone can answer my question. I have spent a day or two scouring the internet for someone who can answer this question to no avail. Some of the answers were a little older from Spring 3 and older Spring Security 3.x. if any more information is needed, please let me know. Thanks!
I wonder ... if I need a CustomUserDetails which implments UserDetails?
Thanks again!
This is probably much easier than what you think.
CustomUser userDetails = new CustomUser();
/* TODO: set username, authorities etc */
MockHttpServletRequestBuilder requestBuilder = MockMvcRequestBuilders.get(BASE_URL + "/1").with(user(userDetails));
This is allowed as long as your CustomUser implements UserDetails interface.

Can Spring MVC controller set the URL to be shown in browser?

We have a Spring MVC Controller method which servers the url pattern /hello/{userName} .
#RequestMapping("/hello/{userName}")
public ModelAndView helloWorld(#PathVariable("userName") String productId) {
String message = "HELLO"+userName;
return new ModelAndView("hellopage", "message", message);
}
Here when we request /hello/Tom ,hellopage.html will be servered with URL in the browser http://localhost:8080/myApp/hello/Tom
We would need the URL to be http://localhost:8080/myApp/Tom .Is there any way I can set the URL to be shown in the browser when returning from the controller.
Sure you can do that using redirect. Write two controllers:
#RequestMapping("/hello/{userName}")
public string helloWorld(#PathVariable("userName") String userName, Model model) {
String message = "HELLO" + userName;
model.addAttribute("message", message);
return "redirect:" + userName;
}
#RequestMapping("/{userName}")
public ModelAndView userHello((#ModelAttribute("message") String message)) {
return new ModelAndView("hellopage", "message", message);
}
I think you could also use tuckey url-rewrite for this: http://tuckey.org/urlrewrite/
This allows Apache mod_rewrite functionality in Tomcat through the use of a filter, and you wouldn't have to write two controllers for everything.

How to return a custom view OR a JSON when the Authorization fail, instead of showing a username and password dialog

I am working on an asp.net mvc 4 web application , and i wrote the following custom authorization class:-
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public class CheckUserPermissionsAttribute : AuthorizeAttribute
{
public string Model { get; set; }
public string Action { get; set; }
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
if (!httpContext.Request.IsAuthenticated)
return false;
//code goes here
if (!repository.can(ADusername, Model, value)) // implement this method based on your tables and logic
{
return false;
//base.HandleUnauthorizedRequest(filterContext);
}
return true;
// base.OnAuthorization(filterContext);
}
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
if (filterContext.HttpContext.Request.IsAjaxRequest())
{
var viewResult = new JsonResult();
viewResult.JsonRequestBehavior = JsonRequestBehavior.AllowGet;
viewResult.Data = (new { IsSuccess = "Unauthorized", description = "Sorry, you do not have the required permission to perform this action." });
filterContext.Result = viewResult;
}
else
{
var viewResult = new ViewResult();
viewResult.ViewName = "~/Views/Errors/_Unauthorized.cshtml";
filterContext.Result = viewResult;
}
base.HandleUnauthorizedRequest(filterContext);
}
}
}
but the only problem i am facing now is that if the authorization fail then the user will be prompted to enter username and password, although i have override the HandleUnauthorizedRequest to return a view or JSON based on if the request is AJAX or not. so can you advice why the user is being prompted to enter his username and password when the authorization fail, instead of receiving the _unauthorized view or the JSON containing an error message
but the only problem i am facing now is that if the authorization fail
then the user will be prompted to enter username and password,
although i have override the HandleUnauthorizedRequest to return a
view or JSON based on if the request is AJAX or not.
That's because you are absolutely always hitting the following line in your HandleUnauthorizedRequest method:
base.HandleUnauthorizedRequest(filterContext);
You know what this line do? It calls the base method. You know what the base method do? It returns 401 status code. You know what happens when 401 status response code is returned in an ASP.NET application in which you are using Forms Authentication? You get the login page.
So yeah, if you are using AJAX or something and intend to be returning some JSON or something make sure that the base stuff is never called. By the way in your else condition you seem to be attempting to render some ~/Views/Errors/_Unauthorized.cshtml view which obviously is useless once again because you are also calling the base method which will simply redirect to the login page.
I think that at this stage of my answer you already know what to do: get rid of this last line of your HandleUnauthorizedRequest method in which you are throwing all your efforts into the trash by calling the base method.
And if you want to do things properly and return 401 status code and not get the login page but instead return some custom JSON you could use the SuppressFormsAuthenticationRedirect property on the Response object. And if you are using some legacy version of the .NET framework which doesn't have this property you might find the following blog post useful in which Phil Haack explains how to handle this case.

Using FormsAuthentication on webMethod

I have a page that has a comment section. This section communicates to a WebMethod in order to insert a new comment.
[WebMethod]
public static bool insertComment(string commentString)
{
//userName validation here
string userName = (FormsAuthentication.Decrypt(Request.Cookies[FormsAuthentication.FormsCookieName].Value).Name);
return new CommentClass().InsertComment(commentString, userName);
}
The problem is: "An object reference is required for the non-static field".
I know I could send the information from a hidden field, or a div, however, that information field may be changed easily.
So which way could be used to know which user is posting, in server side?
thanks a lot!
Request object is an instance that lives in Page, so you need a reference to access this object in a static context. You can use HttpContext.Current.Request for accessing the Request in this context.
[WebMethod]
public static bool insertComment(string commentString)
{
//userName validation here
string userName =
(FormsAuthentication.Decrypt(
HttpContext.Current.Request.Cookies[FormsAuthentication.FormsCookieName].Value).Name);
return new CommentClass().InsertComment(commentString, userName);
}

Resources