cn.dev33.satoken.sso.SaSsoProcessor Maven / Gradle / Ivy
/*
* Copyright 2020-2099 sa-token.cc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.dev33.satoken.sso;
import cn.dev33.satoken.config.SaSsoConfig;
import cn.dev33.satoken.context.SaHolder;
import cn.dev33.satoken.context.model.SaRequest;
import cn.dev33.satoken.context.model.SaResponse;
import cn.dev33.satoken.sso.error.SaSsoErrorCode;
import cn.dev33.satoken.sso.exception.SaSsoException;
import cn.dev33.satoken.sso.name.ApiName;
import cn.dev33.satoken.sso.name.ParamName;
import cn.dev33.satoken.stp.StpLogic;
import cn.dev33.satoken.util.SaFoxUtil;
import cn.dev33.satoken.util.SaResult;
/**
* SSO 请求处理器
*
* @author click33
* @since 1.32.0
*/
public class SaSsoProcessor {
/**
* 底层 SaSsoTemplate 对象
*/
public SaSsoTemplate ssoTemplate = SaSsoUtil.ssoTemplate;
// ----------- SSO-Server 端路由分发 -----------
/**
* 分发 Server 端所有请求
* @return 处理结果
*/
public Object serverDister() {
// 获取对象
SaRequest req = SaHolder.getRequest();
SaSsoConfig cfg = SaSsoManager.getConfig();
ApiName apiName = ssoTemplate.apiName;
// ------------------ 路由分发 ------------------
// ---------- SSO-Server端:授权地址
if(req.isPath(apiName.ssoAuth)) {
return ssoAuth();
}
// ---------- SSO-Server端:RestAPI 登录接口
if(req.isPath(apiName.ssoDoLogin)) {
return ssoDoLogin();
}
// ---------- SSO-Server端:校验ticket 获取账号id
if(req.isPath(apiName.ssoCheckTicket) && cfg.getIsHttp()) {
return ssoCheckTicket();
}
// ---------- SSO-Server端:单点注销
if(req.isPath(apiName.ssoSignout)) {
return ssoSignout();
}
// 默认返回
return SaSsoConsts.NOT_HANDLE;
}
/**
* SSO-Server端:授权地址
* @return 处理结果
*/
public Object ssoAuth() {
// 获取对象
SaRequest req = SaHolder.getRequest();
SaResponse res = SaHolder.getResponse();
SaSsoConfig cfg = SaSsoManager.getConfig();
StpLogic stpLogic = ssoTemplate.getStpLogic();
ParamName paramName = ssoTemplate.paramName;
// ---------- 此处有两种情况分开处理:
// ---- 情况1:在SSO认证中心尚未登录,需要先去登录
if( ! stpLogic.isLogin()) {
return cfg.getNotLoginView().get();
}
// ---- 情况2:在SSO认证中心已经登录,需要重定向回 Client 端,而这又分为两种方式:
String mode = req.getParam(paramName.mode, "");
// 方式1:直接重定向回Client端 (mode=simple)
if(mode.equals(SaSsoConsts.MODE_SIMPLE)) {
String redirect = req.getParam(paramName.redirect);
ssoTemplate.checkRedirectUrl(redirect);
return res.redirect(redirect);
} else {
// 方式2:带着ticket参数重定向回Client端 (mode=ticket)
String redirectUrl = ssoTemplate.buildRedirectUrl(stpLogic.getLoginId(), req.getParam(paramName.client), req.getParam(paramName.redirect));
return res.redirect(redirectUrl);
}
}
/**
* SSO-Server端:RestAPI 登录接口
* @return 处理结果
*/
public Object ssoDoLogin() {
// 获取对象
SaRequest req = SaHolder.getRequest();
SaSsoConfig cfg = SaSsoManager.getConfig();
ParamName paramName = ssoTemplate.paramName;
// 处理
return cfg.getDoLoginHandle().apply(req.getParam(paramName.name), req.getParam(paramName.pwd));
}
/**
* SSO-Server端:校验ticket 获取账号id [模式三]
* @return 处理结果
*/
public Object ssoCheckTicket() {
ParamName paramName = ssoTemplate.paramName;
// 获取参数
SaRequest req = SaHolder.getRequest();
String client = req.getParam(paramName.client);
String ticket = req.getParamNotNull(paramName.ticket);
String sloCallback = req.getParam(paramName.ssoLogoutCall);
// 校验ticket,获取 loginId
Object loginId = ssoTemplate.checkTicket(ticket, client);
if(SaFoxUtil.isEmpty(loginId)) {
return SaResult.error("无效ticket:" + ticket);
}
// 注册此客户端的单点注销回调URL
ssoTemplate.registerSloCallbackUrl(loginId, sloCallback);
// 给 client 端响应结果
return SaResult.data(loginId);
}
/**
* SSO-Server端:单点注销
* @return 处理结果
*/
public Object ssoSignout() {
// 获取对象
SaRequest req = SaHolder.getRequest();
SaSsoConfig cfg = SaSsoManager.getConfig();
ParamName paramName = ssoTemplate.paramName;
// SSO-Server端:单点注销 [用户访问式] (不带loginId参数)
if(cfg.getIsSlo() && ! req.hasParam(paramName.loginId)) {
return ssoSignoutByUserVisit();
}
// SSO-Server端:单点注销 [Client调用式] (带loginId参数 & isHttp=true)
if(cfg.getIsHttp() && cfg.getIsSlo() && req.hasParam(paramName.loginId)) {
return ssoSignoutByClientHttp();
}
// 默认返回
return SaSsoConsts.NOT_HANDLE;
}
/**
* SSO-Server端:单点注销 [用户访问式]
* @return 处理结果
*/
public Object ssoSignoutByUserVisit() {
// 获取对象
SaRequest req = SaHolder.getRequest();
SaResponse res = SaHolder.getResponse();
Object loginId = ssoTemplate.getStpLogic().getLoginIdDefaultNull();
// 单点注销
if(SaFoxUtil.isNotEmpty(loginId)) {
ssoTemplate.ssoLogout(loginId);
}
// 完成
return ssoLogoutBack(req, res);
}
/**
* SSO-Server端:单点注销 [Client调用式]
* @return 处理结果
*/
public Object ssoSignoutByClientHttp() {
ParamName paramName = ssoTemplate.paramName;
// 获取参数
SaRequest req = SaHolder.getRequest();
String loginId = req.getParam(paramName.loginId);
// step.1 校验签名
ssoTemplate.getSignTemplate().checkRequest(req);
// step.2 单点注销
ssoTemplate.ssoLogout(loginId);
// 响应
return SaResult.ok();
}
// ----------- SSO-Client 端路由分发 -----------
/**
* 分发 Client 端所有请求
* @return 处理结果
*/
public Object clientDister() {
ApiName apiName = ssoTemplate.apiName;
// 获取对象
SaRequest req = SaHolder.getRequest();
SaSsoConfig cfg = SaSsoManager.getConfig();
// ------------------ 路由分发 ------------------
// ---------- SSO-Client端:登录地址
if(req.isPath(apiName.ssoLogin)) {
return ssoLogin();
}
// ---------- SSO-Client端:单点注销
if(req.isPath(apiName.ssoLogout)) {
return ssoLogout();
}
// ---------- SSO-Client端:单点注销的回调 [模式三]
if(req.isPath(apiName.ssoLogoutCall) && cfg.getIsSlo() && cfg.getIsHttp()) {
return ssoLogoutCall();
}
// 默认返回
return SaSsoConsts.NOT_HANDLE;
}
/**
* SSO-Client端:登录地址
* @return 处理结果
*/
public Object ssoLogin() {
// 获取对象
SaRequest req = SaHolder.getRequest();
SaResponse res = SaHolder.getResponse();
SaSsoConfig cfg = SaSsoManager.getConfig();
StpLogic stpLogic = ssoTemplate.getStpLogic();
ApiName apiName = ssoTemplate.apiName;
ParamName paramName = ssoTemplate.paramName;
// 获取参数
String back = req.getParam(paramName.back, "/");
String ticket = req.getParam(paramName.ticket);
// 如果当前Client端已经登录,则无需访问SSO认证中心,可以直接返回
if(stpLogic.isLogin()) {
return res.redirect(back);
}
/*
* 此时有两种情况:
* 情况1:ticket无值,说明此请求是Client端访问,需要重定向至SSO认证中心
* 情况2:ticket有值,说明此请求从SSO认证中心重定向而来,需要根据ticket进行登录
*/
if(ticket == null) {
String serverAuthUrl = ssoTemplate.buildServerAuthUrl(SaHolder.getRequest().getUrl(), back);
return res.redirect(serverAuthUrl);
} else {
// ------- 1、校验ticket,获取 loginId
Object loginId = checkTicket(ticket, apiName.ssoLogin);
// Be: 如果开发者自定义了处理逻辑
if(cfg.getTicketResultHandle() != null) {
return cfg.getTicketResultHandle().apply(loginId, back);
}
// ------- 2、如果 loginId 无值,说明 ticket 无效
if(SaFoxUtil.isEmpty(loginId)) {
throw new SaSsoException("无效ticket:" + ticket).setCode(SaSsoErrorCode.CODE_30004);
} else {
// 3、如果 loginId 有值,说明 ticket 有效,此时进行登录并重定向至back地址
stpLogic.login(loginId);
return res.redirect(back);
}
}
}
/**
* SSO-Client端:单点注销
* @return 处理结果
*/
public Object ssoLogout() {
// 获取对象
SaSsoConfig cfg = SaSsoManager.getConfig();
// ---------- SSO-Client端:单点注销 [模式二]
if(cfg.getIsSlo() && ! cfg.getIsHttp()) {
return ssoLogoutType2();
}
// ---------- SSO-Client端:单点注销 [模式三]
if(cfg.getIsSlo() && cfg.getIsHttp()) {
return ssoLogoutType3();
}
// 默认返回
return SaSsoConsts.NOT_HANDLE;
}
/**
* SSO-Client端:单点注销 [模式二]
* @return 处理结果
*/
public Object ssoLogoutType2() {
// 获取对象
SaRequest req = SaHolder.getRequest();
SaResponse res = SaHolder.getResponse();
StpLogic stpLogic = ssoTemplate.getStpLogic();
// 开始处理
if(stpLogic.isLogin()) {
stpLogic.logout(stpLogic.getLoginId());
}
// 返回
return ssoLogoutBack(req, res);
}
/**
* SSO-Client端:单点注销 [模式三]
* @return 处理结果
*/
public Object ssoLogoutType3() {
// 获取对象
SaRequest req = SaHolder.getRequest();
SaResponse res = SaHolder.getResponse();
StpLogic stpLogic = ssoTemplate.getStpLogic();
// 如果未登录,则无需注销
if( ! stpLogic.isLogin()) {
return ssoLogoutBack(req, res);
}
// 调用 sso-server 认证中心单点注销API
String url = ssoTemplate.buildSloUrl(stpLogic.getLoginId());
SaResult result = ssoTemplate.request(url);
// 校验响应状态码
if(SaResult.CODE_SUCCESS == result.getCode()) {
// 极端场景下,sso-server 中心的单点注销可能并不会通知到此 client 端,所以这里需要再补一刀
if(stpLogic.isLogin()) {
stpLogic.logout();
}
return ssoLogoutBack(req, res);
} else {
// 将 sso-server 回应的消息作为异常抛出
throw new SaSsoException(result.getMsg()).setCode(SaSsoErrorCode.CODE_30006);
}
}
/**
* SSO-Client端:单点注销的回调 [模式三]
* @return 处理结果
*/
public Object ssoLogoutCall() {
ParamName paramName = ssoTemplate.paramName;
// 获取对象
SaRequest req = SaHolder.getRequest();
StpLogic stpLogic = ssoTemplate.getStpLogic();
// 获取参数
String loginId = req.getParamNotNull(paramName.loginId);
// 注销当前应用端会话
ssoTemplate.getSignTemplate().checkRequest(req);
stpLogic.logout(loginId);
// 响应
return SaResult.ok("单点注销回调成功");
}
// ----------- 工具方法
/**
* 封装:单点注销成功后返回结果
* @param req SaRequest对象
* @param res SaResponse对象
* @return 返回结果
*/
public Object ssoLogoutBack(SaRequest req, SaResponse res) {
ParamName paramName = ssoTemplate.paramName;
/*
* 三种情况:
* 1. 有back参数,值为SELF -> 回退一级并刷新
* 2. 有back参数,值为url -> 跳转到此url地址
* 3. 无back参数 -> 返回json数据
*/
String back = req.getParam(paramName.back);
if(SaFoxUtil.isNotEmpty(back)) {
if(back.equals(SaSsoConsts.SELF)) {
return "";
}
return res.redirect(back);
} else {
return SaResult.ok("单点注销成功");
}
}
/**
* 封装:校验ticket,取出loginId
* @param ticket ticket码
* @param currUri 当前路由的uri,用于计算单点注销回调地址
* @return loginId
*/
public Object checkTicket(String ticket, String currUri) {
SaSsoConfig cfg = SaSsoManager.getConfig();
ApiName apiName = ssoTemplate.apiName;
// --------- 两种模式
if(cfg.getIsHttp()) {
// q1、使用模式三:使用 http 请求从认证中心校验ticket
// 计算当前 sso-client 的单点注销回调地址
String ssoLogoutCall = null;
if(cfg.getIsSlo()) {
// 如果配置了回调地址,就使用配置的值:
if(SaFoxUtil.isNotEmpty(cfg.getSsoLogoutCall())) {
ssoLogoutCall = cfg.getSsoLogoutCall();
}
// 如果提供了当前 uri,则根据此值来计算:
else if(SaFoxUtil.isNotEmpty(currUri)) {
ssoLogoutCall = SaHolder.getRequest().getUrl().replace(currUri, apiName.ssoLogoutCall);
}
// 否则视为不注册单点注销回调地址
else {
}
}
// 发起请求
String checkUrl = ssoTemplate.buildCheckTicketUrl(ticket, ssoLogoutCall);
SaResult result = ssoTemplate.request(checkUrl);
// 校验
if(result.getCode() != null && result.getCode() == SaResult.CODE_SUCCESS) {
return result.getData();
} else {
// 将 sso-server 回应的消息作为异常抛出
throw new SaSsoException(result.getMsg()).setCode(SaSsoErrorCode.CODE_30005);
}
} else {
// q2、使用模式二:直连Redis校验ticket
return ssoTemplate.checkTicket(ticket);
}
}
// ----------- 全局默认实例 -----------
/**
* 全局默认实例
*/
public static SaSsoProcessor instance = new SaSsoProcessor();
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy