CodeX
Published in

CodeX

ARTICLE

Using the Spring Web Scopes, Part 2

From Spring Start Here by Laurențiu Spilcă

This article covers

  • Using the Spring web scopes
  • Implementing a simple login functionality for a web app
  • Redirecting from one page to another in a web app

Take 40% off Spring Start Here by entering fccspilca2 into the discount code box at checkout at manning.com.

Check out part 1 here.

Using the session scope in a Spring web app

In this section, we discuss the session-scoped beans. When you enter a web app and log in, you expect then to surf through that app’s pages, and the app still remembers you’ve logged in. A session-scoped bean is an object managed by Spring, for which Spring creates an instance and links it to the HTTP session. Once a client sends a request to the server, the server reserves a place in the memory for this request, for the whole duration of their session. Spring creates an instance of a session-scoped bean when the HTTP session is created for a specific client. That instance can be reused for the same client as long as it still has the HTTP session active. The data you store in the session-scoped bean attribute is available for all the client’s requests throughout an HTTP session. This approach of storing the data allows you to store information about what users do as they’re surfing through the pages of your app.

Figure 8 The session-scoped bean is used to keep a bean in the context throughout the client’s full HTTP session. Spring creates an instance of a session-scoped bean for each HTTP session a client opens. The client accesses the same instance for all the requests sent throughout the same HTTP session. Each user has their own session and accesses different instances of the session-scoped bean.

Take time now to compare figure 8, which presents the session-scoped bean, with figure 2, which presents the request-scoped bean. Figure 9 summarizes the comparison between the two approaches as well. When you’ve a request-scoped bean, Spring creates a new instance for every HTTP request, but when you’ve a session-scoped bean, Spring creates only one instance per HTTP session. A session-scoped bean allows us to store data shared by multiple requests of the same client.

Figure 9 A comparison between the request-scoped and session-scoped beans to help you visualize easier the differences between these two web bean scopes. You use request scoped beans when you want Spring to create a new instance for each request. You use a session-scoped bean when you want to keep the bean (together with any details it holds) throughout the client’s HTTP session.

A couple of examples of features you can implement using session-scoped beans are

  • A login — where you need to keep details of the authenticated user when they visit different parts of your app and send multiple requests.
  • An online shopping cart — where the users visit multiple places of your app searching for products they add to the cart. The cart remembers all the products the client added.

In this section, we’ll use a session-scoped bean to make our app aware that a user logged in and recognize them as a logged-in user when they access different pages of the app. This way, the example teaches you all the relevant details you need to know when working with production applications.

Let’s change the application we implemented earlier to display a page that only logged in users can access. Once a user logs in, the app redirects them to this page. The page displays a welcome message containing the logged-in username and offers the user the option to log out by clicking a link on the page.

These are the steps we need to take to implement this change (figure 10):

  1. Create a session-scoped bean to keep the logged-in user’s details.
  2. Create the page a user can only access after login.
  3. Make sure a user can’t access the page created at point 1 without logging in first.
  4. Redirect the user from login to the main page after successful authentication.
Figure 10 We use a session-bean to implement a section of the app that only a logged-in user can access. Once the user authenticates, the app redirects them to the page, which they can only access once authenticated. If the user tries to access this page before authentication, the app redirects them to the login form.

Fortunately, in Spring, to create a session-scoped bean is as simple as using the @SessionScope annotation with the bean class. Let’s create a new class, LoggedUserManagementService, and make it session-scoped as presented in listing 5.

Listing 5. Defining a session-scoped bean to keep the logged user details

@Service    #A
@SessionScope #B
public class LoggedUserManagementService {

private String username;

// Omitted getters and setters
}

#A We add the @Service stereotype annotation to instruct Spring to manage this class as a bean in its context.

#B We use the @SessionScope annotation to change the scope of the bean to session.

Every time a user successfully logs in, we store its name in this bean’s username attribute. We autowire the LoggedUserManagementService bean in the LoginProcessor class, which we implemented earlier to take care of the authentication logic.

Listing 6. Using the LoggedUserManagementService bean in the login logic

@Component
@RequestScope
public class LoginProcessor {

private final LoggedUserManagementService loggedUserManagementService;

private String username;
private String password;

public LoginProcessor( #A
LoggedUserManagementService loggedUserManagementService) {
this.loggedUserManagementService = loggedUserManagementService;
}

public boolean login() {
String username = this.getUsername();
String password = this.getPassword();

boolean loginResult = false;
if ("natalie".equals(username) && "password".equals(password)) {
loginResult = true;
loggedUserManagementService.setUsername(username); #B
}

return loginResult;
}

// Omitted getters and setters
}

