What's the best way to access Session Data in Spring Boot? - spring

What's the best way to access Session Data in Spring Boot?
I'm developing a new microservice that included login/logout/2fa operation.
Login will consist 3 or 4 steps, like -> /validateUser (1.Step), /validateOneTimePassword (2.Step) ... and more some paths/steps.
We want to manage the session in Redis(distributed) and I added the needed conf/depend. When I make a request I can see creating a new session in Redis.
When I searched for the best way, I found some approaches.
Such as:
1:
#PostMapping("/myrequest")
public void handleMyRequest(HttpSession session, #RequestBody RequestObject requestObject) {
session.getAttribute("myKey");
...
}
2:
#PostMapping("/myrequest")
public void handleMyRequest(#RequestBody RequestObject requestObject) {
ServletRequestAttributes attr = (ServletRequestAttributes) RequestContextHolder.currentRequestAttributes();
HttpSession session = attr.getRequest().getSession();
session.getAttribute("myKey");
...
}
3:
#Autowired
HttpSession httpSession;
#PostMapping("/myrequest")
public void handleMyRequest(#RequestBody RequestObject requestObject) {
session.getAttribute("myKey");
...
}
Which one of these? Let's say we decided on the right thing so now I have a new question.
Using session should we pass the session parameter to the session layer? Or where do need we to make set/get session operation? Controller layer, service layer which one?
Please explain that.

Any of the 3 ways are fine but 1 and 3 will give you clean code. If multiple methods are accessing the session then number 3 would be the best.
As far as the other question is concerned, it all depends on use case. If you just need to read the values, you can extract the values in controller layer and pass it to the service layer, increasing chances of reusing the service layer code. If you need to set something in session and need to be done at the middle of business logic, you may have to send it to service layer.

Related

Access other service's API

