SpringBoot+Jpa+SpringSession+Redis实现session共享及单点登录

添加Maven依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.0.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>cn.bdqn</groupId>
    <artifactId>sso</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>sso</name>
    <description>Demo project for Spring Boot</description>
    <url>http://maven.apache.org</url>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <version>2.2.0.RELEASE</version>
        </dependency>
        <!-- 添加mysql的依赖 -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.15</version><!--我装的是8.0.15,根据个人选择相应版本-->
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
            <version>2.2.0.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.session</groupId>
            <artifactId>spring-session-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.session</groupId>
            <artifactId>spring-session</artifactId>
            <version>1.3.5.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.24</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>3.8.1</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.8</version>
        </dependency>
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-entitymanager</artifactId>
            <version>5.3.6.Final</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-ehcache</artifactId>
            <version>5.3.6.Final</version>
        </dependency>
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
            <version>2.7.0</version>
        </dependency>
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger-ui</artifactId>
            <version>2.7.0</version>
        </dependency>
        <dependency>
            <groupId>commons-lang</groupId>
            <artifactId>commons-lang</artifactId>
            <version>2.6</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.data</groupId>
            <artifactId>spring-data-commons</artifactId>
            <version>2.2.0.RELEASE</version>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

SpringBoot属性配置文件(application.yml)

server:
  port: 8088

spring:
  devtools:
    restart:
     enabled: true
  redis:
    host: 122.51.189.218
    port: 6379
    password:
    jedis:
      pool:
        max-active: 8
        max-idle: 500
        min-idle: 0

  thymeleaf:
    prefix: "classpath:/templates/"
    suffix: ".html"
    cache: false

  resources:
    chain:
      strategy:
        content:
          enabled: true
          paths: /**
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://122.51.189.218:3306/sso?useUnicode=true&amp;characterEncoding=utf-8&amp;serverTimezone=UTC
    password: root
    username: root
  jpa:
    show-sql: true
    database: mysql
    hibernate:
      ddl-auto: update
      naming:
        implicit-strategy: org.hibernate.boot.model.naming.ImplicitNamingStrategyComponentPathImpl 
        physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl 

实体类

package cn.bdqn.pojo;
import lombok.Data;
import javax.persistence.*;
import java.io.Serializable;
@Data
@Entity
@Table(name = "User")
public class User  implements Serializable {
    @Id
    @GeneratedValue(strategy= GenerationType.IDENTITY)
    @Column(name = "id")
    private Integer id;
    @Column(name = "userName")
    private String userName;
    @Column(name = "userPwd")
    private String userPwd;
    @Column(name = "age")
    private Integer age;
    @Column(name = "address")
    private String address;
}

数据接口返回结果

package cn.bdqn.util;

import java.io.Serializable;
/**
 * 数据接口返回结果
 * @author linhaiy
 * @date 2019.03.01
 * @param <T>
 */
public class ResponseResult<T> implements Serializable {

    public static final int STATE_ERROR = -1;
    public static final int STATE_OK = 1;
    private static final long serialVersionUID = 2158690201147047546L;
    private int status;           //返回状态
    private String message;       //返回信息
    private T data;               //返回数据

    public ResponseResult() {
        super();
        // TODO Auto-generated constructor stub
    }

    public ResponseResult(int status, String message, T data) {
        super();
        this.status = status;
        this.message = message;
        this.data = data;
    }

    public int getStatus() {
        return status;
    }

    public void setStatus(int status) {
        this.status = status;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((data == null) ? 0 : data.hashCode());
        result = prime * result + ((message == null) ? 0 : message.hashCode());
        result = prime * result + status;
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (obj == null) {
            return false;
        }
        if (getClass() != obj.getClass()) {
            return false;
        }
        ResponseResult other = (ResponseResult) obj;
        if (data == null) {
            if (other.data != null) {
                return false;
            } else if (!data.equals(other.data)) {
                return false;
            }
            if (message == null) {
                if (other.message != null) {
                    return false;
                }
            } else if (!message.equals(other.message)) {
                return false;
            }
            if (status != other.status) {
                return false;
            }
        }
        return true;
    }

