diff --git a/zlt-demo/sso-demo/README.md b/zlt-demo/sso-demo/README.md
index bc41b6eca6e15e89e87cab694ab67c14b4e8c593..f31e384ec4abb554daf2d68ff3c9c0ffdcf3a7aa 100644
--- a/zlt-demo/sso-demo/README.md
+++ b/zlt-demo/sso-demo/README.md
@@ -1,4 +1,4 @@
* **ss-sso**:使用springSecurity来实现自动单点登录,非前后端分离
-
* **web-sso**:前后端分离的单点登录
+* **oidc-sso**:拥有独立用户体系的系统,使用OIDC协议的单点登录
diff --git a/zlt-demo/sso-demo/oidc-sso/README.md b/zlt-demo/sso-demo/oidc-sso/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..154bfec81cd21b8230ea786da886c4e0f4646c0c
--- /dev/null
+++ b/zlt-demo/sso-demo/oidc-sso/README.md
@@ -0,0 +1,25 @@
+## **详细的原理和注意事项请查看**
+[OIDC协议单点登录](https://www.kancloud.cn/zlt2000/microservices-platform/2278851)
+
+## oauth-center数据库执行以下sql
+```sql
+alter table oauth_client_details add support_id_token tinyint(1) DEFAULT 1 COMMENT '是否支持id_token';
+alter table oauth_client_details add id_token_validity int(11) DEFAULT 60 COMMENT 'id_token有效期';
+```
+
+## 启动以下服务
+
+1. zlt-uaa:统一认证中心
+2. user-center:用户服务
+3. sc-gateway:api网关
+4. oidc-sso:单点登录demo(app应用)
+5. ss-sso:单点登录demo(zlt应用)
+
+
+
+## 测试步骤
+
+1. 登录zlt应用:
+ 通过地址 http://127.0.0.1:8080 先登录zlt应用
+2. 访问app应用(单点成功):
+ 在浏览器打开一个新的页签(共享session),通过地址 http://127.0.0.1:8081/index.html 访问zlt应用静态页面,单点登录成功显示当前登录用户名、应用id、token信息
\ No newline at end of file
diff --git a/zlt-demo/sso-demo/oidc-sso/pom.xml b/zlt-demo/sso-demo/oidc-sso/pom.xml
new file mode 100644
index 0000000000000000000000000000000000000000..befd12e9b749cf431b265e93cd366dd88dcec47f
--- /dev/null
+++ b/zlt-demo/sso-demo/oidc-sso/pom.xml
@@ -0,0 +1,50 @@
+
+ 4.0.0
+
+ com.zlt
+ sso-demo
+ 4.4
+
+ oidc-sso
+ OIDC协议单点登录demo
+
+
+
+ cn.hutool
+ hutool-all
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+ org.springframework.cloud
+ spring-cloud-context
+
+
+ com.zlt
+ zlt-common-core
+
+
+ org.springframework.security
+ spring-security-jwt
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+ repackage
+
+
+
+
+
+
+
diff --git a/zlt-demo/sso-demo/oidc-sso/src/main/java/com/sso/demo/OidcSSOApplication.java b/zlt-demo/sso-demo/oidc-sso/src/main/java/com/sso/demo/OidcSSOApplication.java
new file mode 100644
index 0000000000000000000000000000000000000000..1b5b01d8f9861e946531c95a9ebb75a16dce0a9a
--- /dev/null
+++ b/zlt-demo/sso-demo/oidc-sso/src/main/java/com/sso/demo/OidcSSOApplication.java
@@ -0,0 +1,18 @@
+package com.sso.demo;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+/**
+ * @author zlt
+ * @date 2020/5/22
+ *
+ * Blog: https://zlt2000.gitee.io
+ * Github: https://github.com/zlt2000
+ */
+@SpringBootApplication
+public class OidcSSOApplication {
+ public static void main(String[] args) {
+ SpringApplication.run(OidcSSOApplication.class, args);
+ }
+}
diff --git a/zlt-demo/sso-demo/oidc-sso/src/main/java/com/sso/demo/controller/ApiController.java b/zlt-demo/sso-demo/oidc-sso/src/main/java/com/sso/demo/controller/ApiController.java
new file mode 100644
index 0000000000000000000000000000000000000000..03bda0a3a5162535cce1bfce2438c647b10ef1c3
--- /dev/null
+++ b/zlt-demo/sso-demo/oidc-sso/src/main/java/com/sso/demo/controller/ApiController.java
@@ -0,0 +1,207 @@
+package com.sso.demo.controller;
+
+import cn.hutool.core.util.RandomUtil;
+import cn.hutool.core.util.StrUtil;
+import com.central.common.model.Result;
+import com.central.common.utils.JsonUtil;
+import com.central.common.utils.RsaUtils;
+import com.fasterxml.jackson.databind.JsonNode;
+import lombok.Data;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.http.*;
+import org.springframework.security.jwt.Jwt;
+import org.springframework.security.jwt.JwtHelper;
+import org.springframework.security.jwt.crypto.sign.RsaVerifier;
+import org.springframework.security.jwt.crypto.sign.SignatureVerifier;
+import org.springframework.util.Assert;
+import org.springframework.util.LinkedMultiValueMap;
+import org.springframework.util.MultiValueMap;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.client.RestTemplate;
+import sun.misc.BASE64Encoder;
+
+import java.nio.charset.StandardCharsets;
+import java.security.interfaces.RSAPublicKey;
+import java.util.*;
+
+/**
+ * @author zlt
+ * @date 2020/5/22
+ *
+ * Blog: https://zlt2000.gitee.io
+ * Github: https://github.com/zlt2000
+ */
+@RestController
+public class ApiController {
+ private static final String PUBKEY_START = "-----BEGIN PUBLIC KEY-----";
+ private static final String PUBKEY_END = "-----END PUBLIC KEY-----";
+
+ @Value("${zlt.sso.client-id:}")
+ private String clientId;
+
+ @Value("${zlt.sso.client-secret:}")
+ private String clientSecret;
+
+ @Value("${zlt.sso.redirect-uri:}")
+ private String redirectUri;
+
+ @Value("${zlt.sso.access-token-uri:}")
+ private String accessTokenUri;
+
+ @Value("${zlt.sso.jwt-key-uri:}")
+ private String jwtKeyUri;
+
+ /**
+ * 公钥
+ */
+ private static RSAPublicKey publicKey;
+
+ /**
+ * 模拟用户数据库
+ */
+ private static final Map userDb = new HashMap<>();
+
+ /**
+ * nonce存储
+ */
+ private final static ThreadLocal NONCE = new ThreadLocal<>();
+
+ @GetMapping("/token/{code}")
+ public Map tokenInfo(@PathVariable String code) throws Exception {
+ //获取token
+ Map tokenMap = getAccessToken(code);
+ String idTokenStr = (String)tokenMap.get("id_token");
+ //解析id_token
+ JsonNode idToken = this.getIdTokenJson(idTokenStr);
+ //检查id_token的有效性
+ checkToken(idToken);
+ //获取用户信息
+ MyUser user = this.getUserInfo(idToken);
+ //判断用户信息是否存在,否则注册用户信息
+ if (!userDb.containsKey(user.getId())) {
+ userDb.put(user.getId(), user);
+ }
+
+ Map result = new HashMap<>(2);
+ result.put("tokenInfo", tokenMap);
+ result.put("userInfo", user);
+ return result;
+ }
+
+ /**
+ * 检查 id_token 的有效性
+ */
+ private void checkToken(JsonNode idToken) {
+ //token有效期
+ long expiresAt = idToken.get("exp").asLong();
+ long now = System.currentTimeMillis();
+ Assert.isTrue((expiresAt > now), "id_token已过期");
+
+ //应用id
+ String aud = idToken.get("aud").asText();
+ Assert.isTrue(clientId.equals(aud), "非法应用"+aud);
+
+ //随机码
+ String nonce = idToken.get("nonce").asText();
+ Assert.isTrue((StrUtil.isEmpty(nonce) || nonce.equals(NONCE.get())), "nonce参数无效");
+ }
+
+ /**
+ * 获取token
+ */
+ public Map getAccessToken(String code) {
+ RestTemplate restTemplate = new RestTemplate();
+ HttpHeaders headers = new HttpHeaders();
+ headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
+ String base64Auth = this.getBase64ClientParam();
+ headers.add("Authorization", "Basic " + base64Auth);
+
+ MultiValueMap param = new LinkedMultiValueMap<>();
+ param.add("code", code);
+ param.add("grant_type", "authorization_code");
+ param.add("redirect_uri", redirectUri);
+ param.add("scope", "app");
+ param.add("nonce", this.genNonce());
+ HttpEntity> request = new HttpEntity<>(param, headers);
+ ResponseEntity