SpringBoot+MyBatis+SpringSession+Redis实现session共享及单点登录 - Go语言中文社区

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


1.添加Maven依赖

<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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>

	<groupId>com.weichai</groupId>
	<artifactId>SpringSession</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<packaging>war</packaging>

	<name>SpringSession</name>
	<url>http://maven.apache.org</url>

	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.0.0.RELEASE</version>
		<relativePath />
	</parent>

	<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.0.0.RELEASE</version>
		</dependency>
		<!-- 添加MyBatis依赖 -->
		<dependency>
			<groupId>org.mybatis.spring.boot</groupId>
			<artifactId>mybatis-spring-boot-starter</artifactId>
			<version>1.3.0</version>
		</dependency>
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<version>6.0.5</version>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-redis</artifactId>
			<version>2.0.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>
	</dependencies>

	<build>
		<resources>
			<resource>
				<directory>src/main/java</directory>
				<includes>
					<include>**/*.properties</include>
					<include>**/*.xml</include>
				</includes>
				<filtering>false</filtering>
			</resource>
			<resource>
				<directory>src/main/resources</directory>
				<includes>
					<include>**/*.properties</include>
					<include>**/*.xml</include>
				</includes>
				<filtering>false</filtering>
			</resource>
		</resources>
	</build>
</project>

2.SpringBoot属性配置文件(application.properties)

spring.datasource.url=jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf8
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.redis.database=0
spring.redis.host=localhost
spring.redis.port=6380
spring.redis.password=123456
spring.redis.pool.max-active=10
spring.redis.pool.max-wait=-1
spring.redis.pool.max-idle=10
spring.redis.pool.min-idle=0
spring.redis.timeout=2000
server.session.timeout=1800
mybatis.mapper-locations=classpath:mappings/modules/user/*.xml
server.port=8090

3.实体文件

package com.weichai.SpringSession.entity;

import java.io.Serializable;

public class User implements Serializable {

	private Integer id;
	private String userName;
	private String userPwd;
	private Integer age;
	private String address;
	public User(Integer id, String userName, String userPwd, Integer age, String address) {
		super();
		this.id = id;
		this.userName = userName;
		this.userPwd = userPwd;
		this.age = age;
		this.address = address;
	}
	public User() {
		super();
		// TODO Auto-generated constructor stub
	}
	public Integer getId() {
		return id;
	}
	public void setId(Integer id) {
		this.id = id;
	}
	public String getUserName() {
		return userName;
	}
	public void setUserName(String userName) {
		this.userName = userName;
	}
	public String getUserPwd() {
		return userPwd;
	}
	public void setUserPwd(String userPwd) {
		this.userPwd = userPwd;
	}
	public Integer getAge() {
		return age;
	}
	public void setAge(Integer age) {
		this.age = age;
	}
	public String getAddress() {
		return address;
	}
	public void setAddress(String address) {
		this.address = address;
	}
	@Override
	public String toString() {
		return "User [id=" + id + ", userName=" + userName + ", userPwd=" + userPwd + ", age=" + age + ", address="
				+ address + "]";
	}
	
	
}
package com.weichai.SpringSession.entity;

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 + "]";
	}
	
	
}

4.数据访问层

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.weichai.SpringSession.dao.UserDao">
	<sql id="userColumns">
		a.id as "id",
		a.userName as "userName",
		a.userPwd as "userPwd",
		a.age as "age",
		a.address as "address"
	</sql>

	<sql id="userJoins">
	</sql>

	<select id="findByUserNameAndUserPwd"
		resultType="com.weichai.SpringSession.entity.User">
		SELECT
		<include refid="userColumns" />
		FROM user a
		<include refid="userJoins" />
		WHERE a.userName = #{userName} AND a.userPwd = #{userPwd}
	</select>

	<select id="findById"
		resultType="com.weichai.SpringSession.entity.User"
		parameterType="java.lang.Integer">
		SELECT
		<include refid="userColumns" />
		FROM user a
		<include refid="userJoins" />
		WHERE a.id = #{id}
	</select>
</mapper>
package com.weichai.SpringSession.dao;

import com.weichai.SpringSession.entity.User;

public interface UserDao {
	
	public User findByUserNameAndUserPwd(String userName,String userPwd);
	
	public User findById(Integer id);
}

5.服务层

package com.weichai.SpringSession.service;

import javax.annotation.Resource;

import org.springframework.stereotype.Service;

import com.weichai.SpringSession.dao.UserDao;
import com.weichai.SpringSession.entity.User;

@Service
public class UserService {

	@Resource
	private UserDao userDao;
	
	public User findByUserNameAndUserPwd(String userName,String userPwd) {
		return userDao.findByUserNameAndUserPwd(userName, userPwd);
	}
	
	public User findById(Integer id) {
		return userDao.findById(id);
	}
}

6.拦截器

package com.weichai.SpringSession.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 com.weichai.SpringSession.config;

import java.io.IOException;

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

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;
import com.weichai.SpringSession.entity.ResponseResult;

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

	@Autowired
	private StringRedisTemplate redisTemplate;

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

}

7.控制层测试

package com.weichai.SpringSession.controller;

import java.util.HashMap;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;

/**
 * 多端口,多浏览器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", "Haige");
		Map<String, Object> map = new HashMap<>(); 
        map.put("SessionKey", "Haige");  
        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;
	}
}
package com.weichai.SpringSession.controller;

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

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.weichai.SpringSession.entity.ResponseResult;
import com.weichai.SpringSession.entity.User;
import com.weichai.SpringSession.service.UserService;

/**
 * 单点登录测试类
 * @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;
    }
}

8.测试结果

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

8.1 浏览器测试

8.2 SoapUI(接口测试工具)测试

8.3 redis服务器展示

 

两次登录只产生一个loginUser(用户登录身份表示),却产生了多个session

9.单点登录原理解析

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

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

 

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

10.本实例源码------->下载源码

 

 

版权声明:本文来源CSDN,感谢博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/linhaiyun_ytdx/article/details/88079809
站方申明:本站部分内容来自社区用户分享,若涉及侵权,请联系站方删除。
  • 发表于 2020-03-01 22:06:19
  • 阅读 ( 1175 )
  • 分类:Redis

0 条评论

请先 登录 后评论

官方社群

GO教程

猜你喜欢