mirror of
https://gitee.com/dromara/sa-token.git
synced 2025-05-03 20:27:54 +08:00
简化单点登录集成步骤
This commit is contained in:
parent
82f7d7f78c
commit
922e746eb1
@ -1,6 +1,12 @@
|
||||
package cn.dev33.satoken.config;
|
||||
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import cn.dev33.satoken.exception.SaTokenException;
|
||||
import cn.dev33.satoken.util.SaFoxUtil;
|
||||
import cn.dev33.satoken.util.SaResult;
|
||||
|
||||
/**
|
||||
* Sa-Token-SSO 单点登录模块 配置Model
|
||||
@ -39,6 +45,10 @@ public class SaSsoConfig {
|
||||
*/
|
||||
public String sloUrl;
|
||||
|
||||
/**
|
||||
* SSO-Client端 当前Client端的单点注销回调URL (为空时自动获取)
|
||||
*/
|
||||
public String ssoLogoutCall;
|
||||
|
||||
|
||||
/**
|
||||
@ -98,9 +108,11 @@ public class SaSsoConfig {
|
||||
|
||||
/**
|
||||
* @param authUrl SSO-Server端 单点登录地址
|
||||
* @return 对象自身
|
||||
*/
|
||||
public void setAuthUrl(String authUrl) {
|
||||
public SaSsoConfig setAuthUrl(String authUrl) {
|
||||
this.authUrl = authUrl;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -113,8 +125,9 @@ public class SaSsoConfig {
|
||||
/**
|
||||
* @param checkTicketUrl SSO-Server端Ticket校验地址
|
||||
*/
|
||||
public void setCheckTicketUrl(String checkTicketUrl) {
|
||||
public SaSsoConfig setCheckTicketUrl(String checkTicketUrl) {
|
||||
this.checkTicketUrl = checkTicketUrl;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -126,15 +139,34 @@ public class SaSsoConfig {
|
||||
|
||||
/**
|
||||
* @param sloUrl SSO-Server端单点注销地址
|
||||
* @return 对象自身
|
||||
*/
|
||||
public void setSloUrl(String sloUrl) {
|
||||
public SaSsoConfig setSloUrl(String sloUrl) {
|
||||
this.sloUrl = sloUrl;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return SSO-Client端 当前Client端的单点注销回调URL (为空时自动获取)
|
||||
*/
|
||||
public String getSsoLogoutCall() {
|
||||
return ssoLogoutCall;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ssoLogoutCall SSO-Client端 当前Client端的单点注销回调URL (为空时自动获取)
|
||||
* @return 对象自身
|
||||
*/
|
||||
public SaSsoConfig setSsoLogoutCall(String ssoLogoutCall) {
|
||||
this.ssoLogoutCall = ssoLogoutCall;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "SaSsoConfig [ticketTimeout=" + ticketTimeout + ", allowUrl=" + allowUrl + ", secretkey=" + secretkey
|
||||
+ ", authUrl=" + authUrl + ", checkTicketUrl=" + checkTicketUrl + ", sloUrl=" + sloUrl + "]";
|
||||
+ ", authUrl=" + authUrl + ", checkTicketUrl=" + checkTicketUrl + ", sloUrl=" + sloUrl
|
||||
+ ", ssoLogoutCall=" + ssoLogoutCall + ", isHttp=" + isHttp + ", isSlo=" + isSlo + "]";
|
||||
}
|
||||
|
||||
|
||||
@ -147,5 +179,115 @@ public class SaSsoConfig {
|
||||
this.allowUrl = SaFoxUtil.arrayJoin(url);
|
||||
return this;
|
||||
}
|
||||
|
||||
// -------------------- SaSsoHandle 相关配置 --------------------
|
||||
|
||||
/**
|
||||
* 是否使用http请求校验ticket值
|
||||
*/
|
||||
public Boolean isHttp = false;
|
||||
|
||||
/**
|
||||
* 是否打开单点注销
|
||||
*/
|
||||
public Boolean isSlo = false;
|
||||
|
||||
|
||||
/**
|
||||
* @return isHttp 是否使用http请求校验ticket值
|
||||
*/
|
||||
public Boolean getIsHttp() {
|
||||
return isHttp;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param isHttp 是否使用http请求校验ticket值
|
||||
* @return 对象自身
|
||||
*/
|
||||
public SaSsoConfig setIsHttp(Boolean isHttp) {
|
||||
this.isHttp = isHttp;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return 是否打开单点注销
|
||||
*/
|
||||
public Boolean getIsSlo() {
|
||||
return isSlo;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param isSlo 是否打开单点注销
|
||||
* @return 对象自身
|
||||
*/
|
||||
public SaSsoConfig setIsSlo(Boolean isSlo) {
|
||||
this.isSlo = isSlo;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
// -------------------- SaSsoHandle 所有回调函数 --------------------
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* SSO-Server端:未登录时返回的View
|
||||
*/
|
||||
public Supplier<Object> notLoginView = () -> "当前会话在SSO-Server认证中心尚未登录";
|
||||
|
||||
/**
|
||||
* SSO-Server端:登录函数
|
||||
*/
|
||||
public BiFunction<String, String, Object> doLoginHandle = (name, pwd) -> SaResult.error();
|
||||
|
||||
/**
|
||||
* SSO-Client端:Ticket无效时返回的View
|
||||
*/
|
||||
public Function<String, Object> ticketInvalidView = (ticket) -> {
|
||||
// 此处向客户端提示ticket无效即可,不要重定向到SSO认证中心,否则容易引起无限重定向
|
||||
return "ticket无效: " + ticket;
|
||||
};
|
||||
|
||||
/**
|
||||
* SSO-Client端:发送Http请求的处理函数
|
||||
*/
|
||||
public Function<String, Object> sendHttp = url -> {throw new SaTokenException("请配置Http处理器");};
|
||||
|
||||
|
||||
/**
|
||||
* @param notLoginView SSO-Server端:未登录时返回的View
|
||||
* @return 对象自身
|
||||
*/
|
||||
public SaSsoConfig setNotLoginView(Supplier<Object> notLoginView) {
|
||||
this.notLoginView = notLoginView;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param doLoginHandle SSO-Server端:登录函数
|
||||
* @return 对象自身
|
||||
*/
|
||||
public SaSsoConfig setDoLoginHandle(BiFunction<String, String, Object> doLoginHandle) {
|
||||
this.doLoginHandle = doLoginHandle;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ticketInvalidView SSO-Client端:Ticket无效时返回的View
|
||||
* @return 对象自身
|
||||
*/
|
||||
public SaSsoConfig setTicketInvalidView(Function<String, Object> ticketInvalidView) {
|
||||
this.ticketInvalidView = ticketInvalidView;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param sendHttp SSO-Client端:发送Http请求的处理函数
|
||||
* @return 对象自身
|
||||
*/
|
||||
public SaSsoConfig setSendHttp(Function<String, Object> sendHttp) {
|
||||
this.sendHttp = sendHttp;
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,5 +1,7 @@
|
||||
package cn.dev33.satoken.context.model;
|
||||
|
||||
import cn.dev33.satoken.util.SaFoxUtil;
|
||||
|
||||
/**
|
||||
* Request 包装类
|
||||
* @author kong
|
||||
@ -20,6 +22,20 @@ public interface SaRequest {
|
||||
*/
|
||||
public String getParameter(String name);
|
||||
|
||||
/**
|
||||
* 在 [请求体] 里获取一个值,值为空时返回默认值
|
||||
* @param name 键
|
||||
* @param defaultValue 值为空时的默认值
|
||||
* @return 值
|
||||
*/
|
||||
public default String getParameter(String name, String defaultValue) {
|
||||
String value = getParameter(name);
|
||||
if(SaFoxUtil.isEmpty(value)) {
|
||||
return defaultValue;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 在 [请求头] 里获取一个值
|
||||
* @param name 键
|
||||
@ -52,4 +68,12 @@ public interface SaRequest {
|
||||
*/
|
||||
public String getMethod();
|
||||
|
||||
/**
|
||||
* 此请求是否为Ajax请求
|
||||
* @return see note
|
||||
*/
|
||||
public default boolean isAjax() {
|
||||
return getHeader("X-Requested-With") != null;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -46,4 +46,11 @@ public interface SaResponse {
|
||||
return this.setHeader("Server", value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 重定向
|
||||
* @param url 重定向地址
|
||||
* @return 任意值
|
||||
*/
|
||||
public Object redirect(String url);
|
||||
|
||||
}
|
||||
|
@ -7,25 +7,65 @@ package cn.dev33.satoken.sso;
|
||||
*/
|
||||
public class SaSsoConsts {
|
||||
|
||||
/** redirect参数名称 */
|
||||
public static final String REDIRECT_NAME = "redirect";
|
||||
/**
|
||||
* 所有API接口
|
||||
* @author kong
|
||||
*/
|
||||
public static final class Api {
|
||||
|
||||
/** SSO-Server端:授权地址 */
|
||||
public static String ssoAuth = "/ssoAuth";
|
||||
|
||||
/** SSO-Server端:RestAPI 登录接口 */
|
||||
public static String ssoDoLogin = "/ssoDoLogin";
|
||||
|
||||
/** SSO-Server端:校验ticket 获取账号id */
|
||||
public static String ssoCheckTicket = "/ssoCheckTicket";
|
||||
|
||||
/** SSO-Server端 (and Client端):单点注销 */
|
||||
public static String ssoLogout = "/ssoLogout";
|
||||
|
||||
/** SSO-Client端:登录地址 */
|
||||
public static String ssoLogin = "/ssoLogin";
|
||||
|
||||
/** SSO-Client端:单点注销的回调 */
|
||||
public static String ssoLogoutCall = "/ssoLogoutCall";
|
||||
|
||||
}
|
||||
|
||||
/** ticket参数名称 */
|
||||
public static final String TICKET_NAME = "ticket";
|
||||
/**
|
||||
* 所有参数名称
|
||||
* @author kong
|
||||
*/
|
||||
public static final class ParamName {
|
||||
|
||||
/** back参数名称 */
|
||||
public static final String BACK_NAME = "back";
|
||||
/** redirect参数名称 */
|
||||
public static String redirect = "redirect";
|
||||
|
||||
/** ticket参数名称 */
|
||||
public static String ticket = "ticket";
|
||||
|
||||
/** loginId参数名称 */
|
||||
public static final String LOGIN_ID_NAME = "loginId";
|
||||
/** back参数名称 */
|
||||
public static String back = "back";
|
||||
|
||||
/** secretkey参数名称 */
|
||||
public static final String SECRETKEY = "secretkey";
|
||||
|
||||
/** Client端单点注销时-回调URL 参数名称 */
|
||||
public static final String SLO_CALLBACK_NAME = "sloCallback";
|
||||
/** loginId参数名称 */
|
||||
public static String loginId = "loginId";
|
||||
|
||||
/** secretkey参数名称 */
|
||||
public static String secretkey = "secretkey";
|
||||
|
||||
/** Client端单点注销时-回调URL 参数名称 */
|
||||
public static String ssoLogoutCall = "ssoLogoutCall";
|
||||
|
||||
}
|
||||
|
||||
/** Client端单点注销回调URL的Set集合,存储在Session中使用的key */
|
||||
public static final String SLO_CALLBACK_SET_KEY = "SLO_CALLBACK_SET_KEY_";
|
||||
|
||||
/** 表示OK的返回结果 */
|
||||
public static final String OK = "ok";
|
||||
|
||||
/** 表示请求没有得到任何有效处理 */
|
||||
public static final String NOT_HANDLE = "not handle";
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,188 @@
|
||||
package cn.dev33.satoken.sso;
|
||||
|
||||
import cn.dev33.satoken.SaManager;
|
||||
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.router.SaRouter;
|
||||
import cn.dev33.satoken.sso.SaSsoConsts.Api;
|
||||
import cn.dev33.satoken.sso.SaSsoConsts.ParamName;
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
import cn.dev33.satoken.util.SaFoxUtil;
|
||||
import cn.dev33.satoken.util.SaResult;
|
||||
|
||||
/**
|
||||
* Sa-Token-SSO 单点登录请求处理类封装
|
||||
* @author kong
|
||||
*
|
||||
*/
|
||||
public class SaSsoHandle {
|
||||
|
||||
/**
|
||||
* 处理Server端请求
|
||||
* @return 处理结果
|
||||
*/
|
||||
public static Object serverRequest() {
|
||||
|
||||
// 获取变量
|
||||
SaRequest req = SaHolder.getRequest();
|
||||
SaResponse res = SaHolder.getResponse();
|
||||
SaSsoConfig sso = SaManager.getConfig().getSso();
|
||||
|
||||
// ---------- SSO-Server端:单点登录授权地址
|
||||
if(match(Api.ssoAuth)) {
|
||||
// ---------- 此处两种情况分开处理:
|
||||
// 情况1:在SSO认证中心尚未登录,则先去登登录
|
||||
if(StpUtil.isLogin() == false) {
|
||||
return sso.notLoginView.get();
|
||||
}
|
||||
// 情况2:在SSO认证中心已经登录,开始构建授权重定向地址,下放ticket
|
||||
String redirectUrl = SaSsoUtil.buildRedirectUrl(StpUtil.getLoginId(), req.getParameter(ParamName.redirect));
|
||||
return res.redirect(redirectUrl);
|
||||
}
|
||||
|
||||
// ---------- SSO-Server端:RestAPI 登录接口
|
||||
if(match(Api.ssoDoLogin)) {
|
||||
return sso.doLoginHandle.apply(req.getParameter("name"), req.getParameter("pwd"));
|
||||
}
|
||||
|
||||
// ---------- SSO-Server端:校验ticket 获取账号id
|
||||
if(match(Api.ssoCheckTicket) && sso.isHttp) {
|
||||
String ticket = req.getParameter(ParamName.ticket);
|
||||
String sloCallback = req.getParameter(ParamName.ssoLogoutCall);
|
||||
|
||||
// 校验ticket,获取对应的账号id
|
||||
Object loginId = SaSsoUtil.checkTicket(ticket);
|
||||
|
||||
// 注册此客户端的单点注销回调URL
|
||||
SaSsoUtil.registerSloCallbackUrl(loginId, sloCallback);
|
||||
|
||||
// 返回给Client端
|
||||
return loginId;
|
||||
}
|
||||
|
||||
// ---------- SSO-Server端:单点注销
|
||||
if(match(Api.ssoLogout) && sso.isSlo) {
|
||||
String loginId = req.getParameter(ParamName.loginId);
|
||||
String secretkey = req.getParameter(ParamName.secretkey);
|
||||
|
||||
// 遍历通知Client端注销会话
|
||||
SaSsoUtil.singleLogout(secretkey, loginId, url -> sso.sendHttp.apply(url));
|
||||
|
||||
// 完成
|
||||
return SaSsoConsts.OK;
|
||||
}
|
||||
|
||||
// 默认返回
|
||||
return SaSsoConsts.NOT_HANDLE;
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理Client端请求
|
||||
* @return 处理结果
|
||||
*/
|
||||
public static Object clientRequest() {
|
||||
|
||||
// 获取变量
|
||||
SaRequest req = SaHolder.getRequest();
|
||||
SaResponse res = SaHolder.getResponse();
|
||||
SaSsoConfig sso = SaManager.getConfig().getSso();
|
||||
|
||||
// ---------- SSO-Client端:登录地址
|
||||
if(match(Api.ssoLogin)) {
|
||||
String back = req.getParameter(ParamName.back, "/");
|
||||
String ticket = req.getParameter(ParamName.ticket);
|
||||
|
||||
// 如果当前Client端已经登录,则无需访问SSO认证中心,可以直接返回
|
||||
if(StpUtil.isLogin()) {
|
||||
return res.redirect(back);
|
||||
}
|
||||
/*
|
||||
* 此时有两种情况:
|
||||
* 情况1:ticket无值,说明此请求是Client端访问,需要重定向至SSO认证中心
|
||||
* 情况2:ticket有值,说明此请求从SSO认证中心重定向而来,需要根据ticket进行登录
|
||||
*/
|
||||
if(ticket == null) {
|
||||
String serverAuthUrl = SaSsoUtil.buildServerAuthUrl(SaHolder.getRequest().getUrl(), back);
|
||||
return res.redirect(serverAuthUrl);
|
||||
} else {
|
||||
// ------- 1、校验ticket,获取账号id
|
||||
Object loginId = null;
|
||||
if(sso.isHttp) {
|
||||
// 方式1:使用http请求校验ticket
|
||||
String ssoLogoutCall = null;
|
||||
if(sso.isSlo) {
|
||||
ssoLogoutCall = SaHolder.getRequest().getUrl().replace(Api.ssoLogin, Api.ssoLogoutCall);
|
||||
}
|
||||
String checkUrl = SaSsoUtil.buildCheckTicketUrl(ticket, ssoLogoutCall);
|
||||
Object body = sso.sendHttp.apply(checkUrl);
|
||||
loginId = (SaFoxUtil.isEmpty(body) ? null : body);
|
||||
} else {
|
||||
// 方式2:直连Redis校验ticket
|
||||
loginId = SaSsoUtil.checkTicket(ticket);
|
||||
}
|
||||
// ------- 2、如果loginId有值,说明ticket有效,进行登录并重定向至back地址
|
||||
if(loginId != null ) {
|
||||
StpUtil.login(loginId);
|
||||
return res.redirect(back);
|
||||
} else {
|
||||
// 如果ticket无效:
|
||||
return sso.ticketInvalidView.apply(ticket);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---------- SSO-Client端:单点注销 [模式二]
|
||||
if(match(Api.ssoLogout) && sso.isSlo && sso.isHttp == false) {
|
||||
StpUtil.logout();
|
||||
if(req.getParameter(ParamName.back) == null) {
|
||||
return SaResult.ok("单点注销成功");
|
||||
} else {
|
||||
return res.redirect(req.getParameter(ParamName.back, "/"));
|
||||
}
|
||||
}
|
||||
|
||||
// ---------- SSO-Client端:单点注销 [模式三]
|
||||
if(match(Api.ssoLogout) && sso.isSlo && sso.isHttp) {
|
||||
// 如果未登录,则无需注销
|
||||
if(StpUtil.isLogin() == false) {
|
||||
return SaResult.ok();
|
||||
}
|
||||
// 调用SSO-Server认证中心API
|
||||
String url = SaSsoUtil.buildSloUrl(StpUtil.getLoginId());
|
||||
String body = String.valueOf(sso.sendHttp.apply(url));
|
||||
if(SaSsoConsts.OK.equals(body)) {
|
||||
if(req.getParameter(ParamName.back) == null) {
|
||||
return SaResult.ok("单点注销成功");
|
||||
} else {
|
||||
return res.redirect(req.getParameter(ParamName.back, "/"));
|
||||
}
|
||||
}
|
||||
return SaResult.error("单点注销失败");
|
||||
}
|
||||
|
||||
// ---------- SSO-Client端:单点注销的回调 [模式三]
|
||||
if(match(Api.ssoLogoutCall) && sso.isSlo && sso.isHttp) {
|
||||
String loginId = req.getParameter(ParamName.loginId);
|
||||
String secretkey = req.getParameter(ParamName.secretkey);
|
||||
|
||||
SaSsoUtil.checkSecretkey(secretkey);
|
||||
StpUtil.logoutByTokenValue(StpUtil.getTokenValueByLoginId(loginId));
|
||||
return SaSsoConsts.OK;
|
||||
}
|
||||
|
||||
// 默认返回
|
||||
return SaSsoConsts.NOT_HANDLE;
|
||||
}
|
||||
|
||||
/**
|
||||
* 路由匹配算法
|
||||
* @param pattern 路由表达式
|
||||
* @return 是否可以匹配
|
||||
*/
|
||||
static boolean match(String pattern) {
|
||||
return SaRouter.isMatchCurrURI(pattern);
|
||||
}
|
||||
|
||||
}
|
@ -8,6 +8,7 @@ import java.util.Set;
|
||||
import cn.dev33.satoken.SaManager;
|
||||
import cn.dev33.satoken.config.SaSsoConfig;
|
||||
import cn.dev33.satoken.exception.SaTokenException;
|
||||
import cn.dev33.satoken.sso.SaSsoConsts.ParamName;
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
import cn.dev33.satoken.util.SaFoxUtil;
|
||||
|
||||
@ -69,7 +70,7 @@ public interface SaSsoInterface {
|
||||
|
||||
// 构建 授权重定向地址
|
||||
redirect = encodeBackParam(redirect);
|
||||
String redirectUrl = SaFoxUtil.joinParam(redirect, SaSsoConsts.TICKET_NAME, ticket);
|
||||
String redirectUrl = SaFoxUtil.joinParam(redirect, ParamName.ticket, ticket);
|
||||
return redirectUrl;
|
||||
}
|
||||
|
||||
@ -152,8 +153,8 @@ public interface SaSsoInterface {
|
||||
back = SaFoxUtil.encodeUrl(back);
|
||||
|
||||
// 拼接最终地址,格式示例:serverAuthUrl = http://xxx.com?redirectUrl=xxx.com?back=xxx.com
|
||||
clientLoginUrl = SaFoxUtil.joinParam(clientLoginUrl, SaSsoConsts.BACK_NAME, back);
|
||||
String serverAuthUrl = SaFoxUtil.joinParam(serverUrl, SaSsoConsts.REDIRECT_NAME, clientLoginUrl);
|
||||
clientLoginUrl = SaFoxUtil.joinParam(clientLoginUrl, ParamName.back, back);
|
||||
String serverAuthUrl = SaFoxUtil.joinParam(serverUrl, ParamName.redirect, clientLoginUrl);
|
||||
|
||||
// 返回
|
||||
return serverAuthUrl;
|
||||
@ -167,16 +168,16 @@ public interface SaSsoInterface {
|
||||
public default String encodeBackParam(String url) {
|
||||
|
||||
// 获取back参数所在位置
|
||||
int index = url.indexOf("?" + SaSsoConsts.BACK_NAME + "=");
|
||||
int index = url.indexOf("?" + ParamName.back + "=");
|
||||
if(index == -1) {
|
||||
index = url.indexOf("&" + SaSsoConsts.BACK_NAME + "=");
|
||||
index = url.indexOf("&" + ParamName.back + "=");
|
||||
if(index == -1) {
|
||||
return url;
|
||||
}
|
||||
}
|
||||
|
||||
// 开始编码
|
||||
int length = SaSsoConsts.BACK_NAME.length() + 2;
|
||||
int length = ParamName.back.length() + 2;
|
||||
String back = url.substring(index + length);
|
||||
back = SaFoxUtil.encodeUrl(back);
|
||||
|
||||
@ -210,16 +211,16 @@ public interface SaSsoInterface {
|
||||
/**
|
||||
* 构建URL:校验ticket的URL
|
||||
* @param ticket ticket码
|
||||
* @param sloCallbackUrl 单点注销时的回调URL
|
||||
* @param ssoLogoutCallUrl 单点注销时的回调URL
|
||||
* @return 构建完毕的URL
|
||||
*/
|
||||
public default String buildCheckTicketUrl(String ticket, String sloCallbackUrl) {
|
||||
public default String buildCheckTicketUrl(String ticket, String ssoLogoutCallUrl) {
|
||||
String url = SaManager.getConfig().getSso().getCheckTicketUrl();
|
||||
// 拼接ticket参数
|
||||
url = SaFoxUtil.joinParam(url, SaSsoConsts.TICKET_NAME, ticket);
|
||||
// 拼接ticket参数
|
||||
url = SaFoxUtil.joinParam(url, ParamName.ticket, ticket);
|
||||
// 拼接单点注销时的回调URL
|
||||
if(sloCallbackUrl != null) {
|
||||
url = SaFoxUtil.joinParam(url, SaSsoConsts.SLO_CALLBACK_NAME, sloCallbackUrl);
|
||||
if(ssoLogoutCallUrl != null) {
|
||||
url = SaFoxUtil.joinParam(url, ParamName.ssoLogoutCall, ssoLogoutCallUrl);
|
||||
}
|
||||
// 返回
|
||||
return url;
|
||||
@ -251,8 +252,8 @@ public interface SaSsoInterface {
|
||||
|
||||
for (String url : urlSet) {
|
||||
// 拼接:login参数、秘钥参数
|
||||
url = SaFoxUtil.joinParam(url, SaSsoConsts.LOGIN_ID_NAME, loginId);
|
||||
url = SaFoxUtil.joinParam(url, SaSsoConsts.SECRETKEY, secretkey);
|
||||
url = SaFoxUtil.joinParam(url, ParamName.loginId, loginId);
|
||||
url = SaFoxUtil.joinParam(url, ParamName.secretkey, secretkey);
|
||||
// 调用
|
||||
fun.run(url);
|
||||
}
|
||||
@ -266,8 +267,8 @@ public interface SaSsoInterface {
|
||||
public default String buildSloUrl(Object loginId) {
|
||||
SaSsoConfig ssoConfig = SaManager.getConfig().getSso();
|
||||
String url = ssoConfig.getSloUrl();
|
||||
url = SaFoxUtil.joinParam(url, SaSsoConsts.LOGIN_ID_NAME, loginId);
|
||||
url = SaFoxUtil.joinParam(url, SaSsoConsts.SECRETKEY, ssoConfig.getSecretkey());
|
||||
url = SaFoxUtil.joinParam(url, ParamName.loginId, loginId);
|
||||
url = SaFoxUtil.joinParam(url, ParamName.secretkey, ssoConfig.getSecretkey());
|
||||
return url;
|
||||
}
|
||||
|
||||
|
@ -102,11 +102,11 @@ public class SaSsoUtil {
|
||||
/**
|
||||
* 构建URL:校验ticket的URL
|
||||
* @param ticket ticket码
|
||||
* @param sloCallbackUrl 单点注销时的回调URL (如果不需要单点注销功能,此值可以填null)
|
||||
* @param ssoLogoutCallUrl 单点注销时的回调URL
|
||||
* @return 构建完毕的URL
|
||||
*/
|
||||
public static String buildCheckTicketUrl(String ticket, String sloCallbackUrl) {
|
||||
return saSsoInterface.buildCheckTicketUrl(ticket, sloCallbackUrl);
|
||||
public static String buildCheckTicketUrl(String ticket, String ssoLogoutCallUrl) {
|
||||
return saSsoInterface.buildCheckTicketUrl(ticket, ssoLogoutCallUrl);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -145,5 +145,5 @@ public class SaSsoUtil {
|
||||
public static void singleLogout(String secretkey, Object loginId, CallSloUrlFunction fun) {
|
||||
saSsoInterface.singleLogout(secretkey, loginId, fun);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -54,11 +54,11 @@ public class SaFoxUtil {
|
||||
}
|
||||
|
||||
/**
|
||||
* 指定字符串是否为null或者空字符串
|
||||
* @param str 指定字符串
|
||||
* 指定元素是否为null或者空字符串
|
||||
* @param str 指定元素
|
||||
* @return 是否为null或者空字符串
|
||||
*/
|
||||
public static boolean isEmpty(String str) {
|
||||
public static boolean isEmpty(Object str) {
|
||||
return str == null || "".equals(str);
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,96 @@
|
||||
package cn.dev33.satoken.util;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
|
||||
/**
|
||||
* 对Ajax请求返回Json格式数据的简易封装
|
||||
* @author kong
|
||||
*
|
||||
*/
|
||||
public class SaResult implements Serializable{
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public static final int CODE_SUCCESS = 200;
|
||||
public static final int CODE_ERROR = 500;
|
||||
|
||||
/**
|
||||
* 状态码
|
||||
*/
|
||||
public int code;
|
||||
|
||||
/**
|
||||
* 描述信息
|
||||
*/
|
||||
public String msg;
|
||||
|
||||
/**
|
||||
* 携带对象
|
||||
*/
|
||||
public Object data;
|
||||
|
||||
/**
|
||||
* 给code赋值,连缀风格
|
||||
*/
|
||||
public SaResult setCode(int code) {
|
||||
this.code = code;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 给msg赋值,连缀风格
|
||||
*/
|
||||
public SaResult setMsg(String msg) {
|
||||
this.msg = msg;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 给data赋值,连缀风格
|
||||
*/
|
||||
public SaResult setData(Object data) {
|
||||
this.data = data;
|
||||
return this;
|
||||
}
|
||||
|
||||
// ============================ 构建 ==================================
|
||||
|
||||
public SaResult(int code, String msg, Object data) {
|
||||
this.code = code;
|
||||
this.msg = msg;
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
// 构建成功
|
||||
public static SaResult ok() {
|
||||
return new SaResult(CODE_SUCCESS, "ok", null);
|
||||
}
|
||||
public static SaResult ok(String msg) {
|
||||
return new SaResult(CODE_SUCCESS, msg, null);
|
||||
}
|
||||
public static SaResult data(Object data) {
|
||||
return new SaResult(CODE_SUCCESS, "ok", data);
|
||||
}
|
||||
|
||||
// 构建失败
|
||||
public static SaResult error() {
|
||||
return new SaResult(CODE_ERROR, "error", null);
|
||||
}
|
||||
public static SaResult error(String msg) {
|
||||
return new SaResult(CODE_ERROR, msg, null);
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see java.lang.Object#toString()
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return "{"
|
||||
+ "\"code\": " + this.code
|
||||
+ ", \"msg\": \"" + this.msg + "\""
|
||||
+ ", \"data\": \"" + this.data + "\""
|
||||
+ "}";
|
||||
}
|
||||
|
||||
}
|
@ -2,10 +2,8 @@ package com.pj.sso;
|
||||
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.servlet.ModelAndView;
|
||||
|
||||
import cn.dev33.satoken.context.SaHolder;
|
||||
import cn.dev33.satoken.sso.SaSsoUtil;
|
||||
import cn.dev33.satoken.sso.SaSsoHandle;
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
|
||||
/**
|
||||
@ -20,40 +18,16 @@ public class SsoClientController {
|
||||
public String index() {
|
||||
String str = "<h2>Sa-Token SSO-Client 应用端</h2>" +
|
||||
"<p>当前会话是否登录:" + StpUtil.isLogin() + "</p>" +
|
||||
"<p><a href=\"javascript:location.href='/ssoLogin?back=' + encodeURIComponent(location.href);\">登录</a></p>";
|
||||
"<p><a href=\"javascript:location.href='/ssoLogin?back=' + encodeURIComponent(location.href);\">登录</a> " +
|
||||
"<a href=\"javascript:location.href='/ssoLogout?back=' + encodeURIComponent(location.href);\">注销</a></p>";
|
||||
// "<a href='/ssoLogout' target='_blank'>注销</a></p>"; // 上面是[跳页面]方式,这个是[RestAPI]方式 区别在于是否加了back参数
|
||||
return str;
|
||||
}
|
||||
|
||||
// SSO-Client端:登录地址
|
||||
@RequestMapping("ssoLogin")
|
||||
public Object ssoLogin(String back, String ticket) {
|
||||
// 如果当前Client端已经登录,则无需访问SSO认证中心,可以直接返回
|
||||
if(StpUtil.isLogin()) {
|
||||
return new ModelAndView("redirect:" + back);
|
||||
}
|
||||
/*
|
||||
* 接下来两种情况:
|
||||
* ticket无值,说明此请求是Client端访问,需要重定向至SSO认证中心
|
||||
* ticket有值,说明此请求从SSO认证中心重定向而来,需要根据ticket进行登录
|
||||
*/
|
||||
if(ticket == null) {
|
||||
String serverAuthUrl = SaSsoUtil.buildServerAuthUrl(SaHolder.getRequest().getUrl(), back);
|
||||
return new ModelAndView("redirect:" + serverAuthUrl);
|
||||
} else {
|
||||
Object loginId = checkTicket(ticket);
|
||||
if(loginId != null ) {
|
||||
// loginId有值,说明ticket有效
|
||||
StpUtil.login(loginId);
|
||||
return new ModelAndView("redirect:" + back);
|
||||
}
|
||||
// 此处向客户端提示ticket无效即可,不要重定向到SSO认证中心,否则容易引起无限重定向
|
||||
return "ticket无效: " + ticket;
|
||||
}
|
||||
}
|
||||
|
||||
// SSO-Client端:校验ticket,获取账号id
|
||||
private Object checkTicket(String ticket) {
|
||||
return SaSsoUtil.checkTicket(ticket);
|
||||
// SSO-Client端:处理所有SSO相关请求
|
||||
@RequestMapping("/sso*")
|
||||
public Object ssoRequest() {
|
||||
return SaSsoHandle.clientRequest();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,162 +0,0 @@
|
||||
package com.pj.util;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
|
||||
|
||||
/**
|
||||
* ajax请求返回Json格式数据的封装
|
||||
*/
|
||||
public class AjaxJson implements Serializable{
|
||||
|
||||
private static final long serialVersionUID = 1L; // 序列化版本号
|
||||
|
||||
public static final int CODE_SUCCESS = 200; // 成功状态码
|
||||
public static final int CODE_ERROR = 500; // 错误状态码
|
||||
public static final int CODE_WARNING = 501; // 警告状态码
|
||||
public static final int CODE_NOT_JUR = 403; // 无权限状态码
|
||||
public static final int CODE_NOT_LOGIN = 401; // 未登录状态码
|
||||
public static final int CODE_INVALID_REQUEST = 400; // 无效请求状态码
|
||||
|
||||
public int code; // 状态码
|
||||
public String msg; // 描述信息
|
||||
public Object data; // 携带对象
|
||||
public Long dataCount; // 数据总数,用于分页
|
||||
|
||||
/**
|
||||
* 返回code
|
||||
* @return
|
||||
*/
|
||||
public int getCode() {
|
||||
return this.code;
|
||||
}
|
||||
|
||||
/**
|
||||
* 给msg赋值,连缀风格
|
||||
*/
|
||||
public AjaxJson setMsg(String msg) {
|
||||
this.msg = msg;
|
||||
return this;
|
||||
}
|
||||
public String getMsg() {
|
||||
return this.msg;
|
||||
}
|
||||
|
||||
/**
|
||||
* 给data赋值,连缀风格
|
||||
*/
|
||||
public AjaxJson setData(Object data) {
|
||||
this.data = data;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将data还原为指定类型并返回
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T getData(Class<T> cs) {
|
||||
return (T) data;
|
||||
}
|
||||
|
||||
// ============================ 构建 ==================================
|
||||
|
||||
public AjaxJson(int code, String msg, Object data, Long dataCount) {
|
||||
this.code = code;
|
||||
this.msg = msg;
|
||||
this.data = data;
|
||||
this.dataCount = dataCount;
|
||||
}
|
||||
|
||||
// 返回成功
|
||||
public static AjaxJson getSuccess() {
|
||||
return new AjaxJson(CODE_SUCCESS, "ok", null, null);
|
||||
}
|
||||
public static AjaxJson getSuccess(String msg) {
|
||||
return new AjaxJson(CODE_SUCCESS, msg, null, null);
|
||||
}
|
||||
public static AjaxJson getSuccess(String msg, Object data) {
|
||||
return new AjaxJson(CODE_SUCCESS, msg, data, null);
|
||||
}
|
||||
public static AjaxJson getSuccessData(Object data) {
|
||||
return new AjaxJson(CODE_SUCCESS, "ok", data, null);
|
||||
}
|
||||
public static AjaxJson getSuccessArray(Object... data) {
|
||||
return new AjaxJson(CODE_SUCCESS, "ok", data, null);
|
||||
}
|
||||
|
||||
// 返回失败
|
||||
public static AjaxJson getError() {
|
||||
return new AjaxJson(CODE_ERROR, "error", null, null);
|
||||
}
|
||||
public static AjaxJson getError(String msg) {
|
||||
return new AjaxJson(CODE_ERROR, msg, null, null);
|
||||
}
|
||||
|
||||
// 返回警告
|
||||
public static AjaxJson getWarning() {
|
||||
return new AjaxJson(CODE_ERROR, "warning", null, null);
|
||||
}
|
||||
public static AjaxJson getWarning(String msg) {
|
||||
return new AjaxJson(CODE_WARNING, msg, null, null);
|
||||
}
|
||||
|
||||
// 返回未登录
|
||||
public static AjaxJson getNotLogin() {
|
||||
return new AjaxJson(CODE_NOT_LOGIN, "未登录,请登录后再次访问", null, null);
|
||||
}
|
||||
|
||||
// 返回没有权限的
|
||||
public static AjaxJson getNotJur(String msg) {
|
||||
return new AjaxJson(CODE_NOT_JUR, msg, null, null);
|
||||
}
|
||||
|
||||
// 返回一个自定义状态码的
|
||||
public static AjaxJson get(int code, String msg){
|
||||
return new AjaxJson(code, msg, null, null);
|
||||
}
|
||||
|
||||
// 返回分页和数据的
|
||||
public static AjaxJson getPageData(Long dataCount, Object data){
|
||||
return new AjaxJson(CODE_SUCCESS, "ok", data, dataCount);
|
||||
}
|
||||
|
||||
// 返回,根据受影响行数的(大于0=ok,小于0=error)
|
||||
public static AjaxJson getByLine(int line){
|
||||
if(line > 0){
|
||||
return getSuccess("ok", line);
|
||||
}
|
||||
return getError("error").setData(line);
|
||||
}
|
||||
|
||||
// 返回,根据布尔值来确定最终结果的 (true=ok,false=error)
|
||||
public static AjaxJson getByBoolean(boolean b){
|
||||
return b ? getSuccess("ok") : getError("error");
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see java.lang.Object#toString()
|
||||
*/
|
||||
@SuppressWarnings("rawtypes")
|
||||
@Override
|
||||
public String toString() {
|
||||
String data_string = null;
|
||||
if(data == null){
|
||||
|
||||
} else if(data instanceof List){
|
||||
data_string = "List(length=" + ((List)data).size() + ")";
|
||||
} else {
|
||||
data_string = data.toString();
|
||||
}
|
||||
return "{"
|
||||
+ "\"code\": " + this.getCode()
|
||||
+ ", \"msg\": \"" + this.getMsg() + "\""
|
||||
+ ", \"data\": " + data_string
|
||||
+ ", \"dataCount\": " + dataCount
|
||||
+ "}";
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
@ -14,6 +14,8 @@ sa-token:
|
||||
sso:
|
||||
# SSO-Server端 单点登录地址
|
||||
auth-url: http://sa-sso-server.com:9000/ssoAuth
|
||||
# 是否打开单点注销接口
|
||||
is-slo: true
|
||||
|
||||
# 配置Sa-Token单独使用的Redis连接 (此处需要和SSO-Server端连接同一个Redis)
|
||||
alone-redis:
|
||||
|
@ -3,19 +3,19 @@ package com.pj.sso;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||
|
||||
import com.pj.util.AjaxJson;
|
||||
import cn.dev33.satoken.util.SaResult;
|
||||
|
||||
/**
|
||||
* 全局异常处理
|
||||
*/
|
||||
@RestControllerAdvice
|
||||
public class GlobalException {
|
||||
public class ExceptionHandle {
|
||||
|
||||
// 全局异常拦截(拦截项目中的所有异常)
|
||||
// 全局异常拦截
|
||||
@ExceptionHandler
|
||||
public AjaxJson handlerException(Exception e){
|
||||
public SaResult handlerException(Exception e) {
|
||||
e.printStackTrace();
|
||||
return AjaxJson.getError(e.getMessage());
|
||||
return SaResult.error(e.getMessage());
|
||||
}
|
||||
|
||||
}
|
@ -1,13 +1,14 @@
|
||||
package com.pj.sso;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.servlet.ModelAndView;
|
||||
|
||||
import com.pj.util.AjaxJson;
|
||||
|
||||
import cn.dev33.satoken.sso.SaSsoUtil;
|
||||
import cn.dev33.satoken.config.SaTokenConfig;
|
||||
import cn.dev33.satoken.sso.SaSsoHandle;
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
import cn.dev33.satoken.util.SaResult;
|
||||
|
||||
/**
|
||||
* Sa-Token-SSO Server端 Controller
|
||||
@ -17,33 +18,30 @@ import cn.dev33.satoken.stp.StpUtil;
|
||||
@RestController
|
||||
public class SsoServerController {
|
||||
|
||||
// SSO-Server端:授权地址,跳转到登录页面
|
||||
@RequestMapping("ssoAuth")
|
||||
public Object ssoAuth(String redirect) {
|
||||
/*
|
||||
* 此处两种情况分开处理:
|
||||
* 1、如果在SSO认证中心尚未登录,则先去登登录
|
||||
* 2、如果在SSO认证中心尚已登录,则开始对redirect地址下放ticket引导授权
|
||||
*/
|
||||
// 情况1:尚未登录
|
||||
if(StpUtil.isLogin() == false) {
|
||||
// return "当前会话在SSO-Server端尚未登录,请先访问<a href='/doLogin?name=sa&pwd=123456' target='_blank'> doLogin登录 </a>进行登录之后,刷新页面开始授权";
|
||||
return new ModelAndView("sa-login.html");
|
||||
}
|
||||
// 情况2:已经登录,开始构建授权重定向地址,下放ticket
|
||||
String redirectUrl = SaSsoUtil.buildRedirectUrl(StpUtil.getLoginId(), redirect);
|
||||
return new ModelAndView("redirect:" + redirectUrl);
|
||||
// SSO-Server端:处理所有SSO相关请求
|
||||
@RequestMapping("/sso*")
|
||||
public Object ssoRequest() {
|
||||
return SaSsoHandle.serverRequest();
|
||||
}
|
||||
|
||||
// SSO-Server端:登录接口
|
||||
@RequestMapping("doLogin")
|
||||
public AjaxJson doLogin(String name, String pwd) {
|
||||
// 此处仅做模拟登录,真实环境应该查询数据进行登录
|
||||
if("sa".equals(name) && "123456".equals(pwd)) {
|
||||
StpUtil.login(10001);
|
||||
return AjaxJson.getSuccess("登录成功!");
|
||||
}
|
||||
return AjaxJson.getError("登录失败!");
|
||||
// 配置SSO相关参数
|
||||
@Autowired
|
||||
private void configSso(SaTokenConfig cfg) {
|
||||
cfg.sso
|
||||
// 配置:未登录时返回的View
|
||||
.setNotLoginView(() -> {
|
||||
return new ModelAndView("sa-login.html");
|
||||
})
|
||||
// 配置:登录处理函数
|
||||
.setDoLoginHandle((name, pwd) -> {
|
||||
// 此处仅做模拟登录,真实环境应该查询数据进行登录
|
||||
if("sa".equals(name) && "123456".equals(pwd)) {
|
||||
StpUtil.login(10001);
|
||||
return SaResult.ok("登录成功!");
|
||||
}
|
||||
return SaResult.error("登录失败!");
|
||||
})
|
||||
;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,162 +0,0 @@
|
||||
package com.pj.util;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
|
||||
|
||||
/**
|
||||
* ajax请求返回Json格式数据的封装
|
||||
*/
|
||||
public class AjaxJson implements Serializable{
|
||||
|
||||
private static final long serialVersionUID = 1L; // 序列化版本号
|
||||
|
||||
public static final int CODE_SUCCESS = 200; // 成功状态码
|
||||
public static final int CODE_ERROR = 500; // 错误状态码
|
||||
public static final int CODE_WARNING = 501; // 警告状态码
|
||||
public static final int CODE_NOT_JUR = 403; // 无权限状态码
|
||||
public static final int CODE_NOT_LOGIN = 401; // 未登录状态码
|
||||
public static final int CODE_INVALID_REQUEST = 400; // 无效请求状态码
|
||||
|
||||
public int code; // 状态码
|
||||
public String msg; // 描述信息
|
||||
public Object data; // 携带对象
|
||||
public Long dataCount; // 数据总数,用于分页
|
||||
|
||||
/**
|
||||
* 返回code
|
||||
* @return
|
||||
*/
|
||||
public int getCode() {
|
||||
return this.code;
|
||||
}
|
||||
|
||||
/**
|
||||
* 给msg赋值,连缀风格
|
||||
*/
|
||||
public AjaxJson setMsg(String msg) {
|
||||
this.msg = msg;
|
||||
return this;
|
||||
}
|
||||
public String getMsg() {
|
||||
return this.msg;
|
||||
}
|
||||
|
||||
/**
|
||||
* 给data赋值,连缀风格
|
||||
*/
|
||||
public AjaxJson setData(Object data) {
|
||||
this.data = data;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将data还原为指定类型并返回
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T getData(Class<T> cs) {
|
||||
return (T) data;
|
||||
}
|
||||
|
||||
// ============================ 构建 ==================================
|
||||
|
||||
public AjaxJson(int code, String msg, Object data, Long dataCount) {
|
||||
this.code = code;
|
||||
this.msg = msg;
|
||||
this.data = data;
|
||||
this.dataCount = dataCount;
|
||||
}
|
||||
|
||||
// 返回成功
|
||||
public static AjaxJson getSuccess() {
|
||||
return new AjaxJson(CODE_SUCCESS, "ok", null, null);
|
||||
}
|
||||
public static AjaxJson getSuccess(String msg) {
|
||||
return new AjaxJson(CODE_SUCCESS, msg, null, null);
|
||||
}
|
||||
public static AjaxJson getSuccess(String msg, Object data) {
|
||||
return new AjaxJson(CODE_SUCCESS, msg, data, null);
|
||||
}
|
||||
public static AjaxJson getSuccessData(Object data) {
|
||||
return new AjaxJson(CODE_SUCCESS, "ok", data, null);
|
||||
}
|
||||
public static AjaxJson getSuccessArray(Object... data) {
|
||||
return new AjaxJson(CODE_SUCCESS, "ok", data, null);
|
||||
}
|
||||
|
||||
// 返回失败
|
||||
public static AjaxJson getError() {
|
||||
return new AjaxJson(CODE_ERROR, "error", null, null);
|
||||
}
|
||||
public static AjaxJson getError(String msg) {
|
||||
return new AjaxJson(CODE_ERROR, msg, null, null);
|
||||
}
|
||||
|
||||
// 返回警告
|
||||
public static AjaxJson getWarning() {
|
||||
return new AjaxJson(CODE_ERROR, "warning", null, null);
|
||||
}
|
||||
public static AjaxJson getWarning(String msg) {
|
||||
return new AjaxJson(CODE_WARNING, msg, null, null);
|
||||
}
|
||||
|
||||
// 返回未登录
|
||||
public static AjaxJson getNotLogin() {
|
||||
return new AjaxJson(CODE_NOT_LOGIN, "未登录,请登录后再次访问", null, null);
|
||||
}
|
||||
|
||||
// 返回没有权限的
|
||||
public static AjaxJson getNotJur(String msg) {
|
||||
return new AjaxJson(CODE_NOT_JUR, msg, null, null);
|
||||
}
|
||||
|
||||
// 返回一个自定义状态码的
|
||||
public static AjaxJson get(int code, String msg){
|
||||
return new AjaxJson(code, msg, null, null);
|
||||
}
|
||||
|
||||
// 返回分页和数据的
|
||||
public static AjaxJson getPageData(Long dataCount, Object data){
|
||||
return new AjaxJson(CODE_SUCCESS, "ok", data, dataCount);
|
||||
}
|
||||
|
||||
// 返回,根据受影响行数的(大于0=ok,小于0=error)
|
||||
public static AjaxJson getByLine(int line){
|
||||
if(line > 0){
|
||||
return getSuccess("ok", line);
|
||||
}
|
||||
return getError("error").setData(line);
|
||||
}
|
||||
|
||||
// 返回,根据布尔值来确定最终结果的 (true=ok,false=error)
|
||||
public static AjaxJson getByBoolean(boolean b){
|
||||
return b ? getSuccess("ok") : getError("error");
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see java.lang.Object#toString()
|
||||
*/
|
||||
@SuppressWarnings("rawtypes")
|
||||
@Override
|
||||
public String toString() {
|
||||
String data_string = null;
|
||||
if(data == null){
|
||||
|
||||
} else if(data instanceof List){
|
||||
data_string = "List(length=" + ((List)data).size() + ")";
|
||||
} else {
|
||||
data_string = data.toString();
|
||||
}
|
||||
return "{"
|
||||
+ "\"code\": " + this.getCode()
|
||||
+ ", \"msg\": \"" + this.getMsg() + "\""
|
||||
+ ", \"data\": " + data_string
|
||||
+ ", \"dataCount\": " + dataCount
|
||||
+ "}";
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
@ -20,7 +20,7 @@ $('.login-btn').click(function(){
|
||||
// 开始登录
|
||||
setTimeout(function() {
|
||||
$.ajax({
|
||||
url: "doLogin",
|
||||
url: "ssoDoLogin",
|
||||
type: "post",
|
||||
data: {
|
||||
name: $('[name=name]').val(),
|
||||
|
@ -1,15 +1,14 @@
|
||||
package com.pj.sso;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.servlet.ModelAndView;
|
||||
|
||||
import com.ejlchina.okhttps.OkHttps;
|
||||
|
||||
import cn.dev33.satoken.context.SaHolder;
|
||||
import cn.dev33.satoken.sso.SaSsoUtil;
|
||||
import cn.dev33.satoken.config.SaTokenConfig;
|
||||
import cn.dev33.satoken.sso.SaSsoHandle;
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
import cn.dev33.satoken.util.SaFoxUtil;
|
||||
|
||||
/**
|
||||
* Sa-Token-SSO Client端 Controller
|
||||
@ -28,44 +27,21 @@ public class SsoClientController {
|
||||
return str;
|
||||
}
|
||||
|
||||
// SSO-Client端:登录地址
|
||||
@RequestMapping("ssoLogin")
|
||||
public Object ssoLogin(String back, String ticket) {
|
||||
// 如果当前Client端已经登录,则无需访问SSO认证中心,可以直接返回
|
||||
if(StpUtil.isLogin()) {
|
||||
return new ModelAndView("redirect:" + back);
|
||||
}
|
||||
/*
|
||||
* 接下来两种情况:
|
||||
* ticket无值,说明此请求是Client端访问,需要重定向至SSO认证中心
|
||||
* ticket有值,说明此请求从SSO认证中心重定向而来,需要根据ticket进行登录
|
||||
*/
|
||||
if(ticket == null) {
|
||||
String serverAuthUrl = SaSsoUtil.buildServerAuthUrl(SaHolder.getRequest().getUrl(), back);
|
||||
return new ModelAndView("redirect:" + serverAuthUrl);
|
||||
} else {
|
||||
Object loginId = checkTicket(ticket);
|
||||
if(loginId != null ) {
|
||||
// loginId有值,说明ticket有效
|
||||
StpUtil.login(loginId);
|
||||
return new ModelAndView("redirect:" + back);
|
||||
}
|
||||
// 此处向客户端提示ticket无效即可,不要重定向到SSO认证中心,否则容易引起无限重定向
|
||||
return "ticket无效: " + ticket;
|
||||
}
|
||||
// SSO-Client端:处理所有SSO相关请求
|
||||
@RequestMapping("/sso*")
|
||||
public Object ssoRequest() {
|
||||
return SaSsoHandle.clientRequest();
|
||||
}
|
||||
|
||||
// SSO-Client端:校验ticket码,获取对应的账号id
|
||||
private Object checkTicket(String ticket) {
|
||||
// 构建单点注销的回调URL(不需要单点注销时此值可填null )
|
||||
String sloCallback = SaHolder.getRequest().getUrl().replace("/ssoLogin", "/sloCallback");
|
||||
|
||||
// 使用OkHttps请求SSO-Server端,校验ticket
|
||||
String checkUrl = SaSsoUtil.buildCheckTicketUrl(ticket, sloCallback);
|
||||
String loginId = OkHttps.sync(checkUrl).get().getBody().toString();
|
||||
|
||||
// 判断返回值是否为有效账号Id
|
||||
return (SaFoxUtil.isEmpty(loginId) ? null : loginId);
|
||||
|
||||
// 配置SSO相关参数
|
||||
@Autowired
|
||||
private void configSso(SaTokenConfig cfg) {
|
||||
cfg.sso
|
||||
// 配置Http请求处理器
|
||||
.setSendHttp(url -> {
|
||||
return OkHttps.sync(url).get().getBody().toString();
|
||||
})
|
||||
;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,50 +0,0 @@
|
||||
package com.pj.sso;
|
||||
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import com.ejlchina.okhttps.OkHttps;
|
||||
import com.pj.util.AjaxJson;
|
||||
|
||||
import cn.dev33.satoken.sso.SaSsoUtil;
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
|
||||
/**
|
||||
* Sa-Token-SSO Client端 单点注销 Controller
|
||||
* @author kong
|
||||
*/
|
||||
@RestController
|
||||
public class SsoClientLogoutController {
|
||||
|
||||
// SSO-Client端:单端注销 (其它Client端会话不受影响)
|
||||
@RequestMapping("logout")
|
||||
public AjaxJson logout() {
|
||||
StpUtil.logout();
|
||||
return AjaxJson.getSuccess();
|
||||
}
|
||||
|
||||
// SSO-Client端:单点注销 (所有端一起下线)
|
||||
@RequestMapping("ssoLogout")
|
||||
public AjaxJson ssoLogout() {
|
||||
// 如果未登录,则无需注销
|
||||
if(StpUtil.isLogin() == false) {
|
||||
return AjaxJson.getSuccess();
|
||||
}
|
||||
// 调用SSO-Server认证中心API
|
||||
String url = SaSsoUtil.buildSloUrl(StpUtil.getLoginId());
|
||||
String res = OkHttps.sync(url).get().getBody().toString();
|
||||
if(res.equals("ok")) {
|
||||
return AjaxJson.getSuccess("单点注销成功");
|
||||
}
|
||||
return AjaxJson.getError("单点注销失败");
|
||||
}
|
||||
|
||||
// 单点注销的回调
|
||||
@RequestMapping("sloCallback")
|
||||
public String sloCallback(String loginId, String secretkey) {
|
||||
SaSsoUtil.checkSecretkey(secretkey);
|
||||
StpUtil.logoutByLoginId(loginId);
|
||||
return "ok";
|
||||
}
|
||||
|
||||
}
|
@ -1,162 +0,0 @@
|
||||
package com.pj.util;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
|
||||
|
||||
/**
|
||||
* ajax请求返回Json格式数据的封装
|
||||
*/
|
||||
public class AjaxJson implements Serializable{
|
||||
|
||||
private static final long serialVersionUID = 1L; // 序列化版本号
|
||||
|
||||
public static final int CODE_SUCCESS = 200; // 成功状态码
|
||||
public static final int CODE_ERROR = 500; // 错误状态码
|
||||
public static final int CODE_WARNING = 501; // 警告状态码
|
||||
public static final int CODE_NOT_JUR = 403; // 无权限状态码
|
||||
public static final int CODE_NOT_LOGIN = 401; // 未登录状态码
|
||||
public static final int CODE_INVALID_REQUEST = 400; // 无效请求状态码
|
||||
|
||||
public int code; // 状态码
|
||||
public String msg; // 描述信息
|
||||
public Object data; // 携带对象
|
||||
public Long dataCount; // 数据总数,用于分页
|
||||
|
||||
/**
|
||||
* 返回code
|
||||
* @return
|
||||
*/
|
||||
public int getCode() {
|
||||
return this.code;
|
||||
}
|
||||
|
||||
/**
|
||||
* 给msg赋值,连缀风格
|
||||
*/
|
||||
public AjaxJson setMsg(String msg) {
|
||||
this.msg = msg;
|
||||
return this;
|
||||
}
|
||||
public String getMsg() {
|
||||
return this.msg;
|
||||
}
|
||||
|
||||
/**
|
||||
* 给data赋值,连缀风格
|
||||
*/
|
||||
public AjaxJson setData(Object data) {
|
||||
this.data = data;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将data还原为指定类型并返回
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T getData(Class<T> cs) {
|
||||
return (T) data;
|
||||
}
|
||||
|
||||
// ============================ 构建 ==================================
|
||||
|
||||
public AjaxJson(int code, String msg, Object data, Long dataCount) {
|
||||
this.code = code;
|
||||
this.msg = msg;
|
||||
this.data = data;
|
||||
this.dataCount = dataCount;
|
||||
}
|
||||
|
||||
// 返回成功
|
||||
public static AjaxJson getSuccess() {
|
||||
return new AjaxJson(CODE_SUCCESS, "ok", null, null);
|
||||
}
|
||||
public static AjaxJson getSuccess(String msg) {
|
||||
return new AjaxJson(CODE_SUCCESS, msg, null, null);
|
||||
}
|
||||
public static AjaxJson getSuccess(String msg, Object data) {
|
||||
return new AjaxJson(CODE_SUCCESS, msg, data, null);
|
||||
}
|
||||
public static AjaxJson getSuccessData(Object data) {
|
||||
return new AjaxJson(CODE_SUCCESS, "ok", data, null);
|
||||
}
|
||||
public static AjaxJson getSuccessArray(Object... data) {
|
||||
return new AjaxJson(CODE_SUCCESS, "ok", data, null);
|
||||
}
|
||||
|
||||
// 返回失败
|
||||
public static AjaxJson getError() {
|
||||
return new AjaxJson(CODE_ERROR, "error", null, null);
|
||||
}
|
||||
public static AjaxJson getError(String msg) {
|
||||
return new AjaxJson(CODE_ERROR, msg, null, null);
|
||||
}
|
||||
|
||||
// 返回警告
|
||||
public static AjaxJson getWarning() {
|
||||
return new AjaxJson(CODE_ERROR, "warning", null, null);
|
||||
}
|
||||
public static AjaxJson getWarning(String msg) {
|
||||
return new AjaxJson(CODE_WARNING, msg, null, null);
|
||||
}
|
||||
|
||||
// 返回未登录
|
||||
public static AjaxJson getNotLogin() {
|
||||
return new AjaxJson(CODE_NOT_LOGIN, "未登录,请登录后再次访问", null, null);
|
||||
}
|
||||
|
||||
// 返回没有权限的
|
||||
public static AjaxJson getNotJur(String msg) {
|
||||
return new AjaxJson(CODE_NOT_JUR, msg, null, null);
|
||||
}
|
||||
|
||||
// 返回一个自定义状态码的
|
||||
public static AjaxJson get(int code, String msg){
|
||||
return new AjaxJson(code, msg, null, null);
|
||||
}
|
||||
|
||||
// 返回分页和数据的
|
||||
public static AjaxJson getPageData(Long dataCount, Object data){
|
||||
return new AjaxJson(CODE_SUCCESS, "ok", data, dataCount);
|
||||
}
|
||||
|
||||
// 返回,根据受影响行数的(大于0=ok,小于0=error)
|
||||
public static AjaxJson getByLine(int line){
|
||||
if(line > 0){
|
||||
return getSuccess("ok", line);
|
||||
}
|
||||
return getError("error").setData(line);
|
||||
}
|
||||
|
||||
// 返回,根据布尔值来确定最终结果的 (true=ok,false=error)
|
||||
public static AjaxJson getByBoolean(boolean b){
|
||||
return b ? getSuccess("ok") : getError("error");
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see java.lang.Object#toString()
|
||||
*/
|
||||
@SuppressWarnings("rawtypes")
|
||||
@Override
|
||||
public String toString() {
|
||||
String data_string = null;
|
||||
if(data == null){
|
||||
|
||||
} else if(data instanceof List){
|
||||
data_string = "List(length=" + ((List)data).size() + ")";
|
||||
} else {
|
||||
data_string = data.toString();
|
||||
}
|
||||
return "{"
|
||||
+ "\"code\": " + this.getCode()
|
||||
+ ", \"msg\": \"" + this.getMsg() + "\""
|
||||
+ ", \"data\": " + data_string
|
||||
+ ", \"dataCount\": " + dataCount
|
||||
+ "}";
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
@ -14,11 +14,15 @@ sa-token:
|
||||
sso:
|
||||
# SSO-Server端 单点登录地址
|
||||
auth-url: http://sa-sso-server.com:9000/ssoAuth
|
||||
# 使用Http请求校验ticket
|
||||
is-http: true
|
||||
# SSO-Server端 ticket校验地址
|
||||
check-ticket-url: http://sa-sso-server.com:9000/checkTicket
|
||||
# SSO-Server端 单点注销地址
|
||||
check-ticket-url: http://sa-sso-server.com:9000/ssoCheckTicket
|
||||
# 打开单点注销功能
|
||||
is-slo: true
|
||||
# 单点注销地址
|
||||
slo-url: http://sa-sso-server.com:9000/ssoLogout
|
||||
# 接口调用秘钥(用于SSO模式三的单点注销功能)
|
||||
# 接口调用秘钥
|
||||
secretkey: kQwIOrYvnXmSDkwEiFngrKidMcdrgKor
|
||||
|
||||
spring:
|
||||
|
@ -3,19 +3,19 @@ package com.pj.sso;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||
|
||||
import com.pj.util.AjaxJson;
|
||||
import cn.dev33.satoken.util.SaResult;
|
||||
|
||||
/**
|
||||
* 全局异常处理
|
||||
*/
|
||||
@RestControllerAdvice
|
||||
public class GlobalException {
|
||||
public class ExceptionHandle {
|
||||
|
||||
// 全局异常拦截(拦截项目中的所有异常)
|
||||
// 全局异常拦截
|
||||
@ExceptionHandler
|
||||
public AjaxJson handlerException(Exception e) {
|
||||
public SaResult handlerException(Exception e) {
|
||||
e.printStackTrace();
|
||||
return AjaxJson.getError(e.getMessage());
|
||||
return SaResult.error(e.getMessage());
|
||||
}
|
||||
|
||||
}
|
@ -1,13 +1,16 @@
|
||||
package com.pj.sso;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.servlet.ModelAndView;
|
||||
|
||||
import com.pj.util.AjaxJson;
|
||||
import com.ejlchina.okhttps.OkHttps;
|
||||
|
||||
import cn.dev33.satoken.sso.SaSsoUtil;
|
||||
import cn.dev33.satoken.config.SaTokenConfig;
|
||||
import cn.dev33.satoken.sso.SaSsoHandle;
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
import cn.dev33.satoken.util.SaResult;
|
||||
|
||||
/**
|
||||
* Sa-Token-SSO Server端 Controller
|
||||
@ -17,45 +20,35 @@ import cn.dev33.satoken.stp.StpUtil;
|
||||
@RestController
|
||||
public class SsoServerController {
|
||||
|
||||
// SSO-Server端:授权地址,跳转到登录页面
|
||||
@RequestMapping("ssoAuth")
|
||||
public Object ssoAuth(String redirect) {
|
||||
/*
|
||||
* 此处两种情况分开处理:
|
||||
* 1、如果在SSO认证中心尚未登录,则先去登登录
|
||||
* 2、如果在SSO认证中心尚已登录,则开始对redirect地址下放ticket引导授权
|
||||
*/
|
||||
// 情况1:尚未登录
|
||||
if(StpUtil.isLogin() == false) {
|
||||
return new ModelAndView("sa-login.html");
|
||||
}
|
||||
// 情况2:已经登录,开始构建授权重定向地址,下放ticket
|
||||
String redirectUrl = SaSsoUtil.buildRedirectUrl(StpUtil.getLoginId(), redirect);
|
||||
return new ModelAndView("redirect:" + redirectUrl);
|
||||
// SSO-Server端:处理所有SSO相关请求
|
||||
@RequestMapping("/sso*")
|
||||
public Object ssoRequest() {
|
||||
return SaSsoHandle.serverRequest();
|
||||
}
|
||||
|
||||
// SSO-Server端:登录接口
|
||||
@RequestMapping("doLogin")
|
||||
public AjaxJson doLogin(String name, String pwd) {
|
||||
// 此处仅做模拟登录,真实环境应该查询数据进行登录
|
||||
if("sa".equals(name) && "123456".equals(pwd)) {
|
||||
StpUtil.login(10001);
|
||||
return AjaxJson.getSuccess("登录成功!");
|
||||
}
|
||||
return AjaxJson.getError("登录失败!");
|
||||
}
|
||||
|
||||
// SSO-Server端:校验ticket 获取账号id
|
||||
@RequestMapping("checkTicket")
|
||||
public Object checkTicket(String ticket, String sloCallback) {
|
||||
// 校验ticket,获取对应的账号id
|
||||
Object loginId = SaSsoUtil.checkTicket(ticket);
|
||||
|
||||
// 注册此客户端的单点注销回调URL(不需要单点注销功能可删除此行代码)
|
||||
SaSsoUtil.registerSloCallbackUrl(loginId, sloCallback);
|
||||
|
||||
// 返回给Client端
|
||||
return loginId;
|
||||
// 配置SSO相关参数
|
||||
@Autowired
|
||||
private void configSso(SaTokenConfig cfg) {
|
||||
cfg.sso
|
||||
// SSO-Server端:未登录时返回的View
|
||||
.setNotLoginView(() -> {
|
||||
return new ModelAndView("sa-login.html");
|
||||
})
|
||||
// SSO-Server端:登录函数
|
||||
.setDoLoginHandle((name, pwd) -> {
|
||||
// 此处仅做模拟登录,真实环境应该查询数据进行登录
|
||||
if("sa".equals(name) && "123456".equals(pwd)) {
|
||||
StpUtil.login(10001);
|
||||
return SaResult.ok("登录成功!");
|
||||
}
|
||||
return SaResult.error("登录失败!");
|
||||
})
|
||||
// 配置Http请求处理器
|
||||
.setSendHttp(url -> {
|
||||
// 此处为了提高响应速度这里可将sync换为async
|
||||
return OkHttps.sync(url).get();
|
||||
})
|
||||
;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,28 +0,0 @@
|
||||
package com.pj.sso;
|
||||
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import com.ejlchina.okhttps.OkHttps;
|
||||
|
||||
import cn.dev33.satoken.sso.SaSsoUtil;
|
||||
|
||||
/**
|
||||
* Sa-Token-SSO Server端 单点注销 Controller
|
||||
* @author kong
|
||||
*/
|
||||
@RestController
|
||||
public class SsoServerLogoutController {
|
||||
|
||||
// SSO-Server端:单点注销
|
||||
@RequestMapping("ssoLogout")
|
||||
public String ssoLogout(String loginId, String secretkey) {
|
||||
|
||||
// 遍历通知Client端注销会话 (为了提高响应速度这里可将sync换为async)
|
||||
SaSsoUtil.singleLogout(secretkey, loginId, url -> OkHttps.sync(url).get());
|
||||
|
||||
// 完成
|
||||
return "ok";
|
||||
}
|
||||
|
||||
}
|
@ -1,162 +0,0 @@
|
||||
package com.pj.util;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
|
||||
|
||||
/**
|
||||
* ajax请求返回Json格式数据的封装
|
||||
*/
|
||||
public class AjaxJson implements Serializable{
|
||||
|
||||
private static final long serialVersionUID = 1L; // 序列化版本号
|
||||
|
||||
public static final int CODE_SUCCESS = 200; // 成功状态码
|
||||
public static final int CODE_ERROR = 500; // 错误状态码
|
||||
public static final int CODE_WARNING = 501; // 警告状态码
|
||||
public static final int CODE_NOT_JUR = 403; // 无权限状态码
|
||||
public static final int CODE_NOT_LOGIN = 401; // 未登录状态码
|
||||
public static final int CODE_INVALID_REQUEST = 400; // 无效请求状态码
|
||||
|
||||
public int code; // 状态码
|
||||
public String msg; // 描述信息
|
||||
public Object data; // 携带对象
|
||||
public Long dataCount; // 数据总数,用于分页
|
||||
|
||||
/**
|
||||
* 返回code
|
||||
* @return
|
||||
*/
|
||||
public int getCode() {
|
||||
return this.code;
|
||||
}
|
||||
|
||||
/**
|
||||
* 给msg赋值,连缀风格
|
||||
*/
|
||||
public AjaxJson setMsg(String msg) {
|
||||
this.msg = msg;
|
||||
return this;
|
||||
}
|
||||
public String getMsg() {
|
||||
return this.msg;
|
||||
}
|
||||
|
||||
/**
|
||||
* 给data赋值,连缀风格
|
||||
*/
|
||||
public AjaxJson setData(Object data) {
|
||||
this.data = data;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将data还原为指定类型并返回
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T getData(Class<T> cs) {
|
||||
return (T) data;
|
||||
}
|
||||
|
||||
// ============================ 构建 ==================================
|
||||
|
||||
public AjaxJson(int code, String msg, Object data, Long dataCount) {
|
||||
this.code = code;
|
||||
this.msg = msg;
|
||||
this.data = data;
|
||||
this.dataCount = dataCount;
|
||||
}
|
||||
|
||||
// 返回成功
|
||||
public static AjaxJson getSuccess() {
|
||||
return new AjaxJson(CODE_SUCCESS, "ok", null, null);
|
||||
}
|
||||
public static AjaxJson getSuccess(String msg) {
|
||||
return new AjaxJson(CODE_SUCCESS, msg, null, null);
|
||||
}
|
||||
public static AjaxJson getSuccess(String msg, Object data) {
|
||||
return new AjaxJson(CODE_SUCCESS, msg, data, null);
|
||||
}
|
||||
public static AjaxJson getSuccessData(Object data) {
|
||||
return new AjaxJson(CODE_SUCCESS, "ok", data, null);
|
||||
}
|
||||
public static AjaxJson getSuccessArray(Object... data) {
|
||||
return new AjaxJson(CODE_SUCCESS, "ok", data, null);
|
||||
}
|
||||
|
||||
// 返回失败
|
||||
public static AjaxJson getError() {
|
||||
return new AjaxJson(CODE_ERROR, "error", null, null);
|
||||
}
|
||||
public static AjaxJson getError(String msg) {
|
||||
return new AjaxJson(CODE_ERROR, msg, null, null);
|
||||
}
|
||||
|
||||
// 返回警告
|
||||
public static AjaxJson getWarning() {
|
||||
return new AjaxJson(CODE_ERROR, "warning", null, null);
|
||||
}
|
||||
public static AjaxJson getWarning(String msg) {
|
||||
return new AjaxJson(CODE_WARNING, msg, null, null);
|
||||
}
|
||||
|
||||
// 返回未登录
|
||||
public static AjaxJson getNotLogin() {
|
||||
return new AjaxJson(CODE_NOT_LOGIN, "未登录,请登录后再次访问", null, null);
|
||||
}
|
||||
|
||||
// 返回没有权限的
|
||||
public static AjaxJson getNotJur(String msg) {
|
||||
return new AjaxJson(CODE_NOT_JUR, msg, null, null);
|
||||
}
|
||||
|
||||
// 返回一个自定义状态码的
|
||||
public static AjaxJson get(int code, String msg){
|
||||
return new AjaxJson(code, msg, null, null);
|
||||
}
|
||||
|
||||
// 返回分页和数据的
|
||||
public static AjaxJson getPageData(Long dataCount, Object data){
|
||||
return new AjaxJson(CODE_SUCCESS, "ok", data, dataCount);
|
||||
}
|
||||
|
||||
// 返回,根据受影响行数的(大于0=ok,小于0=error)
|
||||
public static AjaxJson getByLine(int line){
|
||||
if(line > 0){
|
||||
return getSuccess("ok", line);
|
||||
}
|
||||
return getError("error").setData(line);
|
||||
}
|
||||
|
||||
// 返回,根据布尔值来确定最终结果的 (true=ok,false=error)
|
||||
public static AjaxJson getByBoolean(boolean b){
|
||||
return b ? getSuccess("ok") : getError("error");
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see java.lang.Object#toString()
|
||||
*/
|
||||
@SuppressWarnings("rawtypes")
|
||||
@Override
|
||||
public String toString() {
|
||||
String data_string = null;
|
||||
if(data == null){
|
||||
|
||||
} else if(data instanceof List){
|
||||
data_string = "List(length=" + ((List)data).size() + ")";
|
||||
} else {
|
||||
data_string = data.toString();
|
||||
}
|
||||
return "{"
|
||||
+ "\"code\": " + this.getCode()
|
||||
+ ", \"msg\": \"" + this.getMsg() + "\""
|
||||
+ ", \"data\": " + data_string
|
||||
+ ", \"dataCount\": " + dataCount
|
||||
+ "}";
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
@ -12,6 +12,10 @@ sa-token:
|
||||
allow-url: http://sa-sso-client1.com:9001/ssoLogin, http://sa-sso-client2.com:9001/ssoLogin, http://sa-sso-client3.com:9001/ssoLogin
|
||||
# 接口调用秘钥(用于SSO模式三的单点注销功能)
|
||||
secretkey: kQwIOrYvnXmSDkwEiFngrKidMcdrgKor
|
||||
# 使用Http请求校验ticket
|
||||
is-http: true
|
||||
# 打开单点注销功能
|
||||
is-slo: true
|
||||
|
||||
spring:
|
||||
# Redis配置
|
||||
|
@ -20,7 +20,7 @@ $('.login-btn').click(function(){
|
||||
// 开始登录
|
||||
setTimeout(function() {
|
||||
$.ajax({
|
||||
url: "doLogin",
|
||||
url: "ssoDoLogin",
|
||||
type: "post",
|
||||
data: {
|
||||
name: $('[name=name]').val(),
|
||||
|
@ -1,9 +1,6 @@
|
||||
# SSO模式一 共享Cookie同步会话
|
||||
|
||||
如果我们的系统可以保证部署在同一个主域名之下,并且后端连接同一个Redis,那么便可以使用 **`[共享Cookie同步会话]`** 的方式做到单点登录
|
||||
|
||||
> Sa-Token整合同域单点登录非常简单,相比于正常的登录,你只需增加配置 `sa-token.cookie-domain=xxx.com` 指定一下Cookie写入时的父级域名即可 <br>
|
||||
> 整合示例在官方仓库的 `/sa-token-demo/sa-token-demo-sso1/`,如遇到难点可结合源码进行测试学习
|
||||
如果我们的多个系统可以做到:前端同域、后端同Redis,那么便可以使用 **`[共享Cookie同步会话]`** 的方式做到单点登录
|
||||
|
||||
---
|
||||
|
||||
@ -25,6 +22,9 @@
|
||||
|
||||
OK,所有理论就绪,下面开始实战
|
||||
|
||||
> Sa-Token整合同域单点登录非常简单,相比于正常的登录,你只需增加配置 `sa-token.cookie-domain=xxx.com` 指定一下Cookie写入时的父级域名即可 <br>
|
||||
> 整合示例在官方仓库的 `/sa-token-demo/sa-token-demo-sso1/`,如遇到难点可结合源码进行测试学习
|
||||
|
||||
|
||||
|
||||
### 1、准备工作
|
||||
|
@ -1,6 +1,6 @@
|
||||
# SSO模式二 URL重定向传播会话
|
||||
|
||||
如果我们的系统部署在不同的域名之下,但是后端可以连接同一个Redis,那么便可以使用 **`[URL重定向传播会话]`** 的方式做到单点登录
|
||||
如果我们的多个系统:部署在不同的域名之下,但是后端可以连接同一个Redis,那么便可以使用 **`[URL重定向传播会话]`** 的方式做到单点登录
|
||||
|
||||
|
||||
### 0、解题思路
|
||||
@ -33,15 +33,9 @@
|
||||
> 搭建示例在官方仓库的 `/sa-token-demo/sa-token-demo-sso2-server/`,如遇到难点可结合源码进行测试学习
|
||||
|
||||
##### 1.1、创建SSO-Server端项目
|
||||
创建一个SpringBoot项目 `sa-token-demo-sso-server`(不会的同学自行百度或参考仓库示例),添加pom依赖:
|
||||
创建SpringBoot项目 `sa-token-demo-sso-server`(不会的同学自行百度或参考仓库示例),添加pom依赖:
|
||||
|
||||
``` xml
|
||||
<!-- SpringBoot依赖 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Sa-Token 权限认证, 在线文档:http://sa-token.dev33.cn/ -->
|
||||
<dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
@ -55,8 +49,6 @@
|
||||
<artifactId>sa-token-dao-redis-jackson</artifactId>
|
||||
<version>1.21.0</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 提供Redis连接池 -->
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-pool2</artifactId>
|
||||
@ -71,35 +63,33 @@
|
||||
@RestController
|
||||
public class SsoServerController {
|
||||
|
||||
// SSO-Server端:授权地址,跳转到登录页面
|
||||
@RequestMapping("ssoAuth")
|
||||
public Object ssoAuth(String redirect) {
|
||||
/*
|
||||
* 此处两种情况分开处理:
|
||||
* 1、如果在SSO认证中心尚未登录,则先去登登录
|
||||
* 2、如果在SSO认证中心尚已登录,则开始对redirect地址下放ticket引导授权
|
||||
*/
|
||||
// 情况1:尚未登录
|
||||
if(StpUtil.isLogin() == false) {
|
||||
String msg = "当前会话在SSO-Server端尚未登录,请先访问"
|
||||
+ "<a href='/doLogin?name=sa&pwd=123456' target='_blank'> doLogin登录 </a>"
|
||||
+ "进行登录之后,刷新页面开始授权";
|
||||
return msg;
|
||||
}
|
||||
// 情况2:已经登录,开始构建授权重定向地址,下放ticket
|
||||
String redirectUrl = SaSsoUtil.buildRedirectUrl(StpUtil.getLoginId(), redirect);
|
||||
return new ModelAndView("redirect:" + redirectUrl);
|
||||
// SSO-Server端:处理所有SSO相关请求
|
||||
@RequestMapping("/sso*")
|
||||
public Object ssoRequest() {
|
||||
return SaSsoHandle.serverRequest();
|
||||
}
|
||||
|
||||
// SSO-Server端:登录接口
|
||||
@RequestMapping("doLogin")
|
||||
public AjaxJson doLogin(String name, String pwd) {
|
||||
// 此处仅做模拟登录,真实环境应该查询数据进行登录
|
||||
if("sa".equals(name) && "123456".equals(pwd)) {
|
||||
StpUtil.login(10001);
|
||||
return AjaxJson.getSuccess("登录成功!");
|
||||
}
|
||||
return AjaxJson.getError("登录失败!");
|
||||
// 配置SSO相关参数
|
||||
@Autowired
|
||||
private void configSso(SaTokenConfig cfg) {
|
||||
cfg.sso
|
||||
// 配置:未登录时返回的View
|
||||
.setNotLoginView(() -> {
|
||||
String msg = "当前会话在SSO-Server端尚未登录,请先访问"
|
||||
+ "<a href='/ssoDoLogin?name=sa&pwd=123456' target='_blank'> doLogin登录 </a>"
|
||||
+ "进行登录之后,刷新页面开始授权";
|
||||
return msg;
|
||||
})
|
||||
// 配置:登录处理函数
|
||||
.setDoLoginHandle((name, pwd) -> {
|
||||
// 此处仅做模拟登录,真实环境应该查询数据进行登录
|
||||
if("sa".equals(name) && "123456".equals(pwd)) {
|
||||
StpUtil.login(10001);
|
||||
return SaResult.ok("登录成功!");
|
||||
}
|
||||
return SaResult.error("登录失败!");
|
||||
})
|
||||
;
|
||||
}
|
||||
|
||||
}
|
||||
@ -153,27 +143,19 @@ public class SaSsoServerApplication {
|
||||
##### 2.1、创建SSO-Client端项目
|
||||
创建一个SpringBoot项目 `sa-token-demo-sso-client`,添加pom依赖:
|
||||
``` xml
|
||||
<!-- SpringBoot依赖 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Sa-Token 权限认证, 在线文档:http://sa-token.dev33.cn/ -->
|
||||
<dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-spring-boot-starter</artifactId>
|
||||
<version>${sa-token-version}</version>
|
||||
<version>1.21.0</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Sa-Token 整合redis (使用jackson序列化方式) -->
|
||||
<dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-dao-redis-jackson</artifactId>
|
||||
<version>${sa-token-version}</version>
|
||||
<version>1.21.0</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 提供Redis连接池 -->
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-pool2</artifactId>
|
||||
@ -202,42 +184,17 @@ public class SsoClientController {
|
||||
public String index() {
|
||||
String str = "<h2>Sa-Token SSO-Client 应用端</h2>" +
|
||||
"<p>当前会话是否登录:" + StpUtil.isLogin() + "</p>" +
|
||||
"<p><a href=\"javascript:location.href='/ssoLogin?back=' + encodeURIComponent(location.href);\">登录</a></p>";
|
||||
"<p><a href=\"javascript:location.href='/ssoLogin?back=' + encodeURIComponent(location.href);\">登录</a> " +
|
||||
"<a href='/ssoLogout' target='_blank'>注销</a></p>";
|
||||
return str;
|
||||
}
|
||||
|
||||
// SSO-Client端:登录地址
|
||||
@RequestMapping("ssoLogin")
|
||||
public Object ssoLogin(String back, String ticket) {
|
||||
// 如果当前Client端已经登录,则无需访问SSO认证中心,可以直接返回
|
||||
if(StpUtil.isLogin()) {
|
||||
return new ModelAndView("redirect:" + back);
|
||||
}
|
||||
/*
|
||||
* 接下来两种情况:
|
||||
* ticket无值,说明此请求是Client端访问,需要重定向至SSO认证中心
|
||||
* ticket有值,说明此请求从SSO认证中心重定向而来,需要根据ticket进行登录
|
||||
*/
|
||||
if(ticket == null) {
|
||||
String serverAuthUrl = SaSsoUtil.buildServerAuthUrl(SaHolder.getRequest().getUrl(), back);
|
||||
return new ModelAndView("redirect:" + serverAuthUrl);
|
||||
} else {
|
||||
Object loginId = checkTicket(ticket);
|
||||
if(loginId != null ) {
|
||||
// loginId有值,说明ticket有效
|
||||
StpUtil.login(loginId);
|
||||
return new ModelAndView("redirect:" + back);
|
||||
}
|
||||
// 此处向客户端提示ticket无效即可,不要重定向到SSO认证中心,否则容易引起无限重定向
|
||||
return "ticket无效: " + ticket;
|
||||
}
|
||||
// SSO-Client端:处理所有SSO相关请求
|
||||
@RequestMapping("/sso*")
|
||||
public Object ssoRequest() {
|
||||
return SaSsoHandle.clientRequest();
|
||||
}
|
||||
|
||||
// SSO-Client端:校验ticket,获取账号id
|
||||
private Object checkTicket(String ticket) {
|
||||
return SaSsoUtil.checkTicket(ticket);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
@ -254,6 +211,8 @@ sa-token:
|
||||
sso:
|
||||
# SSO-Server端 单点登录地址
|
||||
auth-url: http://sa-sso-server.com:9000/ssoAuth
|
||||
# 是否打开单点注销接口
|
||||
is-slo: true
|
||||
|
||||
# 配置Sa-Token单独使用的Redis连接 (此处需要和SSO-Server端连接同一个Redis)
|
||||
alone-redis:
|
||||
@ -385,13 +344,27 @@ Token作为长时间有效的会话凭证,在任何时候都不应该直接在
|
||||
因此Sa-Token-SSO选择先回传ticket,再由ticket获取账号id,且ticket一次性用完即废,提高安全性
|
||||
|
||||
|
||||
|
||||
### 6、跨Redis的单点登录
|
||||
以上流程解决了跨域模式下的单点登录,但是后端仍然采用了共享Redis来同步会话,如果我们的架构设计中Client端与Server端无法共享Redis,又该怎么完成单点登录?
|
||||
|
||||
这就要采用模式三了,且往下看:[Http请求获取会话](/sso/sso-type3)
|
||||
|
||||
|
||||
<!--
|
||||
### 6、如何单点注销?
|
||||
由于Server端与所有Client端都是在共用同一套会话,因此只要一端注销,即可全端下线,达到单点注销的效果
|
||||
|
||||
在`SsoClientController`中添加以下代码:
|
||||
``` java
|
||||
// SSO-Client端:单点注销 (所有端一起下线)
|
||||
@RequestMapping("logout")
|
||||
public SaResult logout() {
|
||||
StpUtil.logout();
|
||||
return SaResult.ok();
|
||||
}
|
||||
``` -->
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -6,7 +6,7 @@
|
||||
|
||||
|
||||
### 0、问题分析
|
||||
我们先来分析一下,当后端不使用共享Redis时,会对架构发生哪些影响
|
||||
我们先来分析一下,当后端不使用共享Redis时,会对架构产生哪些影响
|
||||
|
||||
1. Client端 无法直连 Redis 校验 ticket,取出账号id
|
||||
2. Client端 无法与 Server端 共用一套会话,需要自行维护子会话
|
||||
@ -36,53 +36,37 @@
|
||||
> OkHttps是一个轻量级http请求工具,详情参考:[OkHttps](https://gitee.com/ejlchina-zhxu/okhttps)
|
||||
|
||||
##### 1.2、认证中心开放接口
|
||||
在SSO-Server端的`SsoServerController`中,新增以下接口:
|
||||
``` java
|
||||
// SSO-Server端:校验ticket 获取账号id
|
||||
@RequestMapping("checkTicket")
|
||||
public Object checkTicket(String ticket, String sloCallback) {
|
||||
// 校验ticket,获取对应的账号id
|
||||
Object loginId = SaSsoUtil.checkTicket(ticket);
|
||||
|
||||
// 注册此客户端的单点注销回调URL(不需要单点注销功能可删除此行代码)
|
||||
SaSsoUtil.registerSloCallbackUrl(loginId, sloCallback);
|
||||
|
||||
// 返回给Client端
|
||||
return loginId;
|
||||
}
|
||||
在SSO-Server端的`application.yml`中,新增以下配置:
|
||||
``` yml
|
||||
sa-token:
|
||||
sso:
|
||||
# 使用Http请求校验ticket
|
||||
is-http: true
|
||||
```
|
||||
此接口的作用是让Client端通过http请求校验ticket,获取对应的账号id
|
||||
此配置项的作用是开放ticket校验接口,让Client端通过http请求获取会话
|
||||
|
||||
##### 1.3、Client端新增配置
|
||||
在SSO-Client端的`SsoClientController`中,新增以下配置
|
||||
``` java
|
||||
// 配置SSO相关参数
|
||||
@Autowired
|
||||
private void configSso(SaTokenConfig cfg) {
|
||||
cfg.sso
|
||||
// 配置Http请求处理器
|
||||
.setSendHttp(url -> {
|
||||
return OkHttps.sync(url).get().getBody().toString();
|
||||
})
|
||||
;
|
||||
}
|
||||
```
|
||||
|
||||
``` yml
|
||||
sa-token:
|
||||
sso:
|
||||
# 使用Http请求校验ticket
|
||||
is-http: true
|
||||
# SSO-Server端 ticket校验地址
|
||||
check-ticket-url: http://sa-sso-server.com:9000/checkTicket
|
||||
```
|
||||
|
||||
##### 1.4、修改校验ticket的逻辑
|
||||
在模式二的`SsoClientController`中,校验ticket的方法是:
|
||||
``` java
|
||||
// SSO-Client端:校验ticket,获取账号id
|
||||
private Object checkTicket(String ticket) {
|
||||
return SaSsoUtil.checkTicket(ticket);
|
||||
}
|
||||
```
|
||||
不能直连Redis后,上述方法也将无效,我们把它改为以下方式:
|
||||
``` java
|
||||
// SSO-Client端:校验ticket码,获取对应的账号id
|
||||
private Object checkTicket(String ticket) {
|
||||
// 构建单点注销的回调URL(不需要单点注销时此值可填null )
|
||||
String sloCallback = SaHolder.getRequest().getUrl().replace("/ssoLogin", "/sloCallback");
|
||||
|
||||
// 使用OkHttps请求SSO-Server端,校验ticket
|
||||
String checkUrl = SaSsoUtil.buildCheckTicketUrl(ticket, sloCallback);
|
||||
String loginId = OkHttps.sync(checkUrl).get().getBody().toString();
|
||||
|
||||
// 判断返回值是否为有效账号Id
|
||||
return (SaFoxUtil.isEmpty(loginId) ? null : loginId);
|
||||
}
|
||||
check-ticket-url: http://sa-sso-server.com:9000/ssoCheckTicket
|
||||
```
|
||||
|
||||
##### 1.5 启动项目测试
|
||||
@ -103,110 +87,48 @@ private Object checkTicket(String ticket) {
|
||||
5. Server端注销下线
|
||||
6. 单点注销完成
|
||||
|
||||
##### 2.1、SSO-Server认证中心增加单点注销接口
|
||||
新建 `SsoServerLogoutController` 增加以下代码
|
||||
##### 2.1、SSO-Server认证中心增加配置
|
||||
在 `SsoServerController` 中新增配置
|
||||
``` java
|
||||
/**
|
||||
* Sa-Token-SSO Server端 单点注销 Controller
|
||||
*/
|
||||
@RestController
|
||||
public class SsoServerLogoutController {
|
||||
|
||||
// SSO-Server端:单点注销
|
||||
@RequestMapping("ssoLogout")
|
||||
public String ssoLogout(String loginId, String secretkey) {
|
||||
|
||||
// 遍历通知Client端注销会话 (为了提高响应速度这里可将sync换为async)
|
||||
SaSsoUtil.singleLogout(secretkey, loginId, url -> OkHttps.sync(url).get());
|
||||
|
||||
// 完成
|
||||
return "ok";
|
||||
}
|
||||
|
||||
// 配置SSO相关参数
|
||||
@Autowired
|
||||
private void configSso(SaTokenConfig cfg) {
|
||||
cfg.sso
|
||||
// ... (其它配置保持不变)
|
||||
// 配置Http请求处理器
|
||||
.setSendHttp(url -> {
|
||||
// 此处为了提高响应速度这里可将sync换为async
|
||||
return OkHttps.sync(url).get();
|
||||
})
|
||||
;
|
||||
}
|
||||
```
|
||||
|
||||
并在 `application.yml` 下配置API调用秘钥
|
||||
并在 `application.yml` 下新增配置:
|
||||
``` yml
|
||||
sa-token:
|
||||
sso:
|
||||
# 打开单点注销功能
|
||||
is-slo: true
|
||||
# API调用秘钥(用于SSO模式三的单点注销功能)
|
||||
secretkey: kQwIOrYvnXmSDkwEiFngrKidMcdrgKor
|
||||
```
|
||||
|
||||
##### 2.2、SSO-Client端增加注销接口
|
||||
新建 `SsoClientLogoutController` 增加以下代码
|
||||
``` java
|
||||
/**
|
||||
* Sa-Token-SSO Client端 单点注销 Controller
|
||||
* @author kong
|
||||
*/
|
||||
@RestController
|
||||
public class SsoClientLogoutController {
|
||||
##### 2.2、SSO-Client端新增配置
|
||||
|
||||
// SSO-Client端:单端注销 (其它Client端会话不受影响)
|
||||
@RequestMapping("logout")
|
||||
public AjaxJson logout() {
|
||||
StpUtil.logout();
|
||||
return AjaxJson.getSuccess();
|
||||
}
|
||||
|
||||
// SSO-Client端:单点注销 (所有端一起下线)
|
||||
@RequestMapping("ssoLogout")
|
||||
public AjaxJson ssoLogout() {
|
||||
// 如果未登录,则无需注销
|
||||
if(StpUtil.isLogin() == false) {
|
||||
return AjaxJson.getSuccess();
|
||||
}
|
||||
// 调用SSO-Server认证中心API
|
||||
String url = SaSsoUtil.buildSloUrl(StpUtil.getLoginId());
|
||||
String res = OkHttps.sync(url).get().getBody().toString();
|
||||
if(res.equals("ok")) {
|
||||
return AjaxJson.getSuccess("单点注销成功");
|
||||
}
|
||||
return AjaxJson.getError("单点注销失败");
|
||||
}
|
||||
|
||||
// 单点注销的回调
|
||||
@RequestMapping("sloCallback")
|
||||
public String sloCallback(String loginId, String secretkey) {
|
||||
SaSsoUtil.checkSecretkey(secretkey);
|
||||
StpUtil.logoutByLoginId(loginId);
|
||||
return "ok";
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
!> `logoutByLoginId(id)`为踢人下线,如果要彻底清除数据,可更换为`StpUtil.logoutByTokenValue(StpUtil.getTokenValueByLoginId(loginId));`
|
||||
|
||||
并在 `application.yml` 增加配置: `API调用秘钥` 和 `单点注销接口URL`
|
||||
在 `application.yml` 增加配置:`API调用秘钥` 和 `单点注销接口URL`
|
||||
``` yml
|
||||
sa-token:
|
||||
sso:
|
||||
# SSO-Server端 单点注销地址
|
||||
# 打开单点注销功能
|
||||
is-slo: true
|
||||
# 单点注销地址
|
||||
slo-url: http://sa-sso-server.com:9000/ssoLogout
|
||||
# 接口调用秘钥(用于SSO模式三的单点注销功能)
|
||||
# 接口调用秘钥
|
||||
secretkey: kQwIOrYvnXmSDkwEiFngrKidMcdrgKor
|
||||
```
|
||||
|
||||
##### 2.3 更改Client端首页代码
|
||||
为了方便测试,我们更改一下Client端中`SsoClientController`类的`index`方法代码
|
||||
``` java
|
||||
// SSO-Client端:首页
|
||||
@RequestMapping("/")
|
||||
public String index() {
|
||||
String str = "<h2>Sa-Token SSO-Client 应用端</h2>" +
|
||||
"<p>当前会话是否登录:" + StpUtil.isLogin() + "</p>" +
|
||||
"<p><a href=\"javascript:location.href='/ssoLogin?back=' + encodeURIComponent(location.href);\">登录</a>" +
|
||||
" <a href='/ssoLogout' target='_blank'>注销</a></p>";
|
||||
return str;
|
||||
}
|
||||
```
|
||||
PS:相比于模式二,增加了单点注销的按钮
|
||||
|
||||
|
||||
##### 2.4 启动测试
|
||||
##### 2.3 启动测试
|
||||
启动SSO-Server、SSO-Client,访问测试:[http://sa-sso-client1.com:9001/](http://sa-sso-client1.com:9001/),
|
||||
我们主要的测试点在于 `单点注销`,正常登陆即可
|
||||
|
||||
|
@ -104,6 +104,7 @@ PS:两者的区别在于:**`方式1会覆盖yml中的配置,方式2会与y
|
||||
| authUrl | String | null | SSO-Server端 单点登录地址 |
|
||||
| checkTicketUrl| String | null | SSO-Server端 Ticket校验地址 |
|
||||
| sloUrl | String | null | SSO-Server端 单点注销地址 |
|
||||
| ssoLogoutCall | String | null | SSO-Client端 当前Client端的单点注销回调URL (为空时自动获取) |
|
||||
|
||||
配置示例:
|
||||
``` yml
|
||||
|
@ -1,5 +1,8 @@
|
||||
package cn.dev33.satoken.reactor.model;
|
||||
|
||||
import java.net.URI;
|
||||
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseCookie;
|
||||
import org.springframework.http.ResponseCookie.ResponseCookieBuilder;
|
||||
import org.springframework.http.server.reactive.ServerHttpResponse;
|
||||
@ -79,5 +82,15 @@ public class SaResponseForReactor implements SaResponse {
|
||||
response.getHeaders().set(name, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 重定向
|
||||
*/
|
||||
@Override
|
||||
public Object redirect(String url) {
|
||||
response.setStatusCode(HttpStatus.FOUND);
|
||||
response.getHeaders().setLocation(URI.create(url));
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,9 +1,12 @@
|
||||
package cn.dev33.satoken.servlet.model;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import javax.servlet.http.Cookie;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import cn.dev33.satoken.context.model.SaResponse;
|
||||
import cn.dev33.satoken.exception.SaTokenException;
|
||||
import cn.dev33.satoken.util.SaFoxUtil;
|
||||
|
||||
/**
|
||||
@ -59,7 +62,6 @@ public class SaResponseForServlet implements SaResponse {
|
||||
response.addCookie(cookie);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 在响应头里写入一个值
|
||||
*/
|
||||
@ -69,4 +71,17 @@ public class SaResponseForServlet implements SaResponse {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 重定向
|
||||
*/
|
||||
@Override
|
||||
public Object redirect(String url) {
|
||||
try {
|
||||
response.sendRedirect(url);
|
||||
} catch (IOException e) {
|
||||
throw new SaTokenException(e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,9 +1,10 @@
|
||||
package cn.dev33.satoken.solon.model;
|
||||
|
||||
import cn.dev33.satoken.context.model.SaResponse;
|
||||
import org.noear.solon.Utils;
|
||||
import org.noear.solon.core.handle.Context;
|
||||
|
||||
import cn.dev33.satoken.context.model.SaResponse;
|
||||
|
||||
/**
|
||||
* @author noear
|
||||
* @since 1.4
|
||||
@ -40,4 +41,10 @@ public class SaResponseForSolon implements SaResponse {
|
||||
ctx.headerSet(name, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object redirect(String url) {
|
||||
ctx.redirect(url);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user