Sa-Token 单点登录第六篇:定制化登录页面
大家好,由于单篇博客长度有限,我将针对 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>- 1
方案二:后端拦截重定向
在后端注册全局过滤器(或拦截器、或全局异常处理),拦截需要登录后才能访问的页面资源,将未登录的访问重定向至登录接口
/**
* 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();
}
})
;
}
}- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
方案三:后端拦截 + 前端跳转
首先,后端仍需要提供拦截,但是不直接引导用户重定向,而是返回未登录的提示信息
/**
* 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));
}
})
;
}
}- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
前端接受到返回结果 code=401 时,开始跳转至登录接口
if(res.code == 401) {
location.href = '/sso/login?back=' + encodeURIComponent(location.href);
}- 1
- 2
- 3
这种方案比较适合以 Ajax 访问的 RestAPI 接口重定向
二、如何自定义登录视图?
方式一:在demo示例中直接更改 login.html 页面代码即可
方式二:在配置中配置登录视图地址
// 配置:未登录时返回的View
sso.setNotLoginView(() -> {
return new ModelAndView("xxx.html");
})- 1
- 2
- 3
- 4
三、如何自定义登录API的接口地址?
根据需求点选择解决方案:
3.1、如果只是想在 setDoLoginHandle 函数里获取除 name、pwd 以外的参数?
// 在任意代码处获取前端提交的参数
String xxx = SaHolder.getRequest().getParam("xxx");- 1
- 2
3.2、想完全自定义一个接口来接受前端登录请求?
// 直接定义一个拦截路由为 `/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("登录失败!");
}- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
3.3、不想使用/sso/doLogin这个接口,想自定义一个API地址?
答:直接在前端更改点击按钮时 Ajax 的请求地址即可
四、如何自定义 API 路由
方式一:修改全局变量
在之前的章节中,我们演示了如何搭建一个SSO认证中心:
/**
* Sa-Token-SSO Server端 Controller
*/
@RestController
public class SsoServerController {
// SSO-Server端:处理所有SSO相关请求
@RequestMapping("/sso/*")
public Object ssoRequest() {
return SaSsoProcessor.instance.serverDister();
}
// ... 其它代码
}- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
这种写法集成简单但却不够灵活。例如认证中心地址只能是: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 ... ;
}- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
启动项目,统一认证地址就被我们修改成了: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();
}
// ... 其它方法
}- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
拆分式路由 与 聚合式路由 在功能上完全等价,且提供了更为细致的路由管控。
五、易错点
易错点一:搭建好 sso-server 或 sso-client 服务后,访问返回:{"msg": "not handle"}。
返回这个信息,代表你访问的路由有错误,比如说:
- 统一认证登录地址是:
http://{host}:{port}/sso/auth。 - 而你访问的却是:
http://{host}:{port}/sso/auth2。
地址写错了,框架就不会处理这个请求,会直接返回 {"msg": "not handle"},所有开放地址可参考:SSO 开放接口
如果仔细检查地址后没有写错,却依然返回了这个信息,那有可能是对应的接口没有打开,比如说:
- sso-server 端的单点注销地址:
http://{host}:{port}/sso/signout; - sso-client 端的注销地址:
http://{host}:{port}/sso/logout;
都需要在配置文件配置:sa-token.sso.is-slo=true后,才会打开。
易错点二:参照文档搭建SSO-Client,一直提示:Ticket无效,这是怎么回事?
如果使用的是模式二,出现此异常概率最大的原因是因为 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);
}
}- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
如果使用的是模式三,则排查是否有重复校验 ticket 的代码,一个 ticket 码只能使用一次,多次重复使用就会提示这个。
参考资料
- Sa-Token 文档:https://sa-token.com
- Gitee 仓库地址:https://gitee.com/dromara/sa-token
- GitHub 仓库地址:https://github.com/dromara/sa-token
鲁公网安备37011202002956号