尚筹网项目 十 前台 会员登录 及使用 SpringSession 实现 Session共享

Posted 黑桃️

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了尚筹网项目 十 前台 会员登录 及使用 SpringSession 实现 Session共享相关的知识,希望对你有一定的参考价值。

会员登录


一、思路

二、会员登录代码实现

(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.129

spring.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时使用的localhost
	session:
		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。
    @Override
    public String filterType() 

        // 这里返回“pre”意思是在目标微服务前执行过滤
        return "pre";
    

    // 过滤器的执行顺序,数值越小,优先级越高。
    @Override
    public int filterOrder() 
        return 0;
    


    // 如果应调用run()方法,则为true[不放行]。false不会调用run()方法 [放行]
    @Override
    public 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:说明当前请求不是可以放行的特定请求也不是静态资源,取反为 true
        return !AccessPassResources.judgeCurrentServletPathWetherStaticResource(servletPath);
    

    @Override
    public 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共享的主要内容,如果未能解决你的问题,请参考以下文章

尚筹网项目 八前台 环境搭建

项目一众筹网02_2_管理员登录类名首字母必须大写登录失败还是回到登录页面list的大小是怎么判断的list.size()

项目一众筹网02_1_此次项目的重要性环境搭建-创建常量类管理员登录功能开始表单都是以post方式去提交我们说的控制器就是handlerbase标签的位置实现点击浏览器的上一步

五尚筹网项目-后台-日志系统

五尚筹网项目-后台-日志系统

尚筹网项目 七后台 权限控制 ( 项目中加入 SpringSecurity )