<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.0modelVersion>
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.2.0.RELEASEversion>
<relativePath/>
parent>
<groupId>cn.bdqngroupId>
<artifactId>ssoartifactId>
<version>0.0.1-SNAPSHOTversion>
<name>ssoname>
<description>Demo project for Spring Bootdescription>
<url>http://maven.apache.orgurl>
<properties>
<project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8project.reporting.outputEncoding>
<java.version>1.8java.version>
properties>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
<version>2.2.0.RELEASEversion>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>8.0.15version>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-redisartifactId>
<version>2.2.0.RELEASEversion>
dependency>
<dependency>
<groupId>org.springframework.sessiongroupId>
<artifactId>spring-session-data-redisartifactId>
dependency>
<dependency>
<groupId>org.springframework.sessiongroupId>
<artifactId>spring-sessionartifactId>
<version>1.3.5.RELEASEversion>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>fastjsonartifactId>
<version>1.2.24version>
dependency>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>3.8.1version>
<scope>testscope>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-jpaartifactId>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<version>1.18.8version>
dependency>
<dependency>
<groupId>org.hibernategroupId>
<artifactId>hibernate-entitymanagerartifactId>
<version>5.3.6.Finalversion>
dependency>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>4.12version>
dependency>
<dependency>
<groupId>org.hibernategroupId>
<artifactId>hibernate-ehcacheartifactId>
<version>5.3.6.Finalversion>
dependency>
<dependency>
<groupId>io.springfoxgroupId>
<artifactId>springfox-swagger2artifactId>
<version>2.7.0version>
dependency>
<dependency>
<groupId>io.springfoxgroupId>
<artifactId>springfox-swagger-uiartifactId>
<version>2.7.0version>
dependency>
<dependency>
<groupId>commons-langgroupId>
<artifactId>commons-langartifactId>
<version>2.6version>
dependency>
<dependency>
<groupId>org.springframework.datagroupId>
<artifactId>spring-data-commonsartifactId>
<version>2.2.0.RELEASEversion>
dependency>
dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
plugin>
plugins>
build>
project>
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&characterEncoding=utf-8&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
*/
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上登录的账号就失效了,同样一个账号只能在一处登录(单点登录)。
两次登录只产生一个loginUser(用户登录身份表示),却产生了多个session
用户登录时,在redis中记录该userId对应的sessionId,并将userId保存到session中
HttpSession session = request.getSession();
session.setAttribute("loginSessionId", user.getId());
redisTemplate.opsForValue().set("loginUser:" + user.getId(), session.getId());