实现登录功能
然后再创建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后端技术全栈公众号。