2018.7.27 java电商从1到2--chapter8 SpringSession实现单点登录

在chapter6中用redis+cookie+jackson+filter实现单点登录,并且在chapter7中将redis改为了分布式。

但是这种方式对业务有侵入,可以看到我们需要将session.getAttribute()改为RedisPoolUtil.get()。

使用SpringSession可以实现零侵入。使用者仍然从session中拿到属性即可,也不需要处理cookie。但是这里的session已经是SpringSession包装过的了。它内部的实现中其实做了和chapter6类似的事情。

SpringSession(1.2.1 RELEASE版本)有一个缺点:查看JedisConnectionFactory的源码可知,它在初始化JedisPool时,虽然使用的是ShardedJedis,但是并不是使用List<JedisShardInfo>,而是只放了一个JedisShardInfo。所以它不支持多个redis。

7.1 代码准备

7.1.1 UserSpringSessionController

为了不影响原本代码,复制一份UserController,重命名为UserSpringSessionController。只保留login、logout和getUserInfo,用来测试SpringSession的功能。

package com.mmall.controller.portal;

import com.mmall.common.Const;
import com.mmall.common.ResponseCode;
import com.mmall.common.ServerResponse;
import com.mmall.pojo.User;
import com.mmall.service.IUserService;
import com.mmall.util.CookieUtil;
import com.mmall.util.JsonUtil;
import com.mmall.util.RedisShardedPoolUtil;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

/**
 * Created by geely
 */
@Controller
@RequestMapping("/user/springsession/")
public class UserSpringSessionController {


    @Autowired
    private IUserService iUserService;


    /**
     * 用户登录
     * @param username
     * @param password
     * @param session
     * @return
     */
    @RequestMapping(value = "login.do",method = RequestMethod.GET)
    @ResponseBody
    public ServerResponse<User> login(String username, String password, HttpSession session, HttpServletResponse httpServletResponse){

        //测试全局异常
//        int i = 0;
//        int j = 666/i;

        ServerResponse<User> response = iUserService.login(username,password);
        if(response.isSuccess()){

            session.setAttribute(Const.CURRENT_USER,response.getData());
//            CookieUtil.writeLoginToken(httpServletResponse,session.getId());
//            RedisShardedPoolUtil.setEx(session.getId(), JsonUtil.obj2String(response.getData()),Const.RedisCacheExtime.REDIS_SESSION_EXTIME);

        }
        return response;
    }

    @RequestMapping(value = "logout.do",method = RequestMethod.GET)
    @ResponseBody
    public ServerResponse<String> logout(HttpSession session,HttpServletRequest httpServletRequest,HttpServletResponse httpServletResponse){
//        String loginToken = CookieUtil.readLoginToken(httpServletRequest);
//        CookieUtil.delLoginToken(httpServletRequest,httpServletResponse);
//        RedisShardedPoolUtil.del(loginToken);

        session.removeAttribute(Const.CURRENT_USER);

        return ServerResponse.createBySuccess();
    }

    @RequestMapping(value = "get_user_info.do",method = RequestMethod.GET)
    @ResponseBody
    public ServerResponse<User> getUserInfo(HttpSession session,HttpServletRequest httpServletRequest){

//        String loginToken = CookieUtil.readLoginToken(httpServletRequest);
//        if(StringUtils.isEmpty(loginToken)){
//            return ServerResponse.createByErrorMessage("用户未登录,无法获取当前用户的信息");
//        }
//        String userJsonStr = RedisShardedPoolUtil.get(loginToken);
//        User user = JsonUtil.string2Obj(userJsonStr,User.class);

        User user = (User)session.getAttribute(Const.CURRENT_USER);

        if(user != null){
            return ServerResponse.createBySuccess(user);
        }
        return ServerResponse.createByErrorMessage("用户未登录,无法获取当前用户的信息");
   
}

7.1.2 User需要可序列化

User类需要加上implement Serializer,否则使用SpringSession时会报错如下:

public class User implements Serializable {
    //略
}

7.2 SpringSession的依赖与配置

7.2.1 pom.xml

