cn.dev33.satoken.sso.processor.SaSsoServerProcessor Maven / Gradle / Ivy
The newest version!
/*
* 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.processor;
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.SaSsoManager;
import cn.dev33.satoken.sso.config.SaSsoServerConfig;
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.sso.template.SaSsoServerTemplate;
import cn.dev33.satoken.sso.util.SaSsoConsts;
import cn.dev33.satoken.stp.StpLogic;
import cn.dev33.satoken.util.SaFoxUtil;
import cn.dev33.satoken.util.SaResult;
/**
* SSO 请求处理器 (Server端)
*
* @author click33
* @since 1.38.0
*/
public class SaSsoServerProcessor {
/**
* 全局默认实例
*/
public static SaSsoServerProcessor instance = new SaSsoServerProcessor();
/**
* 底层 SaSsoServerTemplate 对象
*/
public SaSsoServerTemplate ssoServerTemplate = new SaSsoServerTemplate();
// ----------- SSO-Server 端路由分发 -----------
/**
* 分发 Server 端所有请求
* @return 处理结果
*/
public Object dister() {
// 获取对象
SaRequest req = SaHolder.getRequest();
SaSsoServerConfig cfg = ssoServerTemplate.getServerConfig();
ApiName apiName = ssoServerTemplate.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();
SaSsoServerConfig cfg = ssoServerTemplate.getServerConfig();
StpLogic stpLogic = ssoServerTemplate.getStpLogic();
ParamName paramName = ssoServerTemplate.paramName;
// ---------- 此处有两种情况分开处理:
// ---- 情况1:在SSO认证中心尚未登录,需要先去登录
if( ! stpLogic.isLogin()) {
return cfg.notLoginView.get();
}
// ---- 情况2:在SSO认证中心已经登录,需要重定向回 Client 端,而这又分为两种方式:
String mode = req.getParam(paramName.mode, "");
String redirect = req.getParam(paramName.redirect);
// 方式1:直接重定向回Client端 (mode=simple)
if(mode.equals(SaSsoConsts.MODE_SIMPLE)) {
// 若 redirect 为空,则选择 homeRoute,若 homeRoute 也为空,则抛出异常
if(SaFoxUtil.isEmpty(redirect)) {
if(SaFoxUtil.isEmpty(cfg.getHomeRoute())) {
throw new SaSsoException("未指定 redirect 参数,也未配置 homeRoute 路由,无法完成重定向操作").setCode(SaSsoErrorCode.CODE_30014);
}
return res.redirect(cfg.getHomeRoute());
}
ssoServerTemplate.checkRedirectUrl(redirect);
return res.redirect(redirect);
} else {
// 方式2:带着ticket参数重定向回Client端 (mode=ticket)
// 校验提供的client是否为非法字符
String client = req.getParam(paramName.client);
if(SaSsoConsts.CLIENT_WILDCARD.equals(client)) {
throw new SaSsoException("无效 client 标识:" + client).setCode(SaSsoErrorCode.CODE_30013);
}
// 若 redirect 为空,则选择 homeRoute,若 homeRoute 也为空,则抛出异常
if(SaFoxUtil.isEmpty(redirect)) {
if(SaFoxUtil.isEmpty(cfg.getHomeRoute())) {
throw new SaSsoException("未指定 redirect 参数,也未配置 homeRoute 路由,无法完成重定向操作").setCode(SaSsoErrorCode.CODE_30014);
}
return res.redirect(cfg.getHomeRoute());
}
// 构建并跳转
String redirectUrl = ssoServerTemplate.buildRedirectUrl(stpLogic.getLoginId(), client, redirect);
// 构建成功,说明 redirect 地址合法,此时需要更新一下该账号的Session有效期
if(cfg.getAutoRenewTimeout()) {
stpLogic.renewTimeout(stpLogic.getConfigOrGlobal().getTimeout());
}
// 跳转
return res.redirect(redirectUrl);
}
}
/**
* SSO-Server端:RestAPI 登录接口
* @return 处理结果
*/
public Object ssoDoLogin() {
// 获取对象
SaRequest req = SaHolder.getRequest();
SaSsoServerConfig cfg = ssoServerTemplate.getServerConfig();
ParamName paramName = ssoServerTemplate.paramName;
// 处理
return cfg.doLoginHandle.apply(req.getParam(paramName.name), req.getParam(paramName.pwd));
}
/**
* SSO-Server端:校验ticket 获取账号id [模式三]
* @return 处理结果
*/
public Object ssoCheckTicket() {
ParamName paramName = ssoServerTemplate.paramName;
// 1、获取参数
SaRequest req = SaHolder.getRequest();
SaSsoServerConfig ssoServerConfig = ssoServerTemplate.getServerConfig();
String client = req.getParam(paramName.client);
String ticket = req.getParamNotNull(paramName.ticket);
String sloCallback = req.getParam(paramName.ssoLogoutCall);
// 2、校验提供的client是否为非法字符
if(SaSsoConsts.CLIENT_WILDCARD.equals(client)) {
return SaResult.error("无效 client 标识:" + client);
}
// 3、校验签名
if(ssoServerConfig.getIsCheckSign()) {
ssoServerTemplate.getSignTemplate(client).checkRequest(req,
paramName.client, paramName.ticket, paramName.ssoLogoutCall);
} else {
SaSsoManager.printNoCheckSignWarningByRuntime();
}
// 4、校验ticket,获取 loginId
Object loginId = ssoServerTemplate.checkTicket(ticket, client);
if(SaFoxUtil.isEmpty(loginId)) {
return SaResult.error("无效ticket:" + ticket);
}
// 5、注册此客户端的单点注销回调URL
ssoServerTemplate.registerSloCallbackUrl(loginId, client, sloCallback);
// 6、给 client 端响应结果
long remainSessionTimeout = ssoServerTemplate.getStpLogic().getSessionTimeoutByLoginId(loginId);
SaResult result = SaResult.data(loginId).set(paramName.remainSessionTimeout, remainSessionTimeout);
result = ssoServerConfig.checkTicketAppendData.apply(loginId, result);
return result;
}
/**
* SSO-Server端:单点注销
* @return 处理结果
*/
public Object ssoSignout() {
// 获取对象
SaRequest req = SaHolder.getRequest();
SaSsoServerConfig cfg = ssoServerTemplate.getServerConfig();
ParamName paramName = ssoServerTemplate.paramName;
// SSO-Server端:单点注销 [用户访问式] (不带loginId参数)
if(cfg.getIsSlo() && ! req.hasParam(paramName.loginId)) {
return ssoSignoutByUserVisit();
}
// SSO-Server端:单点注销 [Client调用式] (带loginId参数)
if(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 = ssoServerTemplate.getStpLogic().getLoginIdDefaultNull();
// 单点注销
if(SaFoxUtil.isNotEmpty(loginId)) {
ssoServerTemplate.ssoLogout(loginId);
}
// 完成
return ssoLogoutBack(req, res);
}
/**
* SSO-Server端:单点注销 [Client调用式]
* @return 处理结果
*/
public Object ssoSignoutByClientHttp() {
ParamName paramName = ssoServerTemplate.paramName;
// 获取参数
SaRequest req = SaHolder.getRequest();
String loginId = req.getParam(paramName.loginId);
String client = req.getParam(paramName.client);
// step.1 校验签名
if(ssoServerTemplate.getServerConfig().getIsCheckSign()) {
ssoServerTemplate.getSignTemplate(client).checkRequest(req, paramName.client, paramName.loginId);
} else {
SaSsoManager.printNoCheckSignWarningByRuntime();
}
// step.2 单点注销
ssoServerTemplate.ssoLogout(loginId);
// 响应
return SaResult.ok();
}
/**
* 封装:单点注销成功后返回结果
* @param req SaRequest对象
* @param res SaResponse对象
* @return 返回结果
*/
public Object ssoLogoutBack(SaRequest req, SaResponse res) {
return SaSsoProcessorHelper.ssoLogoutBack(req, res, ssoServerTemplate.paramName);
}
}