In our Angular + Spring boot application application, we have 2 Controllers (2 Services are internally referenced). In first controller, We are sending a File from UI and reading the content of the file , query an external application and retrieve a set of data and return only a sub-set of Data, for entering as recommendation for UI fields. why we are returning only sub-set of data received from the external application? Because, we need only those sub-set data for showing recommendations in UI.
Once the rest of the fields are filled, then, we call another controller to generate a report. But, for generation of files, the second service requires the rest of the data from external application, which is received by the first service. I understand that Autowiring the first service in the second service, will create new instance of the first service and I will not get the first service instance, which is used to query the external application. I also like to avoid calling the external application again to retrieve the same data again in the second service. My question is how to fetch the data received by the first service in the second service?
For example:
First controller (ExternalApplicationController), which delegates loading of loading/importing of data from files
public class Department{
private Metadata metadata; // contains data such as name, id, location, etc.,
private Collection<Employee> employees; // the list of employees working in the department.
}
#RestController
#RequestMapping("/externalApp")
public class ExternalApplicationController{
#Autowired
private ExternalApplicationImportService importService;
#PostMapping("/importDepartmentDataFromFiles")
public Metadata importDepartmentDataFromFiles(#RequestParam("files") final MultipartFile[] files) {
return this.importService.loadDepartmentDetails(FileUtils.getInstance().convertToFiles(files)).getMetadata();
}
}
The first service (ExternalApplicationImportService), which delegates the request to the external application for loading of department data.
#Service
public class ExternalApplicationImportService{
private final ExternalApp app;
public Department loadDepartmentDetails(File file){
return app.loadDepartmentDetails(file);
}
}
The Metadata from the ExternalApplicationController is used to populated UI fields and after doing some operations (filling up some data), user requests to generate a report(which contains details from the employees of that department)
#RestController
#RequestMapping("/reportGenerator")
public class ReportController{
#Autowired
private ReportGenerationService generationService;
#PostMapping("/generateAnnualReports")
public void generateAnnualReports(){
generationService.generateAnnualReports();
}
}
#Service
public class ReportGenerationService{
public void generateAnnualReports(){
//here I need access to the data loaded in the ExternalApplicationImportService.
}
}
So, I would like to access the data loaded in the ExternalApplicationImportService in the ReportGenerationService.
I also see that there would be more services created in the future and might need to access the data loaded in the ExternalApplicationImportService.
How can this be designed and achieved?
I feel that I'm missing something how to have a linking between these services, for a given user session.
Thanks,
Paul
You speak about user session. Maybe you could inject the session of your user directly in your controllers and "play" with it?
Just adding HttpSession as parameter of your controllers' methods and spring will inject it for you. Then you just have to put your data in the session during the first WS call. And recover it from the session at the second WS call.
#RestController
#RequestMapping("/reportGenerator")
public class ReportController{
#PostMapping("/generateAnnualReports")
public void generateAnnualReports(HttpSession session){
generationService.generateAnnualReports();
}
}
Alternatively for the second call you could use:
#RestController
#RequestMapping("/reportGenerator")
public class ReportController{
#PostMapping("/generateAnnualReports")
public void generateAnnualReports(#SessionAttribute("<name of your session attribute>") Object yourdata){
generationService.generateAnnualReports();
}
}
You are starting from a wrong assumption:
I understand that Autowiring the first service in the second service, will create new instance of the first service and I will not get the first service instance, which is used to query the external application.
That is not correct: by default, Spring will create your bean as singleton, a single bean definition to a single object instance for each Spring IoC container.
As a consequence, every bean in which you inject ExternalApplicationImportService will receive the same instance.
To solve your problem, you only need a place in where temporarily store the results of your external app calls.
You have several options for that:
As you are receiving the same bean, you can preserve same state in instance fields of ExternalApplicationImportService.
#Service
public class ExternalApplicationImportService{
private final ExternalApp app;
// Maintain state in instance fields
private Department deparment;
public Department loadDepartmentDetails(File file){
if (department == null) {
department = app.loadDepartmentDetails(file);
}
return department;
}
}
Better, you can use some cache mechanism, the Spring builtin is excellent, and return the cached result. You can choose the information that will be used as the key of the cached data, probably some attribute related to your user in this case.
#Service
public class ExternalApplicationImportService{
private final ExternalApp app;
#Cacheable("department")
public Department loadDepartmentDetails(File file){
// will only be invoked if the file argument changes
return app.loadDepartmentDetails(file);
}
}
You can store the information returned from the external app in an intermediate information system like Redis, if available, or even in the application underlying database.
As suggested by Mohicane, in the Web tier, you can use the http sessions to store the attributes you need to, directly as a result of the operations performed by your controllers, or even try using Spring session scoped beans. For example:
#RestController
#RequestMapping("/externalApp")
public class ExternalApplicationController{
#Autowired
private ExternalApplicationImportService importService;
#PostMapping("/importDepartmentDataFromFiles")
public Metadata importDepartmentDataFromFiles(#RequestParam("files") final MultipartFile[] files, HttpSession session) {
Deparment department = this.importService.loadDepartmentDetails(FileUtils.getInstance().convertToFiles(files));
session.setAttribute("department", department);
return deparment.getMetadata();
}
}
And:
#RestController
#RequestMapping("/reportGenerator")
public class ReportController{
#Autowired
private ReportGenerationService generationService;
#PostMapping("/generateAnnualReports")
public void generateAnnualReports(HttpSession session){
Department department = (Department)session.setAttribute("department");
// Probably you need pass that information to you service
// TODO Handle the case in which the information is not present in the session
generationService.generateAnnualReports(department);
}
}
In my opinion, the second of the proposed approaches is the best one but all are valid mechanisms to share your data between the two operations.
my recommendation for you will be to revisit your design of classes and build a proper relationship between them. I feel you need to introduce the extra logic to manage your temporal data for report generation.
#Mohicane suggested to use HTTP Session in above answer. It might be a possible solution, but it has an issue if your service needs to be distributed in the future (e.g. more than one runnable instance will serve your WEB app).
I strongly advise:
creating a separate service to manage Metadata loading process, where you will have load(key) method
you need to determine by yourself what is going to be a key
both of your other services will utilize it
this service with method load(key) can be marked by #Cacheable annotation
configure your cache implementation. As a simple one you can use In-Memory, if a question becomes to scale your back-end app, you can easily switch it to Redis/DynamoDB or other data storages.
Referances:
Spring Caching
Spring Caching Guide

How to cache REST API response in java

I am building an app in java.I hit api more than 15000 times in loop and get the response ( response is static only )
Example
**
username in for loop
GET api.someapi/username
processing
end loop
**
It is taking hours to complete all the calls. Suggest me any way (any cache technology) to reduce the call time.
P.S :
1) i am hitting api from java rest client(Spring resttemplate)
2) that api i am hitting is the public one, not developed by me
3) gonna deploy in heroku
Try using Springs Cache Abstraction, docs.spring.io/spring/docs/current/spring-framework-reference/html/cache.html.
You can use this abstraction in the method which has the restTemplate call.
Any method calls response can be cached using this abstraction, with the method parameters as the keys and the return type as the response.
#Cacheable("username")
public UserResponse getUser(String username){
// Code to call your rest api
}
This creates a Spring AOP advice around the method. Every time the method is called it checks if the data is available in the cache for this key(username), if yes then returns the response from the Cache and not calls the actual method. If the data is not available in the Cache then it calls the actual method and caches the data in the cache, so next time when the same method is called with same key the data can be picked from Cache.
This cache abstraction can be backed by simple JVM caches like Guava or more sophisticated cache implementations like EHCache, Redis, HazelCast as well.
One very important note to that answer: If you ever plan to update those (cached) values, don't forget to use #CacheEvict on save() and delete() in the repositories. Else you will have problems fetching the new record when it is updated.
I have implemented my solution (with EhCache) this way (in the repository):
CurrencyRepository.java:
// define a cacheable statement
#Cacheable("currencyByIdentifier")
public Currency findOneByIdentifier(String identifier);
CacheConfiguration.java: // Define that cache in EhCache Configuration
#Bean
public JCacheManagerCustomizer cacheManagerCustomizer() {
return cm -> {
cm.createCache("currencyByIdentifier", jcacheConfiguration);
cm.createCache("sourceSystemByIdentifier", jcacheConfiguration);
};
}
CurrencyRepository.java:
// evict on save and delete by overriding the default method
#Override
#CacheEvict("currencyByIdentifier")
<S extends Currency> S save(S currency);
#Override
#CacheEvict("currencyByIdentifier")
void delete(Currency currency);
I hope that helps :)