    <!-- spring session 单点登录 -->
    <dependency>
      <groupId>org.springframework.session</groupId>
      <artifactId>spring-session-data-redis</artifactId>
      <version>1.2.0.RELEASE</version>
    </dependency>

这里还需要注意一个问题,SpringSession1.2.0 RELEASE和Spring4.0.0集成时有bug,所以将依赖中的版本改为4.0.3。

    <org.springframework.version>4.0.3.RELEASE</org.springframework.version>

7.2.2 applicationContext.xml

引入SpringSession的配置文件applicationContext-spring-session.xml。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx" xmlns:jdbc="http://www.springframework.org/schema/jdbc"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:task="http://www.springframework.org/schema/task"
       xsi:schemaLocation="http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
     http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
     http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
     http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task.xsd">

    <context:component-scan base-package="com.mmall" annotation-config="true">
        <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller" />
    </context:component-scan>

    <aop:aspectj-autoproxy/>

    <import resource="applicationContext-spring-session.xml"/>
    <import resource="applicationContext-datasource.xml"/>

</beans>

7.2.3 SpringContext-spring-session.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx" xmlns:jdbc="http://www.springframework.org/schema/jdbc"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="
     http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
     http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
     http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
     http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">


    <bean id="redisHttpSessionConfiguration" class="org.springframework.session.data.redis.config.annotation.web.http.RedisHttpSessionConfiguration">
        <property name="maxInactiveIntervalInSeconds" value="1800" />
    </bean>

    <!--可选,如果不配置,将使用默认参数,可以参看源码的默认参数值-->
    <bean id="defaultCookieSerializer" class="org.springframework.session.web.http.DefaultCookieSerializer">
        <property name="domainName" value=".happymmall.com" />
        <property name="useHttpOnlyCookie" value="true" />
        <property name="cookiePath" value="/" />
        <property name="cookieMaxAge" value="31536000" />
    </bean>

    <bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig">
        <property name="maxTotal" value="20"/>
    </bean>

    <bean id="jedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
        <property name="hostName" value="127.0.0.1" />
        <property name="port" value="6379" />
        <property name="poolConfig" ref="jedisPoolConfig" />
    </bean>

</beans>

7.3 SpringSession的校验

7.3.1 集群环境测试

开启tomcat+nginx+redis的集群环境,可以验证访问www.happymmall.com/user/springsession时已经实现了session共享。

还可以查看request中带上的cookie值。

这里的cookie中,key为SESSION,这是类DefaultCookieSerializer中定义的。可以在配置文件中修改。回看自己实现的session共享,可以看到是类似的,只不过这里我们是自己实现了CookieUtil将key命名为mmall_login_token,并且存入了sessionId。

7.3.2 SpringSession在redis中存有的key

SpringSession会在redis中存3个key。

这是SpringSession的一个保守做法,它不是一到失效期就立马删除session。

当session到了我们设置的失效期,SpringSession会删除其他两个key,只剩下spring:session:sessions。它的ttl是SpringSession代码中设置的,不是我们设置的有效期。等它自己的ttl到期才会被删除,即一般来说会延迟删除。

我们设置的有效期影响的是spring:session:sessions:expires,它一旦失效就表示session失效。所以虽然spring:session:sessions保留的session信息还在,但是因为其他两个key已经被删除,这个session是不能被读取的。就是说,前台此时访问接口会被提示未登录。

 

7.4 SpringSession的源码解析

涉及的重要类与接口:

  • AbstractHttpSessionApplicationInitializer
  • HttpSessionStrategy
  • CookieHttpSessionStrategy
  • SessionRepository
  • RedisOperationSessionRepository
  • DefaultCookieSerializer
  • SessionRepositoryFilter
  • JedisConnectionFactory
  • RedisHttpSessionConfiguration
  • DelegatingFilterProxy
  • SessionRepositoryRequestWrapper
  • SessionRepositoryResponseWrapper

猜你喜欢

转载自blog.csdn.net/liyuhui195134/article/details/81240834