SPRING 3.0 MVC 2 page 1 controller - java

I just want to have 2 functions in JAVA Spring 3.0 MVC like page1.htm and page2.htm and just 1 controller, kinda is in CodeIgniter, i don't know why is so hard ...
This is what i did till now
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package controllers;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.validation.BindException;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.SimpleFormController;
// Import models
import models.LoginModel;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
/**
*
* #author Candy
*/
#RequestMapping("/app")
public class LoginController extends SimpleFormController {
public LoginController()
{
//Initialize controller properties here or
//in the Web Application Context
setCommandClass(models.Employee.class);
setCommandName("Employee");
//setSuccessView("pages/login/login_form_view");
//setFormView("pages/login/login_execute_view");
}
// On form load
#Override
protected ModelAndView showForm(HttpServletRequest request, HttpServletResponse response, BindException errors) throws Exception
{
LoginModel loginModel = new LoginModel(request);
if(!loginModel.isLogged())
{
ModelAndView mv = new ModelAndView("pages/login/login_form_view");
// mv.addObject("chestie",loginModel.isLogged());
return mv;
}
else
{
return new ModelAndView("redirect:/dashboard.htm");
}
}
#Override
protected void doSubmitAction(Object command) throws Exception
{
throw new UnsupportedOperationException("Not yet implemented");
}
//Use onSubmit instead of doSubmitAction
//when you need access to the Request, Response, or BindException objects
#Override
protected ModelAndView onSubmit(
HttpServletRequest request,
HttpServletResponse response,
Object command,
BindException errors) throws Exception
{
// User has submited a POST request
ModelAndView mv = new ModelAndView("pages/login/login_execute_view");
// Check if the user is logged or not, user must be NOT LOGGED in order to log
LoginModel loginModel = new LoginModel(request);
if(!loginModel.isLogged())
{
// Just continue
String email = request.getParameter("email").toString();
String password = request.getParameter("password").toString();
// Check this email/pass
Map<String,String> resultLogin = loginModel.login(email, password);
String result = resultLogin.get("result").toString();
String reason = resultLogin.get("reason").toString();
// String qqq = resultLogin.get("qqq").toString();
/*
java.util.Date dt = new java.util.Date();
java.text.SimpleDateFormat sdf =
new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String currentTime = sdf.format(dt);
*/
if(result == "true")
{
// Login is good, redirect to dashboard
return new ModelAndView("redirect:/dashboard.htm");
// Display the error
//mv.addObject("loginResult",result);
//mv.addObject("loginReason",reason);
//mv.addObject("qqq",qqq);
//mv.addObject("qqq2",request.getSession().getAttribute("is_logged").toString());
}
else
{
// Display the error
mv.addObject("loginResult",result);
mv.addObject("loginReason",reason);
return mv;
}
}
else
{
return new ModelAndView("redirect:/login.htm");
}
}
}
this controller will not work if I remove
applicationContext.xml
<bean class="controllers.LoginController"/>
dispatcher-servlet.xml
<bean class="controllers.LoginController" p:formView="pages/login/login_form_view" p:successView="pages/login/login_execute_view"/>
web.xml
<servlet>
<servlet-name>LoginController</servlet-name>
<servlet-class>controllers.LoginController</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>LoginController</servlet-name>
<url-pattern>/login</url-pattern>
</servlet-mapping>
Why i have to edit 3 XML when i create a new controller? Is not a simpler way?
All i want right now is just make another function logout() that loads the ModelAndView mv = new ModelAndView("pages/login/logout_view");, that's all.
And this
#RequestMapping("/app")
#Override
protected ModelAndView showForm(HttpServletRequest request, HttpServletResponse response, BindException errors) throws Exception
{
ModelAndView mv = new ModelAndView("pages/login/login_form_view");
return mv;
}
gives me 404 error ...
What should i use? SimpleFormController, extend Controller, AbstractController (i'm using NETBEANS). I'm confused, i wish there were a good java framework like CodeIgniter. I don't quite understand how mappings work, but i do know how an apache rewrite works.
Thank you very much.

If you are using Spring 3, you can easily use #controller annotation for your controller class which are used for your page request and navigation.
You can use #Service annotation for your business logic and #Repository for your Database transaction and bind these layers together using #Autowired annotation as well .
so your request goes this way ( UI Request -> #Controller -> #Service -> #Repository ) and come back to the original controller class again for navigation.
But about your question, when you mark a class with #Controller annotation in spring, that simply means you candidate that class for your URL mappings and navigations, the sample below show how you can use #RequestMapping annotation inside Controlller Class for your URL mapping:
#Controller
#RequestMapping("/employee")
public class Employees{
#AutoWired
private EmployeeServiceInterface empServInt;
#ModelAttribute("empAdd")
public Employee createEmployeeModel(){
return new Employee();
}
#RequestMapping(value="/add", method = RequestMethod.POST)
public String addEmployee(#ModelAttribute("empAdd")Employee employee,Model model, HttpSesion session){
Employee employee = empServInt.createUser(employee);
if(yourCondition()){
return "redirect:/TO_THIS_PAGE";
}
return "GO_TO_PAGE_NAVIGATION_HERE";
}
}
note that #RequestMapping get activated when a request come with the URL HOST_NAME:PORT/PROJECT_NAME/employee/add because of #RequesMapping annotation value.
Secondly #ModelAttribute you saw in sample create a model for your page that helps you to submit the Employee Object Bean values from UI using commandName attribute in Spring Form tag which binds the html inputs to Employee Bean class by using path="" attribute :
<form:form commandName="empAdd" method="post" >...</form:form>
Thirdly take note that the #AutoWired can automatically wired an interface to an implementation so in my example if a class like EmployeeServiceImpl implements the interface EmployeeServiceInterface by calling the interface methods like: empServInt.createUser(employee); spring automatically call related EmployeeServiceImpl.createUser(employee) implementation method for you.
Fourthly: take note that for you to be able to use Controller and the Service & Repository annotation to work you should have defined the packages that have those annotations in your Spring Descriptor XML file like so:
<context:component-scan base-package="java.package" />
Fifthly if you noticed the method inside Employees has String return type that provide your navigation but for this to work you should have define the page prefix and suffix that is going to return inside your Spring Descriptor XMl file as well like so:
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/view/" />
<property name="suffix" value=".jsp" />
</bean>
according to above config, if your method which is annotated with #RequestMapping is returning employee String, Spring go and fetch the page from this location:
[PREFIX]employee[POSSIX] --> /WEB-INF/view/employee.jsp
For better understanding I found a useful sample link for you. Link
Good Luck !

Related

The appropriate way to add a login endpoint in OpenAPI

I'm using OpenApi for Spring Boot application and I have authorization logic with JWT. The authorization request at /api/v1/login is intercepted and JSON is returned with the user token:
{
"Bearer": "token for user"
}
Security implementation responsible for capturing logins:
#Component
#RequiredArgsConstructor
class RestAuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {
#Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws IOException {
// handler returns body JSON with JWT
}
}
#Configuration
class SecurityConfig extends WebSecurityConfigurerAdapter {
// ...
JsonObjectAuthenticationFilter authenticationFilter() throws Exception {
var authFilter = new JsonObjectAuthenticationFilter();
authFilter.setAuthenticationSuccessHandler(restAuthenticationSuccessHandler );
authFilter.setAuthenticationFailureHandler(RestAuthenticationFailureHandler );
authFilter.setAuthenticationManager(super.authenticationManager());
authFilter.setFilterProcessesUrl("/api/v1/login"); // <- custom login URL
return authFilter;
}
}
It works fine, I don't have to put a separate /api/v1/login endpoint in the controller, so it is not taken into account when creating OpenAPI documentation. However, I want to have this endpoint documented and accessible from there as follows:
My first idea was to just create an interface to add appropriate annotations (assume BearerToken and AuthCredentials are my tranfer objects in correct format):
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
#RestController
#RequestMapping("/api/v1/login")
#Tag(name = "login")
interface LoginController {
#PostMapping
BearerToken login(#RequestBody AuthCredentials authCredentials);
}
However Spring does not register the interface as a beana, an implementation has yet to be provided, so OpenAPI does not add an entry to the documentation. So I turned the interface into a normal class:
#RestController
#RequestMapping("/api/v1/login")
#Tag(name = "login")
class LoginController {
#PostMapping
public BearerToken login(#RequestBody AuthCredentials authCredentials){
return new BearerToken();
}
}
Documentation is generated correctly, but I have a problem with this method. The implementation of the login() method suggests a completely different behavior than what actually takes place underneath in onAuthenticationSuccess().
Therefore, I am looking for a different way to achieve the desired effect.
As per OpenAPI implementation, there is a class OpenAPIService which has a build() method that does the following:
this.mappingsMap.putAll(this.context.getBeansWithAnnotation(RestController.class));
this.mappingsMap.putAll(this.context.getBeansWithAnnotation(RequestMapping.class));
this.mappingsMap.putAll(this.context.getBeansWithAnnotation(Controller.class));
The reason as to why what you want to achieve is not possible is that OpenAPI library relies on Spring annotations that I provided above and has no knowledge of custom ant matcher paths like the one you added:
JsonObjectAuthenticationFilter authenticationFilter() throws Exception {
var authFilter = new JsonObjectAuthenticationFilter();
authFilter.setAuthenticationSuccessHandler(restAuthenticationSuccessHandler );
authFilter.setAuthenticationFailureHandler(RestAuthenticationFailureHandler );
authFilter.setAuthenticationManager(super.authenticationManager());
authFilter.setFilterProcessesUrl("/api/v1/login"); // <- custom login URL
return authFilter;
}
Usually in production-ready applications developers write their own /authenticate API that checks username + password pair against a datasource (mySQL/postgreSQL/other).
This /authenticate API would be whitelisted so that security is not required to attempt login (some people store number of attempts per IP in Redis in case they want to block people from bruteforcing the password).
All other APIs would have to go through public class JwtAuthenticationFilter extends AuthenticationFilter { which simply validates the token and allows request to continue (if token is valid).
If you need more hints let me know.

How to map custom/dynamic requests to a controller (Spring)?

How can I map custom/dunamic requests toa given controller, based on a repository lookup?
The use-case is a CMS-like feature in a web-platform, where certain URL patterns ("pages") stored in the DB should be handled by a separate controller PageController.java. These patterns are not necessarily known at compile-time, and they can also be added and modified while the app is deployed (thus, it cannot be annotation-driven).
I did try to map a controller to "**" (see below), but that did not work for 2 reasons: firstly all other requests resolved to that same controller method (I had hoped that it would use "**" as a fallback and try the others first), and it also ended up resolving all requests to my static/asset files to this controller (resulting in unwanted 404-responses).
#Controller
public class PageController {
#Inject
private PageService pageService;
#RequestMapping(value = "**", method = RequestMethod.GET)
public String getPage(Model model, HttpServletRequest request, #CurrentUser User user) {
String path = request.getRequestURI();
Page page = this.pageService.getByPath(path, user);
if (page == null) {
throw new NotFoundException();
}
model.addAttribute("page", page);
return "web/page";
}
}
The temporary work-around/modification to the above method has so far been to map a pre-defined URL-prefixes to this controller (eg. /page/**, /info/**, /news/** etc), but this is an inelegant solution that adds arbitrary limitations to the system which I now seek to eliminate.
I am currently using Spring Boot 2.0. In addition to the naive mapping to ** in a regular #Controller class (using the #RequestMapping -annotation), I have also tried configuring SimpleUrlHandlerMapping the following way:
#Configuration
public class WebConfig implements WebMvcConfigurer {
#Inject
private PageDao pageDao;
#Bean
public PageController pageController() {
return new PageController();
}
#Bean
public SimpleUrlHandlerMapping pageUrlHandlerMapping() {
SimpleUrlHandlerMapping pageUrlHandlerMapping = new SimpleUrlHandlerMapping();
PageController pageController = this.pageController();
Map<String, Object> urlMap = this.pageDao.findAll().stream()
.map(Page::getNormalizedSlug)
.collect(Collectors.toMap(Function.identity(),
slug -> pageController, (existing, duplicate) -> existing));
pageUrlHandlerMapping.setUrlMap(urlMap);
pageUrlHandlerMapping.setOrder(Ordered.HIGHEST_PRECEDENCE); // <- Cannot be LOWEST_PRECEDENCE for some reason...
return pageUrlHandlerMapping;
}
}
public class PageController implements Controller {
#Inject
private PageService pageService;
#Inject
private DmaWebControllerAdvice controllerAdvice;
#Override
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
User user = null;
Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
if (principal instanceof User) {
user = (User) principal;
}
String path = request.getRequestURI();
Page page = this.pageService.getByPath(path, user);
if (page == null) {
throw new NotFoundException();
}
ModelAndView modelAndView = new ModelAndView();
modelAndView.setViewName("web/page");
modelAndView.addObject("page", page);
controllerAdvice.globalModelAttributes(modelAndView.getModel(), null);
return modelAndView;
}
}
This approach does technically work, but the list of pages will somehow have to be reloaded into the SimpleUrlHandlerMapping whenever one of the pages is changed (i am not quite sure how to do that). This also possibly overwrites some default Spring Boot-configuration, that I would ideally like to keep. It also has some drawbacks compared to resolving controllers using #Controller and #RequesMapping because I currently am injecting certain data into all views resolved that way (mainly model-data used in the overall design of the website, like menu, quicklinks etc). In the above attempt, I have had to set those via a separate call to controllerAdvice-globalModelAttributes().
What I am seeking is a solution where my repository is queried for potential page-matches in runtime, and if it is valid then the request will be handled by the proper page-controller. Is a custom HandlerMapping -implementation the way to do this? And if not, how should I solve this? And if making a separate HandlerMapping for pages, how do I add/register this in my configuration without overwriting the default provided by Spring?
Why don't you just implement a catch-all controller which parses your patterns as a parameter, does a db look-up and then use a forward to specific controllers (info, page, news etc.)? Seems like for a CMS, this look-up logic belongs into your code (e.g. service layer).
Easiest(but not the best) way to achieve what you need is creating custom HandlerMapping implementation:
public class PageMapper implements HandlerMapping, Ordered {
private HandlerMethod handlerMethod;
public CustomMapper(Object controller, Method method) {
this.handlerMethod = new HandlerMethod(controller, method);
}
#Override
public HandlerExecutionChain getHandler(HttpServletRequest httpServletRequest) throws Exception {
return new HandlerExecutionChain(handlerMethod);
}
#Override
public int getOrder() {
return Ordered.LOWEST_PRECEDENCE; //you have to add the handler to the end
}
}
Now remove #Controller annotation from PageController because you don't need it to be detected automatically anymore. After that register controller and mapping to config:
#Configuration
public class AppWebConfig implements WebMvcConfigurer {
#Bean
public PageController pageController() {
return new PageController();
}
#Bean
public HandlerMapping pageMapping(PageController pageController) {
Method method = BeanUtils.resolveSignature("getPage", PageController.class);
return new PageMapping(pageController, method);
}
}
Now every request unrecognized by other HandlerMapping instances will be sent to your mapping hence to your controller. But this approach has obvious disadvantage. Since your mapping is the last in the chain of mappings you never get 404 error. Therefor you never know about something wrong with you resources (e.g. if some of them are missing).
I would prefer let application to distinguish paths by prefix (just like you do it already), where prefix is operation application is going to do with a page. For example if you need to show or edit the page:
#Controller
public class PageController {
private final static String SHOW = "/show";
private final static String EDIT = "/edit";
#Inject
private PageService pageService;
GetMapping(value = SHOW + "/**")
public String getPage(Model model, HttpServletRequest request, #CurrentUser User user) {
String path = request.getRequestURI().substring(SHOW.length());
Page page = this.pageService.getByPath(path, user);
...
model.addAttribute("page", page);
return "web/page";
}
//the same for EDIT operation
}

Why is my request hitting two controllers?

I have the following Spring controller:
#Controller
public class TypoWorkflowController {
#RequestMapping(value = "/workflow/typo-workflow/moreInfo", method = RequestMethod.GET)
public String serveMoreInfo(#RequestParam(value = "taskId", required=false) String taskId, ModelMap modelMap) {
return "typo-workflow-more-info";
}
}
My tiles-def file contains:
<definition name="typo-workflow-more-info" template="/WEB_INF/jsp/workflow/typo-workflow/moreInfo.jsp"/>
My JSP is plain old HTML.
When I hit the url /workflow/typo-workflow/moreInfo, Tomcat throws a StackOverflowError.
When I step through in debug mode, I see that I'm hitting my controller first, as I would expect, but then I hit another controller, at the method:
#Controller
#Order(value = Ordered.LOWEST_PRECEDENCE)
public class ContentServingController {
/* ... */
#RequestMapping({"/*", "/**/*"})
public ModelAndView serveContent(HttpServletResponse response, ModelMap model) {
/* ... */
}
}
As I poked around, it seeeeeeemed like we were in there to respond to a request for /WEB_INF/jsp/workflow/typo-workflow/moreInfo.jsp, but this doesn't happen for other controllers that operate in the same way (returning a View name).
So, can anyone provide me with some pointers for debugging this. Why would I be hitting a controller for a JSP anyway? Isn't a JSP supposed to be a little servlet itself?
Your tiles def is pointing to the WEB_INF folder when it should be pointing to the WEB-INF folder (dash instead of underscore) so spring doesn't know where to look within the app and is just making a normal http request, which is getting caught by the wildcard match.

Spring MVC: bind request attribute to controller method parameter

In Spring MVC, it is easy to bind request parameter to method paramaters handling the request. I just use #RequestParameter("name"). But can I do the same with request attribute? Currently, when I want to access request attribute, I have to do following:
MyClass obj = (MyClass) request.getAttribute("attr_name");
But I really would like to use something like this instead:
#RequestAttribute("attr_name") MyClass obj
Unfortunately, it doesn't work this way. Can I somehow extend Spring functionality and add my own "binders"?
EDIT (what I'm trying to achieve): I store currently logged user inside request attribute. So whenever I want to access currently logged user (which is pretty much inside every method), I have to write this extra line user = (User) request.getAttribute("user");. I would like to make it as short as possible, preferably inject it as a method parameter. Or if you know another way how to pass something across interceptors and controllers, I would be happy to hear it.
Well, I finally understood a little bit how models work and what is #ModelAttribute for. Here is my solution.
#Controller
class MyController
{
#ModelAttribute("user")
public User getUser(HttpServletRequest request)
{
return (User) request.getAttribute("user");
}
#RequestMapping(value = "someurl", method = RequestMethod.GET)
public String HandleSomeUrl(#ModelAttribute("user") User user)
{
// ... do some stuff
}
}
The getUser() method marked with #ModelAttribute annotation will automatically populate all User user parameters marked with #ModelAttribute. So when the HandleSomeUrl method is called, the call looks something like MyController.HandleSomeUrl(MyController.getUser(request)). At least this is how I imagine it. Cool thing is that user is also accessible from the JSP view without any further effort.
This solves exactly my problem however I do have further questions. Is there a common place where I can put those #ModelAttribute methods so they were common for all my controllers? Can I somehow add model attribute from the inside of the preHandle() method of an Interceptor?
Use (as of Spring 4.3) #RequestAttribute:
#RequestMapping(value = "someurl", method = RequestMethod.GET)
public String handleSomeUrl(#RequestAttribute User user) {
// ... do some stuff
}
or if the request attribute name does not match the method parameter name:
#RequestMapping(value = "someurl", method = RequestMethod.GET)
public String handleSomeUrl(#RequestAttribute(name="userAttributeName") User user) {
// ... do some stuff
}
I think what you are looking for is:
#ModelAttribute("attr_name") MyClass obj
You can use that in the parameters for a method in your controller.
Here is a link a to question with details on it What is #ModelAttribute in Spring MVC?
That question links to the Spring Documentation with some examples of using it too. You can see that here
Update
I'm not sure how you are setting up your pages, but you can add the user as a Model Attribute a couple different ways. I setup a simple example below here.
#RequestMapping(value = "/account", method = RequestMethod.GET)
public ModelAndView displayAccountPage() {
User user = new User(); //most likely you've done some kind of login step this is just for simplicity
return new ModelAndView("account", "user", user); //return view, model attribute name, model attribute
}
Then when the user submits a request, Spring will bind the user attribute to the User object in the method parameters.
#RequestMapping(value = "/account/delivery", method = RequestMethod.POST)
public ModelAndView updateDeliverySchedule(#ModelAttribute("user") User user) {
user = accountService.updateDeliverySchedule(user); //do something with the user
return new ModelAndView("account", "user", user);
}
Not the most elegant, but works at least...
#Controller
public class YourController {
#RequestMapping("/xyz")
public ModelAndView handle(
#Value("#{request.getAttribute('key')}") SomeClass obj) {
...
return new ModelAndView(...);
}
}
Source : http://blog.crisp.se/tag/requestattribute
From spring 3.2 it can be done even nicer by using Springs ControllerAdvice annotation.
This then would allow you to have an advice which adds the #ModelAttributes in a separate class, which is then applied to all your controllers.
For completeness, it is also possible to actually make the #RequestAttribute("attr-name") as is.
(below modified from this article to suit our demands)
First, we have to define the annotation:
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.PARAMETER)
public #interface RequestAttribute {
String value();
}
Then we need a [WebArgumentResolver] to handle what needs to be done when the attribute is being bound
public class RequestAttributeWebArgumentResolver implements WebArgumentResolver {
public Object resolveArgument(MethodParameter methodParameter, NativeWebRequest nativeWebRequest) throws Exception {
// Get the annotation
RequestAttribute requestAttributeAnnotation = methodParameter.getParameterAnnotation(RequestAttribute.class);
if(requestAttributeAnnotation != null) {
HttpServletRequest request = (HttpServletRequest) nativeWebRequest.getNativeRequest();
return request.getAttribute(requestAttributeAnnotation.value);
}
return UNRESOLVED;
}
}
Now all we need is to add this customresolver to the config to resolve it:
<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
<property name="customArgumentResolver">
<bean class="com.sergialmar.customresolver.web.support.CustomWebArgumentResolver"/>
</property>
</bean>
And we're done!
Yes, you can add your own 'binders' to the request attribute - see spring-mvc-3-showcase, or use #Peter Szanto's solution.
Alternatively, bind it as a ModelAttribute, as recommended in other answers.
As it's the logged-in user that you want to pass into your controller, you may want to consider Spring Security. Then you can just have the Principle injected into your method:
#RequestMapping("/xyz")
public String index(Principal principle) {
return "Hello, " + principle.getName() + "!";
}
In Spring WebMVC 4.x, it prefer implements HandlerMethodArgumentResolver
#Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.getParameterAnnotation(RequestAttribute.class) != null;
}
#Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
return webRequest.getAttribute(parameter.getParameterAnnotation(RequestAttribute.class).value(), NativeWebRequest.SCOPE_REQUEST);
}
}
Then register it in RequestMappingHandlerAdapter

