其实一直想写一个单点登录系统,现在的现状是公司内部有非常多项目的,然后每个项目一套登录系统,系统和系统之间存在单独的鉴权,每一个操作应用都需要登录一次的话,这样不仅仅用户体验不好,也会出现非常多重复的代码,于是单点登录的需求诞生了。
轻量级且开箱即用,如果你说为何不用shiro
,那么我只能说它比shiro
更加轻量更加好用,只需要简单的配置和写几行代码,即可实现登录功能。
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-toke
n提供的,所以它不仅仅可以用于sa-token
的鉴权,还可以应用于spring
框架的所有拦截操作
实现完上述代码,sa-token 项目的简单鉴权就算是完成了。但其实sa-token还提供了许多功能,比如权限认证
、OAuth2.0
、分布式Session会话
、微服务网关鉴权
等等,有需要还是建议使用前看看官方文档 Sa-Token
Sa-Token 我的理解是:它会更加希望你搭配一套统一的前端来使用,而不是每个系统都搭建一套登录系统,就算是在SSO单点登录模块
里面,它依旧会给出 SSO整合-定制化登录页面
的教程,希望在未登录时跳转至我们编写好的页面,而不是去返回统一状态码给到前端。而我们公司会更加倾向于去返回统一json数据和不同的code状态码,其他交给前端自行判断。我们来看看我公司的处理。
如果我想未登录时返回某个状态码,我们需要在异常处理类中加入NotLoginException
的handler
异常处理类代码如下:
@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());
}
}
上面的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而不是特定的异常,只是如果你想做特殊处理,才需要类似上述的处理代码
好了,以上就是本次博客的全部内容,如有疑问,欢迎留言沟通