I have a question about spring mvc and thread safety.
We are developing web application that will be stored on tomcat. If I understand right, Tomcat creates thread per request and it has some thread pool. Now, dispatcher servlet is shared between requests and probably is thread safe.
But when I create controller like this:
#Controller
#RequestMapping("/manage")
public class QuestionManagementController {
he has Singleton scope so the same controller is used by every request that comes from every user.
I am wondering how this problem is usually solved:
1: are controllers created with Session scope? (but I think that there also could be problems if one user quickly do some things that may lead to race conditions in the controller).
2: controllers are scoped as request
3: creating stateless controllers that don't share any variables at class level, or have them in read only mode
or maybe there is some better "best practice" that solves this kind of problem.
I am asking this question, because now we have them as Singleton scoped, and there is a problem, that in most methods we are querying for user in the database , and we can't store this information in class level variable because of the scope. We could try to use some thread safe collection but later there could be other resources needing synchronized access.
A lot of parameters can be added to the controller methods like request, session, principal etc to avoid your problem.
Usually there's a 3-layers architecture:
#Controller (they delegates to services)
#Service (they do the work using DAOs or Repositories)
#Repository (or DAOs, they do DB access)
So depending on what you are querying for in the DB, I would advise having a service, injected by Spring with a cache if hitting the DB is costly and everytime you need the stuff from the DB call the service (i.e. nothing stored in the controller class level)
A short example, let's say we are behind spring-security and everything need a fully logged-in user. We have an userData table where the key is the user login, we have an url /data to get a page showing my user data:
#Controller
#RequestMapping("/data")
public class UserDataController
{
#Autowired
private UserService userService;
#RequestMapping(value = "", method = RequestMethod.GET)
public ModelAndView data(final Principal principal) {
Assert.notNull(principal); // throw if assertion fails
Assert.hasText(principal.getName());
final UserData userData = this.userService.findByUserName(principal.getName());
Assert.notNull(userData, "userData not found");
return new ModelAndView("userData", "userData", userData);
}
}
#Service("userService")
public class userService
{
private static final String USERDATA_CACHE = "com.acme.foo.UserData";
#Autowired
private UserDataRepository userDataRepository;
#Cacheable(USERDATA_CACHE)
public UserData findByUserName(final String userName) {
return this.userDataRepository.findByUserName(userName);
}
}
#Repository
public class UserDataRepository
{
// or use spring-data-jpa
public UserData findByUserName(final String userName) {
// query table userData and create an UserData from results.
}
}
Here I use principal and spring ensure that this is current user one.
Refs:
#Cachable
see also Initialize Singletons in Spring Framework 3 MVC
Note sure if this answer fully to your concerns
Related
I have an EJB application that consists of two beans, ServiceEJB (web tier) and BusinessEJB (business tier), where BusinessEJBis injected in ServiceEJB.
ServiceEJBreceives HTTP requests from the browser, calls a method in BusinessEJB, gets the result, and sends the HTTP response.
Also, ServiceEJB has access to the HttpSession object, where the userId of the user that logged in is stored. BusinessEJBdoes NOT have access to the HttpSession object.
The application needs to log messages (using sl4j/logback, for example). It could log the message in ServiceEJBor BusinessEJB methods, and when it logs a message, it has to include the userId of the session in the log entry.
Since BusinessEJB doesn't have the userId, it needs to get it from ServiceEJB. The question is what is the best way to achieve that. What I DON'T want to do is to add a userId field to each method in BusinessEJB as a parameter, as there are many ServiceEJBs and BusinessEJBs in the application (and other beans called by BusinessEJB that also generate log entries), and I don't want to pollute the application with the userId field. Instead, I could have a userId field at the EJB level, but how to populate them? Is there a way to achieve this with annotations? Any suggestions will be welcome.
#Produces({MediaType.APPLICATION_JSON})
#Consumes({MediaType.APPLICATION_JSON})
#Stateless
public class ServiceEJB {
#Context
HttpServletRequest httpRequest;
#Inject
private BusinessEJB bean;
private String userId;
#Path("someurl")
public Response someMethod1() {
final HttpSession session = httpRequest.getSession();
// get the userId from the session
String s = bean.someMethod2();
// return Response
}
}
#Stateless
public class BusinessEJB {
private String userId;
public String someMethod2() {
// .... log an entry with userId
return "something";
}
}
A few pointers/comments:
If you integrate with application server security, then the user name is available at any component. EJBs can get it by calling getCallerPrincipal() on the injected variant of the EJBContext, here the javax.ejb.SessionContext:
#Resource
private SessionContext sessionCtx;
Servlets can retrieve the principal from the HttpServletRequest.getUserPrincipal(). JAX-RS components (the ServiceEJB) can retrieve it from the javax.ws.rs.core.SecurityContext.getUserPrincipal().
Is there any reason why you are NOT integrating with the application server security?
If you have a good reason NOT to integrate with application server security, I would propose a variation of the solution from the previous answer. The variation is to set the user data from a filter applied to all resources (either servlet filter or JAX-RS ContainerRequestFilter), so that you do not have to worry about setting it in multiple places.
If you ONLY NEED THE USER ID FOR LOGGING, I'd suggest you take a look at the concept of Mapped Diagnostic Contexts (MDC) in slf4j. With it you can set the user id early at the beginning of the request and make it available to all logging statements thereafter.
Create a request scoped CDI bean i.e. UserContext.
Inject it into both EJBs.
In ServiceEJB set user's id and in BusinessEJB read it.
The setup of the RESPApi project is:
SpringBoot
Spring's OAuth2
In the project we have many clients, so SQL queries almost always have "... and clientId = ?" in the where clause.
We store clientId in the SecurityContext with other user details (we extend Spring's User class).
The question is: how to get the User object in the #Repository?
Possible solutions we can think of:
In every repository implementation add
SecurityContextHolder.getContext().getAuthentication()
cast the result to our custom UserDetails implementation and use it.
Cons: somehow I feel there's a better solution.
Add #AuthenticationPrincipal annotated parameters to the controllers and then pass the parameters to the service layer and then to the repository layer.
Cons: passing the paremeter though 2 layers only to obtain clientId doesn't seem reasonable.
I thought about #Autowired paramter MyUser user in the #Repository class. The first try was to create #Configuration annotated class in which there will be a method
#Bean
public MyUser getUser() {
SecurityContext context = SecurityContextHolder.getContext();
if (context != null) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication != null) {
return (MyUser) authentication.getPrincipal();
}
}
return null;
}
But the bean is null and I cannot use it.
For now we've ended up with solution nr 1 but I feel there must be a better way.
Any ideas how to solve this problem?
If you're using Spring Data (or have the time to switch to using it), you can use the SecurityEvaluationContextExtension and use principal directly in your queries:
https://stackoverflow.com/a/29692158/1777072
If not, you could hide the static access if it offends (or if you want more control over it changing in future):
#Component
public class AuthenticationHelper {
public Authentication getAuthentication() {
return SecurityContextHolder.getContext().getAuthentication();
}
}
Then inject that class into your Repository.
Or your Service. That's probably a better fit than the Repository.
I like to keep Repositories dumb (ultimately using Spring Data to avoid writing them entirely).
And I like to think of Services as being separated out of the web layer, running on separate boxes (even if they aren't). In that situation, you would never pass the Authentication details over HTTP from Controller to Service. The service would obtain authentication details for itself, rather than just trusting what the web layer sent it.
So I think the Service should get the details itself, rather than the Controller passing them through.
Your bean is null because by default beans are singleton and they are created when the application starts, and as you can imagine, you are not going to have a SecurityContext at that point.
Try declaring your bean with request scope, in this way:
#Bean
#Scope(value=WebApplicationContext.SCOPE_REQUEST, proxyMode=ScopedProxyMode.TARGET_CLASS)
public MyUser getUser() {
.....
}
I'm in the process of writing a Spring REST type interface to a database that will retrieve user specific results for various resources.
To hold the user I have a spring #Component annotated bean called CurrentUser as a temporary measure.
#Component
public class CurrentUser {
#Autowired
private UserDAO userDAO;
private String userId;
private String email;
private String notes;
public String getUserId() {
return userId;
}
public void setUserId(String userId) throws ApiException {
User user = userDAO.getUser(userId) // Database call to
if (!user.isValid()) {
throw ApiException(...) // The exception would crash back to the user as a error in the response
}
// Valud user so set these aspects.
this.userId = user.userId;
this.email = user.email;
}
}
This object is initialised in a Spring Interceptor on each call to any method in the API using the following interceptor.
public class AuthenticationInterceptor extends HandlerInterceptorAdapter {
#Autowired
private CurrentUser user;
#Autowired
private RequestParameters requestParameters;
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws ApiException {
user.setUserId(StringUtils.defaultString(request.getParameter("userId"), "defaultUser"));
return true;
}
}
This is only a place-holder to identify the users until proper authentication can be added.
I'm relatively new to Spring, and the reason for this post
is to increase my understanding of the thread safety aspects of Spring in situations like this
I've recently discovered that Spring is not automatically thread safe, I may need to give more consideration to the scopes.
What I want to understand is the following:
For the above setup, is there any danger that 1000s of simultaneous requests, would potentially interfere and overwrite each other?
e.g. A request for one user would potentially be overwritten with a different user from a separate http request, causing the requestor to receive the wrong data.
What would be the best approach to solving this. (Even though it will be replaced, I have other objects instantiated in similar ways)
Options I'm looking at (if this is an issue), is setting prototype scope, or attaching to the request / session directly rather than allowing them their own autowired object.
Any information anyone could give me would be much appreciated, as I'm a fan of getting it right(er) to start with, than dealing with bad assumptions later on.
Answer 1: Yes, and you don't need 1000 requests to get into trouble. 2 requests in parallel are enough.
Answer 2:
The main problem here is one of scoping:
Default scope of Spring managed beans is Singleton. That means that only one instance of your CurrentUser exists per Application.
That is obviously not what you want. (Since you have a severe security issue here, with only one CurrentUser instance per application).
Simple Answer:
I would probably use Spring Security ;-)
Even Simpler Answer:
If that is not an option:
Use a Filter instead of a HandlerInterceptor (more direct control over clean up)
Create a Thread Local to store the user (and use a finally in the Filter to clean it up) and set it in the Filter
Create a request scoped Service (use #ScopedProxy, to be able to wire it into Singletons), that accesses the ThreadLocal as a UserService (you will need an interface to make it work easily)
Autowire this UserService where you need it
Since by specification each request in a Servlet environment is bound to a thread, and thread locals are inherently thread-safe, you are completely thread safe and it will scale well. The overhead of the scoped proxy is minimal.
(This is only one option, other options could make use of the request scope explicitly or use aspects in a slightly more elegant manner. However, it is a rather simple approach and gets the job done. For more complex needs I would seriously recommend looking into Spring Security).
You ca use parameter resolver feature of spring mvc without making it a bean.
to do that implement the interface HandlerMethodArgumentResolver and register that into the container. And then your handler method can have a argument of type current user
import org.springframework.core.MethodParameter;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;
public class CurrentUserArgumentResolver implements HandlerMethodArgumentResolver{
#Override
public boolean supportsParameter(MethodParameter parameter) {
if (parameter.getParameterType().equals(CurrentUser.class)) {
return true;
}
return false;
}
#Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest,
WebDataBinderFactory binderFactory) throws Exception {
if (supportsParameter(parameter)) {
String userId = (String) webRequest.getAttribute("userId", NativeWebRequest.SCOPE_REQUEST);
return new CurrentUser(userId);
}
return null;
}
public class CurrentUser{
public CurrentUser(String userId) {
// TODO Auto-generated constructor stub
}
}
}
After this you can have handler method of stype
#RequestMapping
public String handler(CurrentUser user){
....
}
I want to create a base controller class that my other controllers will inherit from. I have a simple public api that takes the authentication token via the query string, so I want to do this:
public class MyBaseController {
private String token = "";
public MyBaseController() {
}
}
And then my real controller would be like:
#Controller
#RequestMapping("/api/users")
public class UserController extends MyBaseControler {
// controller methods here
}
My question is, how can i get access to the HttpServletRequest in my base controller, and get the querystring parameter "?token=abc123" value and set the token var with the value.
Is this thread safe? It is my understanding that there will be a new controller instance per request correct?
Your controllers are better off if they're stateless.
You can inject them with Spring services as needed, but I don't see any reason why they have to hang onto the value of the token as a member variable.
It's far more likely that you'll want to store the token in session scope. I think your idea is wrong-headed.
I'll point out that Spring itself has moved away from inheritance for controllers. They're all annotation-based now, with no common base class or interface. Why do you think devolving back to the design they abandoned is a good thing?
You don't need a base controller, either.
In order for this session-scoped bean to work, it needs access to the Request object to allow it to determine the privileges of the logged-in user.
It also needs to be able to access the userService - another bean.
What does it need in order to gain access to these resources?
#Configuration
public class ExceptionResolverBuilder
{
#Bean #Scope(value = WebApplicationContext.SCOPE_SESSION, proxyMode = ScopedProxyMode.TARGET_CLASS)
public ExceptionResolver getExceptionResolver()
{
ExceptionResolver er = new ExceptionResolver();
User user = userService.getLoggedInUser(request);
if(user.isAdmin())
{
sendEmail("Caught exception:" + exeption.getMessage());
}
else
{
writeLog("Caught exception:" + exeption.getMessage());
}
return er;
}
}
Rather annoyingly, session-scoped beans don't get easy access to the request that initiated the session.
However, in your case you shouldn't need to. Assuming that your ExceptionResolver is an implementation of HandlerExceptionResolver, then there's no reason to put your logging logic into ExceptionResolverBuilder.getExceptionResolver(), since the resolver will get passed the current HttpServletRequest in the resolveException method.
Also consider using the #ExceptionResolver annotation, which makes life even easier, and also gets access to the current request.
In order for this session-scoped bean to work, it needs access to the Request object to allow it to determine the privileges of the logged-in user.
AFAIK, you can only get the current request if it has been passed as a parameter, or via a thread local.
If you are using SpringSecurity, you get the current request's authentication information by calling SecurityContext.getContext(). (This typically uses a thread local.) However, I'm not sure this will work because your method might be called at a point when the security context hasn't been set.
It also needs to be able to access the userService - another bean.
You need to arrange that this is provided by dependency injection.
For a bean that is defined as a #Component you can autowire the HttpServletRequest like so:
#Component
#Scope("session")
public class Foo {
#Autowired private HTTPServletRequest request;
//
}
But since you are using #Bean you can't do this.
You can get the current request as follows:
ServletRequestAttributes sra = (ServletRequestAttributes)RequestContextHolder.getRequestAttributes();
HttpServletRequest req = sra.getRequest();
This uses thread-local under the covers.
If you are using Spring MVC that's all you need. If you are not using Spring MVC then you will need to register a RequestContextListener or RequestContextFilter in your web.xml.