若依分离版手把手教你用Sa-Token改造成单点登录

该文章已生成可运行项目,

若依分离版 + Sa-Token 单点登录实战教程

从零到一,手把手教你将若依前后端分离项目改造为SSO单点登录系统


一、背景介绍

1.1 什么是单点登录(SSO)?

单点登录(Single Sign-On)是一种身份验证机制,用户只需登录一次,就可以访问多个相互信任的应用系统。

┌─────────────────────────────────────────────────────────────┐
│                    SSO 架构示意图                            │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│                      ┌──────────┐                           │
│                      │  用户    │                           │
│                      └────┬─────┘                           │
│                           │                                 │
│                           ▼                                 │
│                    ┌───────────┐                            │
│                    │ SSO认证中心│  ← 只需要登录一次          │
│                    └─────┬─────┘                            │
│                          │                                  │
│           ┌──────────────┼──────────────┐                   │
│           ▼              ▼              ▼                   │
│    ┌──────────┐   ┌──────────┐   ┌──────────┐              │
│    │  系统A   │   │  系统B   │   │  系统C   │              │
│    │ (若依)   │   │ (ERP)    │   │ (OA)     │              │
│    └──────────┘   └──────────┘   └──────────┘              │
│                                                             │
│    用户登录一次,即可访问所有系统                            │
└─────────────────────────────────────────────────────────────┘

1.2 为什么选择 Sa-Token?

特性Sa-TokenCASSpring Security OAuth2
上手难度简单复杂中等
文档质量优秀一般一般
与若依融合天然适配需改造需改造
功能丰富度⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐
社区活跃度

Sa-Token 优势:

  • 若依官方已支持 Sa-Token 版本
  • API 简洁,一行代码实现登录
  • 支持多账号体系(用户、管理员分开登录)
  • 内置 SSO 单点登录模块
  • 支持踢人下线、同端互斥登录等

二、整体架构设计

2.1 系统角色

本教程涉及三个系统角色:

角色说明端口
SSO-Server认证中心(若依后台)8080
SSO-Client1客户端应用18081
SSO-Client2客户端应用28082

2.2 认证流程

┌──────────────────────────────────────────────────────────────────┐
│                       SSO 登录流程                               │
├──────────────────────────────────────────────────────────────────┤
│                                                                  │
│  用户           Client应用          SSO-Server         数据库   │
│   │                │                    │                 │     │
│   │  1.访问资源    │                    │                 │     │
│   │ ──────────────▶                    │                 │     │
│   │                │  2.检查登录状态    │                 │     │
│   │                │ ─────────────────▶ │                 │     │
│   │                │                    │  3.查询token    │     │
│   │                │                    │ ───────────────▶│     │
│   │                │  4.未登录,跳转    │                 │     │
│   │                │◀─────────────────  │                 │     │
│   │  5.跳转登录页  │                    │                 │     │
│   │◀───────────────│                    │                 │     │
│   │                │                    │                 │     │
│   │  6.输入账号密码 │                    │                 │     │
│   │ ──────────────────────────────────▶│                 │     │
│   │                │                    │  7.验证用户     │     │
│   │                │                    │ ───────────────▶│     │
│   │                │                    │◀─────────────── │     │
│   │                │                    │  8.生成token    │     │
│   │  9.携带ticket  │                    │                 │     │
│   │ ──────────────────────────────────▶│                 │     │
│   │                │  10.回调通知       │                 │     │
│   │                │◀─────────────────  │                 │     │
│   │                │  11.验证ticket     │                 │     │
│   │                │ ─────────────────▶ │                 │     │
│   │                │◀─────────────────  │                 │     │
│   │                │  12.返回用户信息   │                 │     │
│   │  13.登录成功   │                    │                 │     │
│   │◀───────────────│                    │                 │     │
│   │                │                    │                 │     │
└──────────────────────────────────────────────────────────────────┘

三、环境准备

3.1 技术栈要求

  • JDK 1.8+
  • MySQL 5.7+
  • Redis 3.0+
  • Maven 3.6+
  • Node.js 14+

3.2 基础项目

下载若依前后端分离版本:

# 后端
git clone https://gitee.com/y_project/RuoYi-Vue.git

