使用Sa-token实现单点登录

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

使用Sa-token实现单点登录

单点登录需求

     其实一直想写一个单点登录系统现在的现状是公司内部有非常多项目的然后每个项目一套登录系统系统和系统之间存在单独的鉴权每一个操作应用都需要登录一次的话这样不仅仅用户体验不好也会出现非常多重复的代码于是单点登录的需求诞生了。

为何选择Sa-Token

     轻量级且开箱即用如果你说为何不用shiro那么我只能说它比shiro更加轻量更加好用只需要简单的配置和写几行代码即可实现登录功能。

简单使用sa-token

1.往项目中引入sa-token依赖

        <!-- sa-token鉴权模块 -->
        <dependency>
            <groupId>cn.dev33</groupId>
            <artifactId>sa-token-spring-boot-starter</artifactId>
            <version>1.34.0</version>
        </dependency>

        <!-- Sa-Token 插件整合SSO 【sso单点登录依赖】 -->
        <dependency>
            <groupId>cn.dev33</groupId>
            <artifactId>sa-token-sso</artifactId>
            <version>1.34.0</version>
        </dependency>

        <!-- Sa-Token 整合 Redis (使用 jackson 序列化方式) 【后续版本管理改为父级】 -->
        <dependency>
            <groupId>cn.dev33</groupId>
            <artifactId>sa-token-dao-redis-jackson</artifactId>
            <version>1.34.0</version>
        </dependency>

2.编写配置文件

# 端口
server:
    port: 9000

# Sa-Token 配置
sa-token: 
    # ------- SSO-模式一相关配置  (非模式一不需要配置) 
    # cookie:
         # 配置 Cookie 作用域 
         # domain: stp.com
        
    # ------- SSO-模式二相关配置 
    sso: 
        # Ticket有效期 (单位: 秒)默认五分钟 
        ticket-timeout: 300
        # 所有允许的授权回调地址
        allow-url: "*"
        # 是否打开单点注销功能
        is-slo: true
        
        # ------- SSO-模式三相关配置 下面的配置在SSO模式三并且 is-slo=true 时打开 
        # 是否打开模式三 
        isHttp: true
        # 接口调用秘钥用于SSO模式三的单点注销功能
        secretkey: kQwIOrYvnXmSDkwEiFngrKidMcdrgKor
        # ---- 除了以上配置项你还需要为 Sa-Token 配置http请求处理器文档有步骤说明 
        
spring: 
    # Redis配置 SSO模式一和模式二使用Redis来同步会话
    redis:
        # Redis数据库索引默认为0
        database: 1
        # Redis服务器地址
        host: 127.0.0.1
        # Redis服务器连接端口
        port: 6379
        # Redis服务器连接密码默认为空
        password: 
        # 连接超时时间
        timeout: 10s
        lettuce:
            pool:
                # 连接池最大连接数
                max-active: 200
                # 连接池最大阻塞等待时间使用负值表示没有限制
                max-wait: -1ms
                # 连接池中的最大空闲连接
                max-idle: 10
                # 连接池中的最小空闲连接
                min-idle: 0    

3.编写登录接口

// 会话登录接口 
@RequestMapping("doLogin")
public SaResult doLogin(String name, String pwd) {
    // 第一步比对前端提交的账号名称、密码
    if("zhang".equals(name) && "123456".equals(pwd)) {
        // 第二步根据账号id进行登录 
        StpUtil.login(10001);
        return SaResult.ok("登录成功");
    }
    return SaResult.error("登录失败");
}

     上术代码已经实现了登录鉴权但是我们也许可以注意到此处仅仅做了会话登录并没有主动向前端返回 Token 信息。 是因为不需要吗严格来讲是需要的只不过 StpUtil.login(id) 方法利用了 Cookie 自动注入的特性省略了你手写返回 Token 的代码。
     但在某些情况下我们就是需要返回token给到前端那应该如何操作呢请往下看

     登录接口返回token信息

    // 登录接口
    @RequestMapping("doLogin")
    public SaResult doLogin(String username, String password) {
        // 第1步先登录上
        StpUtil.login(10003);

        // 第2步获取 Token  相关参数
        SaTokenInfo tokenInfo = StpUtil.getTokenInfo();
        // 第3步返回给前端
        return SaResult.data(tokenInfo);
    }

     接口将会返回如下的数据
在这里插入图片描述

     其中tokenName是请求头名称tokenValue是请求头的值将它们放入请求头即可保持登录态。

接口如何保持登录态

请求头放入token【登录接口返回】即可比如
在这里插入图片描述
controller代码如下

    @RequestMapping("/userinfo")
    public Object userinfo() {

        // 自定义返回结果模拟
        return SaResult.ok()
                .set("id", StpUtil.getLoginId())
                .set("name", "zengjq")
                .set("sex", "男")
                .set("age", 18);
    }

