Access UserDetails object in Websocket session Spring MVC - spring-mvc

In a call sent over a websocket connection in a Spring MVC 4 application, I can get a java.security.Principal object when adding this as a parameter in the method.
However, this basicly only has the username in it, and I need the (extended) UserDetails object created during logging in.
SecurityContextHolder.getContext().getAuthentication() returns null. As I understood because spring creates a special session for handling the websocket connections.
The Question: Is there a way to access the securitycontext from inside the websocket session/thread?
(Being able to access a session bean would suffice as well).

#AuthenticationPrincipal is specifically for REST/MVC methods.
However, you can get pretty close by adding an argument of Type java.security.Principle to your #MessageMapping annotated method and then taking the real UserDetails object from it as follows:
FooUserDetails currentUser = (FooUserDetails) ((Authentication) user).getPrincipal();
This works for me for Spring Boot 1.5.4

I suppose you have extended AbstractWebSocketHandler
the you could have principal name :
#Override
public void afterConnectionEstablished ( WebSocketSession session ) throws Exception {
logger.info ( "afterConnectionEstablished = " + session.getPrincipal () );
}

I've not tested it, but you should be able to access your user adding a parameter like
#AuthenticationPrincipal CustomUser customUser
to your #MessageMapping method.
You can find more here: http://docs.spring.io/spring-security/site/docs/4.0.x/reference/htmlsingle/#mvc-authentication-principal

Related

Session atributes missing from spring session when setting inside HttpSessionListener

