重构SSO模块,抽离三种模式的统一认证中心

This commit is contained in:
click33
2021-10-09 05:43:31 +08:00
parent 643118177a
commit d491f4083f
85 changed files with 727 additions and 1364 deletions

View File

@@ -13,6 +13,7 @@ import cn.dev33.satoken.annotation.SaCheckRole;
import cn.dev33.satoken.annotation.SaCheckSafe;
import cn.dev33.satoken.basic.SaBasicUtil;
import cn.dev33.satoken.session.SaSession;
import cn.dev33.satoken.strategy.SaStrategy;
import cn.dev33.satoken.util.SaFoxUtil;
import cn.dev33.satoken.util.SaTokenConsts;
@@ -115,34 +116,35 @@ public class SaTokenActionDefaultImpl implements SaTokenAction {
public void validateAnnotation(AnnotatedElement target) {
// 校验 @SaCheckLogin 注解
if(target.isAnnotationPresent(SaCheckLogin.class)) {
SaCheckLogin at = target.getAnnotation(SaCheckLogin.class);
SaManager.getStpLogic(at.type()).checkByAnnotation(at);
SaCheckLogin checkLogin = (SaCheckLogin) SaStrategy.me.getAnnotation.apply(target, SaCheckLogin.class);
if(checkLogin != null) {
SaManager.getStpLogic(checkLogin.type()).checkByAnnotation(checkLogin);
}
// 校验 @SaCheckRole 注解
if(target.isAnnotationPresent(SaCheckRole.class)) {
SaCheckRole at = target.getAnnotation(SaCheckRole.class);
SaManager.getStpLogic(at.type()).checkByAnnotation(at);
SaCheckRole checkRole = (SaCheckRole) SaStrategy.me.getAnnotation.apply(target, SaCheckRole.class);
if(checkRole != null) {
SaManager.getStpLogic(checkRole.type()).checkByAnnotation(checkRole);
}
// 校验 @SaCheckPermission 注解
if(target.isAnnotationPresent(SaCheckPermission.class)) {
SaCheckPermission at = target.getAnnotation(SaCheckPermission.class);
SaManager.getStpLogic(at.type()).checkByAnnotation(at);
SaCheckPermission checkPermission = (SaCheckPermission) SaStrategy.me.getAnnotation.apply(target, SaCheckPermission.class);
if(checkPermission != null) {
SaManager.getStpLogic(checkPermission.type()).checkByAnnotation(checkPermission);
}
// 校验 @SaCheckSafe 注解
if(target.isAnnotationPresent(SaCheckSafe.class)) {
SaCheckSafe at = target.getAnnotation(SaCheckSafe.class);
SaManager.getStpLogic(null).checkByAnnotation(at);
SaCheckSafe checkSafe = (SaCheckSafe) SaStrategy.me.getAnnotation.apply(target, SaCheckSafe.class);
if(checkSafe != null) {
SaManager.getStpLogic(checkSafe.type()).checkByAnnotation(checkSafe);
}
// 校验 @SaCheckBasic 注解
if(target.isAnnotationPresent(SaCheckBasic.class)) {
SaCheckBasic at = target.getAnnotation(SaCheckBasic.class);
SaBasicUtil.check(at.realm(), at.account());
}
SaCheckBasic checkBasic = (SaCheckBasic) SaStrategy.me.getAnnotation.apply(target, SaCheckBasic.class);
if(checkBasic != null) {
SaBasicUtil.check(checkBasic.realm(), checkBasic.account());
}
}
}

View File

@@ -15,4 +15,10 @@ import java.lang.annotation.Target;
@Target({ ElementType.METHOD, ElementType.TYPE })
public @interface SaCheckSafe {
/**
* 多账号体系下所属的账号体系标识
* @return see note
*/
String type() default "";
}

View File

@@ -48,6 +48,9 @@ public class SaSsoConsts {
/** back参数名称 */
public static String back = "back";
/** mode参数名称 */
public static String mode = "mode";
/** loginId参数名称 */
public static String loginId = "loginId";
@@ -71,6 +74,12 @@ public class SaSsoConsts {
/** 表示自己 */
public static final String SELF = "self";
/** 表示简单模式SSO模式一 */
public static final String MODE_SIMPLE = "simple";
/** 表示ticket模式SSO模式二和模式三 */
public static final String MODE_TICKET = "ticket";
/** 表示请求没有得到任何有效处理 {msg: "not handle"} */
public static final String NOT_HANDLE = "{\"msg\": \"not handle\"}";

View File

@@ -20,7 +20,7 @@ import cn.dev33.satoken.util.SaResult;
public class SaSsoHandle {
/**
* 处理所有Server端请求
* 处理Server端所有请求
* @return 处理结果
*/
public static Object serverRequest() {
@@ -46,8 +46,13 @@ public class SaSsoHandle {
return ssoCheckTicket();
}
// SSO-Server端单点注销
if(req.isPath(Api.ssoLogout) && cfg.isSlo) {
// SSO-Server端单点注销 [模式一] (不带loginId参数)
if(req.isPath(Api.ssoLogout) && cfg.isSlo && req.hasParam(ParamName.loginId) == false) {
return ssoServerLogoutType1();
}
// SSO-Server端单点注销 [模式三] (带loginId参数)
if(req.isPath(Api.ssoLogout) && cfg.isHttp && cfg.isSlo && req.hasParam(ParamName.loginId)) {
return ssoServerLogout();
}
@@ -66,15 +71,25 @@ public class SaSsoHandle {
SaSsoConfig cfg = SaManager.getConfig().getSso();
StpLogic stpLogic = SaSsoUtil.saSsoTemplate.stpLogic;
// ---------- 此处两种情况分开处理:
// 情况1在SSO认证中心尚未登录先去登
// ---------- 此处两种情况分开处理:
// ---- 情况1在SSO认证中心尚未登录需要先去登录
if(stpLogic.isLogin() == false) {
return cfg.notLoginView.get();
}
// 情况2在SSO认证中心已经登录开始构建授权重定向地址下放ticket
// ---- 情况2在SSO认证中心已经登录需要重定向回 Client 端,而这又分为两种方式:
String mode = req.getParam(ParamName.mode, "");
// 方式1直接重定向回Client端 (mode=simple)
if(mode.equals(SaSsoConsts.MODE_SIMPLE)) {
String redirect = req.getParam(ParamName.redirect);
SaSsoUtil.checkRedirectUrl(redirect);
return res.redirect(redirect);
} else {
// 方式2带着ticket参数重定向回Client端 (mode=ticket)
String redirectUrl = SaSsoUtil.buildRedirectUrl(stpLogic.getLoginId(), req.getParam(ParamName.redirect));
return res.redirect(redirectUrl);
}
}
/**
* SSO-Server端RestAPI 登录接口
@@ -112,7 +127,24 @@ public class SaSsoHandle {
}
/**
* SSO-Server端单点注销
* SSO-Server端单点注销 [模式一]
* @return 处理结果
*/
public static Object ssoServerLogoutType1() {
// 获取对象
SaRequest req = SaHolder.getRequest();
SaResponse res = SaHolder.getResponse();
StpLogic stpLogic = SaSsoUtil.saSsoTemplate.stpLogic;
// 开始处理
stpLogic.logout();
// 返回
return ssoLogoutBack(req, res);
}
/**
* SSO-Server端单点注销 [模式三]
* @return 处理结果
*/
public static Object ssoServerLogout() {
@@ -126,7 +158,6 @@ public class SaSsoHandle {
String secretkey = req.getParam(ParamName.secretkey);
// 遍历通知Client端注销会话
// SaSsoUtil.singleLogout(secretkey, loginId, url -> cfg.sendHttp.apply(url));
// step.1 校验秘钥
SaSsoUtil.checkSecretkey(secretkey);
@@ -134,7 +165,7 @@ public class SaSsoHandle {
SaSsoUtil.forEachSloUrl(loginId, url -> cfg.sendHttp.apply(url));
// step.3 Server端注销
stpLogic.logoutByTokenValue(stpLogic.getTokenValueByLoginId(loginId));
stpLogic.logout(loginId);
// 完成
return SaSsoConsts.OK;
@@ -142,7 +173,7 @@ public class SaSsoHandle {
/**
* 处理所有Client端请求
* 处理Client端所有请求
* @return 处理结果
*/
public static Object clientRequest() {
@@ -206,20 +237,8 @@ public class SaSsoHandle {
return res.redirect(serverAuthUrl);
} else {
// ------- 1、校验ticket获取账号id
Object loginId = null;
if(cfg.isHttp) {
// 方式1使用http请求校验ticket
String ssoLogoutCall = null;
if(cfg.isSlo) {
ssoLogoutCall = SaHolder.getRequest().getUrl().replace(Api.ssoLogin, Api.ssoLogoutCall);
}
String checkUrl = SaSsoUtil.buildCheckTicketUrl(ticket, ssoLogoutCall);
Object body = cfg.sendHttp.apply(checkUrl);
loginId = (SaFoxUtil.isEmpty(body) ? null : body);
} else {
// 方式2直连Redis校验ticket
loginId = SaSsoUtil.checkTicket(ticket);
}
Object loginId = checkTicket(ticket, Api.ssoLogin);
// Be: 如果开发者自定义了处理逻辑
if(cfg.ticketResultHandle != null) {
return cfg.ticketResultHandle.apply(loginId, back);
@@ -307,7 +326,7 @@ public class SaSsoHandle {
/*
* 三种情况:
* 1. 有back参数值为SELF -> 回退一级并刷新
* 2. 有back参数值为url -> 跳转back地址
* 2. 有back参数值为url -> 跳转到此url地址
* 3. 无back参数 -> 返回json数据
*/
String back = req.getParam(ParamName.back);
@@ -321,4 +340,28 @@ public class SaSsoHandle {
}
}
/**
* 封装校验ticket取出loginId
* @param ticket ticket码
* @param currUri 当前路由的uri用于计算单点注销回调地址
* @return loginId
*/
public static Object checkTicket(String ticket, String currUri) {
SaSsoConfig cfg = SaManager.getConfig().getSso();
// --------- 两种模式
if(cfg.isHttp) {
// 模式三使用http请求校验ticket
String ssoLogoutCall = null;
if(cfg.isSlo) {
ssoLogoutCall = SaHolder.getRequest().getUrl().replace(currUri, Api.ssoLogoutCall);
}
String checkUrl = SaSsoUtil.buildCheckTicketUrl(ticket, ssoLogoutCall);
Object body = cfg.sendHttp.apply(checkUrl);
return (SaFoxUtil.isEmpty(body) ? null : body);
} else {
// 模式二直连Redis校验ticket
return SaSsoUtil.checkTicket(ticket);
}
}
}

View File

@@ -328,10 +328,13 @@ public class SaSsoTemplate {
* @param fun 调用方法
*/
public void forEachSloUrl(Object loginId, CallSloUrlFunction fun) {
String secretkey = SaManager.getConfig().getSso().getSecretkey();
Set<String> urlSet = stpLogic.getSessionByLoginId(loginId).get(SaSsoConsts.SLO_CALLBACK_SET_KEY,
() -> new HashSet<String>());
SaSession session = stpLogic.getSessionByLoginId(loginId, false);
if(session == null) {
return;
}
String secretkey = SaManager.getConfig().getSso().getSecretkey();
Set<String> urlSet = session.get(SaSsoConsts.SLO_CALLBACK_SET_KEY, () -> new HashSet<String>());
for (String url : urlSet) {
// 拼接login参数、秘钥参数
url = SaFoxUtil.joinParam(url, ParamName.loginId, loginId);

View File

@@ -443,7 +443,7 @@ public class StpLogic {
* @param appendFun 追加操作
* @param isLogoutSession 是否注销 User-Session
*/
private void clearTokenCommonMethod(Object loginId, String device, Consumer<String> appendFun, boolean isLogoutSession) {
protected void clearTokenCommonMethod(Object loginId, String device, Consumer<String> appendFun, boolean isLogoutSession) {
// 1. 如果此账号尚未登录,则不执行任何操作
SaSession session = getSessionByLoginId(loginId, false);
if(session == null) {

View File

@@ -1,5 +1,6 @@
package cn.dev33.satoken.strategy;
import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Method;
import java.util.List;
@@ -24,6 +25,7 @@ import cn.dev33.satoken.session.SaSession;
* </p>
*
* @author kong
* @param <T>
*
*/
@SuppressWarnings("deprecation")
@@ -80,12 +82,22 @@ public final class SaStrategy {
/**
* 对一个 [元素] 对象进行注解校验 (注解鉴权内部实现)
* <p> 参数 [target元素]
* <p> 参数 [element元素]
*/
public Consumer<AnnotatedElement> checkElementAnnotation = (element) -> {
// 为了兼容旧版本
SaManager.getSaTokenAction().validateAnnotation(element);
};
/**
* 从元素上获取注解(注解鉴权内部实现)
* <p> 参数 [element元素要获取的注解类型]
*/
public BiFunction<AnnotatedElement, Class<? extends Annotation> , Annotation> getAnnotation = (element, annotationClass)->{
// 默认使用jdk的注解处理器
return element.getAnnotation(annotationClass);
};
//
// 重写策略 set连缀风格
@@ -137,7 +149,7 @@ public final class SaStrategy {
/**
* 对一个 [元素] 对象进行注解校验 (注解鉴权内部实现)
* <p> 参数 [target元素]
* <p> 参数 [element元素]
* @param checkElementAnnotation /
* @return 对象自身
*/
@@ -146,5 +158,16 @@ public final class SaStrategy {
return this;
}
/**
* 从元素上获取注解(注解鉴权内部实现)
* <p> 参数 [element元素要获取的注解类型]
* @param getAnnotation /
* @return 对象自身
*/
public SaStrategy setGetAnnotation(BiFunction<AnnotatedElement, Class<? extends Annotation> , Annotation> getAnnotation) {
this.getAnnotation = getAnnotation;
return this;
}
}

View File

@@ -13,7 +13,7 @@ public class SaTokenConsts {
/**
* Sa-Token 当前版本号
*/
public static final String VERSION_NO = "v1.26.2";
public static final String VERSION_NO = "v1.27.0";
/**
* Sa-Token 开源地址

View File

@@ -10,13 +10,7 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import com.pj.util.AjaxJson;
import cn.dev33.satoken.SaManager;
import cn.dev33.satoken.annotation.SaCheckBasic;
import cn.dev33.satoken.annotation.SaCheckLogin;
import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.dev33.satoken.annotation.SaCheckRole;
import cn.dev33.satoken.annotation.SaCheckSafe;
import cn.dev33.satoken.basic.SaBasicUtil;
import cn.dev33.satoken.context.SaHolder;
import cn.dev33.satoken.filter.SaServletFilter;
import cn.dev33.satoken.interceptor.SaAnnotationInterceptor;
@@ -86,27 +80,8 @@ public class SaTokenConfigure implements WebMvcConfigurer {
@PostConstruct
public void rewriteSaStrategy() {
// 重写Sa-Token的注解处理器增加注解合并功能
SaStrategy.me.setCheckElementAnnotation(element -> {
if(AnnotatedElementUtils.isAnnotated(element, SaCheckLogin.class)) {
SaCheckLogin at = AnnotatedElementUtils.getMergedAnnotation(element, SaCheckLogin.class);
SaManager.getStpLogic(at.type()).checkByAnnotation(at);
}
if(AnnotatedElementUtils.isAnnotated(element, SaCheckRole.class)) {
SaCheckRole at = AnnotatedElementUtils.getMergedAnnotation(element, SaCheckRole.class);
SaManager.getStpLogic(at.type()).checkByAnnotation(at);
}
if(AnnotatedElementUtils.isAnnotated(element, SaCheckPermission.class)) {
SaCheckPermission at = AnnotatedElementUtils.getMergedAnnotation(element, SaCheckPermission.class);
SaManager.getStpLogic(at.type()).checkByAnnotation(at);
}
if(AnnotatedElementUtils.isAnnotated(element, SaCheckSafe.class)) {
SaCheckSafe at = AnnotatedElementUtils.getMergedAnnotation(element, SaCheckSafe.class);
SaManager.getStpLogic(null).checkByAnnotation(at);
}
if(AnnotatedElementUtils.isAnnotated(element, SaCheckBasic.class)) {
SaCheckBasic at = AnnotatedElementUtils.getMergedAnnotation(element, SaCheckBasic.class);
SaBasicUtil.check(at.realm(), at.account());
}
SaStrategy.me.setGetAnnotation((element, annotationClass) -> {
return AnnotatedElementUtils.getMergedAnnotation(element, SaCheckLogin.class);
});
}

View File

@@ -2,6 +2,8 @@ package com.pj.satoken.at;
import java.util.List;
import org.springframework.stereotype.Component;
import cn.dev33.satoken.SaManager;
import cn.dev33.satoken.fun.SaFunction;
import cn.dev33.satoken.session.SaSession;
@@ -14,6 +16,7 @@ import cn.dev33.satoken.stp.StpUtil;
* Sa-Token 权限认证工具类
* @author kong
*/
@Component
public class StpUserUtil {
/**

View File

@@ -42,9 +42,9 @@ sa.ajax = function(url, data, successFn) {
// ----------------------------------- 相关事件 -----------------------------------
// 检查当前是否已经登录,如果已登录则直接开始跳转,如果未登录则等待用户输入账号密码
sa.ajax("/getRedirectUrl", {redirect: getParam('redirect', '')}, function(res) {
sa.ajax("/getRedirectUrl", {redirect: getParam('redirect', ''), mode: getParam('mode', '')}, function(res) {
if(res.code == 200) {
// redirect地址有效开始跳转
// 已登录,并且redirect地址有效开始跳转
location.href = decodeURIComponent(res.data);
} else if(res.code == 401) {
console.log('未登录');

View File

@@ -3,7 +3,7 @@
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-demo-sso3-server</artifactId>
<artifactId>sa-token-demo-sso-server</artifactId>
<version>0.0.1-SNAPSHOT</version>
<!-- SpringBoot -->
@@ -21,7 +21,7 @@
<dependencies>
<!-- SpringBoot依赖 -->
<!-- SpringBoot Web依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
@@ -40,20 +40,18 @@
<artifactId>sa-token-dao-redis-jackson</artifactId>
<version>${sa-token-version}</version>
</dependency>
<!-- 提供redis连接池 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
<!-- 视图引擎 -->
<!-- 视图引擎(在前后端不分离模式下提供视图支持) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!-- Http请求工具 -->
<!-- Http请求工具(在模式三的单点注销功能下用到,如不需要可以注释掉) -->
<dependency>
<groupId>com.ejlchina</groupId>
<artifactId>okhttps</artifactId>

View File

@@ -8,7 +8,7 @@ public class SaSsoServerApplication {
public static void main(String[] args) {
SpringApplication.run(SaSsoServerApplication.class, args);
System.out.println("\nSa-Token-SSO 认证中心启动成功");
System.out.println("\n------ Sa-Token-SSO 统一认证中心启动成功 ");
}
}

View File

@@ -4,8 +4,10 @@ import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import cn.dev33.satoken.sso.SaSsoConsts;
import cn.dev33.satoken.sso.SaSsoUtil;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.util.SaFoxUtil;
import cn.dev33.satoken.util.SaResult;
/**
@@ -21,15 +23,22 @@ public class H5Controller {
* 获取 redirectUrl
*/
@RequestMapping("/getRedirectUrl")
private Object getRedirectUrl(String redirect) {
private Object getRedirectUrl(String redirect, String mode) {
// 未登录情况下返回 code=401
if(StpUtil.isLogin() == false) {
return SaResult.code(401);
}
// 已登录情况下构建 redirectUrl
if(SaSsoConsts.MODE_SIMPLE.equals(mode)) {
// 模式一
SaSsoUtil.checkRedirectUrl(SaFoxUtil.decoderUrl(redirect));
return SaResult.data(redirect);
} else {
// 模式二或模式三
String redirectUrl = SaSsoUtil.buildRedirectUrl(StpUtil.getLoginId(), redirect);
return SaResult.data(redirectUrl);
}
}
// 全局异常拦截
@ExceptionHandler

View File

@@ -0,0 +1,23 @@
package com.pj.sso;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import cn.dev33.satoken.util.SaResult;
/**
* 全局异常处理
* @author kong
*
*/
@RestControllerAdvice
public class GlobalExceptionHandler {
// 全局异常拦截
@ExceptionHandler
public SaResult handlerException(Exception e) {
e.printStackTrace();
return SaResult.error(e.getMessage());
}
}

View File

@@ -1,11 +1,12 @@
package com.pj.sso;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.ExceptionHandler;
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.config.SaTokenConfig;
import cn.dev33.satoken.sso.SaSsoHandle;
import cn.dev33.satoken.stp.StpUtil;
@@ -34,28 +35,26 @@ public class SsoServerController {
// 配置SSO相关参数
@Autowired
private void configSso(SaTokenConfig cfg) {
cfg.sso
// 配置未登录时返回的View
.setNotLoginView(() -> {
cfg.sso.setNotLoginView(() -> {
return new ModelAndView("sa-login.html");
})
});
// 配置登录处理函数
.setDoLoginHandle((name, pwd) -> {
cfg.sso.setDoLoginHandle((name, pwd) -> {
// 此处仅做模拟登录真实环境应该查询数据进行登录
if("sa".equals(name) && "123456".equals(pwd)) {
StpUtil.login(10001);
return SaResult.ok("登录成功!").setData(StpUtil.getTokenValue());
}
return SaResult.error("登录失败!");
})
;
}
});
// 全局异常拦截
@ExceptionHandler
public SaResult handlerException(Exception e) {
e.printStackTrace();
return SaResult.error(e.getMessage());
// 配置 Http 请求处理器 在模式三的单点注销功能下用到如不需要可以注释掉
cfg.sso.setSendHttp(url -> {
return OkHttps.sync(url).get().getBody().toString();
});
}
}

View File

@@ -4,25 +4,32 @@ server:
# Sa-Token 配置
sa-token:
# SSO-相关配置
# -------------- SSO-模式一相关配置 (非模式一不需要配置)
# cookie:
# 配置Cookie作用域
# domain: stp.com
# ------- SSO-模式二相关配置
sso:
# Ticket有效期 (单位: 秒),默认五分钟
ticket-timeout: 300
# 所有允许的授权回调地址
# allow-url: http://sa-sso-client1.com:9001/sso/login, http://sa-sso-client2.com:9001/sso/login, http://sa-sso-client3.com:9001/sso/login
allow-url: "*"
# 接口调用秘钥用于SSO模式三的单点注销功能
secretkey: kQwIOrYvnXmSDkwEiFngrKidMcdrgKor
# 使用Http请求校验ticket
is-http: true
# 打开单点注销功能
# 是否打开单点注销功能
is-slo: true
# ------- SSO-模式三相关配置 下面的配置在SSO模式三并且 is-slo=true 时打开) -------
# 是否打开模式三
isHttp: true
# 接口调用秘钥用于SSO模式三的单点注销功能
secretkey: kQwIOrYvnXmSDkwEiFngrKidMcdrgKor
# ---- 除了以上配置项,你还需要为 Sa-Token 配置http请求处理器文档有步骤说明
spring:
# Redis配置
# Redis配置 SSO模式一和模式二使用Redis来同步会话
redis:
# Redis数据库索引默认为0
database: 5
database: 1
# Redis服务器地址
host: 127.0.0.1
# Redis服务器连接端口

View File

@@ -13,7 +13,7 @@ public class SaSsoClientApplication {
public static void main(String[] args) {
SpringApplication.run(SaSsoClientApplication.class, args);
System.out.println("\nSa-Token-SSO Client端启动成功");
System.out.println("\nSa-Token SSO模式一 Client端启动成功");
}
}

View File

@@ -15,16 +15,14 @@ import cn.dev33.satoken.util.SaResult;
@RestController
public class SsoClientController {
/*
* SSO-Client端首页
*/
// SSO-Client端首页
@RequestMapping("/")
public String index() {
String authUrl = SaManager.getConfig().getSso().getAuthUrl();
String solUrl = SaManager.getConfig().getSso().getSloUrl();
String str = "<h2>Sa-Token SSO-Client 应用端</h2>" +
"<p>当前会话是否登录:" + StpUtil.isLogin() + "</p>" +
"<p><a href=\"javascript:location.href='" + authUrl + "?redirect=' + encodeURIComponent(location.href);\">登录</a> " +
"<p><a href=\"javascript:location.href='" + authUrl + "?mode=simple&redirect=' + encodeURIComponent(location.href);\">登录</a> " +
"<a href=\"javascript:location.href='" + solUrl + "?back=' + encodeURIComponent(location.href);\">注销</a> </p>";
return str;
}

View File

@@ -6,9 +6,9 @@ server:
sa-token:
# SSO-相关配置
sso:
# SSO-Server端 单点登录地址
# SSO-Server端-单点登录授权地址
auth-url: http://sso.stp.com:9000/sso/auth
# SSO-Server端 单点注销地址
# SSO-Server端-单点注销地址
slo-url: http://sso.stp.com:9000/sso/logout
# 配置Sa-Token单独使用的Redis连接 此处需要和SSO-Server端连接同一个Redis

View File

@@ -1,57 +0,0 @@
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-demo-sso1-server</artifactId>
<version>0.0.1-SNAPSHOT</version>
<!-- SpringBoot -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.0.RELEASE</version>
<relativePath/>
</parent>
<!-- 定义sa-token版本号 -->
<properties>
<sa-token-version>1.26.0</sa-token-version>
</properties>
<dependencies>
<!-- 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>
</dependency>
<!-- Sa-Token整合redis (使用jackson序列化方式) -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-dao-redis-jackson</artifactId>
<version>${sa-token-version}</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
<!-- 视图引擎 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -1,19 +0,0 @@
package com.pj;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* SSO模式一Server端 Demo
* @author kong
*
*/
@SpringBootApplication
public class SaSsoServerApplication {
public static void main(String[] args) {
SpringApplication.run(SaSsoServerApplication.class, args);
System.out.println("\nSa-Token-SSO 认证中心启动成功");
}
}

View File

@@ -1,62 +0,0 @@
package com.pj.sso;
import org.springframework.web.bind.annotation.ExceptionHandler;
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.stp.StpUtil;
import cn.dev33.satoken.util.SaFoxUtil;
import cn.dev33.satoken.util.SaResult;
/**
* Sa-Token-SSO Server端 Controller
* @author kong
*
*/
@RestController
public class SsoServerController {
/*
* SSO-Server端统一登录地址
*/
@RequestMapping("/sso/auth")
public Object ssoAuth(String redirect) {
// 如果尚未登录,则返回登录视图进行登录
if(StpUtil.isLogin() == false) {
return new ModelAndView("sa-login.html");
}
// 如果已登录,则原路返回到 Client端
return SaHolder.getResponse().redirect(redirect);
}
// 处理登录请求
@RequestMapping("/sso/doLogin")
public SaResult ssoDoLogin(String name, String pwd) {
// 模拟登录
if("sa".equals(name) && "123456".equals(pwd)) {
StpUtil.login(10001);
return SaResult.ok("登录成功!");
}
return SaResult.error("登录失败!");
}
// 单点注销地址
@RequestMapping("/sso/logout")
public Object ssoLogout(String back) {
StpUtil.logout();
if(SaFoxUtil.isEmpty(back)) {
return SaResult.ok();
}
return SaHolder.getResponse().redirect(back);
}
// 全局异常拦截
@ExceptionHandler
public SaResult handlerException(Exception e) {
e.printStackTrace();
return SaResult.error(e.getMessage());
}
}

View File

@@ -1,38 +0,0 @@
# 端口
server:
port: 9000
# Sa-Token配置
sa-token:
# 写入Cookie时显式指定的作用域, 用于单点登录二级域名共享Cookie
cookie-domain: stp.com
spring:
# Redis配置
redis:
# Redis数据库索引默认为0
database: 1
# Redis服务器地址
host: 127.0.0.1
# Redis服务器连接端口
port: 6379
# Redis服务器连接密码默认为空
password:
# 连接超时时间
timeout: 10s
lettuce:
pool:
# 连接池最大连接数
max-active: 200
# 连接池最大阻塞等待时间(使用负值表示没有限制)
max-wait: -1ms
# 连接池中的最大空闲连接
max-idle: 10
# 连接池中的最小空闲连接
min-idle: 0

View File

@@ -1,45 +0,0 @@
<!DOCTYPE html>
<html lang="zh">
<head>
<title>Sa-SSO-Server 认证中心-登录</title>
<meta charset="utf-8">
<base th:href="@{/}" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no">
<link rel="stylesheet" href="./sa-res/login.css">
</head>
<body>
<div class="view-box">
<div class="bg-1"></div>
<div class="bg-2"></div>
<div class="content-box">
<div class="login-box">
<div class="from-box">
<h2 class="from-title">Sa-SSO-Server 认证中心</h2>
<div class="from-item">
<input class="s-input" name="name" placeholder="请输入账号" />
</div>
<div class="from-item">
<input class="s-input" name="pwd" type="password" placeholder="请输入密码" />
</div>
<div class="from-item">
<button class="s-input s-btn login-btn">登录</button>
</div>
<div class="from-item reset-box">
<a href="javascript: location.reload();" >刷新</a>
</div>
</div>
</div>
</div>
<!-- 底部 版权 -->
<div style="position: absolute; bottom: 40px; width: 100%; text-align: center; color: #666;">
This page is provided by Sa-Token-SSO
</div>
</div>
<!-- scripts -->
<script src="https://unpkg.zhimg.com/jquery@3.4.1/dist/jquery.min.js"></script>
<script src="https://www.layuicdn.com/layer-v3.1.1/layer.js"></script>
<script src="./sa-res/login.js"></script>
</body>
</html>

View File

@@ -1,12 +0,0 @@
target/
node_modules/
bin/
.settings/
unpackage/
.classpath
.project
.idea/
.factorypath

View File

@@ -1,40 +0,0 @@
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-demo-sso1</artifactId>
<version>0.0.1-SNAPSHOT</version>
<!-- SpringBoot -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.0.RELEASE</version>
<relativePath/>
</parent>
<!-- 定义sa-token版本号 -->
<properties>
<sa-token-version>1.26.0</sa-token-version>
</properties>
<dependencies>
<!-- 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>
</dependency>
</dependencies>
</project>

View File

@@ -1,36 +0,0 @@
package com.pj;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import cn.dev33.satoken.SaManager;
@SpringBootApplication
public class SaSsoApplication {
/*
此 demo 意在使用最少的代码演示一下SSO模式一的认证原理
1、改hosts(C:\windows\system32\drivers\etc\hosts)
127.0.0.1 sso.stp.com
127.0.0.1 s1.stp.com
127.0.0.1 s2.stp.com
127.0.0.1 s3.stp.com
2、运行项目
启动 SaSsoApplication
3、浏览器访问
http://s1.stp.com:8081/sso/isLogin
http://s2.stp.com:8081/sso/isLogin
http://s3.stp.com:8081/sso/isLogin
均显示未登录
4、然后访问任意节点的登录接口
http://s1.stp.com:8081/sso/doLogin
5、重复步骤3刷新三个地址
均显示已登录
*/
public static void main(String[] args) {
SpringApplication.run(SaSsoApplication.class, args);
System.out.println("\n启动成功Sa-Token配置如下" + SaManager.getConfig());
}
}

View File

@@ -1,34 +0,0 @@
package com.pj.sso;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.util.SaResult;
/**
* 测试: 同域单点登录
* @author kong
*/
@RestController
@RequestMapping("/sso/")
public class SsoController {
// 测试:进行登录
@RequestMapping("doLogin")
public SaResult doLogin(@RequestParam(defaultValue = "10001") String id) {
System.out.println("---------------- 进行登录 ");
StpUtil.login(id);
return SaResult.ok("登录成功: " + id);
}
// 测试:是否登录
@RequestMapping("isLogin")
public SaResult isLogin() {
System.out.println("---------------- 是否登录 ");
boolean isLogin = StpUtil.isLogin();
return SaResult.ok("是否登录: " + isLogin);
}
}

View File

@@ -1,8 +0,0 @@
# 端口
server:
port: 8081
sa-token:
# 写入Cookie时显式指定的作用域, 用于单点登录二级域名共享Cookie
cookie-domain: stp.com

View File

@@ -8,7 +8,7 @@ public class SaSsoClientApplication {
public static void main(String[] args) {
SpringApplication.run(SaSsoClientApplication.class, args);
System.out.println("\nSa-Token-SSO Client端启动成功");
System.out.println("\nSa-Token SSO模式二 Client端启动成功");
}
}

View File

@@ -4,6 +4,7 @@ import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import cn.dev33.satoken.sso.SaSsoHandle;
import cn.dev33.satoken.sso.SaSsoUtil;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.util.SaResult;
@@ -33,7 +34,7 @@ public class H5Controller {
// 根据ticket进行登录
@RequestMapping("/doLoginByTicket")
public SaResult doLoginByTicket(String ticket) {
Object loginId = checkTicket(ticket);
Object loginId = SaSsoHandle.checkTicket(ticket, "/doLoginByTicket");
if(loginId != null) {
StpUtil.login(loginId);
return SaResult.data(StpUtil.getTokenValue());
@@ -41,11 +42,6 @@ public class H5Controller {
return SaResult.error("无效ticket" + ticket);
}
// 校验 Ticket码获取账号Id
private Object checkTicket(String ticket) {
return SaSsoUtil.checkTicket(ticket);
}
// 全局异常拦截
@ExceptionHandler
public SaResult handlerException(Exception e) {

View File

@@ -8,7 +8,7 @@ sa-token:
sso:
# SSO-Server端 统一认证地址
auth-url: http://sa-sso-server.com:9000/sso/auth
# auth-url: http://127.0.0.1:8848/sa-token-demo-sso2-server-h5/sso-auth.html
# auth-url: http://127.0.0.1:8848/sa-token-demo-sso-server-h5/sso-auth.html
# 是否打开单点注销接口
is-slo: true

View File

@@ -1,12 +0,0 @@
target/
node_modules/
bin/
.settings/
unpackage/
.classpath
.project
.idea/
.factorypath

View File

@@ -1,59 +0,0 @@
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-demo-sso2-server</artifactId>
<version>0.0.1-SNAPSHOT</version>
<!-- SpringBoot -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.0.RELEASE</version>
<relativePath/>
</parent>
<!-- 定义sa-token版本号 -->
<properties>
<sa-token-version>1.26.0</sa-token-version>
</properties>
<dependencies>
<!-- 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>
</dependency>
<!-- Sa-Token整合redis (使用jackson序列化方式) -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-dao-redis-jackson</artifactId>
<version>${sa-token-version}</version>
</dependency>
<!-- 提供redis连接池 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
<!-- 视图引擎 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -1,43 +0,0 @@
# 端口
server:
port: 9000
# Sa-Token配置
sa-token:
# SSO-相关配置
sso:
# Ticket有效期 (单位: 秒),默认五分钟
ticket-timeout: 300
# 所有允许的授权回调地址
# allow-url: http://sa-sso-client1.com:9001/sso/login, http://sa-sso-client2.com:9001/sso/login, http://sa-sso-client3.com:9001/sso/login
allow-url: "*"
spring:
# Redis配置
redis:
# Redis数据库索引默认为0
database: 1
# Redis服务器地址
host: 127.0.0.1
# Redis服务器连接端口
port: 6379
# Redis服务器连接密码默认为空
password:
# 连接超时时间
timeout: 10s
lettuce:
pool:
# 连接池最大连接数
max-active: 200
# 连接池最大阻塞等待时间(使用负值表示没有限制)
max-wait: -1ms
# 连接池中的最大空闲连接
max-idle: 10
# 连接池中的最小空闲连接
min-idle: 0

View File

@@ -1,59 +0,0 @@
*{margin: 0; padding: 0;}
body{font-family: Helvetica Neue,Helvetica,PingFang SC,Tahoma,Arial,sans-serif;}
::-webkit-input-placeholder{color: #ccc;}
/* 视图盒子 */
.view-box{position: relative; width: 100vw; height: 100vh; overflow: hidden;}
/* 背景 EAEFF3 */
.bg-1{height: 50%; background: linear-gradient(to bottom right, #0466c5, #3496F5);}
.bg-2{height: 50%; background-color: #EAEFF3;}
/* 渐变背景 */
/*.bg-1{
background-size: 500%;
background-image: linear-gradient(125deg,#0466c5,#3496F5,#0466c5,#3496F5,#0466c5,#2496F5);
animation: bganimation 30s infinite;
}
@keyframes bganimation{
0%{background-position: 0% 50%;}
50%{background-position: 100% 50%;}
100%{background-position: 0% 50%;}
} */
/* 背景 */
.bg-1{background: #101C34;}
.bg-2{background: #101C34;}
/* .bg-1{height: 100%; background-image: url(./login-bg.png); background-size: 100% 100%;} */
/* 内容盒子 */
.content-box{position: absolute; width: 100vw; height: 100vh; top: 0px;}
/* 登录盒子 */
/* .login-box{width: 400px; height: 400px; position: absolute; left: calc(50% - 200px); top: calc(50% - 200px); max-width: 90%; } */
.login-box{width: 400px; margin: auto; max-width: 90%; height: 100%;}
.login-box{display: flex; align-items: center; text-align: center;}
/* 表单 */
.from-box{flex: 1; padding: 20px 50px; background-color: #FFF;}
.from-box{border-radius: 1px; box-shadow: 1px 1px 20px #666;}
.from-title{margin-top: 20px; margin-bottom: 30px; text-align: center;}
/* 输入框 */
.from-item{border: 0px #000 solid; margin-bottom: 15px;}
.s-input{width: 100%; line-height: 32px; height: 32px; text-indent: 1em; outline: 0; border: 1px #ccc solid; border-radius: 3px; transition: all 0.2s;}
.s-input{font-size: 12px;}
.s-input:focus{border-color: #409eff}
/* 登录按钮 */
.s-btn{ text-indent: 0; cursor: pointer; background-color: #409EFF; border-color: #409EFF; color: #FFF;}
.s-btn:hover{background-color: #50aEFF;}
/* 重置按钮 */
.reset-box{text-align: left; font-size: 12px;}
.reset-box a{text-decoration: none;}
.reset-box a:hover{text-decoration: underline;}
/* loading框样式 */
.ajax-layer-load.layui-layer-dialog{min-width: 0px !important; background-color: rgba(0,0,0,0.85);}
.ajax-layer-load.layui-layer-dialog .layui-layer-content{padding: 10px 20px 10px 40px; color: #FFF;}
.ajax-layer-load.layui-layer-dialog .layui-layer-content .layui-layer-ico{width: 20px; height: 20px; background-size: 20px 20px; top: 12px; }

View File

@@ -1,65 +0,0 @@
// sa
var sa = {};
// 打开loading
sa.loading = function(msg) {
layer.closeAll(); // 开始前先把所有弹窗关了
return layer.msg(msg, {icon: 16, shade: 0.3, time: 1000 * 20, skin: 'ajax-layer-load' });
};
// 隐藏loading
sa.hideLoading = function() {
layer.closeAll();
};
// ----------------------------------- 登录事件 -----------------------------------
$('.login-btn').click(function(){
sa.loading("正在登录...");
// 开始登录
setTimeout(function() {
$.ajax({
url: "sso/doLogin",
type: "post",
data: {
name: $('[name=name]').val(),
pwd: $('[name=pwd]').val()
},
dataType: 'json',
success: function(res){
console.log('返回数据:', res);
sa.hideLoading();
if(res.code == 200) {
layer.msg('登录成功', {anim: 0, icon: 6 });
setTimeout(function() {
location.reload();
}, 800)
} else {
layer.msg(res.msg, {anim: 6, icon: 2 });
}
},
error: function(xhr, type, errorThrown){
sa.hideLoading();
if(xhr.status == 0){
return layer.alert('无法连接到服务器,请检查网络');
}
return layer.alert("异常:" + JSON.stringify(xhr));
}
});
}, 400);
});
// 绑定回车事件
$('[name=name],[name=pwd]').bind('keypress', function(event){
if(event.keyCode == "13") {
$('.login-btn').click();
}
});
// 输入框获取焦点
$("[name=name]").focus();
// 打印信息
var str = "This page is provided by Sa-Token, Please refer to: " + "http://sa-token.dev33.cn/";
console.log(str);

View File

@@ -8,7 +8,7 @@ public class SaSsoClientApplication {
public static void main(String[] args) {
SpringApplication.run(SaSsoClientApplication.class, args);
System.out.println("\nSa-Token-SSO Client端启动成功");
System.out.println("\nSa-Token SSO模式三 Client端启动成功");
}
}

View File

@@ -44,12 +44,10 @@ public class SsoClientController {
// 配置SSO相关参数
@Autowired
private void configSso(SaTokenConfig cfg) {
cfg.sso
// 配置Http请求处理器
.setSendHttp(url -> {
cfg.sso.setSendHttp(url -> {
return OkHttps.sync(url).get().getBody().toString();
})
;
});
}
// 查询我的账号信息

View File

@@ -25,7 +25,7 @@ spring:
# 配置Sa-Token单独使用的Redis连接 此处需要和SSO-Server端连接同一个Redis
redis:
# Redis数据库索引
database: 6
database: 2
# Redis服务器地址
host: 127.0.0.1
# Redis服务器连接端口

View File

@@ -1,12 +0,0 @@
target/
node_modules/
bin/
.settings/
unpackage/
.classpath
.project
.idea/
.factorypath

View File

@@ -1,14 +0,0 @@
package com.pj;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class SaSsoServerApplication {
public static void main(String[] args) {
SpringApplication.run(SaSsoServerApplication.class, args);
System.out.println("\nSa-Token-SSO 认证中心启动成功");
}
}

View File

@@ -1,85 +0,0 @@
package com.pj.sso;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.ExceptionHandler;
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.config.SaTokenConfig;
import cn.dev33.satoken.sso.SaSsoHandle;
import cn.dev33.satoken.sso.SaSsoUtil;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.util.SaResult;
/**
* Sa-Token-SSO Server端 Controller
* @author kong
*
*/
@RestController
public class SsoServerController {
/*
* SSO-Server端处理所有SSO相关请求
* http://{host}:{port}/sso/auth -- 单点登录授权地址接受参数redirect=授权重定向地址
* http://{host}:{port}/sso/doLogin -- 账号密码登录接口接受参数name、pwd
* http://{host}:{port}/sso/checkTicket -- Ticket校验接口isHttp=true时打开接受参数ticket=ticket码、ssoLogoutCall=单点注销回调地址 [可选]
* http://{host}:{port}/sso/logout -- 单点注销地址isSlo=true时打开接受参数loginId=账号id、secretkey=接口调用秘钥
*/
@RequestMapping("/sso/*")
public Object ssoRequest() {
return SaSsoHandle.serverRequest();
}
// 自定义接口获取userinfo
@RequestMapping("/sso/userinfo")
public Object userinfo(String loginId, String secretkey) {
System.out.println("---------------- 获取userinfo --------");
// 校验调用秘钥
SaSsoUtil.checkSecretkey(secretkey);
// 自定义返回结果(模拟)
return SaResult.ok()
.set("id", loginId)
.set("name", "linxiaoyu")
.set("sex", "")
.set("age", 18);
}
// 配置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("登录成功!").setData(StpUtil.getTokenValue());
}
return SaResult.error("登录失败!");
})
// 配置Http请求处理器
.setSendHttp(url -> {
// 此处为了提高响应速度这里可将sync换为async
return OkHttps.sync(url).get();
})
;
}
// 全局异常拦截
@ExceptionHandler
public SaResult handlerException(Exception e) {
e.printStackTrace();
return SaResult.error(e.getMessage());
}
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,2 +0,0 @@
/*! layer mobile-v2.0.0 Web弹层组件 MIT License http://layer.layui.com/mobile By 贤心 */
;!function(e){"use strict";var t=document,n="querySelectorAll",i="getElementsByClassName",a=function(e){return t[n](e)},s={type:0,shade:!0,shadeClose:!0,fixed:!0,anim:"scale"},l={extend:function(e){var t=JSON.parse(JSON.stringify(s));for(var n in e)t[n]=e[n];return t},timer:{},end:{}};l.touch=function(e,t){e.addEventListener("click",function(e){t.call(this,e)},!1)};var r=0,o=["layui-m-layer"],c=function(e){var t=this;t.config=l.extend(e),t.view()};c.prototype.view=function(){var e=this,n=e.config,s=t.createElement("div");e.id=s.id=o[0]+r,s.setAttribute("class",o[0]+" "+o[0]+(n.type||0)),s.setAttribute("index",r);var l=function(){var e="object"==typeof n.title;return n.title?'<h3 style="'+(e?n.title[1]:"")+'">'+(e?n.title[0]:n.title)+"</h3>":""}(),c=function(){"string"==typeof n.btn&&(n.btn=[n.btn]);var e,t=(n.btn||[]).length;return 0!==t&&n.btn?(e='<span yes type="1">'+n.btn[0]+"</span>",2===t&&(e='<span no type="0">'+n.btn[1]+"</span>"+e),'<div class="layui-m-layerbtn">'+e+"</div>"):""}();if(n.fixed||(n.top=n.hasOwnProperty("top")?n.top:100,n.style=n.style||"",n.style+=" top:"+(t.body.scrollTop+n.top)+"px"),2===n.type&&(n.content='<i></i><i class="layui-m-layerload"></i><i></i><p>'+(n.content||"")+"</p>"),n.skin&&(n.anim="up"),"msg"===n.skin&&(n.shade=!1),s.innerHTML=(n.shade?"<div "+("string"==typeof n.shade?'style="'+n.shade+'"':"")+' class="layui-m-layershade"></div>':"")+'<div class="layui-m-layermain" '+(n.fixed?"":'style="position:static;"')+'><div class="layui-m-layersection"><div class="layui-m-layerchild '+(n.skin?"layui-m-layer-"+n.skin+" ":"")+(n.className?n.className:"")+" "+(n.anim?"layui-m-anim-"+n.anim:"")+'" '+(n.style?'style="'+n.style+'"':"")+">"+l+'<div class="layui-m-layercont">'+n.content+"</div>"+c+"</div></div></div>",!n.type||2===n.type){var d=t[i](o[0]+n.type),y=d.length;y>=1&&layer.close(d[0].getAttribute("index"))}document.body.appendChild(s);var u=e.elem=a("#"+e.id)[0];n.success&&n.success(u),e.index=r++,e.action(n,u)},c.prototype.action=function(e,t){var n=this;e.time&&(l.timer[n.index]=setTimeout(function(){layer.close(n.index)},1e3*e.time));var a=function(){var t=this.getAttribute("type");0==t?(e.no&&e.no(),layer.close(n.index)):e.yes?e.yes(n.index):layer.close(n.index)};if(e.btn)for(var s=t[i]("layui-m-layerbtn")[0].children,r=s.length,o=0;o<r;o++)l.touch(s[o],a);if(e.shade&&e.shadeClose){var c=t[i]("layui-m-layershade")[0];l.touch(c,function(){layer.close(n.index,e.end)})}e.end&&(l.end[n.index]=e.end)},e.layer={v:"2.0",index:r,open:function(e){var t=new c(e||{});return t.index},close:function(e){var n=a("#"+o[0]+e)[0];n&&(n.innerHTML="",t.body.removeChild(n),clearTimeout(l.timer[e]),delete l.timer[e],"function"==typeof l.end[e]&&l.end[e](),delete l.end[e])},closeAll:function(){for(var e=t[i](o[0]),n=0,a=e.length;n<a;n++)layer.close(0|e[0].getAttribute("index"))}},"function"==typeof define?define(function(){return layer}):function(){var e=document.scripts,n=e[e.length-1],i=n.src,a=i.substring(0,i.lastIndexOf("/")+1);n.getAttribute("merge")||document.head.appendChild(function(){var e=t.createElement("link");return e.href=a+"need/layer.css?2.0",e.type="text/css",e.rel="styleSheet",e.id="layermcss",e}())}()}(window);

View File

@@ -1,59 +0,0 @@
*{margin: 0; padding: 0;}
body{font-family: Helvetica Neue,Helvetica,PingFang SC,Tahoma,Arial,sans-serif;}
::-webkit-input-placeholder{color: #ccc;}
/* 视图盒子 */
.view-box{position: relative; width: 100vw; height: 100vh; overflow: hidden;}
/* 背景 EAEFF3 */
.bg-1{height: 50%; background: linear-gradient(to bottom right, #0466c5, #3496F5);}
.bg-2{height: 50%; background-color: #EAEFF3;}
/* 渐变背景 */
/*.bg-1{
background-size: 500%;
background-image: linear-gradient(125deg,#0466c5,#3496F5,#0466c5,#3496F5,#0466c5,#2496F5);
animation: bganimation 30s infinite;
}
@keyframes bganimation{
0%{background-position: 0% 50%;}
50%{background-position: 100% 50%;}
100%{background-position: 0% 50%;}
} */
/* 背景 */
.bg-1{background: #101C34;}
.bg-2{background: #101C34;}
/* .bg-1{height: 100%; background-image: url(./login-bg.png); background-size: 100% 100%;} */
/* 内容盒子 */
.content-box{position: absolute; width: 100vw; height: 100vh; top: 0px;}
/* 登录盒子 */
/* .login-box{width: 400px; height: 400px; position: absolute; left: calc(50% - 200px); top: calc(50% - 200px); max-width: 90%; } */
.login-box{width: 400px; margin: auto; max-width: 90%; height: 100%;}
.login-box{display: flex; align-items: center; text-align: center;}
/* 表单 */
.from-box{flex: 1; padding: 20px 50px; background-color: #FFF;}
.from-box{border-radius: 1px; box-shadow: 1px 1px 20px #666;}
.from-title{margin-top: 20px; margin-bottom: 30px; text-align: center;}
/* 输入框 */
.from-item{border: 0px #000 solid; margin-bottom: 15px;}
.s-input{width: 100%; line-height: 32px; height: 32px; text-indent: 1em; outline: 0; border: 1px #ccc solid; border-radius: 3px; transition: all 0.2s;}
.s-input{font-size: 12px;}
.s-input:focus{border-color: #409eff}
/* 登录按钮 */
.s-btn{ text-indent: 0; cursor: pointer; background-color: #409EFF; border-color: #409EFF; color: #FFF;}
.s-btn:hover{background-color: #50aEFF;}
/* 重置按钮 */
.reset-box{text-align: left; font-size: 12px;}
.reset-box a{text-decoration: none;}
.reset-box a:hover{text-decoration: underline;}
/* loading框样式 */
.ajax-layer-load.layui-layer-dialog{min-width: 0px !important; background-color: rgba(0,0,0,0.85);}
.ajax-layer-load.layui-layer-dialog .layui-layer-content{padding: 10px 20px 10px 40px; color: #FFF;}
.ajax-layer-load.layui-layer-dialog .layui-layer-content .layui-layer-ico{width: 20px; height: 20px; background-size: 20px 20px; top: 12px; }

View File

@@ -1,65 +0,0 @@
// sa
var sa = {};
// 打开loading
sa.loading = function(msg) {
layer.closeAll(); // 开始前先把所有弹窗关了
return layer.msg(msg, {icon: 16, shade: 0.3, time: 1000 * 20, skin: 'ajax-layer-load' });
};
// 隐藏loading
sa.hideLoading = function() {
layer.closeAll();
};
// ----------------------------------- 登录事件 -----------------------------------
$('.login-btn').click(function(){
sa.loading("正在登录...");
// 开始登录
setTimeout(function() {
$.ajax({
url: "sso/doLogin",
type: "post",
data: {
name: $('[name=name]').val(),
pwd: $('[name=pwd]').val()
},
dataType: 'json',
success: function(res){
console.log('返回数据:', res);
sa.hideLoading();
if(res.code == 200) {
layer.msg('登录成功', {anim: 0, icon: 6 });
setTimeout(function() {
location.reload();
}, 800)
} else {
layer.msg(res.msg, {anim: 6, icon: 2 });
}
},
error: function(xhr, type, errorThrown){
sa.hideLoading();
if(xhr.status == 0){
return layer.alert('无法连接到服务器,请检查网络');
}
return layer.alert("异常:" + JSON.stringify(xhr));
}
});
}, 400);
});
// 绑定回车事件
$('[name=name],[name=pwd]').bind('keypress', function(event){
if(event.keyCode == "13") {
$('.login-btn').click();
}
});
// 输入框获取焦点
$("[name=name]").focus();
// 打印信息
var str = "This page is provided by Sa-Token, Please refer to: " + "http://sa-token.dev33.cn/";
console.log(str);

View File

@@ -1,45 +0,0 @@
<!DOCTYPE html>
<html lang="zh">
<head>
<title>Sa-SSO-Server 认证中心-登录</title>
<meta charset="utf-8">
<base th:href="@{/}" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no">
<link rel="stylesheet" href="./sa-res/login.css">
</head>
<body>
<div class="view-box">
<div class="bg-1"></div>
<div class="bg-2"></div>
<div class="content-box">
<div class="login-box">
<div class="from-box">
<h2 class="from-title">Sa-SSO-Server 认证中心</h2>
<div class="from-item">
<input class="s-input" name="name" placeholder="请输入账号" />
</div>
<div class="from-item">
<input class="s-input" name="pwd" type="password" placeholder="请输入密码" />
</div>
<div class="from-item">
<button class="s-input s-btn login-btn">登录</button>
</div>
<div class="from-item reset-box">
<a href="javascript: location.reload();" >刷新</a>
</div>
</div>
</div>
</div>
<!-- 底部 版权 -->
<div style="position: absolute; bottom: 40px; width: 100%; text-align: center; color: #666;">
This page is provided by Sa-Token-SSO
</div>
</div>
<!-- scripts -->
<script src="./sa-res/jquery.min.js"></script>
<script src="./sa-res/layer/layer.js"></script>
<script src="./sa-res/login.js"></script>
</body>
</html>

View File

@@ -34,6 +34,7 @@
- **单点登录**
- [单点登录简述](/sso/readme)
- [搭建统一认证中心SSO-Server](/sso/sso-server)
- [SSO模式一 共享Cookie同步会话](/sso/sso-type1)
- [SSO模式二 URL重定向传播会话](/sso/sso-type2)
- [SSO模式三 Http请求获取会话](/sso/sso-type3)

View File

@@ -18,7 +18,7 @@ public class SsoServerController {
return SaSsoHandle.serverRequest();
}
// ... 其它方法
// ... 其它代码
}
```

View File

@@ -80,15 +80,14 @@ if(res.code == 401) {
#### 方式二:在配置中配置登录视图地址
``` java
cfg.sso
// 配置未登录时返回的View
.setNotLoginView(() -> {
cfg.sso.setNotLoginView(() -> {
return new ModelAndView("xxx.html");
})
```
### 3、如何自定义登录API的接口
### 3、如何自定义登录API的接口地址
根据需求点选择解决方案:
#### 3.1、如果只是想在 setDoLoginHandle 函数里获取除 name、pwd 以外的参数?

View File

@@ -30,7 +30,7 @@ public class H5Controller {
// 根据ticket进行登录
@RequestMapping("/doLoginByTicket")
public SaResult doLoginByTicket(String ticket) {
Object loginId = checkTicket(ticket);
Object loginId = SaSsoHandle.checkTicket(ticket);
if(loginId != null) {
StpUtil.login(loginId);
return SaResult.data(StpUtil.getTokenValue());
@@ -38,11 +38,6 @@ public class H5Controller {
return SaResult.error("无效ticket" + ticket);
}
// 校验 Ticket码获取账号Id
private Object checkTicket(String ticket) {
return SaSsoUtil.checkTicket(ticket);
}
// 全局异常拦截
@ExceptionHandler
public SaResult handlerException(Exception e) {
@@ -58,7 +53,7 @@ public class H5Controller {
将其复制到项目中即可
### 3、新建前端项目
任意文件夹新建前端项目:`sa-token-demo-sso2-client-h5`,在根目录添加测试文件:`index.html`
任意文件夹新建前端项目:`sa-token-demo-sso-client-h5`,在根目录添加测试文件:`index.html`
``` js
<!DOCTYPE html>
<html>
@@ -102,43 +97,22 @@ public class H5Controller {
```
### 4、添加登录处理文件`sso-login.html`
源码详见:[sso-login.html](https://gitee.com/dromara/sa-token/tree/master/sa-token-demo/sa-token-demo-sso2-client-h5/sso-login.html)
源码详见:[sso-login.html](https://gitee.com/dromara/sa-token/tree/master/sa-token-demo/sa-token-demo-sso-client-h5/sso-login.html)
将其复制到项目中即可,与`index.html`一样放在根目录下
### 5、测试运行
先启动Server服务端与Client服务端再随便找个能预览html的工具打开前端项目比如[HBuilderX](https://www.dcloud.io/hbuilderx.html)),测试流程与一体版一致
### 6、疑问我在SSO模式三的demo中加入上述代码提示我 ticket无效是怎么回事
上述代码是以SSO模式二为基础的提示“Ticket无效”的原因很简单因为SSO模式三中 Server端 与 Client端 连接的不是同一个Redis
所以Client端校验Ticket时无法在Redis中查询到相应的值才会产生异常“Ticket无效”
要使上述代码生效很简单我们只需更改一下校验Ticket的逻辑即可将 `H5Controller` 中的 `checkTicket` 方法代码改为:
``` java
// 校验 Ticket码获取账号Id
private Object checkTicket(String ticket) {
SaSsoConfig cfg = SaManager.getConfig().getSso();
String ssoLogoutCall = null;
if(cfg.isSlo) {
ssoLogoutCall = SaHolder.getRequest().getUrl().replace("/doLoginByTicket", Api.ssoLogoutCall);
}
String checkUrl = SaSsoUtil.buildCheckTicketUrl(ticket, ssoLogoutCall);
Object body = cfg.sendHttp.apply(checkUrl);
return (SaFoxUtil.isEmpty(body) ? null : body);
}
```
重新运行项目即可在SSO模式三中成功整合前后台分离模式 。
### 7、SSO-Server 端的前后台分离
### 6、SSO-Server 端的前后台分离
疑问:上述代码都是针对 Client 端进行拆分,如果我想在 SSO-Server 端也进行前后台分离改造,应该怎么做?
> 答解决思路都是大同小异的与Client一样我们需要把原本在 “后端处理的授权重定向逻辑” 拿到前端来实现。
由于集成代码与 Client 端类似这里暂不贴详细代码我们可以下载官方仓库里面有搭建好的demo
使用前端ide导入项目 `/sa-token-demo/sa-token-demo-sso2-h5`,浏览器访问 `sso-auth.html` 页面:
使用前端ide导入项目 `/sa-token-demo/sa-token-demo-sso-server-h5`,浏览器访问 `sso-auth.html` 页面:
![sso-type2-server-h5-auth.png](https://oss.dev33.cn/sa-token/doc/sso/sso-type2-server-h5-auth.png 's-w-sh')
@@ -148,7 +122,7 @@ private Object checkTicket(String ticket) {
sa-token:
sso:
# SSO-Server端 统一认证地址
auth-url: http://127.0.0.1:8848/sa-token-demo-sso2-server-h5/sso-auth.html
auth-url: http://127.0.0.1:8848/sa-token-demo-sso-server-h5/sso-auth.html
```
然后我们启动项目 `sa-token-demo-sso2-server` 与 `sa-token-demo-sso2-client`,按照之前的测试步骤访问:

View File

@@ -0,0 +1,297 @@
# 搭建统一认证中心 SSO-Server
在开始SSO三种模式的对接之前我们必须先搭建一个 SSO-Server 认证中心
> 搭建示例在官方仓库的 `/sa-token-demo/sa-token-demo-sso-server/`如遇到难点可结合源码进行测试学习demo里有制作好的登录页面
---
### 1、添加依赖
创建 SpringBoot 项目 `sa-token-demo-sso-server`,引入依赖:
``` xml
<!-- Sa-Token 权限认证, 在线文档http://sa-token.dev33.cn/ -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-spring-boot-starter</artifactId>
<version>${sa.top.version}</version>
</dependency>
<!-- Sa-Token 整合 Redis (使用 jackson 序列化方式) -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-dao-redis-jackson</artifactId>
<version>${sa.top.version}</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
<!-- 视图引擎(在前后端不分离模式下提供视图支持) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!-- Http请求工具在模式三的单点注销功能下用到如不需要可以注释掉 -->
<dependency>
<groupId>com.ejlchina</groupId>
<artifactId>okhttps</artifactId>
<version>3.1.1</version>
</dependency>
```
除了 **sa-token-spring-boot-starter** 以外,其它包都是可选的:
- 在SSO模式三时 Redis 相关包是可选的
- 在前后端分离模式下可以删除 thymeleaf 相关包
- 在不需要SSO模式三单点注销的情况下可以删除 http 工具包
建议先完整测试三种模式之后再对pom依赖进行酌情删减。
### 2、开放认证接口
新建 `SsoServerController`,用于对外开放接口:
``` java
/**
* Sa-Token-SSO Server端 Controller
*/
@RestController
public class SsoServerController {
/*
* SSO-Server端处理所有SSO相关请求 (下面的章节我们会详细列出开放的接口)
*/
@RequestMapping("/sso/*")
public Object ssoRequest() {
return SaSsoHandle.serverRequest();
}
/**
* 配置SSO相关参数
*/
@Autowired
private void configSso(SaTokenConfig cfg) {
// 配置未登录时返回的View
cfg.sso.setNotLoginView(() -> {
String msg = "当前会话在SSO-Server端尚未登录请先访问"
+ "<a href='/sso/doLogin?name=sa&pwd=123456' target='_blank'> doLogin登录 </a>"
+ "进行登录之后,刷新页面开始授权";
return msg;
});
// 配置:登录处理函数
cfg.sso.setDoLoginHandle((name, pwd) -> {
// 此处仅做模拟登录,真实环境应该查询数据进行登录
if("sa".equals(name) && "123456".equals(pwd)) {
StpUtil.login(10001);
return SaResult.ok("登录成功!").setData(StpUtil.getTokenValue());
}
return SaResult.error("登录失败!");
});
// 配置 Http 请求处理器 (在模式三的单点注销功能下用到,如不需要可以注释掉)
cfg.sso.setSendHttp(url -> {
return OkHttps.sync(url).get().getBody().toString();
});
}
}
```
注:在`setDoLoginHandle`函数里如果要获取name, pwd以外的参数可通过`SaHolder.getRequest().getParam("xxx")`来获取
全局异常处理:
``` java
@RestControllerAdvice
public class GlobalExceptionHandler {
// 全局异常拦截
@ExceptionHandler
public SaResult handlerException(Exception e) {
e.printStackTrace();
return SaResult.error(e.getMessage());
}
}
```
### 3、application.yml配置
``` yml
# 端口
server:
port: 9000
# Sa-Token 配置
sa-token:
# -------------- SSO-模式一相关配置 (非模式一不需要配置)
# cookie:
# 配置Cookie作用域
# domain: stp.com
# ------- SSO-模式二相关配置
sso:
# Ticket有效期 (单位: 秒),默认五分钟
ticket-timeout: 300
# 所有允许的授权回调地址
allow-url: "*"
# 是否打开单点注销功能
is-slo: true
# ------- SSO-模式三相关配置 下面的配置在SSO模式三并且 is-slo=true 时打开) -------
# 是否打开模式三
isHttp: true
# 接口调用秘钥用于SSO模式三的单点注销功能
secretkey: kQwIOrYvnXmSDkwEiFngrKidMcdrgKor
# ---- 除了以上配置项,你还需要为 Sa-Token 配置http请求处理器文档有步骤说明
spring:
# Redis配置 SSO模式一和模式二使用Redis来同步会话
redis:
# Redis数据库索引默认为0
database: 1
# Redis服务器地址
host: 127.0.0.1
# Redis服务器连接端口
port: 6379
# Redis服务器连接密码默认为空
password:
```
注意点:`allow-url`为了方便测试配置为`*`线上生产环境一定要配置为详细URL地址 (之后的章节我们会详细阐述此配置项)
### 4、创建启动类
``` java
@SpringBootApplication
public class SaSsoServerApplication {
public static void main(String[] args) {
SpringApplication.run(SaSsoServerApplication.class, args);
System.out.println("\n------ Sa-Token-SSO 认证中心启动成功");
}
}
```
启动项目,不出意外的情况下我们将看到如下输出:
![sso-server-start](https://oss.dev33.cn/sa-token/doc/sso/sso-server-start.png 's-w-sh')
访问统一授权地址:
- [http://localhost:9000/sso/auth](http://localhost:9000/sso/auth)
![sso-server-init-login.png](https://oss.dev33.cn/sa-token/doc/sso/sso-server-init-login.png 's-w-sh')
可以看到这个页面非常简陋这是因为我们以上的代码示例主要目标是为了带大家从零搭建一个可用的SSO认证服务端所以就对一些不太必要的步骤做了简化
大家可以下载运行一下官方仓库里的示例`/sa-token-demo/sa-token-demo-sso-server/`,里面有制作好的登录页面:
![sso-server-init-login2.png](https://oss.dev33.cn/sa-token/doc/sso/sso-server-init-login2.png 's-w-sh')
默认账号密码为:`sa / 123456`,大家先别着急点击登录,因为我们还没有搭建对应的 Client 端项目,
真实项目中我们是不会直接从浏览器访问 `/sso/auth` 授权地址的,我们需要在 Client 端点击登录按钮重定向而来。
现在我们先来看看除了 `/sso/auth` 统一授权地址,这个 SSO-Server 认证中心还开放了哪些API呢且往下看
### 5、API 列表
如果你仅仅使用 Sa-Token 搭建 SSO-Server 端,而 Client 端使用其它框架的话,那么下面的 API 列表将给你的对接步骤做一份参考。
如果你在 Client 端也用到了 Sa-Token 框架那么你可以选择跳过本小节Sa-Token 对 Client 端也提供了相应的封装,你可以直接开始学习:[SSO模式一 共享Cookie同步会话](/sso/sso-type1)
#### 5.1、单点登录授权地址
``` url
http://{host}:{port}/sso/auth
```
接收参数:
| 参数 | 是否必填 | 说明 |
| :-------- | :-------- | :-------- |
| redirect | 是 | 登录成功后的重定向地址,一般填写 location.href从哪来回哪去 |
| mode | 否 | 授权模式,取值 [simple, ticket]simple=登录后直接重定向ticket=带着ticket参数重定向默认值为ticket |
访问接口后有两种情况:
- 情况一:当前会话在 SSO 认证中心未登录,会进入登录页开始登录。
- 情况二:当前会话在 SSO 认证中心已登录,会被重定向至 `redirect` 地址,并携带 `ticket` 参数。
#### 5.2、RestAPI 登录接口
``` url
http://{host}:{port}/sso/doLogin
```
接收参数:
| 参数 | 是否必填 | 说明 |
| :-------- | :-------- | :-------- |
| name | 是 | 用户名 |
| pwd | 是 | 密码 |
此接口属于 RestAPI (使用ajax访问),会进入后端配置的 `setDoLoginHandle` 函数中,另外需要注意:
此接口并非只能携带 name、pwd 参数,因为你可以在 setDoLoginHandle 函数里通过 `SaHolder.getRequest().getParam("xxx")` 来获取其它参数。
#### 5.3、Ticket 校验接口
此接口仅配置模式三 `(isHttp=true)` 时打开
``` url
http://{host}:{port}/sso/checkTicket
```
接收参数:
| 参数 | 是否必填 | 说明 |
| :-------- | :-------- | :-------- |
| ticket | 是 | 在步骤 5.1 中授权重定向时的 ticket 参数 |
| ssoLogoutCall | 否 | 单点注销时的回调通知地址只在SSO模式三时需要携带此参数|
返回值场景:
- 返回空,代表校验失败。
- 返回具体的 loginId例如10001代表校验成功值为此 ticket 码代表的用户id。
#### 5.4、单点注销接口
``` url
http://{host}:{port}/sso/logout
```
接受参数:
| 参数 | 是否必填 | 说明 |
| :-------- | :-------- | :-------- |
| loginId | 否 | 要注销的账号id |
| secretkey | 否 | 接口通信秘钥 |
| back | 否 | 注销成功后的重定向地址 |
此接口有两种调用方式
##### 方式一:在前端页面引导用户直接跳转,并带有 back 参数
例如:`http://{host}:{port}/sso/logout?back=xxx`代表用户注销成功后返回back地址
##### 方式二:在 Client 的后端通过 http 工具来调用
例如:`http://{host}:{port}/sso/logout?loginId={value}&secretkey={value}`,代表注销 账号=loginId 的账号返回json数据结果形如
``` js
{
"code": 200, // 200表示请求成功非200标识请求失败
"msg": "单点注销成功",
"data": null
}
```
<br>
SSO 认证中心只有这四个接口,接下来让我一起来看一下 Client 端的对接流程:[SSO模式一 共享Cookie同步会话](/sso/sso-type1)

View File

@@ -4,7 +4,7 @@
---
### 0、解决思路?
### 1、解决思路?
首先我们分析一下多个系统之间,为什么无法同步登录状态?
1. 前端的 `Token` 无法在多个系统下共享。
@@ -20,12 +20,8 @@
OK所有理论就绪下面开始实战
> Sa-Token整合同域单点登录非常简单相比于正常的登录你只需增加配置 `sa-token.cookie-domain=xxx.com` 指定一下Cookie写入时的父级域名即可。 <br>
> 整合示例在官方仓库的 `/sa-token-demo/sa-token-demo-sso1/`,如遇到难点可结合源码进行测试学习。
### 1、准备工作
### 2、准备工作
首先修改hosts文件`(C:\windows\system32\drivers\etc\hosts)`添加以下IP映射方便我们进行测试
``` url
@@ -35,109 +31,153 @@ OK所有理论就绪下面开始实战
127.0.0.1 s3.stp.com
```
<!-- 其中:`sso.stp.com`为统一认证地址,当用户在其它 Client 端发起登录请求时,均将其重定向至认证中心,待到登录成功之后再原路返回到 Client 端。 -->
其中:`sso.stp.com`为统一认证地址,其它均为 Client 端。
其中:`sso.stp.com`为统一认证中心地址,当用户在其它 Client 端发起登录请求时,均将其重定向至认证中心,待到登录成功之后再原路返回到 Client 端。
### 2、指定Cookie的作用域
在`s1.stp.com`访问服务器其Cookie也只能写入到`s1.stp.com`下为了将Cookie写入到其父级域名`stp.com`下,我们需要新增配置:
### 3、指定Cookie的作用域
在`sso.stp.com`访问服务器其Cookie也只能写入到`sso.stp.com`下为了将Cookie写入到其父级域名`stp.com`下,我们需要更改 SSO-Server 端的 yml 配置:
``` yml
sa-token:
# 写入Cookie时显式指定的作用域, 用于单点登录二级域名共享Cookie
cookie-domain: stp.com
cookie:
# 配置Cookie作用域 (这个配置原本是被注释掉的,现在我们将其打开)
domain: stp.com
```
### 3、新增测试Controller
新建`SsoController.java`控制器,写入代码:
在SSO模式一测试完毕之后一定要将这个配置再次注释掉因为模式一与模式二三使用不同的授权流程这行配置会影响到我们模式二和模式三的正常运行。
### 4、搭建 Client 端项目
> 整合示例在官方仓库的 `/sa-token-demo/sa-token-demo-sso1-client/`,如遇到难点可结合源码进行测试学习。
#### 4.1、引入依赖
新建项目 sa-token-demo-sso1-client并添加以下依赖
``` xml
<!-- Sa-Token 权限认证, 在线文档http://sa-token.dev33.cn/ -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-spring-boot-starter</artifactId>
<version>${sa-token-version}</version>
</dependency>
<!-- Sa-Token 整合redis (使用jackson序列化方式) -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-dao-redis-jackson</artifactId>
<version>${sa-token-version}</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
<!-- Sa-Token插件权限缓存与业务缓存分离 -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-alone-redis</artifactId>
<version>${sa-token-version}</version>
</dependency>
```
#### 4.2、新建 Controller 控制器
``` java
/**
* 测试: 同域单点登录
* Sa-Token-SSO Client端 Controller
* @author kong
*/
@RestController
@RequestMapping("/sso/")
public class SsoController {
public class SsoClientController {
// 测试:进行登录
@RequestMapping("doLogin")
public SaResult doLogin(@RequestParam(defaultValue = "10001") String id) {
System.out.println("---------------- 进行登录 ");
StpUtil.login(id);
return SaResult.ok("登录成功: " + id);
// SSO-Client端首页
@RequestMapping("/")
public String index() {
String authUrl = SaManager.getConfig().getSso().getAuthUrl();
String solUrl = SaManager.getConfig().getSso().getSloUrl();
String str = "<h2>Sa-Token SSO-Client 应用端</h2>" +
"<p>当前会话是否登录:" + StpUtil.isLogin() + "</p>" +
"<p><a href=\"javascript:location.href='" + authUrl + "?mode=simple&redirect=' + encodeURIComponent(location.href);\">登录</a> " +
"<a href=\"javascript:location.href='" + solUrl + "?back=' + encodeURIComponent(location.href);\">注销</a> </p>";
return str;
}
// 测试:是否登录
@RequestMapping("isLogin")
public SaResult isLogin() {
System.out.println("---------------- 是否登录 ");
boolean isLogin = StpUtil.isLogin();
return SaResult.ok("是否登录: " + isLogin);
// 全局异常拦截
@ExceptionHandler
public SaResult handlerException(Exception e) {
e.printStackTrace();
return SaResult.error(e.getMessage());
}
}
```
#### 4.3、application.yml 配置
``` yml
# 端口
server:
port: 9001
# sa-token配置
sa-token:
# SSO-相关配置
sso:
# SSO-Server端-单点登录授权地址
auth-url: http://sso.stp.com:9000/sso/auth
# SSO-Server端-单点注销地址
slo-url: http://sso.stp.com:9000/sso/logout
# 配置Sa-Token单独使用的Redis连接 此处需要和SSO-Server端连接同一个Redis
alone-redis:
# Redis数据库索引
database: 1
# Redis服务器地址
host: 127.0.0.1
# Redis服务器连接端口
port: 6379
# Redis服务器连接密码默认为空
password:
# 连接超时时间
timeout: 10s
```
#### 4.4、启动类
``` java
// 启动类
/**
* SSO模式一Client端 Demo
*/
@SpringBootApplication
public class SaSsoApplication {
public class SaSsoClientApplication {
public static void main(String[] args) {
SpringApplication.run(SaSsoApplication.class, args);
System.out.println("\n启动成功:Sa-Token配置如下:" + SaManager.getConfig());
SpringApplication.run(SaSsoClientApplication.class, args);
System.out.println("\nSa-Token SSO模式一 Client端启动成功");
}
}
```
### 4、访问测试
启动项目,依次访问:
- [http://s1.stp.com:8081/sso/isLogin](http://s1.stp.com:8081/sso/isLogin)
- [http://s2.stp.com:8081/sso/isLogin](http://s2.stp.com:8081/sso/isLogin)
- [http://s3.stp.com:8081/sso/isLogin](http://s3.stp.com:8081/sso/isLogin)
均返回以下结果:
![sso-type1-wd.png](https://oss.dev33.cn/sa-token/doc/sso/sso-type1-wd.png 's-w-sh')
现在访问SSO认证中心的登录接口[http://sso.stp.com:8081/sso/doLogin](http://sso.stp.com:8081/sso/doLogin)
![sso-type1-login.png](https://oss.dev33.cn/sa-token/doc/sso/sso-type1-login.png 's-w-sh')
然后再次刷新上面三个测试接口,均可以得到以下结果:
![sso-type1-yd.png](https://oss.dev33.cn/sa-token/doc/sso/sso-type1-yd.png 's-w-sh')
测试完毕!
### 5、完善统一认证中心
上面的示例我们简单的演示了SSO模式一的认证原理。
当然,在实际的正式项目中,我们肯定不会每个 Client 端都内置一个登录接口一般的做法是只在SSO认证中心保留登录接口我们所有 Client 端的登录请求都会被重定向至认证中心,
待到登录成功之后再原路返回到 Client 端。
我们可以运行一下官方仓库的示例,里面有制作好的登录页面
> 下载官方示例,依次运行:
> - `/sa-token-demo/sa-token-demo-sso1-server/`
> - `/sa-token-demo/sa-token-demo-sso1-client/`
访问三个应用端:
### 5、访问测试
启动项目,依次访问三个应用端
- [http://s1.stp.com:9001/](http://s1.stp.com:9001/)
- [http://s2.stp.com:9001/](http://s2.stp.com:9001/)
- [http://s3.stp.com:9001/](http://s3.stp.com:9001/)
均返回:
![sso1--index.png](https://oss.dev33.cn/sa-token/doc/sso/sso1--index.png 's-w-sh')
然后点击登录被重定向至SSO认证中心
![sso1--login-page.png](https://oss.dev33.cn/sa-token/doc/sso/sso1--login-page.png 's-w-sh')
![sso1--login-page2.png](https://oss.dev33.cn/sa-token/doc/sso/sso1--login-page2.png 's-w-sh')
输入默认测试账号:`sa / 123456`,点击登录
我们点击登录,然后刷新页面:
![sso1-login-ok.png](https://oss.dev33.cn/sa-token/doc/sso/sso1-login-ok.png 's-w-sh')
@@ -148,9 +188,10 @@ public class SaSsoApplication {
测试完成
### 6、跨域模式下的解决方案
如上,我们使用极其简单的步骤实现了同域下的单点登录,聪明如你😏,马上想到了这种模式有着一个不小的限制:
如上,我们使用简单的步骤实现了同域下的单点登录,聪明如你😏,马上想到了这种模式有着一个不小的限制:
> 所有子系统的域名,必须同属一个父级域名
@@ -161,3 +202,4 @@ public class SaSsoApplication {

View File

@@ -3,7 +3,7 @@
如果我们的多个系统部署在不同的域名之下但是后端可以连接同一个Redis那么便可以使用 **`[URL重定向传播会话]`** 的方式做到单点登录。
### 0、解题思路
### 1、解题思路
首先我们再次复习一下,多个系统之间为什么无法同步登录状态?
@@ -30,7 +30,7 @@
下面我们按照步骤依次完成上述过程:
### 1、准备工作
### 2、准备工作
首先修改hosts文件`(C:\windows\system32\drivers\etc\hosts)`添加以下IP映射方便我们进行测试
``` url
127.0.0.1 sa-sso-server.com
@@ -39,134 +39,24 @@
127.0.0.1 sa-sso-client3.com
```
### 2、搭建 SSO-Server 认证中心
> 搭建示例在官方仓库的 `/sa-token-demo/sa-token-demo-sso2-server/`,如遇到难点可结合源码进行测试学习
#### 2.1、创建 SSO-Server 端项目
创建 SpringBoot 项目 `sa-token-demo-sso-server`,引入依赖:
``` xml
<!-- Sa-Token 权限认证, 在线文档http://sa-token.dev33.cn/ -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-spring-boot-starter</artifactId>
<version>${sa.top.version}</version>
</dependency>
<!-- Sa-Token 整合 Redis (使用 jackson 序列化方式) -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-dao-redis-jackson</artifactId>
<version>${sa.top.version}</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
```
#### 2.2、开放认证接口
一个完整的SSO认证中心应该至少包含以下接口
- `/sso/auth`:单点登录统一认证地址。
- `/sso/doLogin`RestAPI 登录接口,根据账号密码进行登录。
- `/sso/logout`:统一单点注销地址,一次注销,全端下线。
别急,这里不需要你亲自完成这些接口 —— Sa-Token 已经为你封装了实现。你要做的,就是提供一个访问入口,接入 Sa-Token 的方法。
``` java
/**
* Sa-Token-SSO Server端 Controller
*/
@RestController
public class SsoServerController {
/*
* SSO-Server端处理所有SSO相关请求
* http://{host}:{port}/sso/auth -- 单点登录授权地址接受参数redirect=授权重定向地址
* http://{host}:{port}/sso/doLogin -- 账号密码登录接口接受参数name、pwd
* http://{host}:{port}/sso/checkTicket -- Ticket校验接口isHttp=true时打开接受参数ticket=ticket码、ssoLogoutCall=单点注销回调地址 [可选]
* http://{host}:{port}/sso/logout -- 单点注销地址isSlo=true时打开接受参数loginId=账号id、secretkey=接口调用秘钥
*/
@RequestMapping("/sso/*")
public Object ssoRequest() {
return SaSsoHandle.serverRequest();
}
// 配置SSO相关参数
@Autowired
private void configSso(SaTokenConfig cfg) {
cfg.sso
// 配置未登录时返回的View
.setNotLoginView(() -> {
String msg = "当前会话在SSO-Server端尚未登录请先访问"
+ "<a href='/sso/doLogin?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("登录失败!");
})
;
}
}
```
注意:在`setDoLoginHandle`函数里如果要获取name, pwd以外的参数可通过`SaHolder.getRequest().getParam("xxx")`来获取
#### 2.3、application.yml配置
``` yml
# 端口
server:
port: 9000
# Sa-Token配置
sa-token:
# SSO-相关配置
sso:
# Ticket有效期 (单位: 秒),默认五分钟
ticket-timeout: 300
# 所有允许的授权回调地址 (此处为了方便测试配置为*,线上生产环境一定要配置为详细地地址)
allow-url: "*"
spring:
# Redis配置
redis:
# Redis数据库索引默认为0
database: 1
# Redis服务器地址
host: 127.0.0.1
# Redis服务器连接端口
port: 6379
# Redis服务器连接密码默认为空
password:
```
注意点:`allow-url`为了方便测试配置为`*`线上生产环境一定要配置为详细URL地址 (之后的章节我们会详细阐述此配置项)
#### 2.4、创建SSO-Server端启动类
``` java
@SpringBootApplication
public class SaSsoServerApplication {
public static void main(String[] args) {
SpringApplication.run(SaSsoServerApplication.class, args);
System.out.println("\nSa-Token-SSO 认证中心启动成功");
}
}
```
### 3、搭建 SSO-Client 应用端
### 3、搭建 Client 端项目
> 搭建示例在官方仓库的 `/sa-token-demo/sa-token-demo-sso2-client/`,如遇到难点可结合源码进行测试学习
#### 3.1、创建SSO-Client端项目
#### 3.1、去除 SSO-Server 的 Cookie 作用域配置
在SSO模式一章节中我们打开了配置
``` yml
sa-token:
cookie:
# 配置Cookie作用域
domain: stp.com
```
此为模式一专属配置,现在我们将其注释掉,并按照注释提示打开其他相应的注释
#### 3.2、创建 SSO-Client 端项目
创建一个 SpringBoot 项目 `sa-token-demo-sso-client`,引入依赖:
``` xml
<!-- Sa-Token 权限认证, 在线文档http://sa-token.dev33.cn/ -->
@@ -196,7 +86,7 @@ public class SaSsoServerApplication {
```
#### 3.2、创建 SSO-Client 端认证接口
#### 3.3、创建 SSO-Client 端认证接口
同 SSO-Server 一样Sa-Token 为 SSO-Client 端所需代码也提供了完整的封装,你只需提供一个访问入口,接入 Sa-Token 的方法即可。
@@ -232,7 +122,7 @@ public class SsoClientController {
}
```
##### 3.3、配置SSO认证中心地址
##### 3.4、配置SSO认证中心地址
你需要在 `application.yml` 配置如下信息:
``` yml
# 端口
@@ -261,7 +151,7 @@ sa-token:
```
注意点:`sa-token.alone-redis` 的配置需要和SSO-Server端连接同一个Redisdatabase也要一样
#### 3.4、写启动类
#### 3.5、写启动类
``` java
@SpringBootApplication
public class SaSsoClientApplication {
@@ -314,7 +204,7 @@ public class SaSsoClientApplication {
![sso-genzong](https://oss.dev33.cn/sa-token/doc/sso/sso-genzong.png 's-w-sh')
<!--
### 5、运行官方仓库
以上示例,虽然完整的复现了单点登录的过程,但是页面还是有些简陋,我们可以运行一下官方仓库的示例,里面有制作好的登录页面
@@ -329,30 +219,16 @@ public class SaSsoClientApplication {
![sso-server-login-hua](https://oss.dev33.cn/sa-token/doc/sso/sso-server-login-hua.png 's-w-sh')
默认测试密码:`sa / 123456`,其余流程保持不变
-->
### 6、跨Redis的单点登录
### 5、跨 Redis 的单点登录
以上流程解决了跨域模式下的单点登录但是后端仍然采用了共享Redis来同步会话如果我们的架构设计中Client端与Server端无法共享Redis又该怎么完成单点登录
这就要采用模式三了,且往下看:[SSO模式三Http请求获取会话](/sso/sso-type3)
<!--
### 6、如何单点注销
由于Server端与所有Client端都是在共用同一套会话因此只要一端注销即可全端下线达到单点注销的效果
在`SsoClientController`中添加以下代码:
``` java
// SSO-Client端单点注销 (所有端一起下线)
@RequestMapping("logout")
public SaResult logout() {
StpUtil.logout();
return SaResult.ok();
}
``` -->

View File

@@ -5,7 +5,7 @@
> 阅读本篇之前请务必先熟读SSO模式二因为模式三仅仅属于模式二的一个特殊场景熟读模式二有助于您快速理解本章内容
### 0、问题分析
### 1、问题分析
我们先来分析一下,当后端不使用共享 Redis 时,会对架构产生哪些影响:
1. Client 端无法直连 Redis 校验 ticket取出账号id。
@@ -14,19 +14,13 @@
所以模式三的主要目标:也就是在 模式二的基础上 解决上述 三个难题
> 模式三的 Demo 示例地址:
>
> - SSO-Server 端:`/sa-token-demo/sa-token-demo-sso3-server/` [源码链接](https://gitee.com/dromara/sa-token/tree/dev/sa-token-demo/sa-token-demo-sso3-server) <br/>
> - SSO-Client 端:`/sa-token-demo/sa-token-demo-sso3-client/` [源码链接](https://gitee.com/dromara/sa-token/tree/dev/sa-token-demo/sa-token-demo-sso3-client) <br/>
>
> 如遇难点可参考示例
> 模式三的 Demo 示例地址:`/sa-token-demo/sa-token-demo-sso3-client/`
> [源码链接](https://gitee.com/dromara/sa-token/tree/dev/sa-token-demo/sa-token-demo-sso3-client),如遇难点可参考示例
### 1、SSO-Server 认证中心开放 Ticket 校验接口
既然 Client 端无法直连 Redis 校验 Ticket那我们就在 Server 端开放 Ticket 校验接口,然后 Client 端通过 http 请求获取数据。
### 2、在Client 端更改 Ticket 校验方式
#### 1.1、添加依赖
首先在 Server 端和 Client 端均添加以下依赖(如果不需要单点注销功能则 Server 端可不引入)
#### 2.1、增加 pom.xml 配置
``` xml
<!-- Http请求工具 -->
<dependency>
@@ -37,49 +31,42 @@
```
> OkHttps是一个轻量级http请求工具详情参考[OkHttps](https://gitee.com/ejlchina-zhxu/okhttps)
#### 1.2、认证中心开放接口
在 SSO-Server 端的 `application.yml` 中,新增以下配置:
``` yml
sa-token:
sso:
# 使用Http请求校验ticket
is-http: true
```
此配置项的作用是开放ticket校验接口让Client端通过http请求获取会话
#### 1.3、Client端新增配置
#### 2.2、配置 http 请求处理器
在SSO-Client端的 `SsoClientController` 中,新增以下配置
``` java
// 配置SSO相关参数
@Autowired
private void configSso(SaTokenConfig cfg) {
cfg.sso
// ... 其他代码
// 配置 Http 请求处理器
.setSendHttp(url -> {
cfg.sso.setSendHttp(url -> {
return OkHttps.sync(url).get().getBody().toString();
})
;
});
}
```
#### 2.3、application.yml 新增配置
``` yml
sa-token:
sso:
# 使用Http请求校验ticket
# 打开模式三(使用Http请求校验ticket
is-http: true
# SSO-Server端 ticket校验地址
check-ticket-url: http://sa-sso-server.com:9000/sso/checkTicket
```
#### 1.4、启动项目测试
启动SSO-Server、SSO-Client,访问测试:[http://sa-sso-client1.com:9001/](http://sa-sso-client1.com:9001/)
#### 2.4、启动项目测试
重启项目,访问测试:[http://sa-sso-client1.com:9001/](http://sa-sso-client1.com:9001/)
> 注如果已测试运行模式二可先将Redis中的数据清空以防旧数据对测试造成干扰
### 2、获取 Userinfo
除了账号id我们可能还需要将用户的昵称、头像等信息从 Server端 带到 Client端用户资料的同步。要解决这个需求,我们只需:
### 3、获取 Userinfo
除了账号id我们可能还需要将用户的昵称、头像等信息从 Server端 带到 Client端用户资料的同步。
#### 2.1、在 Server 端自定义接口,查询用户资料
在模式二中我们只需要将需要同步的资料放到 SaSession 即可但是在模式三中两端不再连接同一个Redis这时候我们需要通过http接口来同步信息
#### 3.1、在 Server 端自定义接口,查询用户资料
``` java
// 自定义接口获取userinfo
@RequestMapping("/sso/userinfo")
@@ -98,7 +85,7 @@ public Object userinfo(String loginId, String secretkey) {
}
```
#### 2.2、在 Client 端调用此接口查询 userinfo
#### 3.2、在 Client 端调用此接口查询 userinfo
首先在yml中配置接口地址
``` yml
sa-token:
@@ -122,7 +109,7 @@ public Object myinfo() {
### 3、无刷单点注销
### 4、无刷单点注销
有了单点登录就必然要有单点注销网上给出的大多数解决方案是将注销请求重定向至SSO-Server中心逐个通知Client端下线
@@ -137,34 +124,7 @@ public Object myinfo() {
这些逻辑 Sa-Token 内部已经封装完毕,你只需按照文章增加以下配置即可:
#### 2.1、SSO-Server认证中心增加配置
在 `SsoServerController` 中新增配置
``` java
// 配置SSO相关参数
@Autowired
private void configSso(SaTokenConfig cfg) {
cfg.sso
// ... (其它配置保持不变)
// 配置Http请求处理器
.setSendHttp(url -> {
// 此处为了提高响应速度这里可将sync换为async
return OkHttps.sync(url).get();
})
;
}
```
并在 `application.yml` 下新增配置:
``` yml
sa-token:
sso:
# 打开单点注销功能
is-slo: true
# API调用秘钥用于SSO模式三的单点注销功能
secretkey: kQwIOrYvnXmSDkwEiFngrKidMcdrgKor
```
#### 2.2、SSO-Client 端新增配置
#### 4.1、SSO-Client 端新增配置
在 `application.yml` 增加配置:`API调用秘钥` 和 `单点注销接口URL`。
``` yml
@@ -177,9 +137,11 @@ sa-token:
# 接口调用秘钥
secretkey: kQwIOrYvnXmSDkwEiFngrKidMcdrgKor
```
注意 secretkey 秘钥需要与SSO认证中心的一致
#### 2.3 启动测试
启动SSO-Server、SSO-Client访问测试[http://sa-sso-client1.com:9001/](http://sa-sso-client1.com:9001/)
#### 4.2 启动测试
重启项目,访问测试:[http://sa-sso-client1.com:9001/](http://sa-sso-client1.com:9001/)
我们主要的测试点在于 `单点注销`,正常登录即可。
![sso-type3-client-index.png](https://oss.dev33.cn/sa-token/doc/sso/sso-type3-client-index.png 's-w-sh')

View File

@@ -108,15 +108,12 @@ implementation 'cn.dev33:sa-token-core:${sa.top.version}'
├── sa-token-demo-solon // [示例] Sa-Token 集成 Solon
├── sa-token-demo-quick-login // [示例] Sa-Token 集成 quick-login 模块
├── sa-token-demo-alone-redis // [示例] Sa-Token 集成 alone-redis 模块
├── sa-token-demo-sso1 // [示例] Sa-Token 集成 SSO单点登录-模式一简单测试
├── sa-token-demo-sso1-server // [示例] Sa-Token 集成 SSO单点登录-模式一 认证中心
├── sa-token-demo-sso-server // [示例] Sa-Token 集成 SSO单点登录-Server认证中心
├── sa-token-demo-sso1-client // [示例] Sa-Token 集成 SSO单点登录-模式一 应用端
├── sa-token-demo-sso2-server // [示例] Sa-Token 集成 SSO单点登录-模式二 认证中心
├── sa-token-demo-sso2-client-h5 // [示例] Sa-Token 集成 SSO单点登录-模式二 应用端
├── sa-token-demo-sso2-server // [示例] Sa-Token 集成 SSO单点登录-模式二 认证中心 (前后端分离)
├── sa-token-demo-sso2-client-h5 // [示例] Sa-Token 集成 SSO单点登录-模式二 应用端 (前后端分离)
├── sa-token-demo-sso3-server // [示例] Sa-Token 集成 SSO单点登录-模式三 认证中心
├── sa-token-demo-sso2-client // [示例] Sa-Token 集成 SSO单点登录-模式二 应用端
├── sa-token-demo-sso3-client // [示例] Sa-Token 集成 SSO单点登录-模式三 应用端
├── sa-token-demo-sso-server-h5 // [示例] Sa-Token 集成 SSO单点登录-Server认证中心 (前后端分离)
├── sa-token-demo-sso-client-h5 // [示例] Sa-Token 集成 SSO单点登录-client应用端 (前后端分离)
├── sa-token-demo-oauth2-server // [示例] Sa-Token 集成 OAuth2.0 (服务端)
├── sa-token-demo-oauth2-client // [示例] Sa-Token 集成 OAuth2.0 (客户端)
├── sa-token-doc // [文档] Sa-Token 开发文档