是不是非常简单。我们通过StpUtil.getLoginId()即可拿到登录用户的id但实际情况下我们往往需要拿到用户名称部门之类的更多信息而登录态工具类StpUtil并未不能获取到有同学肯定想到了我拿用户id去访问数据库呀但是每次都去查数据库那么它的压力就太大啦其实sa-token提供了一个TokenSession
它是会话中的数据缓存组件通过 Session 我们可以很方便的缓存一些高频读写数据提高程序性能例如

// 在登录时缓存user对象 
StpUtil.getTokenSession().set("user", user);

// 然后我们就可以在任意处使用这个user对象
SysUser user = (SysUser) StpUtil.getTokenSession().get("user");

其实Sa-Token还提供了别的session类这里不做赘述有兴趣可以移步官方文档 sa-token session

使用拦截器实现鉴权

     我们来看拦截器代码

import cn.dev33.satoken.interceptor.SaInterceptor;
import cn.dev33.satoken.stp.StpUtil;
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 SaTokenConfigure implements WebMvcConfigurer {

    // 注册拦截器
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // 新增登录校验拦截器 校验规则为 StpUtil.checkLogin() 登录校验。
        SaInterceptor saInterceptor = new SaInterceptor(handle -> StpUtil.checkLogin());

        // 注册 Sa-Token 拦截器
        registry.addInterceptor(saInterceptor)
                .addPathPatterns("/**")
                .excludePathPatterns("/user/doLogin", "/demo/**");  // 排除了/user/doLogin接口用来开放登录
    }
}

     这里的拦截器使用的是spring框架自带的并不是sa-token提供的所以它不仅仅可以用于sa-token的鉴权还可以应用于spring框架的所有拦截操作

     实现完上述代码sa-token 项目的简单鉴权就算是完成了。但其实sa-token还提供了许多功能比如权限认证OAuth2.0分布式Session会话微服务网关鉴权 等等有需要还是建议使用前看看官方文档 Sa-Token

聊聊Sa-Token的理解

     Sa-Token 我的理解是它会更加希望你搭配一套统一的前端来使用而不是每个系统都搭建一套登录系统就算是在SSO单点登录模块里面它依旧会给出 SSO整合-定制化登录页面 的教程希望在未登录时跳转至我们编写好的页面而不是去返回统一状态码给到前端。而我们公司会更加倾向于去返回统一json数据和不同的code状态码其他交给前端自行判断。我们来看看我公司的处理。
     如果我想未登录时返回某个状态码我们需要在异常处理类中加入NotLoginExceptionhandler
异常处理类代码如下

@ControllerAdvice
public class GlobalExceptionHandler {
	private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);

	@ExceptionHandler(NotLoginException.class)
	@ResponseStatus(HttpStatus.OK)	// HttpStatus.OK是因为前端不基于httpStatus去判断接口是否异常
	@ResponseBody
	public SaResult handleNoHandlerFoundException(NotLoginException e) {
		log.error("用户未登录 ", e);
		// ErrorCode.LOGIN_REQUIRE.getCode() 是未登录时的与前端约定好的异常编码
		return SaResult.error(e.getMessage()).setCode(ErrorCode.LOGIN_REQUIRE.getCode());
	}


	@ExceptionHandler(SaTokenException.class)
	@ResponseStatus(HttpStatus.OK)
	@ResponseBody
	public SaResult handleSaTokenException(SaTokenException e) {
		log.error("sa-token抛出其他异常 ", e);
		return SaResult.error(e.getMessage());
	}
}

聊聊遇到的一些问题

当接口404时spring会拦截后直接抛出异常信息
在这里插入图片描述

上面的json其实并不是我们返回的而是spring框架层返回的如果我想改成自定义json应该怎么处理呢请往下看

自定义接口404响应信息
1、在配置文件中加入配置

spring.mvc.throw-exception-if-no-handler-found=true
spring.resources.add-mappings=false

throw-exception-if-no-handler-found表示当没有对应处理器时允许抛出异常而add-mappings表示是否为静态资源添加对应的处理器。而默认404异常不被全局处理器拦截才导致未抛出异常。

2、在全局异常处理中捕获404

	@ExceptionHandler(NoHandlerFoundException.class)
	@ResponseStatus(HttpStatus.OK)
	@ResponseBody
	public SaResult handle404Error(NoHandlerFoundException e) {
		log.error("访问资源不存在", e);
		return new SaResult(404, "访问的资源不存在", null);
	}

其实一般我们会直接捕获Exception.class而不是特定的异常只是如果你想做特殊处理才需要类似上述的处理代码

好了以上就是本次博客的全部内容如有疑问欢迎留言沟通

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6