mirror of
https://gitee.com/dromara/sa-token.git
synced 2025-09-19 10:08:07 +08:00
单点登录:CAS模式
This commit is contained in:
@@ -0,0 +1,112 @@
|
||||
package cn.dev33.satoken.config;
|
||||
|
||||
import cn.dev33.satoken.util.SaFoxUtil;
|
||||
|
||||
/**
|
||||
* Sa-Token-SSO 单点登录模块 配置Model
|
||||
* @author kong
|
||||
*
|
||||
*/
|
||||
public class SaSsoConfig {
|
||||
|
||||
/**
|
||||
* Ticket有效期 (单位: 秒)
|
||||
*/
|
||||
public long ticketTimeout = 60 * 5;
|
||||
|
||||
/**
|
||||
* 所有允许的授权回调地址,多个用逗号隔开 (不在此列表中的URL将禁止下放ticket)
|
||||
*/
|
||||
public String allowUrl = "*";
|
||||
|
||||
/**
|
||||
* 调用秘钥
|
||||
*/
|
||||
public String secretkey;
|
||||
|
||||
/**
|
||||
* SSO-Server端授权地址
|
||||
*/
|
||||
public String serverUrl;
|
||||
|
||||
/**
|
||||
* @return SSO-Server端授权地址
|
||||
*/
|
||||
public String getServerUrl() {
|
||||
return serverUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param serverUrl SSO-Server端授权地址
|
||||
*/
|
||||
public void setServerUrl(String serverUrl) {
|
||||
this.serverUrl = serverUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Ticket有效期 (单位: 秒)
|
||||
*/
|
||||
public long getTicketTimeout() {
|
||||
return ticketTimeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ticketTimeout Ticket有效期 (单位: 秒)
|
||||
* @return 对象自身
|
||||
*/
|
||||
public SaSsoConfig setTicketTimeout(long ticketTimeout) {
|
||||
this.ticketTimeout = ticketTimeout;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return 所有允许的授权回调地址,多个用逗号隔开 (不在此列表中的URL将禁止下放ticket)
|
||||
*/
|
||||
public String getAllowUrl() {
|
||||
return allowUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param allowUrl 所有允许的授权回调地址,多个用逗号隔开 (不在此列表中的URL将禁止下放ticket)
|
||||
* @return 对象自身
|
||||
*/
|
||||
public SaSsoConfig setAllowUrl(String allowUrl) {
|
||||
this.allowUrl = allowUrl;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return 调用秘钥
|
||||
*/
|
||||
public String getSecretkey() {
|
||||
return secretkey;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param secretkey 调用秘钥
|
||||
* @return 对象自身
|
||||
*/
|
||||
public SaSsoConfig setSecretkey(String secretkey) {
|
||||
this.secretkey = secretkey;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "SaSsoConfig [ticketTimeout=" + ticketTimeout + ", allowUrl=" + allowUrl + ", secretkey=" + secretkey
|
||||
+ ", serverUrl=" + serverUrl + "]";
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 以数组形式写入允许的授权回调地址
|
||||
* @param url 所有集合
|
||||
* @return 对象自身
|
||||
*/
|
||||
public SaSsoConfig setAllow(String ...url) {
|
||||
this.allowUrl = SaFoxUtil.arrayJoin(url);
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
@@ -66,6 +66,11 @@ public class SaTokenConfig {
|
||||
*/
|
||||
private String jwtSecretKey;
|
||||
|
||||
/**
|
||||
* SSO单点登录配置对象
|
||||
*/
|
||||
public SaSsoConfig sso = new SaSsoConfig();
|
||||
|
||||
|
||||
/**
|
||||
* @return token名称 (同时也是cookie名称)
|
||||
@@ -343,6 +348,22 @@ public class SaTokenConfig {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return SSO单点登录配置对象
|
||||
*/
|
||||
public SaSsoConfig getSso() {
|
||||
return sso;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param sso SSO单点登录配置对象
|
||||
*/
|
||||
public void setSso(SaSsoConfig sso) {
|
||||
this.sso = sso;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* toString()
|
||||
*/
|
||||
@@ -354,10 +375,12 @@ public class SaTokenConfig {
|
||||
+ tokenStyle + ", dataRefreshPeriod=" + dataRefreshPeriod + ", tokenSessionCheckLogin="
|
||||
+ tokenSessionCheckLogin + ", autoRenew=" + autoRenew + ", cookieDomain=" + cookieDomain
|
||||
+ ", tokenPrefix=" + tokenPrefix + ", isPrint=" + isPrint + ", isLog=" + isLog + ", jwtSecretKey="
|
||||
+ jwtSecretKey + "]";
|
||||
+ jwtSecretKey + ", sso=" + sso + "]";
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* <h1> 本函数设计已过时,未来版本可能移除此函数,请及时更换为 setIsConcurrent() ,使用方式保持不变 </h1>
|
||||
* @param allowConcurrentLogin see note
|
||||
@@ -369,7 +392,6 @@ public class SaTokenConfig {
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* <h1> 本函数设计已过时,未来版本可能移除此函数,请及时更换为 setIsConcurrent() ,使用方式保持不变 </h1>
|
||||
* @param isV see note
|
||||
|
@@ -40,6 +40,12 @@ public interface SaRequest {
|
||||
*/
|
||||
public String getRequestPath();
|
||||
|
||||
/**
|
||||
* 返回当前请求的url,例:http://xxx.com/?id=127
|
||||
* @return see note
|
||||
*/
|
||||
public String getUrl();
|
||||
|
||||
/**
|
||||
* 返回当前请求的类型
|
||||
* @return see note
|
||||
|
@@ -0,0 +1,19 @@
|
||||
package cn.dev33.satoken.sso;
|
||||
|
||||
/**
|
||||
* Sa-Token-SSO模块相关常量
|
||||
* @author kong
|
||||
*
|
||||
*/
|
||||
public class SaSsoConsts {
|
||||
|
||||
/** redirect参数名称 */
|
||||
public static final String REDIRECT_NAME = "redirect";
|
||||
|
||||
/** ticket参数名称 */
|
||||
public static final String TICKET_NAME = "ticket";
|
||||
|
||||
/** back参数名称 */
|
||||
public static final String BACK_NAME = "back";
|
||||
|
||||
}
|
@@ -0,0 +1,180 @@
|
||||
package cn.dev33.satoken.sso;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import cn.dev33.satoken.SaManager;
|
||||
import cn.dev33.satoken.exception.SaTokenException;
|
||||
import cn.dev33.satoken.util.SaFoxUtil;
|
||||
|
||||
/**
|
||||
* Sa-Token-SSO 单点登录接口
|
||||
* @author kong
|
||||
*
|
||||
*/
|
||||
public interface SaSsoInterface {
|
||||
|
||||
/**
|
||||
* 创建一个 Ticket码
|
||||
* @param loginId 账号id
|
||||
* @return 票据
|
||||
*/
|
||||
public default String createTicket(Object loginId) {
|
||||
// 随机一个ticket
|
||||
String ticket = SaFoxUtil.getRandomString(64);
|
||||
|
||||
// 保存入库
|
||||
long ticketTimeout = SaManager.getConfig().getSso().getTicketTimeout();
|
||||
SaManager.getSaTokenDao().set(splicingKeyTicketToId(ticket), String.valueOf(loginId), ticketTimeout);
|
||||
SaManager.getSaTokenDao().set(splicingKeyIdToTicket(loginId), String.valueOf(ticket), ticketTimeout);
|
||||
|
||||
// 返回
|
||||
return ticket;
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除一个 Ticket码
|
||||
* @param ticket Ticket码
|
||||
*/
|
||||
public default void deleteTicket(String ticket) {
|
||||
SaManager.getSaTokenDao().delete(splicingKeyTicketToId(ticket));
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据 账号id & 重定向地址,构建[SSO-Client端-重定向地址]
|
||||
* @param loginId 账号id
|
||||
* @param redirect 重定向地址
|
||||
* @return [SSO-Client端-重定向地址]
|
||||
*/
|
||||
public default String buildRedirectUrl(Object loginId, String redirect) {
|
||||
// 校验授权地址
|
||||
checkAuthUrl(redirect);
|
||||
|
||||
// 删掉旧ticket
|
||||
String oldTicket = SaManager.getSaTokenDao().get(splicingKeyIdToTicket(loginId));
|
||||
if(oldTicket != null) {
|
||||
deleteTicket(oldTicket);
|
||||
}
|
||||
|
||||
// 获取新ticket
|
||||
String ticket = createTicket(loginId);
|
||||
|
||||
// 构建 授权重定向地址
|
||||
redirect = encodeBackParam(redirect);
|
||||
String redirectUrl = SaFoxUtil.joinParam(redirect, SaSsoConsts.TICKET_NAME + "=" + ticket);
|
||||
return redirectUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据 Ticket码 获取账号id,如果Ticket码无效则返回null
|
||||
* @param ticket Ticket码
|
||||
* @return 账号id
|
||||
*/
|
||||
public default Object getLoginId(String ticket) {
|
||||
if(SaFoxUtil.isEmpty(ticket)) {
|
||||
return null;
|
||||
}
|
||||
return SaManager.getSaTokenDao().get(splicingKeyTicketToId(ticket));
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据 Ticket码 获取账号id,并转换为指定类型
|
||||
* @param ticket Ticket码
|
||||
* @return 账号id
|
||||
*/
|
||||
public default <T> T getLoginId(String ticket, Class<T> cs) {
|
||||
return SaFoxUtil.getValueByType(getLoginId(ticket), cs);
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验url合法性
|
||||
* @param url 地址
|
||||
*/
|
||||
public default void checkAuthUrl(String url) {
|
||||
|
||||
// 1、是否是一个有效的url
|
||||
if(SaFoxUtil.isUrl(url) == false) {
|
||||
throw new SaTokenException("无效回调地址:" + url);
|
||||
}
|
||||
|
||||
// 2、是否在[允许地址列表]之中
|
||||
String authUrl = SaManager.getConfig().getSso().getAllowUrl();
|
||||
List<String> authUrlList = Arrays.asList(authUrl.split(","));
|
||||
if(SaManager.getSaTokenAction().hasElement(authUrlList, url) == false) {
|
||||
throw new SaTokenException("非法回调地址:" + url);
|
||||
}
|
||||
|
||||
// 验证通过
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据 Client端登录地址 & back地址 ,构建[SSO-Server端-认证地址]
|
||||
* @param clientLoginUrl Client端登录地址
|
||||
* @param back 回调路径
|
||||
* @return [SSO-Server端-认证地址 ]
|
||||
*/
|
||||
public default String buildServerAuthUrl(String clientLoginUrl, String back) {
|
||||
// 服务端认证地址
|
||||
String serverUrl = SaManager.getConfig().getSso().getServerUrl();
|
||||
|
||||
// 对back地址编码
|
||||
back = (back == null ? "" : back);
|
||||
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);
|
||||
|
||||
// 返回
|
||||
return serverAuthUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* 对url中的back参数进行URL编码, 解决超链接重定向后参数丢失的bug
|
||||
* @param url url
|
||||
* @return 编码过后的url
|
||||
*/
|
||||
public default String encodeBackParam(String url) {
|
||||
|
||||
// 获取back参数所在位置
|
||||
int index = url.indexOf("?" + SaSsoConsts.BACK_NAME + "=");
|
||||
if(index == -1) {
|
||||
index = url.indexOf("&" + SaSsoConsts.BACK_NAME + "=");
|
||||
if(index == -1) {
|
||||
return url;
|
||||
}
|
||||
}
|
||||
|
||||
// 开始编码
|
||||
int length = SaSsoConsts.BACK_NAME.length() + 2;
|
||||
String back = url.substring(index + length);
|
||||
back = SaFoxUtil.encodeUrl(back);
|
||||
|
||||
// 放回url中
|
||||
url = url.substring(0, index + length) + back;
|
||||
return url;
|
||||
}
|
||||
|
||||
|
||||
// ------------------- 返回相应key -------------------
|
||||
|
||||
/**
|
||||
* 拼接key:Ticket 查 账号Id
|
||||
* @param ticket
|
||||
* @return key
|
||||
*/
|
||||
public default String splicingKeyTicketToId(String ticket) {
|
||||
return SaManager.getConfig().getTokenName() + ":ticket:" + ticket;
|
||||
}
|
||||
|
||||
/**
|
||||
* 拼接key:账号Id 反查 Ticket
|
||||
* @param id 账号id
|
||||
* @return key
|
||||
*/
|
||||
public default String splicingKeyIdToTicket(Object id) {
|
||||
return SaManager.getConfig().getTokenName() + ":id-ticket:" + id;
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,78 @@
|
||||
package cn.dev33.satoken.sso;
|
||||
|
||||
/**
|
||||
* Sa-Token-SSO 单点登录工具类
|
||||
* @author kong
|
||||
*
|
||||
*/
|
||||
public class SaSsoUtil {
|
||||
|
||||
/**
|
||||
* 底层 SaSsoServerInterface 对象
|
||||
*/
|
||||
public static SaSsoInterface saSsoInterface = new SaSsoInterface() {};
|
||||
|
||||
/**
|
||||
* 创建一个 Ticket票据
|
||||
* @param loginId 账号id
|
||||
* @return 票据
|
||||
*/
|
||||
public static String createTicket(Object loginId) {
|
||||
return saSsoInterface.createTicket(loginId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除一个 Ticket码
|
||||
* @param ticket Ticket码
|
||||
*/
|
||||
public static void deleteTicket(String ticket) {
|
||||
saSsoInterface.deleteTicket(ticket);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据 账号id & 重定向地址,构建[SSO-Client端-重定向地址]
|
||||
* @param loginId 账号id
|
||||
* @param redirect 重定向地址
|
||||
* @return [SSO-Client端-重定向地址]
|
||||
*/
|
||||
public static String buildRedirectUrl(Object loginId, String redirect) {
|
||||
return saSsoInterface.buildRedirectUrl(loginId, redirect);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据 Ticket码 获取账号id,如果Ticket码无效则返回null
|
||||
* @param ticket Ticket码
|
||||
* @return 账号id
|
||||
*/
|
||||
public static Object getLoginId(String ticket) {
|
||||
return saSsoInterface.getLoginId(ticket);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据 Ticket码 获取账号id,并转换为指定类型
|
||||
* @param ticket Ticket码
|
||||
* @return 账号id
|
||||
*/
|
||||
public static <T> T getLoginId(String ticket, Class<T> cs) {
|
||||
return saSsoInterface.getLoginId(ticket, cs);
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验url合法性
|
||||
* @param url 地址
|
||||
*/
|
||||
public static void checkAuthUrl(String url) {
|
||||
saSsoInterface.checkAuthUrl(url);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据 Client端登录地址 & back地址 ,构建[SSO-Server端-认证地址]
|
||||
* @param clientLoginUrl Client端登录地址
|
||||
* @param back 回调路径
|
||||
* @return [SSO-Server端-认证地址 ]
|
||||
*/
|
||||
public static String buildServerAuthUrl(String clientLoginUrl, String back) {
|
||||
return saSsoInterface.buildServerAuthUrl(clientLoginUrl, back);
|
||||
}
|
||||
|
||||
}
|
@@ -1,5 +1,8 @@
|
||||
package cn.dev33.satoken.util;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URLDecoder;
|
||||
import java.net.URLEncoder;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
@@ -9,6 +12,8 @@ import java.util.List;
|
||||
import java.util.Random;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import cn.dev33.satoken.exception.SaTokenException;
|
||||
|
||||
/**
|
||||
* Sa-Token 内部工具类
|
||||
*
|
||||
@@ -147,7 +152,6 @@ public class SaFoxUtil {
|
||||
return Pattern.matches(patt.replaceAll("\\*", ".*"), str);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 将指定值转化为指定类型
|
||||
* @param <T> 泛型
|
||||
@@ -186,5 +190,104 @@ public class SaFoxUtil {
|
||||
return (T)obj3;
|
||||
}
|
||||
|
||||
/**
|
||||
* 在url上拼接上kv参数并返回
|
||||
* @param url url
|
||||
* @param parameStr 参数, 例如 id=1001
|
||||
* @return 拼接后的url字符串
|
||||
*/
|
||||
public static String joinParam(String url, String parameStr) {
|
||||
// 如果参数为空, 直接返回
|
||||
if(parameStr == null || parameStr.length() == 0) {
|
||||
return url;
|
||||
}
|
||||
if(url == null) {
|
||||
url = "";
|
||||
}
|
||||
int index = url.indexOf('?');
|
||||
// ? 不存在
|
||||
if(index == -1) {
|
||||
return url + '?' + parameStr;
|
||||
}
|
||||
// ? 是最后一位
|
||||
if(index == url.length() - 1) {
|
||||
return url + parameStr;
|
||||
}
|
||||
// ? 是其中一位
|
||||
if(index > -1 && index < url.length() - 1) {
|
||||
String separatorChar = "&";
|
||||
// 如果最后一位是 不是&, 且 parameStr 第一位不是 &, 就增送一个 &
|
||||
if(url.lastIndexOf(separatorChar) != url.length() - 1 && parameStr.indexOf(separatorChar) != 0) {
|
||||
return url + separatorChar + parameStr;
|
||||
} else {
|
||||
return url + parameStr;
|
||||
}
|
||||
}
|
||||
// 正常情况下, 代码不可能执行到此
|
||||
return url;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将数组的所有元素使用逗号拼接在一起
|
||||
* @param arr 数组
|
||||
* @return 字符串,例: a,b,c
|
||||
*/
|
||||
public static String arrayJoin(String[] arr) {
|
||||
if(arr == null) {
|
||||
return "";
|
||||
}
|
||||
String str = "";
|
||||
for (int i = 0; i < arr.length; i++) {
|
||||
str += arr[i];
|
||||
if(i != arr.length - 1) {
|
||||
str += ",";
|
||||
}
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证URL的正则表达式
|
||||
*/
|
||||
public static final String URL_REGEX = "(https?|ftp|file)://[-A-Za-z0-9+&@#/%?=~_|!:,.;]+[-A-Za-z0-9+&@#/%=~_|]";
|
||||
|
||||
/**
|
||||
* 使用正则表达式判断一个字符串是否为URL
|
||||
* @param str 字符串
|
||||
* @return 拼接后的url字符串
|
||||
*/
|
||||
public static boolean isUrl(String str) {
|
||||
if(str == null) {
|
||||
return false;
|
||||
}
|
||||
return str.toLowerCase().matches(URL_REGEX);
|
||||
}
|
||||
|
||||
/**
|
||||
* URL编码
|
||||
* @param url see note
|
||||
* @return see note
|
||||
*/
|
||||
public static String encodeUrl(String url) {
|
||||
try {
|
||||
return URLEncoder.encode(url, "UTF-8");
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
throw new SaTokenException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* URL解码
|
||||
* @param url see note
|
||||
* @return see note
|
||||
*/
|
||||
public static String decoderUrl(String url) {
|
||||
try {
|
||||
return URLDecoder.decode(url, "UTF-8");
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
throw new SaTokenException(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
Reference in New Issue
Block a user