SpringBoot集成redis+session实现分布式单点登录

本文最后更新于2023.08.30-08:11,某些文章具有时效性,若有错误或已失效,请在下方留言或联系涛哥

前言

由于涛哥考虑到cookie的安全性问题,就有了下面这个版本的sso

单点登录 SSO(Single Sign On)

什么是单点登录?

单点登录的英文名叫做:Single Sign On(简称SSO),指在同一帐号平台下的多个应用系统中,用户只需登录一次,即可访问所有相互信任的系统。简而言之,多个系统,统一登陆。

我们可以这样理解,在一个服务模块登录后,其他模块无需再登录

实现方式

  • session广播机制实现(老方法)​ 当模块较多时,比较浪费资源;数据冗余,存在多份一样的数据​ session默认过期时间30分钟
  • 基于cookie+redis实现                                                                                                                                                                          
    ​ 在项目中任何一个模块登录后,把数据放到两个地方​ redis:key:生成唯一随机值(ip、用户id等) value:用户数据​ cookie:存放redis生成的key值放到cookie​ 访问其他模块,发送请求带着cookie进行发送,服务器获取cookie值,在redis中查询,根据key进行查询,如果找到就是登录状态
  • 分布式session方式实现单点登录                                                                                                                                                             
    流程运行:(1) 用户第一次登录时,将会话信息(用户Id和用户信息),比如以用户Id为Key,写入分布式Session;(2) 用户再次登录时,获取分布式Session,是否有会话信息,如果没有则调到登录页;(3) 一般采用Cache中间件实现,建议使用Redis,因此它有持久化功能,方便分布式Session宕机后,可以从持久化存储中加载会话信息;(4) 存入会话时,可以设置会话保持的时间,比如15分钟,超过后自动超时;结合Cache中间件,实现的分布式Session,可以很好的模拟Session会话。
  • token验证                                                                                                                                                                                             
    在项目某个模块进行登录,登录之后,按照jwt规则生成字待串,把登录之后用户包含到生成字符串里面,把字符串返回
    (1)可以把字符串通过cookie返回
    (2)把字符串通过地址栏返回前端收到token之后将token存储在自己的请求头之中或者url后面,这样每次请求都可以带着token请求。再去访问项目其他模块,获取地址栏或者请求头里面的token,根据字符串获取用户信息。同时为了设置失效时间,可以将token放在redis中,设置失效时间,判断过期。
  • CAS 中央认证服务

开发技术

  • SpringBoot
  • Redis
  • Session

涛哥单点登录实现流程

  1. 用户在登录时,登录成功以后得到当前sessionid
  2. 将用户信息存储在redis里面,设置有效时间30分钟,以key-value形式,sessionid作为key,登录成功后的用户信息作为value
  3. 访问时通过拦截器拦截请求,判断当前sessionid是否在redis里,再则延长寿命,不再提示身份过期
  4. 完成登录验证后,放行执行访问请求

实现案例

实现效果:使用nginx做轮询分发请求,在任何一个服务登录成功以后,在访问其他服务时就不需要再去登录

1,首先创建一个boot项目

2,导入pom依赖

       <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.session</groupId>
            <artifactId>spring-session-data-redis</artifactId>
        </dependency>

3,配置核心文件

注意这里只有一个配置文件,若想启动两个端口。就在编辑页面的  VM OPTIONS 里配置如下 -Dserver.port=8082

server:
  port: 8081
## redis
#session存储类型
spring:
  application:
    name: redis_session
  redis:
    host: 127.0.0.1
    port: 6379
    #没用就填空
    password:
    jedis:
      pool:
        #连接池最大连接数
        max-active: 8
        #阻塞时间 (负表示没有)
        max-wait: -1
        #最大空闲连接
        max-idle: 8
        #最小空闲连接
        min-idle: 0
    #连接超时时间
    timeout: 30000
    database: 0

4,编写用户类

package com.gxhh.redis_session.bean;

/**
 * @Program: LoginDemo
 * @ClassName User
 * @Description: 用户类
 * @Author: liutao
 * @Create: 2022/7/8 16:04
 * @Version: 1.0
 */
public class User {
    private String username;
    private String pwd;

    public User() {

    }
    public User(String username, String pwd) {
        this.username = username;
        this.pwd = pwd;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPwd() {
        return pwd;
    }

    public void setPwd(String pwd) {
        this.pwd = pwd;
    }

    @Override
    public String toString() {
        return "User{" +
                "username='" + username + '\'' +
                ", pwd='" + pwd + '\'' +
                '}';
    }
}

5,编写登录接口和业务逻辑

package com.gxhh.redis_session.web;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.gxhh.redis_session.bean.User;
import com.gxhh.redis_session.utils.CookieUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
/**
 * @Program: redis_session
 * @ClassName LoginController
 * @Author: liutao
 * @Description: 用户登录和测试接口
 * @Create: 2022-07-09 19:50
 * @Version 1.0
 **/
@RestController
public class LoginController {
    @Autowired
    RedisTemplate redisTemplate;
   
