sa-token-doc/blog/essays/sa-token-dan-dian-deng-lu-di-liu-pian-ding-zhi-hua-deng-lu-ye-mian.html
2023-04-24随笔
大家好,由于单篇博客长度有限,我将针对 SSO 单点登录 开启一个系列文章,此为第六篇,想完整的了解 SSO 技术的同学欢迎点开我的账号动态参考本系列历史文章。
Sa-Token 是一个轻量级 java 权限认证框架,主要解决登录认证、权限认证、单点登录、OAuth2、微服务网关鉴权 等一系列权限相关问题。
Gitee 开源地址:https://gitee.com/dromara/sa-token
本篇将介绍在集成 Sa-Token SSO 模块时,如何自定义登录页面等信息。
前端页面准备一个**[登录]**按钮,当用户点击按钮时,跳转到登录接口
<a href="javascript:location.href='/sso/login?back=' + encodeURIComponent(location.href);">登录</a>
在后端注册全局过滤器(或拦截器、或全局异常处理),拦截需要登录后才能访问的页面资源,将未登录的访问重定向至登录接口
/**
* Sa-Token 配置类
*/
@Configuration
public class SaTokenConfigure implements WebMvcConfigurer {
/** 注册 [Sa-Token全局过滤器] */
@Bean
public SaServletFilter getSaServletFilter() {
return new SaServletFilter()
.addInclude("/**")
.addExclude("/sso/*", "/favicon.ico")
.setAuth(obj -> {
if(StpUtil.isLogin() == false) {
String back = SaFoxUtil.joinParam(SaHolder.getRequest().getUrl(), SpringMVCUtil.getRequest().getQueryString());
SaHolder.getResponse().redirect("/sso/login?back=" + SaFoxUtil.encodeUrl(back));
SaRouter.back();
}
})
;
}
}
首先,后端仍需要提供拦截,但是不直接引导用户重定向,而是返回未登录的提示信息
/**
* Sa-Token 配置类
*/
@Configuration
public class SaTokenConfigure implements WebMvcConfigurer {
/** 注册 [Sa-Token全局过滤器] */
@Bean
public SaServletFilter getSaServletFilter() {
return new SaServletFilter()
.addInclude("/**")
.addExclude("/sso/*", "/favicon.ico")
.setAuth(obj -> {
if(StpUtil.isLogin() == false) {
// 与前端约定好,code=401时代表会话未登录
SaRouter.back(SaResult.ok().setCode(401));
}
})
;
}
}
前端接受到返回结果 code=401 时,开始跳转至登录接口
if(res.code == 401) {
location.href = '/sso/login?back=' + encodeURIComponent(location.href);
}
这种方案比较适合以 Ajax 访问的 RestAPI 接口重定向
// 配置:未登录时返回的View
sso.setNotLoginView(() -> {
return new ModelAndView("xxx.html");
})
根据需求点选择解决方案:
// 在任意代码处获取前端提交的参数
String xxx = SaHolder.getRequest().getParam("xxx");
// 直接定义一个拦截路由为 `/sso/doLogin` 的接口即可
@RequestMapping("/sso/doLogin")
public SaResult ss(String name, String pwd) {
System.out.println("------ 请求进入了自定义的API接口 ---------- ");
if("sa".equals(name) && "123456".equals(pwd)) {
StpUtil.login(10001);
return SaResult.ok("登录成功!");
}
return SaResult.error("登录失败!");
}
/sso/doLogin这个接口,想自定义一个API地址?答:直接在前端更改点击按钮时 Ajax 的请求地址即可
在之前的章节中,我们演示了如何搭建一个SSO认证中心:
/**
* Sa-Token-SSO Server端 Controller
*/
@RestController
public class SsoServerController {
// SSO-Server端:处理所有SSO相关请求
@RequestMapping("/sso/*")
public Object ssoRequest() {
return SaSsoProcessor.instance.serverDister();
}
// ... 其它代码
}
这种写法集成简单但却不够灵活。例如认证中心地址只能是:http://{host}:{port}/sso/auth,如果我们想要自定义其API地址,应该怎么做呢?
打开SSO模块相关源码,有关 API 的设计都定义在:
ApiName.java
中,我们可以对其进行二次修改。
例如,我们可以在 Main 方法启动类或者 SSO 配置方法中修改变量值:
// 配置SSO相关参数
@Autowired
private void configSso(SaSsoConfig sso) {
// 自定义API地址
SaSsoUtil.ssoTemplate.apiName.ssoAuth = "/sso/auth2";
// ...
// SSO 相关配置
sso.setXxx ... ;
}
启动项目,统一认证地址就被我们修改成了:http://{host}:{port}/sso/auth2
根据上述路由入口:@RequestMapping("/sso/*"),我们给它起一个合适的名字 —— 聚合式路由。
与之对应的,我们可以将其修改为拆分式路由:
/**
* Sa-Token-SSO Server端 Controller
*/
@RestController
public class SsoServerController {
// SSO-Server:统一认证地址
@RequestMapping("/sso/auth")
public Object ssoAuth() {
return SaSsoProcessor.instance.ssoAuth();
}
// SSO-Server:RestAPI 登录接口
@RequestMapping("/sso/doLogin")
public Object ssoDoLogin() {
return SaSsoProcessor.instance.ssoDoLogin();
}
// SSO-Server:校验ticket 获取账号id
@RequestMapping("/sso/checkTicket")
public Object ssoCheckTicket() {
return SaSsoProcessor.instance.ssoCheckTicket();
}
// SSO-Server:单点注销
@RequestMapping("/sso/signout")
public Object ssoSignout() {
return SaSsoProcessor.instance.ssoSignout();
}
// ... 其它方法
}
拆分式路由 与 聚合式路由 在功能上完全等价,且提供了更为细致的路由管控。
sso-server 或 sso-client 服务后,访问返回:{"msg": "not handle"}。返回这个信息,代表你访问的路由有错误,比如说:
http://{host}:{port}/sso/auth。http://{host}:{port}/sso/auth2。地址写错了,框架就不会处理这个请求,会直接返回 {"msg": "not handle"},所有开放地址可参考:SSO 开放接口
如果仔细检查地址后没有写错,却依然返回了这个信息,那有可能是对应的接口没有打开,比如说:
http://{host}:{port}/sso/signout;http://{host}:{port}/sso/logout;都需要在配置文件配置:sa-token.sso.is-slo=true后,才会打开。
如果使用的是模式二,出现此异常概率最大的原因是因为 Client 与 Server 没有连接同一个Redis,SSO模式二中两者必须连接同一个 Redis 才可以登录成功。
你可能会问:我看配置文件明明是同一个啊?
我的建议是:排查时不要仅凭肉眼判断,分别在你的 Client 与 Server 启动后调用 SaManager.getSaTokenDao().set("name", "value", 100000);
随便写入一个值,看看能不能根据你的预期写进同一个Redis里,如果能的话才能证明 Client 与 Server 连接的Reids 是同一个,再进行下一步排查。
@SpringBootApplication
public class SaSsoServerApplication {
public static void main(String[] args) {
SpringApplication.run(SaSsoServerApplication.class, args);
System.out.println("\n------ Sa-Token-SSO 统一认证中心启动成功 ");
// 分别在 Client 与 Server 启动后调用 set 数据代码,看看能否根据预期写入同一个 reids
SaManager.getSaTokenDao().set("name", "value", 100000);
}
}
如果使用的是模式三,则排查是否有重复校验 ticket 的代码,一个 ticket 码只能使用一次,多次重复使用就会提示这个。
← Sa-Token 单点登录第五篇:前后端分离架构下的 SSO 对接步骤Sa-Token 单点登录第四篇:跨域、跨 Redis 下的 SSO 对接步骤 →