黑狐家游戏

前后端分离用户登录,前后端分离单点登录原理

欧气 5 0

前后端分离单点登录原理

一、引言

随着互联网技术的不断发展,前后端分离架构已经成为了主流,在这种架构下,前端和后端分别负责不同的功能,前端负责用户界面的展示,后端负责数据的处理和业务逻辑的实现,单点登录(Single Sign-On,SSO)是一种身份验证机制,它允许用户在一次登录后访问多个系统,而无需再次输入用户名和密码,本文将介绍前后端分离单点登录的原理,并通过一个具体的案例来实现前后端分离单点登录。

二、前后端分离单点登录原理

前后端分离单点登录的原理主要包括以下几个方面:

1、用户认证:用户在前端输入用户名和密码,前端将用户信息发送到后端进行认证,后端验证用户信息的正确性,并生成一个认证令牌(Token)。

2、令牌生成:后端生成的认证令牌包含了用户的身份信息和过期时间等信息,令牌可以是一个字符串、一个数字或者一个 JSON 对象等。

3、令牌存储:后端将生成的认证令牌存储到数据库、缓存或者本地存储等地方,令牌的存储方式可以根据实际情况进行选择。

4、令牌返回:后端将生成的认证令牌返回给前端,前端将令牌存储到本地存储或者 Cookie 中,以便在后续的请求中携带令牌。

5、请求拦截:前端在发送请求到后端之前,会先检查本地存储或者 Cookie 中是否存在认证令牌,如果存在令牌,则将令牌携带在请求头中发送到后端。

6、令牌验证:后端在接收到请求后,会先检查请求头中是否携带了认证令牌,如果存在令牌,则验证令牌的有效性,如果令牌有效,则允许用户访问系统;如果令牌无效,则返回错误信息。

7、令牌刷新:后端在令牌过期之前,会自动刷新令牌,刷新后的令牌会覆盖原来的令牌,以保证用户的登录状态始终有效。

三、前后端分离单点登录案例实现

为了更好地理解前后端分离单点登录的原理,下面通过一个具体的案例来实现前后端分离单点登录。

1、后端实现

(1)创建数据库表

CREATE TABLE users (
    id INT AUTO_INCREMENT PRIMARY KEY,
    username VARCHAR(50) NOT NULL,
    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)创建用户实体类

@Entity
@Table(name = "users")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    @Column(name = "username", nullable = false, unique = true)
    private String username;
    @Column(name = "password", nullable = false)
    private String password;
    // 省略 getter 和 setter 方法
}

(3)创建令牌实体类

@Entity
@Table(name = "tokens")
public class Token {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    @Column(name = "user_id", nullable = false)
    private Long userId;
    @Column(name = "token", nullable = false, unique = true)
    private String token;
    @Column(name = "expires_at", nullable = false)
    private LocalDateTime expiresAt;
    // 省略 getter 和 setter 方法
}

(4)创建用户服务接口

public interface UserService {
    User findByUsername(String username);
    void save(User user);
}

(5)创建用户服务实现类

@Service
public class UserServiceImpl implements UserService {
    @Autowired
    private UserRepository userRepository;
    @Override
    public User findByUsername(String username) {
        return userRepository.findByUsername(username);
    }
    @Override
    public void save(User user) {
        userRepository.save(user);
    }
}

(6)创建令牌服务接口

public interface TokenService {
    Token generateToken(User user);
    boolean validateToken(String token);
    void refreshToken(String token);
}

(7)创建令牌服务实现类

@Service
public class TokenServiceImpl implements TokenService {
    @Autowired
    private UserService userService;
    @Autowired
    private TokenRepository tokenRepository;
    @Override
    public Token generateToken(User user) {
        String token = UUID.randomUUID().toString();
        LocalDateTime expiresAt = LocalDateTime.now().plusHours(1);
        Token tokenEntity = new Token();
        tokenEntity.setUserId(user.getId());
        tokenEntity.setToken(token);
        tokenEntity.setExpiresAt(expiresAt);
        tokenRepository.save(tokenEntity);
        return tokenEntity;
    }
    @Override
    public boolean validateToken(String token) {
        Token tokenEntity = tokenRepository.findByToken(token);
        if (tokenEntity == null || tokenEntity.getExpiresAt().isBefore(LocalDateTime.now())) {
            return false;
        }
        return true;
    }
    @Override
    public void refreshToken(String token) {
        Token tokenEntity = tokenRepository.findByToken(token);
        if (tokenEntity!= null) {
            LocalDateTime expiresAt = LocalDateTime.now().plusHours(1);
            tokenEntity.setExpiresAt(expiresAt);
            tokenRepository.save(tokenEntity);
        }
    }
}

