本文主要是介绍【尚筹网项目】 十、 【前台】 会员登录 及使用 SpringSession 实现 Session共享,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
会员登录
- 一、思路
- 二、会员登录代码实现
- (1) 调整 member-login 页面
- (2) 创建 MemberLoginVO
- (3) 增加一些需要用到的 viewController
- (4) MemberHandler
- ★ 遇到的问题
- (5) 退出登录
- 三、会员登录功能延伸
- 四、会话控制回顾
- (1) Cookie 的工作机制
- (2) Session 的工作机制
- 五、Session 共享
- (1) 后端统一存储 Session 数据
- (2) SpringSession 使用 (测试)
- ① 引入依赖
- ② 配置文件
- ③ handler
- ④ 测试
- ⑤ 查看 redis
- (3) SpringSession 基本原理
- ① SpringSession 需要完成的任务
- ② SessionRepositoryFilter
- ③ HttpSessionStrategy
- ④ SessionRepository
- ⑤ RedisOperationsSessionRepository
- 六、登录检查
- (1) 思路
- (2) 设置 Session 共享
- ① 加入依赖
- ② 加入配置
- (3) 准备不需要登录检查的资源
- (4) 判断当前请求是否为静态资源
- (5) ZuulFilter
- (6) 登录页面读取 Session 域
- (7) Zuul 中的特殊设置
一、思路
二、会员登录代码实现
(1) 调整 member-login 页面
(2) 创建 MemberLoginVO
登录成功后,将MemberLoginVO存入session中
@Data
@NoArgsConstructor
@AllArgsConstructor
public class MemberLoginVO {private Integer id;private String username;private String email;
}
(3) 增加一些需要用到的 viewController
// 浏览器访问的地址
// ① 注册请求
String toMemberRegPath = "/auth/member/to/reg/page.html";
String toMemberCenterPath = "/auth/member/to/center/page";
String toMemberLoginPath = "/auth/member/to/login/page";// 目标视图的名称
// ① 注册页面
String viewName = "member-reg";
String MemberCenterViewName = "member-center";
String MemberLoginViewName = "member-login";// 添加一个viewController
registry.addViewController(toMemberRegPath).setViewName(viewName);
registry.addViewController(toMemberCenterPath).setViewName(MemberCenterViewName);
registry.addViewController(toMemberLoginPath).setViewName(MemberLoginViewName);
(4) MemberHandler
// 登录请求
@RequestMapping("/auth/do/member/login")
public String login(@RequestParam("loginacct") String loginacct,@RequestParam("userpswd") String userpswd,ModelMap modelMap,HttpSession session
) {// 1 从mysql中通过loginacct查询对象ResultEntity<MemberPO> memberPO = mySQLRemoteService.getMemberPOByLoginAcctRemote(loginacct);// 2 如果查询失败if (ResultEntity.FAILED.equals(memberPO.getOperationResult())) {// 存储错误信息modelMap.addAttribute(CrowdConstant.ATTR_NAME_MESSAGE,memberPO.getOperationMessage());return "member-login";}// 3 查询成功,比较二者的密码// ① 取出数据库中查询到的对象数据内容MemberPO mysqlData = memberPO.getQueryData();// ② 如果数据不存在if (mysqlData == null) {modelMap.addAttribute(CrowdConstant.ATTR_NAME_MESSAGE,CrowdConstant.MESSAGE_LOGIN_FAILED);return "member-login";}String mysqlDataUserpswd = mysqlData.getUserpswd();// ③ 比较密码,因为盐值加密方式生成盐是随机的,所以不能使用equals等进行判等BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();boolean matchesResult = bCryptPasswordEncoder.matches(userpswd, mysqlDataUserpswd);// ④ 密码不一致 登录失败if(matchesResult == false) {modelMap.addAttribute(CrowdConstant.ATTR_NAME_MESSAGE,CrowdConstant.MESSAGE_LOGIN_FAILED);return "member-login";}// 登录成功,将登录信息存入session中MemberLoginVO memberLoginVO = new MemberLoginVO(mysqlData.getId(),mysqlData.getUsername(),mysqlData.getEmail());session.setAttribute(CrowdConstant.ATTR_NAME_LOGIN_MEMBER, memberLoginVO);// 重定向概到 用户中心页面 (产生问题:session丢失,无法被thymeleaf解析到)return "redirect:/auth/member/to/center/page";// return "member-center";
}
★ 遇到的问题
无法解析session,推测 是由于zuul代理后,重定向后请求地址如下图所示,地址栏改变,且不保存第一次请求的数据,所以session失效了
此时访问的地址
解决办法:在zuul的配置文件中加入以下内容
(5) 退出登录
// 退出登录
@RequestMapping("/auth/do/logout")
public String logout(HttpSession session) {// 使session失效session.invalidate();return "redirect:/";
}
三、会员登录功能延伸
四、会话控制回顾
回顾以前学的 cookie 和 session 都有笔记
(1) Cookie 的工作机制
服务器端返回 Cookie 信息给浏览器
Java 代码:response.addCookie(cookie 对象);
HTTP 响应消息头:Set-Cookie: Cookie 的名字=Cookie 的值
浏览器接收到服务器端返回的 Cookie,以后的每一次请求都会把 Cookie 带上
HTTP 请求消息头:Cookie: Cookie
(2) Session 的工作机制
获取 Session 对象:request.getSession()
检查当前请求是否携带了 JSESSIONID 这个 Cookie
带了:根据这个 JSESSIONID 在服务器端查找对应的 Session 对象
能找到:就把找到的 Session 对象返回
没找到:新建Session 对象返回,同时返回 JSESSIONID 的 Cookie
没带:新建 Session 对象返回,同时返回JSESSIONID 的 Cookie
五、Session 共享
在分布式和集群环境下,每个具体模块运行在单独的 Tomcat 上,而 Session 是被不同Tomcat 所“区隔”的,所以不能互通,会导致程序运行时,用户会话数据发生错误。有的服务器上有,有的服务器上没有。
(1) 后端统一存储 Session 数据
后端存储 Session 数据时,一般需要使用 Redis 这样的内存数据库,而一 般不采用 MySQL 这样的关系型数据库。原因如下:
Session 数据存取比较频繁。内存访问速度快。
Session 有过期时间,Redis 这样的内存数据库能够比较方便实现过期释放。
Redis 可以配置主从复制集群,不担心单点故障。
(2) SpringSession 使用 (测试)
① 引入依赖
<parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.0.0.RELEASE</version>
</parent><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!-- 引入 springboot&redis 整合场景 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><!-- 引入 springboot&springsession 整合场景 --><dependency><groupId>org.springframework.session</groupId><artifactId>spring-session-data-redis</artifactId></dependency>
</dependencies>
② 配置文件
ps:端口号要不同
注意:存入 Session 域的实体类对象需要支持序列化!!!
# redis 配置
spring.redis.host=192.168.44.129spring.redis.jedis.pool.max-idle=100# springsession 配置
spring.session.store-type=redis
③ handler
spring_session_a
@RestController
public class setSession {@RequestMapping("/set/session")public String setSession(HttpSession session) {session.setAttribute("springSession","hello springSession");return "添加session成功";}}
spring_session_b
@RestController
public class getSession {@RequestMapping("/get/session")public String getSession(HttpSession session) {return (String)session.getAttribute( "springSession");}
}
④ 测试
⑤ 查看 redis
(3) SpringSession 基本原理
① SpringSession 需要完成的任务
② SessionRepositoryFilter
③ HttpSessionStrategy
封装 Session 的存取策略;cookie 还是 http headers 等方式
④ SessionRepository
指定存取/删除/过期 session 操作的 repository
⑤ RedisOperationsSessionRepository
使用 Redis 将 session
六、登录检查
把项目中必须登录才能访问的功能保护起来,如果没有登录就访问则跳转到登录页面。
(1) 思路
(2) 设置 Session 共享
① 加入依赖
zuul 工程 和 auth-consumer 工程 都加入以下依赖
<!-- 引入 springboot&redis 整合场景 -->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- 引入 springboot&springsession 整合场景 -->
<dependency><groupId>org.springframework.session</groupId><artifactId>spring-session-data-redis</artifactId>
</dependency>
② 加入配置
zuul 工程 和 auth-consumer 工程 都加入以下配置
spring:redis:host: 192.168.44.129 # 连接redis时使用的localhostsession:store-type: redis
(3) 准备不需要登录检查的资源
// 允许通过的请求路径集合
public static final Set<String> PASS_RES_SET = new HashSet<>();static {PASS_RES_SET.add("/");PASS_RES_SET.add("/auth/member/send/short/message.json"); // 发送验证码PASS_RES_SET.add("/auth/do/member/register"); // 执行注册PASS_RES_SET.add("/auth/do/member/login"); // 执行登录PASS_RES_SET.add("/auth/do/logout"); // 退出登录PASS_RES_SET.add("/auth/member/to/reg/page.html"); // 到用户注册页面PASS_RES_SET.add("/auth/member/to/login/page"); // 到用户登录页面
}// 允许通过的静态资源集合
public static final Set<String> STATIC_RES_SET = new HashSet<>();static {STATIC_RES_SET.add("bootstrap");STATIC_RES_SET.add("css");STATIC_RES_SET.add("fonts");STATIC_RES_SET.add("img");STATIC_RES_SET.add("jquery");STATIC_RES_SET.add("layer");STATIC_RES_SET.add("script");STATIC_RES_SET.add("ztree");
}
(4) 判断当前请求是否为静态资源
// 用于判断某个 ServletPath 值是否对应一个静态资源
public static boolean judgeCurrentServletPathWetherStaticResource(String servletPath) {// 假如字符串无效if (servletPath == null || servletPath.length() == 0) {throw new RuntimeException(CrowdConstant.MESSAGE_ACCESS_FORBIDEN);}// 拆分字符串,分割路径判断是否为静态资源String[] strings = servletPath.split("/");// 考虑到第一个斜杠左边经过拆分后得到一个空字符串是数组的第一个元素,所以需要使用下标 1 取第二个元素String firstLevelPath = strings[1];// 判断是否在静态资源中return STATIC_RES_SET.contains(firstLevelPath);
}
(5) ZuulFilter
@Slf4j
@Component
public class CrowdAccessFilter extends ZuulFilter {// 过滤器类型,可选值有 pre、route、post、error。@Overridepublic String filterType() {// 这里返回“pre”意思是在目标微服务前执行过滤return "pre";}// 过滤器的执行顺序,数值越小,优先级越高。@Overridepublic int filterOrder() {return 0;}// 如果应调用run()方法,则为true[不放行]。false不会调用run()方法 [放行]@Overridepublic boolean shouldFilter() {log.info("到达shouldFilter");// 获取 RequestContext 对象RequestContext currentContext = RequestContext.getCurrentContext();// 通过 RequestContext 对象获取当前请求对象(框架底层是借助 ThreadLocal 从当前线程上获取事先绑定的 Request 对象)HttpServletRequest request = currentContext.getRequest();// 3.获取 servletPath 值String servletPath = request.getServletPath();// 根据 servletPath 判断当前请求是否对应可以直接放行的特定功能if (AccessPassResources.PASS_RES_SET.contains(servletPath)) {log.info("放行" + servletPath);// 在允许通过的请求路径集合中,返回false,不经过run()方法,即为放行return false;}// 5.判断当前请求是否为静态资源// 工具方法返回 true:说明当前请求是静态资源请求,取反为 false 表示放行不做登录检查// 工具方法返回 false:说明当前请求不是可以放行的特定请求也不是静态资源,取反为 truereturn !AccessPassResources.judgeCurrentServletPathWetherStaticResource(servletPath);}@Overridepublic Object run() throws ZuulException {// 不放行某个资源后,来到这个run()方法中// 目标: 判断当前用户是否处于登录状态// 获取 RequestContext 对象RequestContext currentContext = RequestContext.getCurrentContext();// 通过 RequestContext 对象获取当前请求对象(框架底层是借助 ThreadLocal 从当前线程上获取事先绑定的 Request 对象)HttpServletRequest request = currentContext.getRequest();log.info("被拦截: " + request.getServletPath());// 获取当前 Session 对象HttpSession session = request.getSession();// 从sesison中取出当前登录的用户信息MemberLoginVO loginMember = (MemberLoginVO) session.getAttribute(CrowdConstant.ATTR_NAME_LOGIN_MEMBER);// 若当前登录的用户信息为空,即未登录状态if (loginMember == null) {//log.info("当前无人登录");// 往session保存错误信息session.setAttribute(CrowdConstant.ATTR_NAME_MESSAGE,CrowdConstant.MESSAGE_No_LOGIN_MEMBER);// 获取 Response 对象,用来进行重定向HttpServletResponse response = currentContext.getResponse();try {// 重定向到 用户登录页面response.sendRedirect("/auth/member/to/login/page");} catch (IOException e) {e.printStackTrace();}}return null;}
}
(6) 登录页面读取 Session 域
<p th:text="${session.message}">这里登录检查后发现不允许访问时的提示消息</p>
(7) Zuul 中的特殊设置
这篇关于【尚筹网项目】 十、 【前台】 会员登录 及使用 SpringSession 实现 Session共享的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!