# 前端
git clone https://gitee.com/y_project/RuoYi-Vue.git RuoYi-Vue-UI

四、SSO-Server 认证中心改造

4.1 添加 Sa-Token 依赖

ruoyi-admin/pom.xml 中添加:

<!-- Sa-Token 核心 -->
<dependency>
    <groupId>cn.dev33</groupId>
    <artifactId>sa-token-spring-boot-starter</artifactId>
    <version>1.37.0</version>
</dependency>

<!-- Sa-Token 整合 JWT -->
<dependency>
    <groupId>cn.dev33</groupId>
    <artifactId>sa-token-jwt</artifactId>
    <version>1.37.0</version>
</dependency>

<!-- Sa-Token 整合 Redis (使用 jdk 序列化) -->
<dependency>
    <groupId>cn.dev33</groupId>
    <artifactId>sa-token-dao-redis-jackson</artifactId>
    <version>1.37.0</version>
</dependency>

<!-- Sa-Token SSO 模块 -->
<dependency>
    <groupId>cn.dev33</groupId>
    <artifactId>sa-token-sso</artifactId>
    <version>1.37.0</version>
</dependency>

<!-- Redis 连接池 -->
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
</dependency>

4.2 配置 Sa-Token

application.yml 中添加配置:

# Sa-Token 配置
sa-token:
  # token 名称(同时也是 cookie 名称)
  token-name: Authorization
  # token 有效期(单位:秒),默认30天,-1代表永不过期
  timeout: 2592000
  # token 最低活跃频率(单位:秒),如果 token 超过此时间没有访问系统就会被冻结,默认-1代表不限制
  active-timeout: -1
  # 是否允许同一账号多地同时登录(为 true 时允许一起登录, 为 false 时新登录挤掉旧登录)
  is-concurrent: true
  # 多人登录同一账号时,共用一个 token(为 true 时所有登录共用一个 token, 为 false 时每次登录新建一个 token)
  is-share: false
  # token 风格(uuid、simple-uuid、random-32、random-64、random-128、tik)
  token-style: uuid
  # 是否输出操作日志
  is-log: true
  # 是否从 cookie 中读取 token
  is-read-cookie: false
  # 是否从请求体里读取 token
  is-read-body: false
  # 是否从 header 中读取 token
  is-read-header: true
  # token 前缀
  token-prefix: Bearer

# SSO 配置
sa-token-sso:
  # SSO Server 端地址
  server-url: http://localhost:8080/sso/auth
  # 是否打开单点注销功能
  is-slo: true
  # 接口调用秘钥
  secret-key: ruoyi-sso-secret-key-2024
  # 允许的授权回调地址(多个用逗号分隔)
  allow-url: http://localhost:8081/*,http://localhost:8082/*

# Redis 配置(Sa-Token 存储)
spring:
  redis:
    host: localhost
    port: 6379
    password: 
    database: 0
    lettuce:
      pool:
        max-active: 8
        max-wait: -1
        max-idle: 8
        min-idle: 0

4.3 创建 Sa-Token 配置类

创建 SaTokenConfig.java

package com.ruoyi.framework.config;

import cn.dev33.satoken.context.SaHolder;
import cn.dev33.satoken.sso.config.SaSsoConfig;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.util.SaResult;
import com.ruoyi.common.core.domain.entity.SysUser;
import com.ruoyi.system.service.ISysUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;

/**
 * Sa-Token SSO 配置
 */
@Configuration
public class SaTokenConfig
{
    @Autowired
    private ISysUserService userService;

