Springboot+redis实现单点登陆

SpringDataJpa+redis+SpringSession 浏览器共享Session实现单点登陆

单点登录,简称为 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去访问用户数据
Springboot+redis实现单点登陆_第1张图片
这里是未登陆状态
之后 咱们再来登陆

Springboot+redis实现单点登陆_第2张图片
再去访问用户数据
Springboot+redis实现单点登陆_第3张图片
如果这个时候 从火狐浏览器再去登陆 Chrome登陆就会失效 用户数据访问不到

这时只能从火狐浏览器访问

Springboot+redis实现单点登陆_第4张图片
Springboot+redis实现单点登陆_第5张图片
以上代码就实现了一个简单的单点登录

你可能感兴趣的:(Springboot+redis实现单点登陆)