    @Override
    public String toString() {
        return "ResponseResult [status=" + status + ", message=" + message + ", data=" + data + "]";
    }
}

数据访问层

package cn.bdqn.dao;
import cn.bdqn.pojo.User;
import org.springframework.data.jpa.repository.JpaRepository;

public interface UserRepository extends JpaRepository<User,Integer> {
    User findByUserNameAndUserPwd(String userName,String userPWd);
}

拦截器

package cn.bdqn.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession;

/**
 * 添加@EnableRedisHttpSession来开启spring session支持
 * @author linhaiy
 * @date 2019.03.01
 * 拦截器
 */
@Configuration
//maxInactiveIntervalInSeconds 默认是1800秒过期,这里测试修改为60秒
@EnableRedisHttpSession(maxInactiveIntervalInSeconds=60)
public class RedisSessionConfig {

}
package cn.bdqn.config;

import java.io.IOException;

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

import cn.bdqn.util.ResponseResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import com.alibaba.fastjson.JSONObject;

/**
 * 登录状态拦截器RedisSessionInterceptor
 * @author linhaiy
 * @date 2019.03.01
 */
public class RedisSessionInterceptor implements HandlerInterceptor {

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
            throws Exception {
        // 无论访问的地址是不是正确的,都进行登录验证,登录成功后的访问再进行分发,404的访问自然会进入到错误控制器中
        HttpSession session = request.getSession();
        if (session.getAttribute("loginSessionId") != null) {
            try {
                // 验证当前请求的session是否是已登录的session
                String loginSessionId = redisTemplate.opsForValue()
                        .get("loginUser:" + (Integer) session.getAttribute("loginSessionId"));
                System.out.println("用户已登录,sessionId为: " + loginSessionId);
                if (loginSessionId != null && loginSessionId.equals(session.getId())) {
                    return true;
                }
            } catch (Exception e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
        response401(response);
        return false;
    }

    private void response401(HttpServletResponse response) {
        response.setCharacterEncoding("UTF-8");
        response.setContentType("application/json; charset=utf-8");
        try {
            System.out.println("用户未登录!");
            response.getWriter().print(JSONObject.toJSONString(new ResponseResult<>(404, "用户未登录!", null)));
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
                           ModelAndView modelAndView) throws Exception {

    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
            throws Exception {

    }

}
package cn.bdqn.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;

/**
 * Session配置拦截器
 * @author linhaiy
 * @date 2019.03.01
 */
@Configuration
public class WebSecurityConfig extends WebMvcConfigurerAdapter {

    @Bean
    public RedisSessionInterceptor getSessionInterceptor() {
        return new RedisSessionInterceptor();
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // 所有已api开头的访问都要进入RedisSessionInterceptor拦截器进行登录验证,并排除login接口(全路径)。必须写成链式,分别设置的话会创建多个拦截器。
        // 必须写成getSessionInterceptor(),否则SessionInterceptor中的@Autowired会无效
        registry.addInterceptor(getSessionInterceptor()).addPathPatterns("/api/**")
                .excludePathPatterns("/api/user/login");
        super.addInterceptors(registry);
    }

}

服务层

package cn.bdqn.service;
import cn.bdqn.dao.UserRepository;
import cn.bdqn.pojo.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class UserService {
    @Autowired
    UserRepository UserRepository;
    public User findByUserNameAndUserPwd(String UserName,String UserPwd) {
        User byNameAndUserPwd = UserRepository.findByUserNameAndUserPwd(UserName, UserPwd);
        return byNameAndUserPwd;
    }
    public User findById(Integer id) {
        return (User) UserRepository.findById(id).get();
    }
}

控制层测试

package cn.bdqn.controller;

import cn.bdqn.pojo.User;
import cn.bdqn.service.UserService;
import cn.bdqn.util.ResponseResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.*;

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

/**
 * 单点登录测试类
 * @author linhaiy
 *
 */
@RestController
@RequestMapping(value = "/api/user")
public class UserController {
    @Autowired
    private UserService userService;

    @Autowired
    private StringRedisTemplate redisTemplate;

    @RequestMapping("/login")
    public ResponseResult<User> login(HttpServletRequest request, String userName, String userPwd) {
        User user = userService.findByUserNameAndUserPwd(userName, userPwd);
        ResponseResult<User> resData = new ResponseResult<>();
        if (user != null) {
            HttpSession session = request.getSession();
            session.setAttribute("loginSessionId", user.getId());
            redisTemplate.opsForValue().set("loginUser:" + user.getId(), session.getId());
            resData.setData(user);
            resData.setStatus(0);
            resData.setMessage("登录成功!");
        } else {
            resData.setData(null);
            resData.setStatus(1);
            resData.setMessage("用户信息输入错误!");
        }
        return resData;
    }

    @RequestMapping(value = "/getUserInfo")
    public ResponseResult<User> get( Integer id) {
        User user = userService.findById(id);
        ResponseResult<User> resData = new ResponseResult<>();
        if (user != null) {
            resData.setData(user);
            resData.setStatus(0);
            resData.setMessage("查询成功!");
        } else {
            resData.setData(user);
            resData.setStatus(1);
            resData.setMessage("没有符合该查询条件的数据!");
        }
        return resData;
    }
}

package cn.bdqn.controller;

import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;

/**
 * 多端口,多浏览器session共享测试类
 * 可以开启多个端口,如8088,8090,8080等几个端口测试以下接口,发现获取的session是一致的,说明redis实现了session共享
 * @author linhaiy
 */
@RestController
@RequestMapping("/Spring")
public class HelloController {

    @RequestMapping("/getSessionId")
    @ResponseBody
    public Map<String, Object> SessionIdTest(HttpServletRequest request) {
        Map<String, Object> sessionIdMap = new HashMap<String, Object>();
        String SessionId = request.getSession().getId();        //获取SessionId
        int Port = request.getServerPort();
        sessionIdMap.put("服务器端口:", Port);
        sessionIdMap.put("sessionId:", SessionId);
        return sessionIdMap;
    }

    @RequestMapping(value = "setSessionId", method = RequestMethod.GET)
    @ResponseBody
    public Map<String, Object> SetSessionId(HttpServletRequest request) {
        request.getSession().setAttribute("SessionKey", "XiaoXiao");
        Map<String, Object> map = new HashMap<>();
        map.put("SessionKey", "XiaoXiao");
        return map;
    }

    @RequestMapping(value = "getSessionId", method = RequestMethod.GET)
    @ResponseBody
    public Object GetSessionId(HttpServletRequest request) {
        Map<String, Object> map = new HashMap<>();
        map.put("sessionId", request.getSession().getId());
        map.put("SessionKey", request.getSession().getAttribute("SessionKey"));
        return map;
    }
}

测试结果

先在浏览器上进行登录操作,getUserInfo接口获取用户信息,再在SoapUI(接口测试工具)上登录相同的账号,浏览器再获取用户信息,就会提示401错误了,浏览器需要重新登录才能获取得到用户信息,同样,SoapUI上登录的账号就失效了,同样一个账号只能在一处登录(单点登录)。

浏览器测试

在这里插入图片描述
在这里插入图片描述

SoapUI(接口测试工具)测试

在这里插入图片描述
在这里插入图片描述

redis服务器展示

在这里插入图片描述
两次登录只产生一个loginUser(用户登录身份表示),却产生了多个session

解析

用户登录时,在redis中记录该userId对应的sessionId,并将userId保存到session中

HttpSession session = request.getSession();
session.setAttribute("loginSessionId", user.getId());
redisTemplate.opsForValue().set("loginUser:" + user.getId(), session.getId());

访问接口时,会在RedisSessionInterceptor拦截器中的preHandle()中捕获,然后根据该请求发起者的session中保存的userId去redis查当前已登录的sessionId,若查到的sessionId与访问者的sessionId相等,那么说明请求合法,放行。否则抛出401异常给全局异常捕获器去返回给客户端401状态。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值