标题:基于 Redis 的 SSO 单点登录实现原理与代码示例
一、引言
在当今的互联网应用中,用户常常需要在多个不同的系统或网站上进行登录,单点登录(Single Sign-On,SSO)技术的出现解决了用户在多个系统之间重复登录的繁琐问题,提高了用户体验和安全性,Redis 作为一种高性能的内存数据库,在 SSO 单点登录实现中发挥着重要作用,本文将详细介绍基于 Redis 的 SSO 单点登录原理,并提供相应的代码示例。
二、SSO 单点登录原理
SSO 的基本思想是用户在首次登录时,系统会生成一个唯一的会话标识(Session ID),并将其存储在服务器端,随后,用户在访问其他需要登录的系统时,只需携带这个会话标识,系统就可以验证用户的身份,无需再次进行登录操作。
在基于 Redis 的 SSO 单点登录实现中,我们可以利用 Redis 的存储和分布式特性来管理会话标识,当用户登录成功后,系统将会话标识存储在 Redis 中,并设置一个过期时间,当用户访问其他系统时,系统会从 Redis 中获取会话标识,并验证其是否有效,如果会话标识有效,则用户无需再次登录;如果会话标识无效,则系统会要求用户重新登录。
三、Redis 简介
Redis 是一种开源的内存数据库,它具有以下特点:
1、高性能:Redis 基于内存存储数据,因此具有极高的读写性能。
2、数据结构丰富:Redis 支持多种数据结构,如字符串、哈希表、列表、集合、有序集合等,可以满足不同的业务需求。
3、分布式支持:Redis 可以通过集群的方式进行分布式部署,提高系统的可用性和扩展性。
4、持久化:Redis 支持 RDB 和 AOF 两种持久化方式,可以保证数据的安全性。
四、基于 Redis 的 SSO 单点登录实现步骤
1、创建用户表和会话表
我们需要创建用户表和会话表,用于存储用户信息和会话标识,用户表的结构如下:
CREATE TABLE users ( id INT AUTO_INCREMENT PRIMARY KEY, username VARCHAR(50) NOT NULL, password VARCHAR(255) NOT NULL );
会话表的结构如下:
CREATE TABLE sessions ( id INT AUTO_INCREMENT PRIMARY KEY, user_id INT NOT NULL, session_id VARCHAR(50) NOT NULL, expiration_time INT NOT NULL );
2、生成会话标识
当用户登录成功后,系统需要生成一个唯一的会话标识,我们可以使用 UUID 算法来生成会话标识,示例代码如下:
import java.util.UUID; public class SessionUtil { public static String generateSessionId() { return UUID.randomUUID().toString(); } }
3、存储会话标识
生成会话标识后,系统需要将其存储在 Redis 中,我们可以使用 Jedis 客户端来操作 Redis,示例代码如下:
import redis.clients.jedis.Jedis; public class SessionManager { private static final String REDIS_HOST = "localhost"; private static final int REDIS_PORT = 6379; private static final String REDIS_PASSWORD = ""; public static void storeSession(String sessionId, int userId, int expirationSeconds) { Jedis jedis = new Jedis(REDIS_HOST, REDIS_PORT); if (!REDIS_PASSWORD.isEmpty()) { jedis.auth(REDIS_PASSWORD); } try { jedis.setex(sessionId, expirationSeconds, String.valueOf(userId)); } finally { jedis.close(); } } }
4、验证会话标识
当用户访问其他系统时,系统需要从 Redis 中获取会话标识,并验证其是否有效,示例代码如下:
import redis.clients.jedis.Jedis; public class SessionValidator { private static final String REDIS_HOST = "localhost"; private static final int REDIS_PORT = 6379; private static final String REDIS_PASSWORD = ""; public static boolean validateSession(String sessionId) { Jedis jedis = new Jedis(REDIS_HOST, REDIS_PORT); if (!REDIS_PASSWORD.isEmpty()) { jedis.auth(REDIS_PASSWORD); } try { String userIdStr = jedis.get(sessionId); if (userIdStr == null) { return false; } int userId = Integer.parseInt(userIdStr); // 可以根据实际情况验证用户是否仍然有效 return true; } finally { jedis.close(); } } }
5、单点登录流程
基于 Redis 的 SSO 单点登录流程如下:
1、用户访问系统 A,并输入用户名和密码进行登录。
2、系统 A 验证用户信息,如果验证成功,则生成一个会话标识,并将其存储在 Redis 中。
3、系统 A 将会话标识返回给用户,并设置一个 Cookie,将会话标识存储在用户的浏览器中。
4、用户点击系统 B 的链接,访问系统 B。
5、系统 B 检测到用户携带了会话标识,从 Cookie 中获取会话标识,并调用 SessionValidator 类的 validateSession 方法验证会话标识是否有效。
6、如果会话标识有效,则系统 B 认为用户已经登录,并允许用户访问系统 B 的资源;如果会话标识无效,则系统 B 要求用户重新登录。
五、代码示例
以下是一个基于 Spring Boot 的 SSO 单点登录示例代码,其中包括了用户表和会话表的创建、会话标识的生成和存储、会话标识的验证等功能。
import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.Bean; import org.springframework.data.jpa.repository.config.EnableJpaRepositories; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import redis.clients.jedis.Jedis; @SpringBootApplication @EnableJpaRepositories public class SsoApplication { public static void main(String[] args) { SpringApplication.run(SsoApplication.class, args); } @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } @Bean public Jedis jedis() { return new Jedis("localhost", 6379); } }
import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; @Entity public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String username; private String password; // 省略 getter 和 setter 方法 }
import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.JoinColumn; import javax.persistence.ManyToOne; @Entity public class Session { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @ManyToOne @JoinColumn(name = "user_id") private User user; private String sessionId; private int expirationSeconds; // 省略 getter 和 setter 方法 }
import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Service; import java.util.Optional; @Service public class UserDetailsServiceImpl implements UserDetailsService { @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { // 这里可以根据实际情况从数据库中查询用户信息 Optional<User> userOptional = UserRepository.findById(1L); if (user
评论列表