#A We autowire the LoggedUserManagementService bean

#B We store the username on the LoggedUserManagementService bean

Observe that the LoginProcessor bean stays request-scoped. We still need Spring to create this instance for each login request. We only need the username and password attributes’ values during the request to execute the authentication logic.

Because the LoggedUserManagementService bean’s session-scoped, the username value is accessible now throughout the entire HTTP session. You can use this value to know if someone is logged in and who. You don’t have to worry about the case where multiple users are logged in; the application framework makes sure to link each HTTP request to the correct session. Figure 11 visually describes the login flow.

Figure 11 The login flow implemented in the example. When the user submits their credentials, the login process begins. If the user’s credentials are correct, the username is stored in the session-scoped bean, and the app redirects the user to the main page. If the credentials are invalid, the app redirects the user back to the login page and displays a failed login message.

Now we create a new page and make sure one can access it only if they’ve already logged in. We define a new controller (that we’ll call MainController) for the new page. We’ll define an action and map it to the /main path. To make sure a user can access this path only if they logged in, we check if the LoggedUserManagementService bean stores any username. If it doesn’t store a username, we redirect the user to the login page. To redirect the user to another page, the controller action needs to return the string “redirect:” followed by the path to which the action wants to redirect the user. Figure 12 visually presents you the logic behind the main page.

Figure 12 Someone can access the main page only after they are authenticated. When the app authenticates the user, it stores the username in the session-scoped bean. This way, the app knows later the user had already logged in. When someone accesses the main page, and the username isn’t in the session-scoped bean (they didn’t authenticate), the app redirects them to the login page.

Listing 6 shows the MainController class.

Listing 6. The MainController class

@Controller
public class MainController {

private final LoggedUserManagementService loggedUserManagementService;

public MainController( #A
LoggedUserManagementService loggedUserManagementService) {
this.loggedUserManagementService = loggedUserManagementService;
}

@GetMapping("/main")
public String home() {
String username = #B
loggedUserManagementService.getUsername();

if (username == null) { #C
return "redirect:/";
}

return "main.html"; #D
}
}

#A We autowire the LoggedUserManagementService bean to find out if the user already logged in.

#B We take the username value, which should be different than null if someone logged in.

#C If the user isn’t logged in, we redirect the user to the login page.

#D If the user is logged in, we return the view for the main page.

You need to add the main.html that defines the view in the resources/templates folder of your Spring Boot project. Listing 7 shows the content of the main.html page.

Listing 7. The content of the main.html page

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Welcome</title>
</head>
<body>
<h1>Welcome</h1>
</body>
</html>

To allow the user to log out is also easy; you need to set the username in the LoggedUserManagementService session bean as null. Let’s create a logout link on the page and also add the logged-in username in the welcome message. Listing 8 shows the changes to the main.html page that defines our view.

Listing 8. Adding a logout link to the main.html page

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Login</title>
</head>
<body>
<h1>Welcome, <span th:text="${username}"></span></h1> #A
<a href="/main?logout">Log out</a> #B
</body>
</html>

#A We get the username from the controller and display it on the page in the welcome message.

#B We add a link on the page that sets an HTTP request parameter named “logout”. When the controller gets this parameter, it erases the value of the username from the session.

These main.html page changes also assume some changes in the controller for the functionality to be complete. Listing 9 shows how to get the logout request parameter in the controller’s action and send the username to the view where it’s displayed on the page.

Listing 9. Logging out the user based on the logout request parameter

@Controller
public class MainController {

// Omitted code

@GetMapping("/main")
public String home(
@RequestParam(required = false) String logout, #A
Model model #B
) {
if (logout != null) { #C
loggedUserManagementService.setUsername(null);
}

String username = loggedUserManagementService.getUsername();

if (username == null) {
return "redirect:/";
}

model.addAttribute("username" , username); #D
return "main.html";
}
}

#A We get the logout request parameter if present.

#B We add a Model parameter to send the username to the view.

#C If the logout parameter is present, we erase the username from the LoggedUserManagementService bean.

#D We send the username to the view.

To complete the app, we’d like to change the LoginController to redirect the users to the main page once they authenticate. To achieve this result, we need to change the LoginController’s action as presented in listing 10.

Listing 10. Redirecting the user to the main page after login

@Controller
public class LoginController {

// Omitted code

@PostMapping("/")
public String loginPost(
@RequestParam String username,
@RequestParam String password,
Model model
) {
loginProcessor.setUsername(username);
loginProcessor.setPassword(password);
boolean loggedIn = loginProcessor.login();

if (loggedIn) { #A
return "redirect:/main";
}

model.addAttribute("message", "Login failed!");
return "login.html";
}
}