    @Value("${server.port}")
    String port;
    /**
     * 登录接口
     * @param user User对象
     * @return 提示信息
     * @throws JsonProcessingException
     */
    @PostMapping(value = "/doLogin", produces = "text/html;charset=utf-8")
    public String login(HttpServletRequest request, HttpServletResponse response, User user) throws JsonProcessingException {
        System.out.println(user);
        ValueOperations ops = redisTemplate.opsForValue();
        String s = request.getSession().getId();
            if (redisTemplate.hasKey(s)) {//登录过
                    return "重复登录";
            } else {//未登录
                if ("sso".equals(user.getUsername()) && "123456".equals(user.getPwd())) {
                    ObjectMapper om = new ObjectMapper();
                    ops.set(s, om.writeValueAsString(user));//将凭证存入Redis
                    redisTemplate.expire(s, 30, TimeUnit.MINUTES);//设置过期时间,30分钟
                    return "登录成功";
                }else {
                    return "登录失败!";
                }
            }
    }

    /**
     * 退出接口
     * @return
     * @throws JsonProcessingException
     */
    @RequestMapping (value = "/logout", produces = "text/html;charset=utf-8")
    public String logout(HttpServletRequest request, HttpServletResponse response, User user) throws JsonProcessingException {
            System.out.println(user);
            if(redisTemplate.delete(request.getSession().getId())){
                request.getSession().invalidate();
                return "成功退出,请登录!";
            }else {
                return "系统异常!";
            }
    }
    /**
     * 测试接口
     * @param
     * @return
     */
    @GetMapping("/hello")
    public String hello(){
        return "hello 我是端口"+port;
    }
}

6,配置WebMVC拦截器,拦截所有请求,只放行登录接口

package com.gxhh.redis_session.config;

import com.gxhh.redis_session.interceptor.LoginInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
 * @Program: redis_session
 * @ClassName WebMVCConfig
 * @Author: liutao
 * @Description: WebMVC拦截器
 * @Create: 2022-07-09 19:50
 * @Version 1.0
 **/
@Configuration
public class WebMVCConfig  implements WebMvcConfigurer  {
    @Autowired
    LoginInterceptor loginInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(loginInterceptor)
                .addPathPatterns("/**")//需要拦截的路径
                .excludePathPatterns("/doLogin","/login.html") ;//排除/doLogin路径

    }


}

7,配置请求拦截器

package com.gxhh.redis_session.interceptor;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.gxhh.redis_session.utils.CookieUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.PrintWriter;
import java.util.concurrent.TimeUnit;

/**
 * @Program: redis_session
 * @ClassName LoginInterceptor
 * @Author: liutao
 * @Description: 用户登录拦截器,校验session,身份验证
 * @Create: 2022-07-09 19:50
 * @Version 1.0
 **/
@Component
public class LoginInterceptor implements HandlerInterceptor {
    @Autowired
    RedisTemplate redisTemplate;
    
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("拦截到请求:"+request.getRequestURI());
        System.out.println("当前令牌:"+request.getSession().getId());
        String s = request.getSession().getId();
        System.out.println("登录状态:"+redisTemplate.hasKey(s));
        if (redisTemplate.hasKey(s)) {//延长登录状态
            redisTemplate.expire(s, 30, TimeUnit.MINUTES);//设置过期时间,30分钟
            return true;
        }else {//身份过期
            response.setContentType("text/html;charset=utf-8");
            PrintWriter out = response.getWriter();
            out.write("身份过期,非法请求");
            return false;
        }
    }
}

8,nginx分发轮询配置

#user  nobody;
worker_processes  1;

#error_log  logs/error.log;
#error_log  logs/error.log  notice;
#error_log  logs/error.log  info;

#pid        logs/nginx.pid;


events {
    worker_connections  1024;
}


http {
    include       mime.types;
    default_type  application/octet-stream;

    #log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
    #                  '$status $body_bytes_sent "$http_referer" '
    #                  '"$http_user_agent" "$http_x_forwarded_for"';

    #access_log  logs/access.log  main;

    sendfile        on;
    #tcp_nopush     on;

    #keepalive_timeout  0;
    keepalive_timeout  65;

    #gzip  on;
    upstream mysvr{
        server localhost:8081;
        server localhost:8082;

    }
    server {
        listen       8052;
        server_name  localhost;

        #charset koi8-r;

        #access_log  logs/host.access.log  main;
        #  location / {
        #     root   html;
        #     index  index.html index.htm;
        #     proxy_pass http://localhost:8011;
        # }

        location / {
            # root   html;
            # index  index.html index.htm;
            proxy_pass http://mysvr;
        }

        #error_page  404              /404.html;

        # redirect server error pages to the static page /50x.html
        #
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }

        # proxy the PHP scripts to Apache listening on 127.0.0.1:80
        #
        #location ~ \.php$ {
        #    proxy_pass   http://127.0.0.1;
        #}

        # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
        #
        #location ~ \.php$ {
        #    root           html;
        #    fastcgi_pass   127.0.0.1:9000;
        #    fastcgi_index  index.php;
        #    fastcgi_param  SCRIPT_FILENAME  /scripts$fastcgi_script_name;
        #    include        fastcgi_params;
        #}

        # deny access to .htaccess files, if Apache's document root
        # concurs with nginx's one
        #
        #location ~ /\.ht {
        #    deny  all;
        #}
    }

看效果

先访问测试接口

然后再登录

访问测试接口

关闭浏览器后访问

相关推荐

SpringBoot集成redis+cookie实现分布式单点登录
本文最后更新于2023.08.30-08:11,某些文章具有时效性,若有错误或已失效,请在下方留言或联系涛哥。前言 因为涛哥刚进入公司,易哥在开发一个系统的迭代版本……

结尾

到这里结束了,涛哥也是第一次接触分布式。欢迎阅读访问,评论,点赞三连。

阅读剩余
THE END