How to generically authorize or validate a JSON rest request based on the authenticated user and an attribute of the requestbody

My current Spring3 REST JSON api is authenticated with the default InMemory properties file/basic-authentication authentication manager. That has worked fine thus far, but I need to further validate that an incoming request is allowed to be made for that user. The Role concept seems to work fine as a gateway for entry to a particular controller's url, but it doesn't go far enough to validate that the user is permitted to ask for the data being requested.
In my app, each B2B partner that will be making requests to the API is assigned an applicationId. That partner user account is only allowed to make requests for that applicationId. The applicationId is passed as an attribute of the RequestBody POJO for all the POST API messages. I would like to decline requests that are made for improper applicationIds.
How can I validate that the authenticated user is making a permitted request?
I've started down the path of creating a custom AuthenticationProvider, but I don't know how to get access to the applicationId within the RequestBody bean that hadn't been marshalled into the java bean yet.
Perhaps a custom AuthenticationProvider isn’t the right solution, and a request validator of some sort is needed. If so, how would the validator on the appId attribute get access to the Principal (authenticated user object)
With any solution, I would like it be invisible to the controller, so that requests that do make it to the controller are permitted ones. Also, ideally, the solution should not depend on an engineer to remember some annotation to make the logic work.
Thanks in advance,
JasonV
EDIT 1: By implementing an InitBinder in the controller, and using the #Valid annotation on the RequestBody I was able to validate a request. However, this is not the Droids (er I mean solution) I'm looking for. I need to find a more generic way to handle it without all those Binders and annotations; too much to remember and spread around the application over dozens of request controllers, and it will be forgotten in the future.
The usual way to implement this is using #PreAuthorize.
#PreAuthorize("hasRole('USER') and authentication.principal.approvedAppId == #dto.applicationId")
#RequestMapping...
public ... someMethod(#RequestBody Dto dto, ...)
If you're worried about the repetition of the SpEL, define a new annotation like #PreAuthorizeUser and set the #PreAuthorize as a meta-annotation on it.
I was able to utilize an aspect to solve the problem generically.
I would still like to see if it is possible to do the following:
Get a marshalled RequestBody from the request object in the context of an AuthenticationProvider.
Here is the aspect code for future help to others.
#Pointcut("within(#org.springframework.stereotype.Controller *)")
public void controllerBean() {
}
#Pointcut(
"execution(org.springframework.http.ResponseEntity *(.., #org.springframework.web.bind.annotation.RequestBody (*),..))")
public void methodPointcut() {
}
#Around("controllerBean() && methodPointcut()")
public Object beforeMethodInControllerClass(ProceedingJoinPoint jp) throws Throwable {
Object[] args = jp.getArgs();
long requestAppId = Long.parseLong(BeanUtils.getProperty(args[0], "applicationId"));
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
User principal = (User) auth.getPrincipal();
String username = principal.getUsername();
long[] approvedAppIds = getApprovedAppIdsForUsername(username);
for (long approvedAppId : approvedAppIds) {
if (approvedAppId == requestAppId) {
isAllowedAccess = true;
break;
}
}
if (isAllowedAccess) {
return jp.proceed(args);
} else {
LOGGER.warn("There was an attempt by a user to access an appId they are not approved to access: username="+username+", attempted appId="+requestAppId);
return new ResponseEntity(HttpStatus.FORBIDDEN);
}
}

Apache Wicket: Injecting dependencies in Session (using Guice)