#A When the user successfully authenticates, the app redirects them to the main page.

Now you can start the application and test the login. When you provide the correct credentials, the app redirects you to the main page. Press the “logout” link, and the app redirects you back to the login. If you try to access the main page without authenticating first, the app redirects you to log in.

Figure 13 This visual presents the flow between the two pages. When the user logs in, the app redirects them to the main page. The user can click on the logout link, and the app redirects them back to the login form.

Using the application scope in a Spring web app

In this section, we discuss the application scope. I want to mention its existence and make you aware of how it works and emphasize that it’s better not to use it in a production app. All client requests share an application-scoped bean (figure 14).

Figure 14 Understanding the application scope in a Spring web app. The instance of an application-scoped bean is shared by all the HTTP requests from all the clients. The Spring context provides only one instance of the bean’s type used by anyone who needs it.

The application scope is close to how a singleton works. The difference is that you can’t have more instances of the same type in the context and that we always use the HTTP requests as a reference point when discussing the lifecycle of web scopes (including the application scopeIit is better to have immutable attributes for the singleton beans, as well as application-scoped beans, but if you make the attributes immutable, you can directly use a singleton bean instead.

Generally, I recommend developers to avoid using application-scoped beans. Generally, it’s better to directly use a persistence layer, such as a database, instead of working with data in an application-scoped bean.

It’s always best to see an example to understand the case better. Let’s change the application we worked on in this article and add a feature that counts the login attempts.

Because we have to count the login attempts from all the users, we’ll store the count in an application-scoped bean. Let’s create a LoginCountService application-scoped bean that stores the count in an attribute. Listing 11 shows the definition of this class.

Listing 11. The LoginCountService class counts the login attempts

@Service
@ApplicationScope #A
public class LoginCountService {

private int count;

public void increment() {
count++;
}

public int getCount() {
return count;
}
}

#A The @ApplicationScope annotation changes the scope of this bean to the application scope.

The LoginProcessor can then autowire this bean and call the increment() method for any new login attempt, as presented in listing 12.

Listing 12. Incrementing the login count for every login request

@Component
@RequestScope
public class LoginProcessor {

private final LoggedUserManagementService loggedUserManagementService;
private final LoginCountService loginCountService;

private String username;
private String password;

public LoginProcessor( #A
LoggedUserManagementService loggedUserManagementService,
LoginCountService loginCountService) {
this.loggedUserManagementService = loggedUserManagementService;
this.loginCountService = loginCountService;
}

public boolean login() {
loginCountService.increment(); #B

String username = this.getUsername();
String password = this.getPassword();

boolean loginResult = false;
if ("natalie".equals(username) && "password".equals(password)) {
loginResult = true;
loggedUserManagementService.setUsername(username);
}

return loginResult;
}

// Omitted code
}

#A We inject the LoginCountService bean through the constructor’s parameters

#B We increment the count for each login attempt.

The last thing you need to do now is to display this value. You can use a Model parameter in the controller’s action to send the count value to the view. You can then use Thymeleaf to display the value in the view. Listing 13 shows you how to send the value from the controller to the view.

Listing 13. Sending the count value from controller to be displayed on the main page

@Controller
public class MainController {

// Omitted code

@GetMapping("/main")
public String home(
@RequestParam(required = false) String logout,
Model model
) {
if (logout != null) {
loggedUserManagementService.setUsername(null);
}

String username = loggedUserManagementService.getUsername();
int count = loginCountService.getCount(); #A

if (username == null) {
return "redirect:/";
}

model.addAttribute("username" , username);
model.addAttribute("loginCount", count); #B

return "main.html";
}
}

#A Getting the count from the application-scoped bean.

#B Sending the count value to the view.

Listing 14 shows you how to display the count value on the page.

Listing 14. Displaying the count value on the main page

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Login</title>
</head>
<body>
<h1>Welcome, <span th:text="${username}"></span>!</h1>
<h2>
Your login number is
<span th:text="${loginCount}"></span> #A
</h2>
<a href="/main?logout">Log out</a>
</body>
</html>

#A Displaying the count on the page.

Running your app, you find the total number of login attempts on the main page, as presented in figure 15.

Figure 15 The result of the application is a web page that displays the total number of logins for all the users. This main page displays the total number of login attempts.

That’s all for now. If you want to see more, check out the book on Manning’s liveBook platform here.

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store