How to get active user's UserDetails

In my controllers, when I need the active (logged in) user, I am doing the following to get my UserDetails implementation:
User activeUser = (User)SecurityContextHolder.getContext().getAuthentication().getPrincipal();
log.debug(activeUser.getSomeCustomField());
It works fine, but I would think Spring could make life easier in a case like this. Is there a way to have the UserDetails autowired into either the controller or the method?
For example, something like:
public ModelAndView someRequestHandler(Principal principal) { ... }
But instead of getting the UsernamePasswordAuthenticationToken, I get a UserDetails instead?
I'm looking for an elegant solution. Any ideas?
Preamble: Since Spring-Security 3.2 there is a nice annotation #AuthenticationPrincipal described at the end of this answer. This is the best way to go when you use Spring-Security >= 3.2.
When you:
use an older version of Spring-Security,
need to load your custom User Object from the Database by some information (like the login or id) stored in the principal or
want to learn how a HandlerMethodArgumentResolver or WebArgumentResolver can solve this in an elegant way, or just want to an learn the background behind #AuthenticationPrincipal and AuthenticationPrincipalArgumentResolver (because it is based on a HandlerMethodArgumentResolver)
then keep on reading — else just use #AuthenticationPrincipal and thank to Rob Winch (Author of #AuthenticationPrincipal) and Lukas Schmelzeisen (for his answer).
(BTW: My answer is a bit older (January 2012), so it was Lukas Schmelzeisen that come up as the first one with the #AuthenticationPrincipal annotation solution base on Spring Security 3.2.)
Then you can use in your controller
public ModelAndView someRequestHandler(Principal principal) {
User activeUser = (User) ((Authentication) principal).getPrincipal();
...
}
That is ok if you need it once. But if you need it several times its ugly because it pollutes your controller with infrastructure details, that normally should be hidden by the framework.
So what you may really want is to have a controller like this:
public ModelAndView someRequestHandler(#ActiveUser User activeUser) {
...
}
Therefore you only need to implement a WebArgumentResolver. It has a method
Object resolveArgument(MethodParameter methodParameter,
NativeWebRequest webRequest)
throws Exception
That gets the web request (second parameter) and must return the User if its feels responsible for the method argument (the first parameter).
Since Spring 3.1 there is a new concept called HandlerMethodArgumentResolver. If you use Spring 3.1+ then you should use it. (It is described in the next section of this answer))
public class CurrentUserWebArgumentResolver implements WebArgumentResolver{
Object resolveArgument(MethodParameter methodParameter, NativeWebRequest webRequest) {
if(methodParameter is for type User && methodParameter is annotated with #ActiveUser) {
Principal principal = webRequest.getUserPrincipal();
return (User) ((Authentication) principal).getPrincipal();
} else {
return WebArgumentResolver.UNRESOLVED;
}
}
}
You need to define the Custom Annotation -- You can skip it if every instance of User should always be taken from the security context, but is never a command object.
#Target(ElementType.PARAMETER)
#Retention(RetentionPolicy.RUNTIME)
#Documented
public #interface ActiveUser {}
In the configuration you only need to add this:
<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter"
id="applicationConversionService">
<property name="customArgumentResolver">
<bean class="CurrentUserWebArgumentResolver"/>
</property>
</bean>
#See: Learn to customize Spring MVC #Controller method arguments
It should be noted that if you're using Spring 3.1, they recommend HandlerMethodArgumentResolver over WebArgumentResolver. - see comment by Jay
The same with HandlerMethodArgumentResolver for Spring 3.1+
public class CurrentUserHandlerMethodArgumentResolver
implements HandlerMethodArgumentResolver {
#Override
public boolean supportsParameter(MethodParameter methodParameter) {
return
methodParameter.getParameterAnnotation(ActiveUser.class) != null
&& methodParameter.getParameterType().equals(User.class);
}
#Override
public Object resolveArgument(MethodParameter methodParameter,
ModelAndViewContainer mavContainer,
NativeWebRequest webRequest,
WebDataBinderFactory binderFactory) throws Exception {
if (this.supportsParameter(methodParameter)) {
Principal principal = webRequest.getUserPrincipal();
return (User) ((Authentication) principal).getPrincipal();
} else {
return WebArgumentResolver.UNRESOLVED;
}
}
}
In the configuration, you need to add this
<mvc:annotation-driven>
<mvc:argument-resolvers>
<bean class="CurrentUserHandlerMethodArgumentResolver"/>
</mvc:argument-resolvers>
</mvc:annotation-driven>
#See Leveraging the Spring MVC 3.1 HandlerMethodArgumentResolver interface
Spring-Security 3.2 Solution
Spring Security 3.2 (do not confuse with Spring 3.2) has own build in solution: #AuthenticationPrincipal (org.springframework.security.web.bind.annotation.AuthenticationPrincipal) . This is nicely described in Lukas Schmelzeisen`s answer
It is just writing
ModelAndView someRequestHandler(#AuthenticationPrincipal User activeUser) {
...
}
To get this working you need to register the AuthenticationPrincipalArgumentResolver (org.springframework.security.web.bind.support.AuthenticationPrincipalArgumentResolver) : either by "activating" #EnableWebMvcSecurity or by registering this bean within mvc:argument-resolvers - the same way I described it with may Spring 3.1 solution above.
#See Spring Security 3.2 Reference, Chapter 11.2. #AuthenticationPrincipal
Spring-Security 4.0 Solution
It works like the Spring 3.2 solution, but in Spring 4.0 the #AuthenticationPrincipal and AuthenticationPrincipalArgumentResolver was "moved" to an other package:
org.springframework.security.core.annotation.AuthenticationPrincipal
org.springframework.security.web.method.annotation.AuthenticationPrincipalArgumentResolver
(But the old classes in its old packges still exists, so do not mix them!)
It is just writing
import org.springframework.security.core.annotation.AuthenticationPrincipal;
ModelAndView someRequestHandler(#AuthenticationPrincipal User activeUser) {
...
}
To get this working you need to register the (org.springframework.security.web.method.annotation.) AuthenticationPrincipalArgumentResolver : either by "activating" #EnableWebMvcSecurity or by registering this bean within mvc:argument-resolvers - the same way I described it with may Spring 3.1 solution above.
<mvc:annotation-driven>
<mvc:argument-resolvers>
<bean class="org.springframework.security.web.method.annotation.AuthenticationPrincipalArgumentResolver" />
</mvc:argument-resolvers>
</mvc:annotation-driven>
#See Spring Security 5.0 Reference, Chapter 39.3 #AuthenticationPrincipal
While Ralphs Answer provides an elegant solution, with Spring Security 3.2 you no longer need to implement your own ArgumentResolver.
If you have a UserDetails implementation CustomUser, you can just do this:
#RequestMapping("/messages/inbox")
public ModelAndView findMessagesForUser(#AuthenticationPrincipal CustomUser customUser) {
// .. find messages for this User and return them...
}
See Spring Security Documentation: #AuthenticationPrincipal
Spring Security is intended to work with other non-Spring frameworks, hence it is not tightly integrated with Spring MVC. Spring Security returns the Authentication object from the HttpServletRequest.getUserPrincipal() method by default so that's what you get as the principal. You can obtain your UserDetails object directly from this by using
UserDetails ud = ((Authentication)principal).getPrincipal()
Note also that the object types may vary depending on the authentication mechanism used (you may not get a UsernamePasswordAuthenticationToken, for example) and the Authentication doesn't strictly have to contain a UserDetails. It can be a string or any other type.
If you don't want to call SecurityContextHolder directly, the most elegant approach (which I would follow) is to inject your own custom security context accessor interface which is customized to match your needs and user object types. Create an interface, with the relevant methods, for example:
interface MySecurityAccessor {
MyUserDetails getCurrentUser();
// Other methods
}
You can then implement this by accessing the SecurityContextHolder in your standard implementation, thus decoupling your code from Spring Security entirely. Then inject this into the controllers which need access to security information or information on the current user.
The other main benefit is that it is easy to make simple implementations with fixed data for testing, without having to worry about populating thread-locals and so on.
Implement the HandlerInterceptor interface, and then inject the UserDetails into each request that has a Model, as follows:
#Component
public class UserInterceptor implements HandlerInterceptor {
....other methods not shown....
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
if(modelAndView != null){
modelAndView.addObject("user", (User)SecurityContextHolder.getContext().getAuthentication().getPrincipal());
}
}
Starting with Spring Security version 3.2, the custom functionality that has been implemented by some of the older answers, exists out of the box in the form of the #AuthenticationPrincipal annotation that is backed by AuthenticationPrincipalArgumentResolver.
An simple example of it's use is:
#Controller
public class MyController {
#RequestMapping("/user/current/show")
public String show(#AuthenticationPrincipal CustomUser customUser) {
// do something with CustomUser
return "view";
}
}
CustomUser needs to be assignable from authentication.getPrincipal()
Here are the corresponding Javadocs of
AuthenticationPrincipal and AuthenticationPrincipalArgumentResolver
#Controller
public abstract class AbstractController {
#ModelAttribute("loggedUser")
public User getLoggedUser() {
return (User)SecurityContextHolder.getContext().getAuthentication().getPrincipal();
}
}
And if you need authorized user in templates (e.g. JSP) use
<%# taglib prefix="sec" uri="http://www.springframework.org/security/tags" %>
<sec:authentication property="principal.yourCustomField"/>
together with
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-taglibs</artifactId>
<version>${spring-security.version}</version>
</dependency>
You can try this:
By Using Authentication Object from Spring we can get User details from it in the controller method . Below is the example , by passing Authentication object in the controller method along with argument.Once user is authenticated the details are populated in the Authentication Object.
#GetMapping(value = "/mappingEndPoint") <ReturnType> methodName(Authentication auth) {
String userName = auth.getName();
return <ReturnType>;
}
To get the Active Users Details you can use #AuthenticationPrincipal in your controller like this:-
public String function(#AuthenticationPrincipal UserDetailsImpl user,
Model model){
model.addAttribute("username",user.getName()); //this user object
contains user details
return "";
}
UserDetailsImpl.java
import com.zoom.model.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.Collection;
import java.util.List;
public class UserDetailsImpl implements UserDetails {
#Autowired
private User user;
public UserDetailsImpl(User user) {
this.user = user;
}
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
SimpleGrantedAuthority simpleGrantedAuthority = new SimpleGrantedAuthority("ADMIN");
return List.of(simpleGrantedAuthority);
}
#Override
public String getPassword() {
return user.getPassword();
}
#Override
public String getUsername() {
return user.getEmail();
}
#Override
public boolean isAccountNonExpired() {
return true;
}
#Override
public boolean isAccountNonLocked() {
return true;
}
#Override
public boolean isCredentialsNonExpired() {
return true;
}
#Override
public boolean isEnabled() {
return true;
}
public String getRole(){
return user.getRole();
}
public String getName(){
return user.getUsername();
}
public User getUser(){
return user;
}
}

Categories