什么是Sa-Token
官网:https://sa-token.dev33.cn
Sa-Token 是一个轻量级 Java 权限认证框架,主要解决:登录认证
、权限认证
、Session会话
、单点登录
、OAuth2.0
、微服务网关鉴权
等一系列权限相关问题。
快速使用
引入Maven依赖
<!-- web支持 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Sa-Token-Quick-Login 插件 -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-quick-login</artifactId>
<version>1.33.0</version>
</dependency>
配置参数
server:
port: 8080
# Sa-Token-Quick-Login 配置
sa:
# 登录账号
name: admin
# 登录密码
pwd: 123456
# 是否自动随机生成账号密码 (此项为true时, name与pwd失效)
auto: false
# 是否开启全局认证(关闭后将不再强行拦截)
auth: true
# 登录页标题
title: Charles Index 登录
# 是否显示底部版权信息
copr: true
# 指定拦截路径
include: /**
# 指定排除路径
exclude: /test
编写Controller
@RestController
public class TestController {
/**
* 不需要认证
*
* @return
*/
@GetMapping("test")
public String test() {
return "test";
}
/**
* 需要认证
*
* @return
*/
@GetMapping("test1")
public String test1() {
return "test1";
}
}
测试
- 访问 http://localhost:8080/test1
由于没有登录,被拦截了,到了登录页面
- 访问 http://localhost:8080/test,可以获取数据。
源码解析
- 引入
sa-token-quick-login
,会加载jar包中的spring.factories
。
org.springframework.boot.autoconfigure.EnableAutoConfiguration=cn.dev33.satoken.quick.SaQuickInject
SaQuickInject
类上面注解是@Import({SaQuickController.class, SaQuickRegister.class})
,所以会添加SaQuickController
和SaQuickRegister
。SaQuickController
主要是提供登录页面和提供登录接口
@Controller
public class SaQuickController {
public SaQuickController() {
}
@GetMapping({"/saLogin"})
public String saLogin(HttpServletRequest request) {
request.setAttribute("cfg", SaQuickManager.getConfig());
return "sa-login.html";
}
@PostMapping({"/doLogin"})
@ResponseBody
public SaResult doLogin(String name, String pwd) {
if (!SaFoxUtil.isEmpty(name) && !SaFoxUtil.isEmpty(pwd)) {
SaQuickConfig config = SaQuickManager.getConfig();
if (name.equals(config.getName()) && pwd.equals(config.getPwd())) {
StpUtil.login(config.getName());
return SaResult.get(200, "ok", StpUtil.getTokenInfo());
} else {
return SaResult.get(500, "账号或密码输入错误", (Object)null);
}
} else {
return SaResult.get(500, "请输入账号和密码", (Object)null);
}
}
}
SaQuickRegister
是核心,提供了SaServletFilter
。SaServletFilter
用来做拦截认证。
@Configuration
public class SaQuickRegister {
public SaQuickRegister() {
}
@Bean
@ConfigurationProperties(
prefix = "sa"
)
public SaQuickConfig getSaQuickConfig() {
return new SaQuickConfig();
}
@Bean
@Order(-101)
public SaServletFilter getSaServletFilter() {
return (new SaServletFilter()).addInclude(new String[]{"/**"}).addExclude(new String[]{"/favicon.ico", "/saLogin", "/doLogin", "/sa-res/**"}).setAuth((obj) -> {
SaRouter.match(SaQuickManager.getConfig().getInclude().split(",")).notMatch(SaQuickManager.getConfig().getExclude().split(",")).check((r) -> {
if (SaQuickManager.getConfig().getAuth() && !StpUtil.isLogin()) {
SaHolder.getRequest().forward("/saLogin");
SaRouter.back();
}
});
}).setError((e) -> {
return e.getMessage();
});
}
}
- 访问
http://localhost:8080/test
,看下SaServletFilter
是如何放行的。
// cn.dev33.satoken.filter.SaServletFilter#doFilter
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
try {
SaRouter.match(this.includeList).notMatch(this.excludeList).check((r) -> {
this.beforeAuth.run((Object)null);
this.auth.run((Object)null);
});
} catch (StopMatchException var6) {
} catch (Throwable var7) {
String result = var7 instanceof BackResultException ? var7.getMessage() : String.valueOf(this.error.run(var7));
if (response.getContentType() == null) {
response.setContentType("text/plain; charset=utf-8");
}
response.getWriter().print(result);
return;
}
chain.doFilter(request, response);
}
- 该auth方法是在
SaQuickRegister
类中定义的。因为/test
是在配置文件中定义的,所以在执行SaRouter.match(SaQuickManager.getConfig().getInclude().split(",")).notMatch(SaQuickManager.getConfig().getExclude().split(","))
后,SaRouterStaff
中的isHit
变量为false,所以SaRouterStaff#check()
不需要执行。
public SaRouterStaff notMatch(String... patterns) {
if (this.isHit) {
this.isHit = !SaRouter.isMatchCurrURI(patterns);
}
return this;
}
test1
方法由于不在exclude
配置中,所以会执行SaRouterStaff#check()
。该方法中主要有两个判断,SaQuickManager.getConfig().getAuth()
和StpUtil.isLogin() == false
。配置中的auth
设置为true,所以第一个判断为true。StpUtil.isLogin()
用来判断是否登录。跟踪到获取到token的方法,获取不到token,返回false,跳转到saLogin
页面。
getTokenValueNotCut:249, StpLogic (cn.dev33.satoken.stp)
getTokenValue:201, StpLogic (cn.dev33.satoken.stp)
getLoginIdDefaultNull:746, StpLogic (cn.dev33.satoken.stp)
isLogin:659, StpLogic (cn.dev33.satoken.stp)
isLogin:282, StpUtil (cn.dev33.satoken.stp)
- 登录方法,
SaQuickController#doLogin()
。创造token,并且存储在客户端。
login:331, StpLogic (cn.dev33.satoken.stp)
login:293, StpLogic (cn.dev33.satoken.stp)
login:135, StpUtil (cn.dev33.satoken.stp)
doLogin:53, SaQuickController (cn.dev33.satoken.quick.web)
public void login(Object id, SaLoginModel loginModel) {
// 1、创建会话
String token = createLoginSession(id, loginModel);
// 2、在当前客户端注入Token
setTokenValue(token, loginModel);
}
- 存储在客户端。先保留一份在
SaStorage
,再存入cookie。
public void setTokenValue(String tokenValue, SaLoginModel loginModel){
if(SaFoxUtil.isEmpty(tokenValue)) {
return;
}
// 1. 将 Token 保存到 [存储器] 里
setTokenValueToStorage(tokenValue);
// 2. 将 Token 保存到 [Cookie] 里
if (getConfig().getIsReadCookie()) {
setTokenValueToCookie(tokenValue, loginModel.getCookieTimeout());
}
// 3. 将 Token 写入到响应头里
if(loginModel.getIsWriteHeaderOrGlobalConfig()) {
setTokenValueToResponseHeader(tokenValue);
}
}
- 登录的时候,从cookie中获取token,判断是否过期,如果能获取loginId,则判断是登录状态,
SaTokenDaoDefaultImpl#get
。
@Override
public String get(String key) {
clearKeyByTimeout(key);
return (String)dataMap.get(key);
}