分布式会话解决方案

958 阅读2分钟

分布式会话之token解决方案实现

用token代替session,基于redis实现用户会话管理,配置登录拦截器,步骤如下
1、实现接口:HandlerInterceptor,重写 preHandle方法。

public class UserTokenInterceptor implements HandlerInterceptor {

    @Autowired
    private RedisOperator redisOperator;

    public static final String REDIS_USER_TOKEN = "redis_user_token";

    /**
     * 拦截请求,在访问controller调用之前
     * @param request
     * @param response
     * @param handler
     * @return
     * @throws Exception
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

//        System.out.println("进入到拦截器,被拦截。。。");

        String userId = request.getHeader("headerUserId");
        String userToken = request.getHeader("headerUserToken");

        if (StringUtils.isNotBlank(userId) && StringUtils.isNotBlank(userToken)) {
            String uniqueToken = redisOperator.get(REDIS_USER_TOKEN + ":" + userId);
            if (StringUtils.isBlank(uniqueToken)) {
//                System.out.println("请登录...");
                returnErrorResponse(response, IMOOCJSONResult.errorMsg("请登录..."));
                return false;
            } else {
                if (!uniqueToken.equals(userToken)) {
//                    System.out.println("账号在异地登录...");
                    returnErrorResponse(response, IMOOCJSONResult.errorMsg("账号在异地登录..."));
                    return false;
                }
            }
        } else {
//            System.out.println("请登录...");
            returnErrorResponse(response, IMOOCJSONResult.errorMsg("请登录..."));
            return false;
        }


        /**
         * false: 请求被拦截,被驳回,验证出现问题
         * true: 请求在经过验证校验以后,是OK的,是可以放行的
         */
        return true;
    }

    public void returnErrorResponse(HttpServletResponse response,
                                    IMOOCJSONResult result) {
        OutputStream out = null;
        try {
            response.setCharacterEncoding("utf-8"); // 解决乱码
            response.setContentType("text/json");
            out = response.getOutputStream();  //printwriter类也可以
            out.write(JsonUtils.objectToJson(result).getBytes("utf-8"));
            out.flush();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (out != null) {
                    out.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 请求访问controller之后,渲染视图之前
     * @param request
     * @param response
     * @param handler
     * @param modelAndView
     * @throws Exception
     */
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {

    }

    /**
     * 请求访问controller之后,渲染视图之后
     * @param request
     * @param response
     * @param handler
     * @param ex
     * @throws Exception
     */
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {

    }
}

2、配置拦截器

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

    @Bean
    public UserTokenInterceptor userTokenInterceptor() {
        return new UserTokenInterceptor();
    }

    /**
     * 注册拦截器
     * @param registry
     */
    @Override
    public void addInterceptors(InterceptorRegistry registry) {

        registry.addInterceptor(userTokenInterceptor())
                .addPathPatterns("/hello")
                .addPathPatterns("/shopcart/add")
                .addPathPatterns("/shopcart/del")
                .addPathPatterns("/address/list")
                .addPathPatterns("/address/add")
                .addPathPatterns("/address/update")
                .addPathPatterns("/address/setDefalut")
                .addPathPatterns("/address/delete")
                .addPathPatterns("/orders/*")
                .addPathPatterns("/center/*")
                .addPathPatterns("/userInfo/*")
                .addPathPatterns("/myorders/*")
                .addPathPatterns("/mycomments/*")
                .excludePathPatterns("/myorders/deliver")
                .excludePathPatterns("/orders/notifyMerchantOrderPaid");

        WebMvcConfigurer.super.addInterceptors(registry);
    }
}

3、登录/注册代码

    @ApiOperation(value = "用户注册", notes = "用户注册", httpMethod = "POST")
    @PostMapping("/regist")
    public IMOOCJSONResult regist(@RequestBody UserBo userBo, HttpServletRequest request,
                                  HttpServletResponse response) {
        String userName = userBo.getUsername();
        String password = userBo.getPassword();
        String comfirmPwd = userBo.getConfirmPassword();
        if (StringUtils.isBlank(userName) || StringUtils.isBlank(password)
                || StringUtils.isBlank(comfirmPwd)) {
            return IMOOCJSONResult.errorMsg("用户名或密码不能为空");
        }
        boolean isExist = userService.queryUserNameIsExist(userName);
        if (isExist) {
            return IMOOCJSONResult.errorMsg("用户名已经存在");
        }
        if (password.length() < 6) {
            return IMOOCJSONResult.errorMsg("密码长度不能少于6");
        }
        if (!password.equals(comfirmPwd)) {
            return IMOOCJSONResult.errorMsg("两次密码不一致");
        }
        Users users = userService.createUser(userBo);
        UsersVO usersVO = new UsersVO(); // 展示到前端的用户基本信息,包含token信息
        BeanUtils.copyProperties(users,usersVO);
        // 实现用户的redis会话
        String uniqueToken = UUID.randomUUID().toString().trim();
        redisOperator.set(REDIS_USER_TOKEN + ":" + users.getId(), uniqueToken);
        usersVO.setUserUniqueToken(uniqueToken);
        CookieUtils.setCookie(request, response, "user",
                JsonUtils.objectToJson(usersVO), true);
        return IMOOCJSONResult.ok();
    }

分布式会话之Spring-session解决方案

参考文章
www.cnblogs.com/toov5/p/990…
blog.csdn.net/qq_43371556…
这两篇文章讲的比较细致。
案例如下
前提:spring-session,需要依赖spring,这里使用springboot框架
1、依赖包

<!-- 引入 spring-session 依赖 -->
<dependency>
    <groupId>org.springframework.session</groupId>
    <artifactId>spring-session-data-redis</artifactId>
</dependency>

<!-- 引入 springboot 安全框架,不然会启动报错 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>

<!-- 引入 redis 依赖 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

2、配置

spring:
  session:
    store-type: redis #session 存储类型
  redis:
    #单节点redis
    database: 1
    host: 127.0.0.1
    port: 6379

3、启动类入口文件

@SpringBootApplication(exclude = {SecurityAutoConfiguration.class}) 
// 排除 SecurityAutoConfiguration.class文件,不然会跳到登录页面
@EnableRedisHttpSession  //开启使用redis存储session

4、测试

 @GetMapping("/setSession")
    public Object setSession(HttpServletRequest request){
        HttpSession session = request.getSession();
        session.setAttribute("userInfo", "new user");
        session.setMaxInactiveInterval(3600);
        session.getAttribute("userInfo");
        return "OK";
    }

如果有其他语言调到该redis的session 不适用这种方式,耦合Spring。