實戰 用戶登錄、Session校驗、分布式存儲Session
實現登錄功能
然后再創建login.css存放于在static下,css目錄中,id 為 content 的 樣式;
- #content {
- margin-left: 220px;
- margin-right: 1420px;
- margin-top: 100px;
- margin-bottom: auto;
- background-color: orange;
- }
創建login.html登錄頁面
- <!DOCTYPE html>
- <html lang="zh" xmlns:th="http://www.thymeleaf.org">
- <head>
- <meta charset="UTF-8">
- <title>登錄</title>
- <!-- 如何引入本地css文件-->
- <link rel="stylesheet" th:href="@{/css/login.css}"/>
- </head>
- <body>
- <div id="content">
- <!-- 錯誤是提示-->
- <label id="errorMsg" style="color: crimson">[[${errorMsg}]]</label>
- <form id="login_form" action="/login" method="post">
- 姓名:<input type="text" id="uname" name="uname"><br/>
- 密碼:<input type="password" id="password" name="password"><br/>
- <button onclick="login()">登錄</button>
- </form>
- </div>
- </body>
- </html>
前面的這一部分是前端的,下面來把后端代碼給寫完:
UserRepository中添加方法的定義:
- //通過用戶名和密碼查找用戶
- List<User> findByUnameAndPassword(String uname, String password);
UserService和實現類中添加方法如下:
- /通過用戶名和密碼查找用戶
- List<User> findByUnameAndPassword(String uname, String password);
- UserService和實現類中添加方法如下:
- // UserService
- User login(User user);
- @Service
- //把事務注解放在類上了,這樣下面就不需要每次都在方法寫這個注解了
- @Transactional(rollbackFor = Exception.class)
- public class UserServiceImpl implements UserService {
- //......
- @Override
- public User login(User user) {
- List<User> userList = userRepository.findByUnameAndPassword(user.getUname(), user.getPassword());
- //防止有多個用戶名相同,并且密碼也相同的用戶
- if (!CollectionUtils.isEmpty(userList)) {
- return userList.get(0);
- }
- return null;
- }
- }
UserController中添加方法如下:
- @RequestMapping(value = "/loginPage", method = RequestMethod.GET)
- public String loginPage(Model model) {
- return "login";
- }
- @RequestMapping(value = "/login", method = RequestMethod.POST)
- public String login(Model model, User user) {
- User result = userService.login(user);
- if (result != null) {
- //登錄成功,跳轉到用戶列表
- return "redirect:/userList";
- }
- //不成功,提示
- model.addAttribute("errorMsg", "用戶名或密碼不正確");
- return "login";
- }
啟動項目,訪問
http://localhost:8080/loginPage
進入登錄頁面。
輸入用戶名密碼。密碼錯誤:
輸入正確的用戶名和密碼,那么跳轉到用戶列表。
這樣,我們一個簡單的登錄功能就搞定了。
如果我們需要在修改用戶信息的時候,校驗是否已經登錄,怎么辦呢?
攔截器
創建自定義的攔截器并實現HandlerInterceptor接口 。
- import org.springframework.lang.Nullable;
- import org.springframework.web.servlet.HandlerInterceptor;
- import org.springframework.web.servlet.ModelAndView;
- import javax.servlet.http.HttpServletRequest;
- import javax.servlet.http.HttpServletResponse;
- public class SessionInterceptor implements HandlerInterceptor {
- @Override
- public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
- //session校驗
- Object object = request.getSession().getAttribute("users");
- if (null == object) {
- response.sendRedirect("/loginPage");
- return false;
- }
- return true;
- }
- }
創建一個java類繼承WebMvcConfiguraeAdapter并重寫addInterceptor方法(該類用來添加配置攔截器在該類中添加配置攔截器,以及配置過濾)。
- import org.springframework.context.annotation.Configuration;
- import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
- import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
- @Configuration
- public class MyInterceptor extends WebMvcConfigurerAdapter {
- @Override
- public void addInterceptors(InterceptorRegistry registry) {
- //可以添加多個攔截
- registry.addInterceptor(new SessionInterceptor())
- //也可以添加多個攔截路徑,"/**"攔截所有
- .addPathPatterns("/update/**");
- }
- }
再把登錄Controller方法調整,把session信息存進去。
- @RequestMapping(value = "/login", method = RequestMethod.POST)
- public String login(Model model, User user, HttpServletRequest request) {
- User result = userService.login(user);
- if (result != null) {
- //用戶信息保存在session
- request.getSession().setAttribute("users", user.getUname());
- return "redirect:/userList";
- }
- model.addAttribute("errorMsg", "用戶名或密碼不正確");
- return "login";
- }
再次訪問用戶列表:
http://localhost:8080/userList
這時候,我們訪問修改用戶信息這個功能,跳轉到了登錄頁面。
登錄后,再次訪問修改用戶信息這個功能。
這樣便來到用戶信息修改頁面。
到此,我們就實現了一個簡單的session來接校驗。
如果,我們服務器重啟后,session就沒了,因為session是保存在我們服務端的,并且還是在服務器內存里的。
session分布式有四種方案
方案一:客戶端存儲
直接將信息存儲在cookie中,cookie是存儲在客戶端上的一小段數據,客戶端通過http協議和服務器進行cookie交互,通常用來存儲一些不敏感信息
缺點
- 數據存儲在客戶端,存在安全隱患。
- cookie存儲大小、類型存在限制。
- 數據存儲在cookie中,如果一次請求cookie過大,會給網絡增加更大的開銷。
方案二:session復制
session復制是小型企業應用使用較多的一種服務器集群session管理機制,在真正的開發使用的并不是很多,通過對web服務器(例如Tomcat)進行搭建集群。
缺點
session同步的原理是在同一個局域網里面通過發送廣播來異步同步session的,一旦服務器多了,并發上來了,session需要同步的數據量就大了,需要將其他服務器上的session全部同步到本服務器上,會帶來一定的網路開銷,在用戶量特別大的時候,會出現內存不足的情況。
優點
服務器之間的session信息都是同步的,任何一臺服務器宕機的時候不會影響另外服務器中session的狀態,配置相對簡單
Tomcat內部已經支持分布式架構開發管理機制,可以對tomcat修改配置來支持session復制,在集群中的幾臺服務器之間同步session對象,使每臺服務器上都保存了所有用戶的session信息,這樣任何一臺本機宕機都不會導致session數據的丟失,而服務器使用session時,也只需要在本機獲取即可。
如何配置?
在Tomcat安裝目錄下的config目錄中的server.xml文件中,將注釋打開,tomcat必須在同一個網關內,要不然收不到廣播,同步不了session,在web.xml中開啟session復制:。
方案三:session綁定:
Nginx是一款自由的、開源的、高性能的http服務器和反向代理服務器
Nginx能做什么?
反向代理、負載均衡、http服務器(動靜代理)、正向代理
如何使用nginx進行session綁定
我們利用nginx的反向代理和負載均衡,之前是客戶端會被分配到其中一臺服務器進行處理,具體分配到哪臺服務器進行處理還得看服務器的負載均衡算法(輪詢、隨機、ip-hash、權重等),但是我們可以基于nginx的ip-hash策略,可以對客戶端和服務器進行綁定,同一個客戶端就只能訪問該服務器,無論客戶端發送多少次請求都被同一個服務器處理。
缺點
容易造成單點故障,如果有一臺服務器宕機,那么該臺服務器上的session信息將會丟失
前端不能有負載均衡,如果有,session綁定將會出問題
優點
- 配置簡單
方案四:基于redis存儲session方案
優點
- 這是企業中使用的最多的一種方式
- spring為我們封裝好了spring-session,直接引入依賴即可
- 數據保存在redis中,無縫接入,不存在任何安全隱患
- redis自身可做集群,搭建主從,同時方便管理
缺點
多了一次網絡調用,web容器需要向redis訪問。
一般會將web容器所在的服務器和redis所在的服務器放在同一個機房,減少網絡開銷,走內網進行連接。
來源:http://45dwz.com/xeP0J
實現基于redis分布式存儲session方案
安裝Redis,這里就不說了,不會安裝可以聯系我。
集成Redis
添加依賴
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-data-redis</artifactId>
- </dependency>
- <!-- 連接池-->
- <dependency>
- <groupId>org.apache.commons</groupId>
- <artifactId>commons-pool2</artifactId>
- </dependency>
- <dependency>
- <groupId>org.springframework.session</groupId>
- <artifactId>spring-session-data-redis</artifactId>
- </dependency>
添加Redis配置
- # Redis數據庫索引(默認為0)
- spring.redis.database=0
- # Redis服務器地址
- spring.redis.host=127.0.0.1
- # Redis服務器連接端口
- spring.redis.port=6379
- # Redis服務器連接密碼(默認為空)
- spring.redis.password=
- # 連接池最大連接數(使用負值表示沒有限制)
- spring.redis.jedis.pool.max-active=20
- # 連接池最大阻塞等待時間(使用負值表示沒有限制)
- spring.redis.jedis.pool.max-wait=-1
- # 連接池中的最大空閑連接
- spring.redis.jedis.pool.max-idle=10
- # 連接池中的最小空閑連接
- spring.redis.jedis.pool.min-idle=0
- # 連接超時時間(毫秒)
- spring.redis.timeout=1000
將session添加入Redis中
在啟動類上添加@EnableRedisHttpSession注解。
- @SpringBootApplication
- @EnableRedisHttpSession
- public class Application {
- public static void main(String[] args) {
- SpringApplication.run(Application.class, args);
- }
- }
啟動項目,然后,再次登錄后,便可以在Redis里查到了
再次重啟項目后,發現修改用戶信息的時候,并不需要重新登錄了。
到此,基于Redis分布式存儲session方案就已經搞定了。
總結
本文首先是實戰了登錄功能,其次接著實現了校驗session攔截處理,然后總結出session分布式四種方案,最后實現了基于redis存儲session的方案。
本文轉載自微信公眾號「Java后端技術全棧」,可以通過以下二維碼關注。轉載本文請聯系Java后端技術全棧公眾號。