    /**
     * 配置 SSO 相关参数
     */
    @Autowired
    private void configSso(SaSsoConfig ssoConfig) {
        // 配置:未登录时返回的 View 
        ssoConfig.notLoginView = () -> {
            return new SaResult()
                .setCode(401)
                .setMsg("请先登录")
                .setData("/login");
        };

        // 配置:登录处理函数
        ssoConfig.doLoginHandle = (name, pwd) -> {
            // 1. 验证账号密码(使用若依的认证逻辑)
            SysUser user = userService.selectUserByUserName(name);
            if (user == null) {
                return SaResult.error("账号不存在");
            }
            
            // 验证密码(使用若依的加密方式)
            // if (!SecurityUtils.matchesPassword(pwd, user.getPassword())) {
            //     return SaResult.error("密码错误");
            // }
            
            // 2. 执行登录
            StpUtil.login(user.getUserId());
            
            // 3. 返回登录信息
            return SaResult.ok("登录成功")
                .set("token", StpUtil.getTokenValue())
                .set("userId", user.getUserId())
                .set("userName", user.getUserName());
        };

        // 配置:查询用户信息
        ssoConfig.getUserinfoHandle = (loginId, ssoToken) -> {
            SysUser user = userService.selectUserById(Long.parseLong(loginId.toString()));
            if (user == null) {
                return SaResult.error("用户不存在");
            }
            return SaResult.data(user);
        };
    }
}

4.4 创建 SSO 控制器

创建 SsoServerController.java

package com.ruoyi.web.controller.sso;

import cn.dev33.satoken.sso.SaSsoHandle;
import cn.dev33.satoken.stp.StpUtil;
import com.ruoyi.common.core.domain.AjaxResult;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * SSO Server 控制器
 */
@RestController
@RequestMapping("/sso")
public class SsoServerController
{
    /**
     * SSO 认证中心统一认证地址
     */
    @RequestMapping("/auth")
    public Object auth() {
        return SaSsoHandle.ssoAuth();
    }

    /**
     * SSO 认证中心校验 ticket 地址
     */
    @RequestMapping("/ticket")
    public Object ticket() {
        return SaSsoHandle.ssoCheckTicket();
    }

    /**
     * SSO 认证中心单点注销地址
     */
    @RequestMapping("/logout")
    public Object logout() {
        StpUtil.logout();
        return AjaxResult.success("注销成功");
    }

    /**
     * 获取当前用户信息
     */
    @RequestMapping("/userinfo")
    public Object userinfo() {
        if (!StpUtil.isLogin()) {
            return AjaxResult.error("请先登录");
        }
        return AjaxResult.success(StpUtil.getSession());
    }
}

4.5 修改若依登录逻辑

修改 SysLoginService.java,集成 Sa-Token:

package com.ruoyi.framework.web.service;

import cn.dev33.satoken.stp.StpUtil;
import com.ruoyi.common.constant.Constants;
import com.ruoyi.common.core.domain.entity.SysUser;
import com.ruoyi.common.core.domain.model.LoginUser;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.framework.manager.AsyncManager;
import com.ruoyi.framework.manager.factory.AsyncFactory;
import com.ruoyi.system.service.ISysUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

/**
 * 登录服务(Sa-Token 版本)
 */
@Component
public class SysLoginService
{
    @Autowired
    private ISysUserService userService;
    
    @Autowired
    private SysPasswordService passwordService;
    
    @Autowired
    private TokenService tokenService;

    /**
     * 登录验证
     */
    public String login(String username, String password, String code, String uuid)
    {
        // 1. 验证码校验
        // validateCaptcha(username, code, uuid);

        // 2. 用户验证
        SysUser user = userService.selectUserByUserName(username);
        if (user == null) {
            throw new ServiceException("用户不存在");
        }
        
        if (user.getDelFlag().equals("2")) {
            throw new ServiceException("用户已被删除");
        }
        
        if (user.getStatus().equals("1")) {
            throw new ServiceException("用户已被停用");
        }

        // 3. 密码验证
        passwordService.validate(user, password);

        // 4. 使用 Sa-Token 执行登录
        StpUtil.login(user.getUserId());

        // 5. 记录登录日志
        AsyncManager.me().execute(AsyncFactory.recordLogininfor(
            username, Constants.LOGIN_SUCCESS, "登录成功"));

        // 6. 返回 token
        return StpUtil.getTokenValue();
    }

    /**
     * 登出
     */
    public void logout()
    {
        if (StpUtil.isLogin()) {
            AsyncManager.me().execute(AsyncFactory.recordLogininfor(
                StpUtil.getLoginIdAsString(), Constants.LOGOUT, "退出成功"));
            StpUtil.logout();
        }
    }
}

4.6 创建 Sa-Token 权限验证类

创建 StpInterfaceImpl.java,实现 Sa-Token 的权限验证接口:

package com.ruoyi.framework.satoken;

