In MVC pattern, which is better to use HttpSession between Controller or Service? - spring

Currently, by declaring HttpSession inside Service, session is created and the value for the key is retrieved.
#Service
#RequiredArgsConstructor
public class FormAnswerService {
private final FormAnswerRepository formAnswerRepository;
private final FormContentService formContentService;
private final MemberService memberService;
private final HttpServletRequest request;
public List<Long> createFormAnswer(Map<Long,String> answer) {
List<Long> formAnswerIdList = new ArrayList<>();
HttpSession httpSession = request.getSession();
Long memberId;
if(httpSession.getAttribute("login-user") != null) {
memberId = (Long)httpSession.getAttribute("login-user");
}
Is the above expression correct? Or is it correct to validate session in Controller in the first place?

In spring mvc session is usually used on controller. In spring you can use #SessionAttributes annotation to define session attributes on class scope and #ModelAttribute annotation in method scope.
#Controller
#SessionAttributes("login-user")
#RequestMapping("/formAnswer")
public class FormAnswerController {
#RequestMapping("/**")
public String handleFromAnswerRequest(#ModelAttribute("login-user") LoginUser loginUser,
Model model,
HttpServletRequest request) {
.......
}
}

Related

Why #Autowired and #Value fields are not injected in my class marked as #Component? [duplicate]

This question already has answers here:
Why is my Spring #Autowired field null?
(21 answers)
Closed 2 months ago.
I tried different solutions trying to use #Value within a class, even added #Autowire to the constructor, nut the #Value fields will still be null. I understand that this fields are injected after the construction of the object, but for me, their value is null, even if I just added a string, and not a property.
What am I doing wrong? I am using Spring boot 3, but anyway I have Controllers where this works, so probably I am wrong somewhere...
#Slf4j
#Component
public class TokenReceiver {
#Value("openid")
private String scope;
#Value("${spring.security.oauth2.client.registration.keycloak.client-id}")
private String clientId;
#Value("${spring.security.oauth2.client.registration.keycloak.client-secret}")
private String clientSecret;
private String grantType = "password";
#Autowired
private RestTemplate restTemplate;
public String getAccesToken(String username, String password) {
String accessTokenUrl = "https://keycloak.fh-kufstein.ac.at:8443/realms/BigOpenRealm/protocol/openid-connect/token";
LinkedMultiValueMap<String, String> requestParams = new LinkedMultiValueMap<>();
requestParams.add("scope", scope);
requestParams.add("grant_type", grantType);
requestParams.add("client_id", clientId);
requestParams.add("client_secret", clientSecret);
requestParams.add("username", username);
requestParams.add("password", password);
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(requestParams, headers);
KeycloakToken keycloakAccessToken = getAccessTokenResponse(request, accessTokenUrl);
return keycloakAccessToken.getAccess_token();
}
and the class from which the method it is called:
#Slf4j
#Component
public class GetProxyStrategy extends AbstractProxyStrategy {
#Autowired
TokenReceiver tokenReceiver;
public GetProxyStrategy() {
super();
}
public GetProxyStrategy(HttpServletRequest httpServletRequest, HttpHeaders headers, RestTemplate restTemplate) {
super(httpServletRequest, headers);
}
private StatusAwareEntityHolder callWebservice(String serviceUrl,
String username, String password)
throws IOException, ProxiedWebServiceExecutionException {
String accessToken = tokenReceiver.getAccesToken(username, password);
both classes are in the packages that are scanned:
#SpringBootApplication(scanBasePackages = {"my.domain.boo.microservice.portal.*"})
Because you create the TokenReceiver object by yourself. In this class Spring has nothing to do - it doesn't interfere. So it doesn't inject anything and doesn't address your #Value or #Component annotation for this object.
Instead you should let spring create the instance for you.
Since you've put the #Component annotation, the chances are that it will be able to create a corresponding bean and put it onto the application context. So you should just inject it:
#Service // should be a bean by itself
public class MyWebServiceCaller {
#Autowired
TokenReceiver tokenReceiver; // <--- Note the injection here!
private StatusAwareEntityHolder callWebservice(String serviceUrl,
String username, String password)
throws IOException, ProxiedWebServiceExecutionException {
String accessToken = tokenReceiver.getAccesToken(username, password);
[...]
}
}

Spring boot - Pass argument from interceptor to method in controller

For learning purposes, I have made a custom authentication system where I pass a token from the client to the server through the Authorization header.
In the server side, I'd like to know if it's possible to create in the interceptor, before the request reaches a method in the controller, an User object with the email from the token as a property, and then pass this user object to every request where I require it.
This what I'd like to get, as an example:
#RestController
public class HelloController {
#RequestMapping("/")
public String index(final User user) {
return user.getEmail();
}
}
public class User {
private String email;
}
Where user is an object that I created in the pre-interceptor using the request Authorization header and then I can pass, or not, to any method in the RestController.
Is this possible?
#Recommended solution
I would create a #Bean with #Scope request which would hold the user and then put the appropriate entity into that holder and then take from that holder inside the method.
#Component
#Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class CurrentUser {
private User currentUser;
public User getCurrentUser() {
return currentUser;
}
public void setCurrentUser(User currentUser) {
this.currentUser = currentUser;
}
}
and then
#Component
public class MyInterceptor implements HandlerInterceptor {
private CurrentUser currentUser;
#Autowired
MyInterceptor(CurrentUser currentUser) {
this.currentUser = currentUser;
}
#Override
public boolean preHandle(
HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
this.currentUser.setCurrentUser(new User("whatever"));
return true;
}
}
and in the Controller
#RestController
public class HelloController {
private CurrentUser currentUser;
#Autowired
HelloController(CurrentUser currentUser) {
this.currentUser = currentUser;
}
#RequestMapping("/")
public String index() {
return currentUser.getCurrentUser().getEmail();
}
}
#Alternative solution
In case your object that you would like to have, only contains one field, you can just cheat on that and add that field to the HttpServletRequest parameters and just see the magic happen.
#Component
public class MyInterceptor implements HandlerInterceptor {
#Override
public boolean preHandle(
HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//TRY ONE AT THE TIME: email OR user
//BOTH SHOULD WORK BUT SEPARATELY OF COURSE
request.setAttribute("email", "login#domain.com");
request.setAttribute("user", new User("login#domain.com"));
return true;
}
}
You can use a local thread context object as follows - which will be handling one parameter per request thread (thread safe):
public abstract class LoggedUserContext {
private static ThreadLocal<User> currentLoggedUser = new ThreadLocal<>();
public static void setCurrentLoggedUser(User loggedUser) {
if (currentLoggedUser == null) {
currentLoggedUser = new ThreadLocal<>();
}
currentLoggedUser.set(loggedUser);
}
public static User getCurrentLoggedUser() {
return currentLoggedUser != null ? currentLoggedUser.get() : null;
}
public static void clear() {
if (currentLoggedUser != null) {
currentLoggedUser.remove();
}
}
}
Then in the interceptor prehandle function:
LoggedUserContext.setCurrentLoggedUser(loggedUser);
And in the interceptor postHandler function:
LoggedUserContext.clear();
From any other place:
User loggedUser = LoggedUserContext.getCurrentLoggedUser();

Scope of RestController and Controller in Spring

What should be the scope of Controller and RestController in a Spring application? The default behavior if being Singleton. But will not having single bean cross over the request/responses from multiple clients, as our Controller will call some other bean (say #Service) which handles user specific request (like fetching user details from a DB or from another REST/SOAP service).
You have two options here:
Option 1 - In your service classes, ensure that you do not save any request specific details in instance variables. Instead pass them around as method arguments.
Ex:
The below code will corrupt the value stored in userId if there are simultaneous requests.
#Service
public class SomeService {
String userId;
public void processRequest(String userId, String orderId) {
this.userId = userId;
// Some code
process(orderId);
}
public void process(String orderId) {
// code that uses orderId
}
}
While, the following code is safe.
#Service
public class SomeService {
private String userId;
public void processRequest(String userId, String orderId) {
// Some code
process(userId, orderId);
}
public void process(String userId, String orderId) {
// code that uses userId and orderId
}
}
Option 2:
You can save request specific data in request scoped beans and inject them in your singletons. Spring creates a proxy for the injected request scoped beans and proxies calls to the bean associated with the current request.
#RequestScoped
class UserInfo {
}
#Service
class UserService {
#Autowired
private UserInfo userInfo;
public void process(String orderId) {
// It is safe to invoke methods of userInfo here. The calls will be passed to the bean associated with the current request.
}
}

Spring MVC - Autowired field from header

I made web service with spring mvc(version 4).
This service used token in http header for authorization.
I want to value in http header bind to field in model class auto.
Is it possible? How can I do?
(See below code and comment)
Controller
#Controller
#RequestMapping(value = "/order")
public class OrderController {
private static final Logger logger = LoggerFactory.getLogger(OrderController.class);
#Autowired
private OrderService orderService;
#RequestMapping(value = "/")
#ResponseBody
public List<Order> getAll() throws Exception {
// I want to remove two line below with auto binding (userToken field in model)
// in all controller using token value
String token = request.getHeader("X-Auth-Token"); // remove~
orderService.setUserToken(token); // remove~
orderService.getAllbyUser()
return items;
}
}
Model(Service)
#Service
public class OrderService {
//#Autowired - is it possible?
private String userToken;
public String setUserToken(String userToken)
{
this.userToken = userToken;
}
public List<Order> getAllbyUser() {
String userId = userMapper.getUserId(userToken);
List<Order> list = orderMapper.getAllbyUser(userId);
return list;
}
}
#Autowire is for Spring to inject beans one to another. If you want to inject a String to a bean you can with the org.springframework.beans.factory.annotation.Value annotation.
For example:
#Value("${user.token}")
private String userToken;
This will make Spring search of the user.token in the VM args and other places (which I don't remember and in some specific order).
But again, as said in my initial comment, from the code you show here it seems to be an error setting this field as it is context specific and the #Service (by default) indicates that the OrderService is a singleton.
In order to read a header value from request, you can use #RequestHeader("X-Auth-Token") in your controller, as shown below:
#RequestMapping(value = "/")
#ResponseBody
public List<Order> getAll(#RequestHeader("X-Auth-Token") String token) throws Exception {
orderService.setUserToken(token); // remove~
orderService.getAllbyUser()
return items;
}
Hope this helps you.

Testing #ModelAttribute method on a controller

This are the annotated methods in the controller:
#RequestMapping(method = RequestMethod.GET)
public String getClient(#PathVariable("contractUuid") UUID contractUuid, Model model) {
ClientDto clientDto = new ClientDto();
clientDto.setContractUuid(contractUuid);
model.addAttribute("client", clientDto);
return "addClient";
}
#ModelAttribute("contract")
public ContractDto getContract(#PathVariable("contractUuid") UUID contractUuid) throws ContractNotFoundException {
return contractService.fromEntity(contractService.findByUuid(contractUuid));
}
The test method that I am trying is shown below, but it fails for attribute contract. The attribute client is added to the Model in a #RequestMapping method.
private MockMvc mockMvc;
#Autowired
private ContractService contractServiceMock;
#Autowired
private ClientService clientServiceMock;
#Autowired
protected WebApplicationContext wac;
#Before
public void setup() {
Mockito.reset(contractServiceMock);
Mockito.reset(clientServiceMock);
this.mockMvc = webAppContextSetup(this.wac).build();
}
#Test
public void test() throws Exception {
UUID uuid = UUID.randomUUID();
Contract contract = new Contract(uuid);
when(contractServiceMock.findByUuid(uuid)).thenReturn(contract);
mockMvc.perform(get("/addClient/{contractUuid}", uuid))
.andExpect(status().isOk())
.andExpect(view().name("addClient"))
.andExpect(forwardedUrl("/WEB-INF/pages/addClient.jsp"))
.andExpect(model().attributeExists("client"))
.andExpect(model().attributeExists("contract"));
}
The contract attribute shows in the jsp page when I run the application, since I use some of its attributes, but since it fails in the test method is there another way to test it ?
It fails with the message:
java.lang.AssertionError: Model attribute 'contract' does not exist
Spring is 4.0.1.RELEASE
It seems it was my fault.
Even though the #ModelAttribute method returns an instance of ContractDto I only mocked one method used from the service:
when(contractServiceMock.findByUuid(uuid)).thenReturn(contract);
and so findByUuid returned something, but contractService.fromEntity was left untouched so I had to also mock it:
UUID uuid = UUID.randomUUID();
Contract contract = new Contract(uuid);
ContractDto contractDto = new ContractDto(uuid);
when(contractServiceMock.findByUuid(uuid)).thenReturn(contract);
when(contractServiceMock.fromEntity(contract)).thenReturn(contractDto);

Resources