(8)创建用户控制器

@RestController
@RequestMapping("/api/users")
public class UserController {
    @Autowired
    private UserService userService;
    @Autowired
    private TokenService tokenService;
    @PostMapping("/login")
    public String login(@RequestBody User user) {
        User existingUser = userService.findByUsername(user.getUsername());
        if (existingUser == null ||!existingUser.getPassword().equals(user.getPassword())) {
            return "Invalid username or password";
        }
        Token token = tokenService.generateToken(existingUser);
        return token.getToken();
    }
    @GetMapping("/logout")
    public void logout(@RequestHeader("Authorization") String token) {
        tokenService.refreshToken(token);
    }
}

(9)创建令牌控制器

@RestController
@RequestMapping("/api/tokens")
public class TokenController {
    @Autowired
    private TokenService tokenService;
    @GetMapping("/validate")
    public boolean validate(@RequestHeader("Authorization") String token) {
        return tokenService.validateToken(token);
    }
}

(10)创建数据库连接池

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
    <groupId>com.zaxxer</groupId>
    <artifactId>HikariCP</artifactId>
    <version>3.4.5</version>
</dependency>

(11)创建配置文件

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/sso?useSSL=false&serverTimezone=UTC
    username: root
    password: root
    driver-class-name: com.mysql.cj.jdbc.Driver
  security:
    user:
      name: user
      password: password

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>登录</title>
</head>
<body>
    <form action="/api/users/login" method="post">
        <label for="username">用户名:</label><input type="text" id="username" name="username"><br>
        <label for="password">密码:</label><input type="password" id="password" name="password"><br>
        <input type="submit" value="登录">
    </form>
</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>首页</title>
</head>
<body>
    <h1>欢迎来到首页!</h1>
    <a href="/logout">退出</a>
</body>
</html>

(3)创建请求拦截器

axios.interceptors.request.use(
    function(config) {
        // 在发送请求之前,从本地存储中获取令牌
        var token = localStorage.getItem('token');
        if (token) {
            // 如果存在令牌,则将令牌添加到请求头中
            config.headers.Authorization = 'Bearer'+ token;
        }
        return config;
    },
    function(error) {
        // 对请求错误进行处理
        return Promise.reject(error);
    }
);

(4)创建登录逻辑

document.getElementById('loginForm').addEventListener('submit', function(event) {
    event.preventDefault();
    var username = document.getElementById('username').value;
    var password = document.getElementById('password').value;
    axios.post('/api/users/login', {
            username: username,
            password: password
        })
     .then(function(response) {
            // 如果登录成功,则将令牌存储到本地存储中
            localStorage.setItem('token', response.data);
            // 跳转到首页
            window.location.href = '/';
        })
     .catch(function(error) {
            // 如果登录失败,则显示错误信息
            document.getElementById('loginError').innerHTML = error.response.data;
        });
});

(5)创建退出逻辑

document.getElementById('logout').addEventListener('click', function() {
    axios.get('/api/tokens/validate', {
            headers: {
                Authorization: 'Bearer'+ localStorage.getItem('token')
            }
        })
     .then(function(response) {
            // 如果令牌有效,则调用退出接口
            if (response.data) {
                axios.get('/api/tokens/logout', {
                        headers: {
                            Authorization: 'Bearer'+ localStorage.getItem('token')
                        }
                    })
                 .then(function(response) {
                        // 如果退出成功,则清除令牌并跳转到登录页面
                        localStorage.removeItem('token');
                        window.location.href = '/login';
                    })
                 .catch(function(error) {
                        // 如果退出失败,则显示错误信息
                        document.getElementById('logoutError').innerHTML = error.response.data;
                    });
            }
        })
     .catch(function(error) {
            // 如果令牌无效,则直接跳转到登录页面
            localStorage.removeItem('token');
            window.location.href = '/login';
        });
});

四、总结

本文介绍了前后端分离单点登录的原理,并通过一个具体的案例实现了前后端分离单点登录,通过前后端分离单点登录,可以提高系统的安全性和用户体验,减少用户的操作步骤,提高系统的性能和可扩展性。

标签: #前后端分离 #用户登录 #单点登录 #原理

黑狐家游戏
  • 评论列表

留言评论