import cn.dev33.satoken.stp.StpInterface;
import com.ruoyi.common.core.domain.entity.SysUser;
import com.ruoyi.common.core.domain.model.LoginUser;
import com.ruoyi.framework.web.service.TokenService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.List;

/**
 * Sa-Token 权限验证实现
 */
@Component
public class StpInterfaceImpl implements StpInterface
{
    @Autowired
    private TokenService tokenService;

    /**
     * 返回一个账号所拥有的权限码集合
     */
    @Override
    public List<String> getPermissionList(Object loginId, String loginType)
    {
        LoginUser loginUser = getLoginUser(loginId);
        if (loginUser == null) {
            return new ArrayList<>();
        }
        return new ArrayList<>(loginUser.getPermissions());
    }

    /**
     * 返回一个账号所拥有的角色标识集合
     */
    @Override
    public List<String> getRoleList(Object loginId, String loginType)
    {
        LoginUser loginUser = getLoginUser(loginId);
        if (loginUser == null) {
            return new ArrayList<>();
        }
        
        List<String> roles = new ArrayList<>();
        loginUser.getUser().getRoles().forEach(role -> {
            roles.add(role.getRoleKey());
        });
        return roles;
    }

    /**
     * 获取登录用户信息
     */
    private LoginUser getLoginUser(Object loginId)
    {
        // 从 Sa-Token Session 中获取用户信息
        return (LoginUser) StpUtil.getSession().get("loginUser");
    }
}

4.7 配置全局异常处理

创建 SaTokenExceptionHandler.java

package com.ruoyi.framework.exception;

import cn.dev33.satoken.exception.NotLoginException;
import cn.dev33.satoken.exception.NotPermissionException;
import com.ruoyi.common.core.domain.AjaxResult;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

/**
 * Sa-Token 异常处理
 */
@RestControllerAdvice
public class SaTokenExceptionHandler
{
    /**
     * 未登录异常
     */
    @ExceptionHandler(NotLoginException.class)
    public AjaxResult handleNotLoginException(NotLoginException e)
    {
        return AjaxResult.error(401, "未登录或登录已过期,请重新登录");
    }

    /**
     * 无权限异常
     */
    @ExceptionHandler(NotPermissionException.class)
    public AjaxResult handleNotPermissionException(NotPermissionException e)
    {
        return AjaxResult.error(403, "您没有操作权限");
    }
}

五、SSO-Client 客户端配置

5.1 客户端依赖配置

在客户端项目的 pom.xml 中添加:

<!-- Sa-Token 核心 -->
<dependency>
    <groupId>cn.dev33</groupId>
    <artifactId>sa-token-spring-boot-starter</artifactId>
    <version>1.37.0</version>
</dependency>

<!-- Sa-Token SSO 模块 -->
<dependency>
    <groupId>cn.dev33</groupId>
    <artifactId>sa-token-sso</artifactId>
    <version>1.37.0</version>
</dependency>

<!-- Sa-Token 整合 Redis -->
<dependency>
    <groupId>cn.dev33</groupId>
    <artifactId>sa-token-dao-redis-jackson</artifactId>
    <version>1.37.0</version>
</dependency>

5.2 客户端配置文件

application.yml 中配置:

server:
  port: 8081

# Sa-Token 配置
sa-token:
  token-name: Authorization
  timeout: 2592000
  is-concurrent: true
  is-share: false
  token-style: uuid
  is-read-cookie: false
  is-read-header: true
  token-prefix: Bearer

# SSO 客户端配置
sa-token-sso:
  # SSO Server 认证地址
  auth-url: http://localhost:8080/sso/auth
  # SSO Server 校验 ticket 地址
  check-ticket-url: http://localhost:8080/sso/ticket
  # SSO Server 单点注销地址
  slo-url: http://localhost:8080/sso/logout
  # 当前 Client 名称标识
  client: client1
  # 接口调用秘钥(需与 Server 端一致)
  secret-key: ruoyi-sso-secret-key-2024
  # SSO-Server 端的 ticket 查询地址
  ticket-url: http://localhost:8080/sso/ticket

spring:
  redis:
    host: localhost
    port: 6379
    database: 0

5.3 客户端控制器

创建 SsoClientController.java