I'm using the Wicket Auth/Roles and I ran into the same problem as the OP of this thread.
I need to access the DB service layer in the AuthenticatedWebSession (for user authentication). I followed Steve Flasby's suggestion and did the following:
#Override
public Session newSession(Request request, Response response) {
Session s = new MySession(request);
mInjector.inject(s);
return s;
}
Unfortunately this results in
java.lang.IllegalStateException: EntityManager is closed
(presumably due to the fact that (a) I'm using open session in view, and (b) the session spans over several requests).
I solved this by moving the injection into the AuthenticatedWebSession.authenticate method.
#Override
public boolean authenticate(String username, String pass) {
Injector.get().inject(this);
...
}
I suspect that this is not best practice, because now I need to access to the service layer in other methods too, and it doesn't seem like a good idea to add Injector.get().inject(this) in each such method.
My question:
How do I perform injection into the session object upon each request? (Or, if this is a bad approach all together, how do I access the service layer in the AuthenticatedWebSession?)
You can implement IRequestCycleListener (extend AbstractRequestCycleListener) and implement:
#Override
public void onBeginRequest(RequestCycle cycle)
{
if (Session.exists()) {
Injector.get().inject(Session.get());
}
}
Register your IRequestCycleListener in Application#init() with getRequestCycleListeners().add(new YourRequestCycleListener()).

What happens if I forget to mark the Spring SessionStatus as "Complete"?

In Spring MVC, suppose I define a SessionAttribute, using the #SessionAttribute tag like so:
#SessionAttributes(value = "myModel")
public class MyController{
...
}
Suppose that I forget to call status.setComplete() on the SessionStatus like so:
#RequestMapping(method = RequestMethod.POST)
public void doSomething(#ModelAttribute("myModel") MyModel model, SessionStatus status){
...
//status.setComplete(); <-- Never gets called
}
Will the model stay in the session forever? Will it ever get cleaned out, or will the session keep growing larger and larger as the user navigates the site?
There is a big debate on whether the session attributes are cleared after controller exit.
To clarify once and for all, we can look at the Spring MVC 3.1.0 RELEASE source code.
The interface org.springframework.web.bind.support.SessionAttributeStore exposes the following methods:
void storeAttribute(WebRequest request, String attributeName, Object attributeValue);
Object retrieveAttribute(WebRequest request, String attributeName);
void cleanupAttribute(WebRequest request, String attributeName);
The default implementation is org.springframework.web.bind.support.DefaultSessionAttributeStore
By doing a "Open Call Hierarchy" on cleanupAttribute() in Eclipse, we can see that the method is called by 2 different flows:
1) org.springframework.web.method.annotation.ModelFactory
public void updateModel(NativeWebRequest request, ModelAndViewContainer mavContainer) throws Exception {
if (mavContainer.getSessionStatus().isComplete()){
this.sessionAttributesHandler.cleanupAttributes(request);
}
else {
this.sessionAttributesHandler.storeAttributes(request, mavContainer.getModel());
}
if (!mavContainer.isRequestHandled()) {
updateBindingResult(request, mavContainer.getModel());
}
}
2) org.springframework.web.bind.annotation.support.HandlerMethodInvoker
public final void updateModelAttributes(Object handler, Map<String, Object> mavModel,
ExtendedModelMap implicitModel, NativeWebRequest webRequest) throws Exception {
if (this.methodResolver.hasSessionAttributes() && this.sessionStatus.isComplete()) {
for (String attrName : this.methodResolver.getActualSessionAttributeNames()) {
this.sessionAttributeStore.cleanupAttribute(webRequest, attrName);
}
}
...
}
It is clear that in both cases, the session attribute is removed only when this.sessionStatus.isComplete() is called.
I dug into the code of DefaultSessionAttributeStore. Under the hood, it gets the real HTTP Session object to store attributes, so they can potentially be accessed by other controllers in the same session.
So no, the session attributes are not removed after a clean POST.
EDIT #2: Note that this answer is no longer correct. See #doanduyhai's answer below.
EDIT: Please note that this is for Spring 2.5 and may, but does not necessarily ensure that the same is for Spring 3.x. Double check the docs!
This is along the lines of what #Gandalf said.
Form controllers model a form request lifespan, from initial viewing of the form through form submission. After the form is submitted, the form controller's job is done, and it will remove the command object from the session.
So, to keep the command object in the session between form workflows you will need to manage the session manually. After a clean POST, the object is removed from session.
In short, I believe the setComplete() method is just good practice but is not necessarily required.
EDIT: I just looked in my Spring book to confirm this. I'll quote it:
When #SessionAttribute is not used, a
new command object will be created on
each request, even when rendering the
form again due to binding errors. If
this annotation is enabled, the
command object will be stored in the
session for subsequent uses, until
the form completes successfully. Then
this command object will be cleared
from the session. This is usually
used when the command object is a
persistent object that needs to be
identical across different requests
for tracking changes.
Essentially that's what I was saying above. It stores it in the session until you either A) call setComplete() or B) the controller successfully completes a POST.
Is there some reason why you would want to do that?
from this thread : #SessionAttribute Problem
The #SessionAttributes works in the same way as the sessionForm of the SimpleFormController. It puts the command (or for the #SessionAttributes any object) in the session for the duration between the first and the last request (most of the time the initial GET and the final POST). After that the stuff is removed.

Resources