In servlet based application I want to make session replication using Spring Session Data Redis. Everything works fine but for csrf security we are using owasp scrfgaurd, where in that flow session token will set at HttpSessionListener.The token is missing while validating.
I tried creating standalone code reproduce the issue.I have created SampleHttpListener and setting some attributes to the session, but these values I am not able to see at servlet level.
Please tell me anything I am missing or any other approach for this senario.
I have added source code here
https://github.com/surya0420/SpringSession
public class CsrfGuardHttpSessionListener implements HttpSessionListener {
#Override
public void sessionCreated(HttpSessionEvent event) {
HttpSession session = event.getSession();
CsrfGuard csrfGuard = CsrfGuard.getInstance();
csrfGuard.updateToken(session);
if(session.getServletContext()!=null){
session.getServletContext().setAttribute(OWASP_CSRFTOKEN,session.getAttribute(OWASP_CSRFTOKEN));
}
}
Since session attributes are missing which are setting at HttpSessionListener level, so I am setting it to
session.getServletContext().setAttribute(OWASP_CSRFTOKEN,session.getAttribute(OWASP_CSRFTOKEN));
after session got created I am setting back the attributes at filter level as shown below at Filter level
if(((HttpServletRequest) request).getSession().getAttribute(OWASP_CSRFTOKEN) == null) {
((HttpServletRequest) request).getSession().setAttribute(OWASP_CSRFTOKEN, request.getServletContext().getAttribute(OWASP_CSRFTOKEN));
}

springboot+mybatis, About Native Dao development

I'm trying a new development method.
In mybatis3, I write mapper.java and mapper.xml usually.
I know, the sql statements is corresponded by sqlId(namespace+id).
I want to execute the sql statement like this :
SqlSession sqlSession = sessionFactory.openSession();
return sqlSession.selectList(sqlId, param);
but I get a error:
Cause: java.lang.IllegalArgumentException: Mapped Statements collection does not contain value for mapper.JinBoot.test
at org.apache.ibatis.exceptions.ExceptionFactory.wrapException(ExceptionFactory.java:30)
at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:150)
at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:141)
at cn.tianyustudio.jinboot.dao.BaseDao.select(BaseDao.java:20)
at cn.tianyustudio.jinboot.service.BaseService.select(BaseService.java:10)
at cn.tianyustudio.jinboot.controller.BaseController.test(BaseController.java:21)
here is my BaseDao.java
public class BaseDao {
private static SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
public static List<Map> select(String sqlId, Map param) {
try {
factoryBean.setDataSource(new DruidDataSource());
SqlSessionFactory sessionFactory = factoryBean.getObject();
SqlSession sqlSession = sessionFactory.openSession();
return sqlSession.selectList(sqlId, param);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
here is UserMapper.xml
<mapper namespace="mapper.JinBoot">
<select id="test" parameterType="hashMap" resultType="hashMap">
select * from user
</select>
</mapper>
the application.properties
mybatis.mapperLocations=classpath:mapper/*.xml
I start the project, the send a http request, after controller and service ,the param 'sqlId' in BaseDao is 'mapper.JinBoot.test' (see error info).
In method 'BaseDao.select', both the parameter and the result type is Map.
So I don't want to create UserMapper.java, I want try it.
How can I resolve it? What's the malpractice of this way?
This does not work because spring boot creates its own SqlSessionFactory. And the option in application.properties that specifies where mappers should be looked for is only set for that SqlSessionFactory. You are creating unrelated session factory in your DAO and it does not know where to load mappers definition.
If you want to make it work you need that you DAO is spring managed so that you can inject mybatis session factory into it and use it in select. This would also require that you convert select into non static method.
As I understand you want to have only one method in you base DAO class and use it in individual specific DAO classes. I would say it makes little sense. If the method returns Map there will be some place that actually maps this generic type to some application specific types. This would probably be in the child DAOs. So you still need to create the API of the child DAO with the signature that uses some input parameters and returns some domain objects. And that's exactly what you want to avoid by not creating mybatis mapper classes.
The thing is that you can treat your mytabis mappers as DAOs. That is you mappers would be your DAOs. And you don't need another layer. As I understand now you have two separate layers - DAO and mappers and you want to remove boilerplate code. I think it is better to remove DAO classes. They are real boilerplate and mybatis mapper can serve as DAO perfectly. You inject it directly to you service and service depends only on the mapper class. The logic of the mapping is in the mapper xml file. See also answer to this question Can Spring DAO be merged into Service layer?

Spring REST Exception Handling FileUploadBase$SizeLimitExceededException

I am using Spring Boot 1.5.3.RELEASE and using a Controller that takes a MultipartFile with some other information as arguments and returns a file.
Now I am facing the org.apache.tomcat.util.http.fileupload.FileUploadBase$SizeLimitExceededException when the file exceeds the maximum Sizes.
spring.http.multipart.maxFileSize=17728640
spring.http.multipart.maxRequestSize=17728640
This works well but i need a custom Response and actually the Exception is throwed only at server side before the method call.
Can anyone tell me how can I define a Custom Error Handler that handles this exception and response something like ResponseEntity.status(HttpStatus.CONFLICT).body("size_exceeded")
My Method:
#SuppressWarnings("rawtypes")
#RequestMapping(value = "/{Id}/attachments", method = RequestMethod.POST)
public ResponseEntity addTaskAttachment(#RequestParam("file") MultipartFile file, #PathVariable Long Id,
#CurrentUser User currentUser) {
// Some code here
ResponseEntity.status(HttpStatus.OK).body(attachmentAsByteArray);
}
You are correct in your observation that an Exception Handler with #RestControllerAdvice wouldn't work for multi part exceptions and reason being MultipartFile parsing & validation step preceding the mapping resolver step.
As advised in first accepted answer by geoand for this SO question here , you need to define and register an ErrorController.
Also, note that as already mentioned in that answer , Spring Boot already defines a BasicErrorController that you can extend to add new content types to return a JSON etc ( since default is text/html ) by adding a new public method with #RequestMapping & #Produces .

#ModelAttribute and #SessionAttribute in Spring

As Spring Specification said, #ModelAttribute will executed before the mapping handler and #SessionAttribute will keep the model attribute in session.
Consider below scenario: form bean is created after the controller is called and is set as session attribute as well. Next time MenuController is called, createForm() will be executed again and create another new form bean. My question is: will this latest created form bean be set as session attribute? and which form bean will be bind to the parameter in method bookList()?
Hope you guys can help. Thank you.
#Controller
#RequestMapping("/store")
#SessionAttribute("form")
public class MenuController {
#ModelAttribute("form")
public Form createForm() {
return new Form();
}
#RqeustMapping("/book")
public String bookList(#ModelAttribute("form") Form form){
//processing the form
}
}
When the bookList method is invoked for the first time in a given session, then method with #ModelAttribute('form) is invoked, the returned value (Form object) is stored in HttpSession and finally the bookList method is invoked with the same Form object passed as an argument (obtained from session).
For the subsequent requests within the same HttpSession, Spring retrieves the same Form object from the session and doesn't call the method with #ModelAttribute('form') again till the end of the session.
After each end of the bookList method invocation Spring stores updated version of Form object in HttpSession.
If you are using Spring Boot 2.x you can debug DefaultSessionAttributeStore#retrieveAttribute method to understand this behaviour.
Remember that your mapping is generalised. It will map both to a GET method and a POST method.
If your request mapping is a GET method,
The session attribute will hold the value of the #ModelAttribute("form") from the method createForm.
If an attribute form is returned from a POST request,
The session Attribute will override the #Model Attribute from the createForm method.
It is helpful to remember that the #ModelAttribute will execute before the mapping handler.
the sessionAttribute indicates that the "form" will be saved in the session. not meaning the "form" is retrieved from the session.

How do I omit ModelAttribute parameters from the ModelAndView returned by a Spring 3 MVC action?

I have a Spring MVC controller with an action that's called using AJAX.
#SessionAttributes({"userContext"})
public class Controller
{
...
#RequestMapping(value = "/my-url", method= { RequestMethods.POST })
public ModelAndView doSomething(#ModelAttribute("userContext") UserContext context,
SessionStatus sessionStatus)
{
BusinessObject obj = doSomeBusinessLogic(context.getUserName());
sessionStatus.setComplete();
ModelAndView mav = new ModelAndView("jsonView");
mav.addObject("someInt", obj.getId());
return mav;
}
}
When I run this action, I get the following exception:
net.sf.json.JSONException: There is a cycle in the hierarchy!
at t.sf.json.util.CycleDetectionStrategy$StrictCycleDetectionStrategy.handleRepeatedReferenceAsObject(CycleDetectionStrategy.java:97)
at net.sf.json.JSONObject._fromBean(JSONObject.java:833)
at net.sf.json.JSONObject.fromObject(JSONObject.java:168)
at org.springframework.web.servlet.view.json.writer.jsonlib.PropertyEditorRegistryValueProcessor.processObjectValue(PropertyEditorRegistryValueProcessor.java:127)
at net.sf.json.JSONObject._fromMap(JSONObject.java:1334)
Truncated. see log file for complete stacktrace
After doing some debugging I found out that Spring is placing the UserContext object onto the ModelAndView that I am returning. If I hard-code my user name and remove the context object from the method's parameters, the action runs successfully. Is there a way to configure Spring to omit the ModelAttribute-annotated parameters from the returned ModelAndView? As you can see, sessionStatus.setComplete() has no effect.
I've had similar problems in the past with #SessionAttributes. By declaring #SessionAttributes({"userContext"}) you're telling Spring that you want "userContext" to always be available in the model, and so Spring has no choice but to send your UserContext object out to the model, just in case you're going to be redirecting or doing something else which might end up at another Controller.
The "solution" (and I didn't like it much, but it worked) was to omit the #SessionAttributes annotation on the controller, add an HttpSession parameter to the necessary methods and "manually" manage what's in it.
I'm interested to see if there's a better way, because it seems #SessionAttributes has tremendous potential to tidy up controller-level code.
I registered a WebArgumentResolver to get to my session variable. This allowed me to keep this session variable out of the response while keeping my action unit testable.
Along with #ModelAttribute, pass #ModelMap as a method argument.
Based on business logic, error conditions -- if you do not need the attribute for certain scenarios, then remove it from the map.
public ModelAndView foo(#ModelAttribute("userContext") UserContext, #ModelMap map){
if(success){
return success.jsp
}
else{
map.remove("userContext");
return "error.jsp"
}
}
Not totally satisfied with having to pass the ModelMap as well, but I did not find any other easier way of doing it.
Cheers!!

Resources