单点登录,简称为 SSO,是目前比较流行的企业业务整合的解决方案之一。SSO的定义是在多个应用系统中,用户只需要登录一次就可以访问所有应用系统 或者是说只允许在一处登陆 就称单点登陆
下面就开始写一个简单的登录
本项目SpringBoot集成redis
建立Springboot项目之后 导入对应依赖
<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>8.0.16</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>
<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>
</dependencies>
</project>
配置yml文件
server:
port: 8081
spring:
devtools:
restart:
enabled: true
redis:
host: 121.199.47.53
port: 6379
password:
jedis:
pool:
max-active: 8
max-idle: 500
min-idle: 0
thymeleaf:
prefix: "classpath:/templates/"
suffix: ".html"
cache: false
datasource:
username: root
password: hzy
url: jdbc:mysql://121.199.47.53:3306/SSO?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=Asia/Shanghai
driver-class-name: com.mysql.jdbc.Driver
mvc:
date-format: "yyyy-MM-dd"
#showSql
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.xiaohe.entity;
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;
}
UserRepository接口
数据访问层
package cn.xiaohe.dao;
import cn.xiaohe.entity.User;
import org.springframework.data.jpa.repository.JpaRepository;
public interface UserRepository extends JpaRepository<User,Integer> {
User findByUserNameAndUserPwd(String UserName,String UserPwd);
}
这里还有一个用于接受返回数据的工具类
package cn.xiaohe.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 + "]";
}
}
业务层
Service
package cn.xiaohe.Service;
import cn.xiaohe.dao.UserRepository;
import cn.xiaohe.entity.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 byUserNameAndUserPwd = UserRepository.findByUserNameAndUserPwd(UserName, UserPwd);
return byUserNameAndUserPwd;
}
public User findById(Integer id) {
return (User) UserRepository.findById(id).get();
}
}
下面是拦截器以及开启Spring session的配置类
RedisSessionConfig
package cn.xiaohe.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 {
}
RedisSessionInterceptor
package cn.xiaohe.config;
import java.io.IOException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import cn.xiaohe.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;
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 {
}
}
WebSecurityConfig
package cn.xiaohe.config;
import cn.xiaohe.config.RedisSessionInterceptor;
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);
}
}
最后就是Controller
HelloController
package cn.xiaohe.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", "XiaoHong");
Map<String, Object> map = new HashMap<>();
map.put("SessionKey", "XiaoHong");
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;
}
}
UserController
package cn.xiaohe.Controller;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import cn.xiaohe.Service.UserService;
import cn.xiaohe.Util.ResponseResult;
import cn.xiaohe.entity.User;
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;
/**
* 单点登录测试类
* @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;
}
}
最后的测试效果是
打开Chrome浏览器通过id去访问用户数据
这里是未登陆状态
之后 咱们再来登陆
再去访问用户数据
如果这个时候 从火狐浏览器再去登陆 Chrome登陆就会失效 用户数据访问不到
这时只能从火狐浏览器访问