package com.ruoyi.web.controller.sso;

import cn.dev33.satoken.sso.SaSsoHandle;
import cn.dev33.satoken.stp.StpUtil;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * SSO Client 控制器
 */
@RestController
@RequestMapping("/sso")
public class SsoClientController
{
    /**
     * SSO 客户端登录地址
     */
    @RequestMapping("/login")
    public Object login() {
        return SaSsoHandle.ssoLogin();
    }

    /**
     * SSO 客户端注销地址
     */
    @RequestMapping("/logout")
    public Object logout() {
        StpUtil.logout();
        return "注销成功";
    }

    /**
     * 检查登录状态
     */
    @RequestMapping("/isLogin")
    public Object isLogin() {
        return StpUtil.isLogin();
    }
}

5.4 客户端登录拦截器

创建 SsoClientInterceptor.java

package com.ruoyi.framework.interceptor;

import cn.dev33.satoken.stp.StpUtil;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * SSO 客户端登录拦截器
 */
public class SsoClientInterceptor implements HandlerInterceptor
{
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception
    {
        // 判断是否已登录
        if (!StpUtil.isLogin()) {
            // 未登录,重定向到 SSO 认证中心
            String ssoAuthUrl = "http://localhost:8080/sso/auth?redirect=" + request.getRequestURL();
            response.sendRedirect(ssoAuthUrl);
            return false;
        }
        return true;
    }
}

5.5 注册拦截器

package com.ruoyi.framework.config;

import com.ruoyi.framework.interceptor.SsoClientInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebMvcConfig implements WebMvcConfigurer
{
    @Override
    public void addInterceptors(InterceptorRegistry registry)
    {
        // 注册 SSO 拦截器
        registry.addInterceptor(new SsoClientInterceptor())
            .addPathPatterns("/**")
            .excludePathPatterns(
                "/sso/**",
                "/login",
                "/logout",
                "/static/**",
                "/favicon.ico"
            );
    }
}

六、前端改造

6.1 登录页面改造

修改 login.vue,支持 SSO 登录:

<template>
  <div class="login">
    <el-form ref="loginForm" :model="loginForm" :rules="loginRules" class="login-form">
      <h3 class="title">若依后台管理系统</h3>
      
      <!-- SSO 登录按钮 -->
      <div class="sso-login">
        <el-button type="primary" @click="ssoLogin" :loading="ssoLoading">
          统一认证登录
        </el-button>
      </div>
      
      <el-divider>或</el-divider>
      
      <!-- 本地账号登录 -->
      <el-form-item prop="username">
        <el-input v-model="loginForm.username" placeholder="账号" prefix-icon="el-icon-user" />
      </el-form-item>
      <el-form-item prop="password">
        <el-input v-model="loginForm.password" type="password" placeholder="密码" prefix-icon="el-icon-lock" @keyup.enter.native="handleLogin" />
      </el-form-item>
      
      <el-form-item>
        <el-button :loading="loading" type="primary" style="width:100%" @click.native.prevent="handleLogin">
          登录
        </el-button>
      </el-form-item>
    </el-form>
  </div>
</template>

<script>
import { getToken, setToken } from '@/utils/auth'

export default {
  name: 'Login',
  data() {
    return {
      loginForm: {
        username: '',
        password: ''
      },
      loginRules: {
        username: [{ required: true, message: '请输入账号', trigger: 'blur' }],
        password: [{ required: true, message: '请输入密码', trigger: 'blur' }]
      },
      loading: false,
      ssoLoading: false,
      redirect: undefined
    }
  },
  created() {
    // 检查 URL 中是否携带 ticket(SSO 回调)
    this.checkSsoTicket()
  },
  methods: {
    // SSO 登录
    ssoLogin() {
      this.ssoLoading = true
      // 跳转到 SSO 认证中心
      const currentUrl = window.location.href
      const ssoAuthUrl = process.env.VUE_APP_SSO_AUTH_URL + '?redirect=' + encodeURIComponent(currentUrl)
      window.location.href = ssoAuthUrl
    },
    
    // 检查 SSO Ticket
    async checkSsoTicket() {
      const ticket = this.$route.query.ticket
      if (ticket) {
        try {
          // 验证 ticket 并获取 token
          const res = await this.$store.dispatch('SsoLogin', ticket)
          if (res.code === 200) {
            setToken(res.token)
            this.$router.push({ path: this.redirect || '/' })
          }
        } catch (error) {
          this.$message.error('SSO 登录失败')
        }
      }
    },
    
    // 本地登录
    handleLogin() {
      this.$refs.loginForm.validate(async valid => {
        if (valid) {
          this.loading = true
          try {
            await this.$store.dispatch('Login', this.loginForm)
            this.$router.push({ path: this.redirect || '/' })
          } catch (error) {
            this.loading = false
          }
        }
      })
    }
  }
}
</script>

