前后端分离单点登录(SSO)实战指南
本文详细介绍了前后端分离架构下单点登录(SSO)的实现原理和技术选型,通过一个完整的前后端分离 SSO 示例,展示了如何在前后端分离的环境中实现用户身份验证和授权的一致性,提高系统的安全性和用户体验。
一、引言
随着互联网技术的发展,前后端分离架构已经成为主流,在前后端分离架构下,前端和后端分别负责不同的职责,前端负责页面展示和用户交互,后端负责数据处理和业务逻辑,这种架构的优点是提高了开发效率和可维护性,但是也带来了单点登录的问题。
单点登录(SSO)是指用户只需登录一次,就可以访问多个系统或应用程序,在前后端分离架构下,由于前端和后端分别运行在不同的服务器上,用户登录信息需要在前后端进行传递和验证,这就增加了单点登录的难度。
本文将介绍前后端分离架构下单点登录的实现原理和技术选型,并通过一个完整的前后端分离 SSO 示例,展示如何在前后端分离的环境中实现用户身份验证和授权的一致性,提高系统的安全性和用户体验。
二、前后端分离架构下单点登录的实现原理
前后端分离架构下,单点登录的实现原理主要包括以下几个步骤:
1、用户登录:用户在前端页面输入用户名和密码,点击登录按钮,前端将用户输入的用户名和密码发送到后端进行验证。
2、后端验证用户身份:后端接收到前端发送的用户名和密码,进行验证,如果验证成功,后端生成一个令牌(Token),并将令牌返回给前端。
3、前端存储令牌:前端接收到后端返回的令牌,将令牌存储在本地存储或 sessionStorage 中。
4、前端携带令牌访问后端接口:前端在访问后端接口时,将令牌携带在请求头中,后端接收到请求后,验证令牌的有效性,如果令牌有效,后端允许访问接口;如果令牌无效,后端返回 401 错误。
5、后端验证令牌有效性:后端接收到前端携带的令牌,进行验证,如果令牌有效,后端允许访问接口;如果令牌无效,后端返回 401 错误。
6、用户注销:用户在前端点击注销按钮,前端将令牌发送到后端进行注销,后端接收到令牌后,将令牌删除,并返回注销成功的响应。
三、前后端分离架构下单点登录的技术选型
前后端分离架构下,单点登录的技术选型主要包括以下几个方面:
1、令牌生成和验证:令牌是单点登录的核心,令牌的生成和验证需要保证安全性和高效性,常用的令牌生成和验证技术包括 JWT(JSON Web Token)、OAuth 2.0 等。
2、前端存储令牌:前端需要将令牌存储在本地存储或 sessionStorage 中,以便在后续的请求中携带令牌,常用的前端存储技术包括 localStorage、sessionStorage 等。
3、后端验证令牌有效性:后端需要验证令牌的有效性,以确保用户的身份和权限,常用的后端验证技术包括 JWT 验证、数据库查询等。
4、用户注销:用户注销时,需要将令牌发送到后端进行注销,常用的用户注销技术包括后端删除令牌、前端清空令牌等。
四、前后端分离架构下单点登录的示例实现
为了更好地理解前后端分离架构下单点登录的实现原理和技术选型,下面我们通过一个完整的前后端分离 SSO 示例,展示如何在前后端分离的环境中实现用户身份验证和授权的一致性,提高系统的安全性和用户体验。
1、后端实现
(1)创建数据库表
CREATE TABLE users ( id INT AUTO_INCREMENT PRIMARY KEY, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(255) NOT NULL ); CREATE TABLE tokens ( id INT AUTO_INCREMENT PRIMARY KEY, user_id INT NOT NULL, token VARCHAR(255) NOT NULL, expires_at TIMESTAMP NOT NULL );
(2)创建用户服务
@Service public class UserService { @Autowired private UserRepository userRepository; public User findUserByUsername(String username) { return userRepository.findByUsername(username); } public User saveUser(User user) { return userRepository.save(user); } }
(3)创建令牌服务
@Service public class TokenService { @Autowired private UserService userService; public String generateToken(User user) { // 生成令牌 String token = UUID.randomUUID().toString(); // 设置令牌过期时间 Date expiresAt = new Date(System.currentTimeMillis() + 60 * 60 * 1000); // 保存令牌 Token tokenEntity = new Token(); tokenEntity.setUserId(user.getId()); tokenEntity.setToken(token); tokenEntity.setExpiresAt(expiresAt); tokenRepository.save(tokenEntity); return token; } public boolean validateToken(String token) { // 查询令牌 Token tokenEntity = tokenRepository.findByToken(token); if (tokenEntity == null) { return false; } // 判断令牌是否过期 if (tokenEntity.getExpiresAt().before(new Date())) { return false; } return true; } public void invalidateToken(String token) { // 删除令牌 tokenRepository.deleteByToken(token); } }
(4)创建用户控制器
@RestController @RequestMapping("/api/users") public class UserController { @Autowired private UserService userService; @Autowired private TokenService tokenService; @PostMapping("/login") public ResponseEntity<String> login(@RequestBody User user) { // 验证用户 User existingUser = userService.findUserByUsername(user.getUsername()); if (existingUser == null ||!existingUser.getPassword().equals(user.getPassword())) { return ResponseEntity.badRequest().body("Invalid username or password"); } // 生成令牌 String token = tokenService.generateToken(existingUser); return ResponseEntity.ok(token); } @GetMapping("/logout") public ResponseEntity<Void> logout(@RequestHeader("Authorization") String authorizationHeader) { // 验证令牌 String token = authorizationHeader.substring("Bearer ".length()); if (!tokenService.validateToken(token)) { return ResponseEntity.badRequest().body(null); } // 注销令牌 tokenService.invalidateToken(token); return ResponseEntity.ok().build(); } }
2、前端实现
(1)创建登录页面
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Login</title> </head> <body> <h2>Login</h2> <form id="loginForm"> <label for="username">Username:</label> <input type="text" id="username" /> <label for="password">Password:</label> <input type="password" id="password" /> <button type="submit">Login</button> </form> <script src="https://unpkg.com/axios/dist/axios.min.js"></script> <script> document.getElementById('loginForm').addEventListener('submit', function (e) { e.preventDefault(); // 获取用户名和密码 var username = document.getElementById('username').value; var password = document.getElementById('password').value; // 发送登录请求 axios.post('/api/users/login', { username: username, password: password }) .then(response => { // 保存令牌 localStorage.setItem('token', response.data); // 跳转到首页 window.location.href = '/'; }) .catch(error => { // 显示错误信息 document.getElementById('loginError').innerHTML = error.response.data; }); }); </script> </body> </html>
(2)创建首页
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Home</title> </head> <body> <h2>Home</h2> <button id="logoutButton">Logout</button> <script src="https://unpkg.com/axios/dist/axios.min.js"></script> <script> // 验证令牌 axios.get('/api/users/logout', { headers: { Authorization: 'Bearer'+ localStorage.getItem('token') } }) .then(response => { // 跳转到登录页面 window.location.href = '/login'; }) .catch(error => { // 显示错误信息 document.getElementById('logoutError').innerHTML = error.response.data; }); </script> </body> </html>
3、测试
(1)启动后端服务
mvn spring-boot:run
(2)启动前端服务
npm install npm start
(3)访问登录页面
http://localhost:3000/login
(4)输入用户名和密码,点击登录按钮
(5)登录成功后,跳转到首页
(6)点击注销按钮,跳转到登录页面
五、总结
本文详细介绍了前后端分离架构下单点登录(SSO)的实现原理和技术选型,并通过一个完整的前后端分离 SSO 示例,展示了如何在前后端分离的环境中实现用户身份验证和授权的一致性,提高系统的安全性和用户体验,希望本文能够对你有所帮助。
评论列表