I have a controller with a requestmapping..
#Controller
public class TestController {
private static final String template = "Hello there, %s!";
private final AtomicLong counter = new AtomicLong();
#RequestMapping("/hello")
public #ResponseBody String hello() {
return "Hello";
}
}
How can I make it such that everytime a user goes to a RequestMapping, or whichever url, some other method is called to println to the console the URL the user is at before it actually enters the method "hello"?
I would like to use this method to ensure users have proper credentials in the future. I see there are #PreAuthorize annotation, but there doesnt seem to be a method associated with it that I can write my own logic, i.e. do a simple println to the console with the current URL the user is at.
You have a number of options.
With Spring, you can implement and register a HandlerInterceptor and implement its preHandle method to log the request URL, which you can reconstruct with the various HttpServletRequest methods.
With pure servlet-api, you can implement and register your own Filter which logs the request URL and then continues the chain, with doFilter(..).
You can also use AOP, advise all your #RequestMapping annotated methods with a #Before advice that logs the URL.
Related
I have written a book catalog in Spring.
It collects books (pdf, epub, mobi, ebook) from a directory, collects some metadata from them, stores them in a DB and then puts them in a List that is made available to my views:
#Slf4j
#Controller
public class BookCatalogController {
// == Fields ==
private final BookService bookService;
#Autowired
public BookCatalogController(BookService bookService){this.bookService = bookService; }
// == Model attributes ==
#ModelAttribute
public List<Book> bookData(){ return bookService.getBooksFromMemory(); }
public static final File bookDirectory= new File("D:\\edu_repo\\ebooks_test\\");
.
.
.
// Catalog Simple View
#GetMapping(Mappings.CATALOG_SIMPLE)
public String catalogSimple(Model model){
log.info("catalogSimple method called");
// This is adding the entire BookManager book list into the model.
model.addAttribute(AttributeNames.BOOK_DATA, bookData());
return ViewNames.CATALOG_SIMPLE;
}
// Catalog Detail View
#GetMapping(Mappings.CATALOG_DETAIL)
public String catalogDetail(Model model){
log.info("catalogDetail method called");
// This is adding the entire BookManager book list into the model.
model.addAttribute(AttributeNames.BOOK_DATA, bookData());
return ViewNames.CATALOG_DETAIL;
}
.
.
.
#GetMapping(Mappings.LOAD_BOOKS)
public void loadBooks(Model model) {
bookService.loadBooksFromDirectory(bookDirectory);
}
}
Obviously I'm not using #GetMapping(Mappings.LOAD_BOOKS) properly as you can see in the error below:
The error:
There was an unexpected error (type=Internal Server Error, status=500).
Error resolving template [load-books], template might not exist or might not be accessible by any of the configured Template Resolvers
org.thymeleaf.exceptions.TemplateInputException: Error resolving template [load-books], template might not exist or might not be accessible by any of the configured Template Resolvers
How does one invoke a method like I am doing but without Spring trying to redirect the user to another view?
I'm not expecting the page to update at all since I'm not returning a View!
When you click a link in your browser with a load-books anchor, your browser sends it to the server and waits for result, which causes your page to be reloaded. Once the request to a load-books endpoint reached to the server, Spring MVC handles this and starting to looking up an appropriate controller with its method. It founds public void loadBooks(Model model) in your case. When Spring MVC invokes the method, it expects to obtain a view name to resolve and return back to your browser.
Since you haven't provided a View or String as a return type, Spring MVC used the endpoint's path as a view name (I'm not seeing your Mappings.LOAD_BOOKS constant, but it supposed to be load-books).
If you're not going to return any view back to the browser, you can annotate the method like that:
#GetMapping(Mappings.LOAD_BOOKS)
#ResponseBody
public void loadBooks(Model model) {
which tells Spring to treat void as a response body.
But it's not preventing a page refreshing, you'll just see an empty page after clicking the link. In order to fix this you can redirect a user to another page by returning the following string (without ResponseBody annotation on the method)
return "redirect:/path-to-redirect";
When Spring MVC sees this prefix it redirects you to another controller, but user going to notice that too.
If you really don't want to see a blank page for a moment, you'll have to use some JavaScript to perform AJAX request to the server when button is clicked.
Actually, it seems that you want to preload some files in a service by a given path. If it's all you want to do, you can use Spring's runners like that:
#Component
class Preloader implements ApplicationRunner {
private final BookCatalogService bookService;
#Autowired
public Preloader(BookCatalogService service) {
this.bookService = service;
}
#Override
public void run(ApplicationArguments args) throws Exception {
bookService.loadBooksFromDirectory(BookCatalogController.bookDirectory);
}
}
Spring automatically calls all registered runners when application is ready, so your code will be executed without having a user to visit load-books endpoint.
Actually my code look like that:
#PreAuthorize("hasAuthority('admin')")
#RequestMapping(value = "/xxxx", method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_UTF8_VALUE)
public ResponseEntity<> method(#RequestBody RequestClass request) {
}
As you can see the allowed authorities are hard-coded in java code.
Is there a way to override the behaviour of PreAuthorize or to load the proper endpoint configuration at startup from an external source(database or configuration file)?
I suppose you might give something like this a try (might need some tweaking) but it's ugly and I would not do it myself...
Setup your method to role mappings in your configuration. For example:
permissions.method1: admin
permissions.method2: admin
permissions.method3: user
Then use an #ConfigurationProperties class to load in your map into a Map.
#ConfigurationProperties("")
public class SecurityMappingProperties {
private final Map<String, String> permissions = new HashMap<>();
public Map<String, String> getPermissions() {
return permissions;
}
}
Then setup a service to handle the lookup.
#Service
public class MethodPermissionService {
#Autowired
private SecurityMappingProperties mappingProperties;
//lookup the mapped role and see if you user has it..
public Boolean lookupPermissionForMethod(String method){
return doesUserHaveRole(mappingProperties.get(method));
}
private Boolean doesUserHaveRole(String role){
//implement whatever logic you want to look up the requesting user's role...
}
}
Then in your controllers, invoke the methodPermissionService and pass in the method name, like so...
#PreAuthorize("#methodPermissionService('method1')")
This, of course, would require you to have every secured method in all of your controllers to have an #Preauthorize with the matching method name as the argument to the methodPermissionService('xxx').
Since we are already in this rabbit hole, if you really wanted to, you could also just have a single place to declare all of them in some sort of MethodRoleHolder class where you can make them static Strings like the following:
public static final String METHOD1_SECURITY = "#methodPermissionService('method1')";
public static final String METHOD2_SECURITY = "#methodPermissionService('method2')";
then use them in your controllers...
#PreAuthorize(MethodRoleHolder.METHOD1_SECURITY)
Upfront caveat: I haven't actually tried this myself exactly as I laid out here but I have implemented a security scheme similar to this, just without the dynamic role mapping look up part.
Nowadays, I've been making an API system and I've got stuck in a problem regarding forwarding requests.
In my API system, all of requests from users come to proxy controllers first, and then the requests are forwarded to real API versionized.
// proxy controller
#Controller
#RequestMapping("/proxy")
public class ProxyController {
private static final String VERSION = "v1";
#RequestMapping(value = "/users", method = RequestMethod.GET)
public String getUsers() {
return String.format("forward:/%s/users", VERSION);
}
}
// Real API controller
#RestController("v1UsersController")
#RequestMapping("/v1/users")
public class UsersController {
#RequestMapping(value = "/users", method = RequestMethod.GET)
public ApiResultsResponse<Users> findUsers(#RequestParam userId, ...) {
// ...
// ...
// ...
// return users found
}
I've located the proxy controller because I could easily change the real api when some case happens like upgrading version of the api.
However, because I've used forwarding with String(in this case, return String.format("forward:/%s/users", VERSION)), I'm not sure whether the forwarded real api exist or not currently.
So I'd like to be sure this by calling the real api's controller method directly. If so, when the called method doesn't exists, I can notice this with compile errors. The problem is, I gotta synchronize parameters condition of real api handler with proxy's.(It's kinda dirty works.)
Is it possible to call forwarded controller method directly with using the same HttpServletRequest object?
You could use spring HandlerInterceptorAdapter to intercept before call your controllers
Maybe this solution will help you.
#RequestMapping(value = "/users", method = RequestMethod.GET)
public void getUsers(HttpServletRequest request,HttpServletResponse response) {
String path = String.format("forward:/%s/users", VERSION);
RequestDispatcher dispatcher=request.getRequestDispatcher(path);
dispatcher.forward(request, response);
}
Trying to do something that should be really easy...
Im using Spring MVC 3 and I want to redirect a call to a controller from inside another controller... ex:
#Controller
#SessionAttributes
public class UserFormController {
#Autowired
private UserService userService;
#RequestMapping(value = "/method1", method = RequestMethod.GET)
public ModelAndView redirectFormPage() {
//redirect to controller2 here,
//pointing to a method inside it, without going to any url first
}
Tried to do that with modelandview but it seems to me that all the solutions have to go through an url first and just then i can redirect to the controller.
thanks for all the help!
=============== more info ===========
The flow is like that: methodA on controller1 is called... do some stuff... and then wants to redirect the user to a listPage... this page has a list of objects, which a methodB on controller2 is able to load and send it to the this listPage. What im trying to achieve is : always that someone needs to get this page, i will call this method onn this controller2 and load it.
You can do
public String redirectFormPage() {
return "redirect:/url/controller2";
}
I don't know why you want to do this but this may work:
#Controller
#SessionAttributes
public class UserFormController {
#Autowired
private UserService userService;
#Autowired
private Controller2 controller2;
#RequestMapping(value = "/method1", method = RequestMethod.GET)
public ModelAndView redirectFormPage() {
return controller2.redirectMethod();
}
just inject Controller2 and call the desired method
Great question. I like to use struts redirect action result type to avoid putting knowledge of the next view in the actions that handle posted forms.
For example, the action method for a class like UpdateContactInfoAction would return contactInfoSaved instead of preparing data for the next view and returning gotoShippingOptionsPage. The knowledge that the next step in the flow is to choose a shipping option is only in the struts config.
Anyone know how to do this with Spring MVC?
I am trying to do a simple android application that communicates with a Spring server.
I'd like to use Sessions to store data of each logged in User.
My App exchange Json objects with the server and the Request Mapping is like this:
#Controller
public class LoginController {
#Autowired
private IUserDao userDao;
#RequestMapping( value = "/loginJson",method = RequestMethod.POST)
public #ResponseBody loginResponse login(#RequestBody loginModel login) {
loginResponse response=userDao.checkCredentials(login.getUsername(),login.getPassword());
System.out.println("Result="+response.isSuccess());
System.out.println("Received:"+login.getUsername()+" "+login.getPassword());
return response;
}
}
The controller is working fine, but I can't figure out how to store a sessione variable. I found many documents explaining Spring Sessions, but each of them different from the other.
Someone can suggest me some simple way to do this or some kind of good tutorial?
Not sure what you mean by saying Spring session, but you can declare additional HttpSession parameter in your method, and then do whatever you like inside of the method. Is this what you wanted to find out?