<style scoped>
.sso-login {
  text-align: center;
  margin-bottom: 20px;
}
</style>

6.2 配置环境变量

创建 .env.development

# SSO 配置
VUE_APP_SSO_AUTH_URL=http://localhost:8080/sso/auth
VUE_APP_SSO_TICKET_URL=http://localhost:8080/sso/ticket

6.3 Store 改造

store/modules/user.js 中添加 SSO 登录方法:

import { login, logout, getInfo, ssoLogin } from '@/api/login'

const user = {
  actions: {
    // SSO 登录
    SsoLogin({ commit }, ticket) {
      return new Promise((resolve, reject) => {
        ssoLogin(ticket).then(res => {
          commit('SET_TOKEN', res.token)
          setToken(res.token)
          resolve(res)
        }).catch(error => {
          reject(error)
        })
      })
    }
  }
}

export default user

6.4 API 接口

api/login.js 中添加:

import request from '@/utils/request'

// SSO 登录验证 ticket
export function ssoLogin(ticket) {
  return request({
    url: '/sso/login',
    method: 'post',
    params: { ticket }
  })
}

七、测试验证

7.1 启动顺序

# 1. 启动 Redis
redis-server

# 2. 启动 SSO Server(若依后台,端口 8080)
java -jar ruoyi-admin.jar

# 3. 启动 SSO Client1(端口 8081)
java -jar client1.jar

# 4. 启动 SSO Client2(端口 8082)
java -jar client2.jar

# 5. 启动前端
npm run dev

7.2 测试流程

  1. 访问 Client1:http://localhost:8081
  2. 自动跳转到 SSO 认证中心登录页
  3. 输入账号密码登录
  4. 登录成功后自动跳回 Client1
  5. 再访问 Client2:http://localhost:8082
  6. 无需再次登录,直接进入系统

7.3 测试单点注销

  1. 在任意系统点击注销
  2. 所有系统的登录状态都会失效
  3. 再次访问任意系统需要重新登录

八、常见问题与解决方案

Q1:跨域问题如何解决?

在 SSO Server 中配置跨域:

@Configuration
public class CorsConfig implements WebMvcConfigurer
{
    @Override
    public void addCorsMappings(CorsRegistry registry)
    {
        registry.addMapping("/**")
            .allowedOriginPatterns("*")
            .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
            .allowedHeaders("*")
            .allowCredentials(true)
            .maxAge(3600);
    }
}

Q2:如何实现记住登录?

// 登录时设置
StpUtil.login(userId, new SaLoginModel()
    .setTimeout(60 * 60 * 24 * 30) // 30天有效
    .setDevice("PC") // 指定设备类型
);

Q3:如何踢人下线?

// 踢指定用户下线
StpUtil.kickout(userId);

// 踢指定 token 下线
StpUtil.kickoutByTokenValue(token);

Q4:如何实现同端互斥登录?

sa-token:
  is-concurrent: false  # 不允许同端多登录

九、总结

本文完整介绍了将若依前后端分离项目改造为 Sa-Token SSO 单点登录系统的过程:

改造内容说明
后端改造添加 Sa-Token 依赖、配置 SSO Server
认证改造集成若依用户体系
客户端配置SSO Client 配置和拦截器
前端改造支持 SSO 登录流程
测试验证单点登录和单点注销

改造优势:

  • 改造成本低(约 2-3 天完成)
  • 与若依权限体系完美融合
  • 支持多客户端接入
  • 可扩展性强(支持踢人下线、同端互斥等功能)

本文章已经生成可运行项目
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值