统一认证目标系统DEMO

main
mengqiang 4 years ago
parent 6cdeb15e5b
commit f92d75a3ca
  1. 8
      .gitignore
  2. 83
      sso-sys-client1/pom.xml
  3. 18
      sso-sys-client1/src/main/java/com/example/sso/client/SsoSysClient01Application.java
  4. 104
      sso-sys-client1/src/main/java/com/example/sso/client/auth/AuthService.java
  5. 98
      sso-sys-client1/src/main/java/com/example/sso/client/auth/model/ApplyAuthVO.java
  6. 67
      sso-sys-client1/src/main/java/com/example/sso/client/auth/model/MenuTreeAuthVO.java
  7. 94
      sso-sys-client1/src/main/java/com/example/sso/client/config/SysConfigProperty.java
  8. 23
      sso-sys-client1/src/main/java/com/example/sso/client/constant/SsoConstant.java
  9. 35
      sso-sys-client1/src/main/java/com/example/sso/client/controller/IndexController.java
  10. 74
      sso-sys-client1/src/main/java/com/example/sso/client/controller/LoginController.java
  11. 68
      sso-sys-client1/src/main/java/com/example/sso/client/interceptor/WebConfig.java
  12. 89
      sso-sys-client1/src/main/java/com/example/sso/client/interceptor/WebInterceptor.java
  13. 54
      sso-sys-client1/src/main/java/com/example/sso/client/model/ResultModel.java
  14. 56
      sso-sys-client1/src/main/java/com/example/sso/client/utils/CookieUtil.java
  15. 71
      sso-sys-client1/src/main/java/com/example/sso/client/utils/SSOClientHelper.java
  16. 46
      sso-sys-client1/src/main/java/com/example/sso/client/utils/SessionContext.java
  17. 40
      sso-sys-client1/src/main/java/com/example/sso/client/utils/SessionListener.java
  18. 84
      sso-sys-client1/src/main/java/com/example/sso/client/utils/SsoSignUtil.java
  19. 18
      sso-sys-client1/src/main/resources/application.properties
  20. 67
      sso-sys-client1/src/main/resources/static/css/json-viewer.min.css
  21. 2595
      sso-sys-client1/src/main/resources/static/js/jquery.min.js
  22. 51
      sso-sys-client1/src/main/resources/static/js/json-viewer.min.js
  23. 49
      sso-sys-client1/src/main/resources/templates/index.html
  24. 13
      sso-sys-client1/src/main/resources/templates/welcome.html
  25. 83
      sso-sys-client2/pom.xml
  26. 17
      sso-sys-client2/src/main/java/com/example/sso/client/SsoSysClient02Application.java
  27. 118
      sso-sys-client2/src/main/java/com/example/sso/client/auth/AuthService.java
  28. 98
      sso-sys-client2/src/main/java/com/example/sso/client/auth/model/ApplyAuthVO.java
  29. 67
      sso-sys-client2/src/main/java/com/example/sso/client/auth/model/MenuTreeAuthVO.java
  30. 95
      sso-sys-client2/src/main/java/com/example/sso/client/config/SysConfigProperty.java
  31. 23
      sso-sys-client2/src/main/java/com/example/sso/client/constant/SsoConstant.java
  32. 35
      sso-sys-client2/src/main/java/com/example/sso/client/controller/IndexController.java
  33. 74
      sso-sys-client2/src/main/java/com/example/sso/client/controller/LoginController.java
  34. 68
      sso-sys-client2/src/main/java/com/example/sso/client/interceptor/WebConfig.java
  35. 93
      sso-sys-client2/src/main/java/com/example/sso/client/interceptor/WebInterceptor.java
  36. 54
      sso-sys-client2/src/main/java/com/example/sso/client/model/ResultModel.java
  37. 56
      sso-sys-client2/src/main/java/com/example/sso/client/utils/CookieUtil.java
  38. 71
      sso-sys-client2/src/main/java/com/example/sso/client/utils/SSOClientHelper.java
  39. 46
      sso-sys-client2/src/main/java/com/example/sso/client/utils/SessionContext.java
  40. 40
      sso-sys-client2/src/main/java/com/example/sso/client/utils/SessionListener.java
  41. 84
      sso-sys-client2/src/main/java/com/example/sso/client/utils/SsoSignUtil.java
  42. 133
      sso-sys-client2/src/main/java/com/example/sso/client/utils/sign/RSASignUtils.java
  43. 67
      sso-sys-client2/src/main/java/com/example/sso/client/utils/sign/SignContentUtil.java
  44. 17
      sso-sys-client2/src/main/resources/application.properties
  45. 67
      sso-sys-client2/src/main/resources/static/css/json-viewer.min.css
  46. 2595
      sso-sys-client2/src/main/resources/static/js/jquery.min.js
  47. 51
      sso-sys-client2/src/main/resources/static/js/json-viewer.min.js
  48. 49
      sso-sys-client2/src/main/resources/templates/index.html
  49. 13
      sso-sys-client2/src/main/resources/templates/welcome.html

8
.gitignore vendored

@ -0,0 +1,8 @@
# Created by .ignore support plugin (hsz.mobi)
.idea/
logs/
*.log
*.iml
target/
.DS_store

@ -0,0 +1,83 @@
<?xml version="1.0" encoding="UTF-8"?>
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.0.RELEASE</version>
<relativePath/>
</parent>
<groupId>com.example</groupId>
<artifactId>sso-sys-client1</artifactId>
<version>1.0-SNAPSHOT</version>
<name>sso-client1</name>
<description>sso单点登录客户端1</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.4</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.4.3</version>
</dependency>
<!-- fastjson -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.73</version>
</dependency>
<!-- io -->
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.6</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.9</version>
</dependency>
<dependency>
<groupId>org.apache.directory.studio</groupId>
<artifactId>org.apache.commons.codec</artifactId>
<version>1.8</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

@ -0,0 +1,18 @@
package com.example.sso.client;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* SSO-客户端1-Demo启动类
*
* @author 程序员小强
*/
@SpringBootApplication
public class SsoSysClient01Application {
public static void main(String[] args) {
SpringApplication.run(SsoSysClient01Application.class, args);
}
}

@ -0,0 +1,104 @@
package com.example.sso.client.auth;
import cn.hutool.http.HttpUtil;
import com.alibaba.fastjson.JSON;
import com.example.sso.client.auth.model.ApplyAuthVO;
import com.example.sso.client.config.SysConfigProperty;
import com.example.sso.client.constant.SsoConstant;
import com.example.sso.client.model.ResultModel;
import com.example.sso.client.utils.SsoSignUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
@Slf4j
@Service
public class AuthService {
private static final Integer SUCCESS_CODE = 200;
@Autowired
private SysConfigProperty sysConfigProperty;
/**
* 请求认证中心-申请认证
*
* @param token
* @return
*/
public ApplyAuthVO authToken(String token, String sessionId) {
//构建参数
Map<String, Object> paramMap = this.buildParamMap(token, sysConfigProperty.getSsoAuthSecret(), sessionId);
long start = System.currentTimeMillis();
log.info("[ 请求认证中心-申请认证 ] start >> paramMap:{}", JSON.toJSONString(paramMap));
String response = HttpUtil.post(sysConfigProperty.getSsoAuthGetWayUrl(), paramMap);
log.info("[ 请求认证中心-申请认证 ] end >> times:{} ms, response:{}", (System.currentTimeMillis() - start), response);
//正式环境需对异常统一除以,这里只做了模拟默认抛出了 RuntimeException
if (StringUtils.isBlank(response)) {
throw new RuntimeException("认证中心-无返回");
}
ResultModel resultModel = JSON.parseObject(response, ResultModel.class);
if (null == resultModel || StringUtils.isBlank(resultModel.getData().toString())) {
throw new RuntimeException("认证中心-无返回");
}
if (!resultModel.getCode().equals(SUCCESS_CODE)) {
throw new RuntimeException("认证中心调用失败," + resultModel.getMsg());
}
return JSON.parseObject(resultModel.getData().toString(), ApplyAuthVO.class);
}
/**
* 构建参数
*
* @param token
* @param md5Secret
* @param sessionId
*/
private Map<String, Object> buildParamMap(String token, String md5Secret, String sessionId) {
Map<String, Object> paramMap = new HashMap<>(16);
//系统编码
paramMap.put("sysCode", sysConfigProperty.getMySysCode());
//申请认证接口地址 (com.sso.applyAuth)
paramMap.put("method", SsoConstant.METHOD_APPLY_AUTH);
//版本号
paramMap.put("version", "1.0");
//请求唯一标识
paramMap.put("apiRequestId", UUID.randomUUID());
//签名类型 1-MD5;2-RSA;3-RSA2
paramMap.put("signType", "1");
//当前时间戳
paramMap.put("timestamp", System.currentTimeMillis());
//业务参数
Map<String, Object> contentMap = this.buildContentMap(token, sessionId);
//业务参数JSON 值
paramMap.put("content", JSON.toJSONString(contentMap));
//生成签名
String sign = SsoSignUtil.getMd5Sign(paramMap, Arrays.asList("signType", "sign"), md5Secret);
paramMap.put("sign", sign);
return paramMap;
}
private Map<String, Object> buildContentMap(String token, String sessionId) {
Map<String, Object> contentMap = new HashMap<>(4);
//退出当前子系统地址 示例:http://www.sysclient1.com:8801/logOutBySessionId?sessionId=xxx
contentMap.put("loginOutUrl", String.format("%s?sessionId=%s", sysConfigProperty.getMyLoginOutUrl(), sessionId));
//返回菜单类型 1-普通列表 2-树型结构列表
contentMap.put("menuType", 2);
contentMap.put("ssoToken", token);
return contentMap;
}
}

@ -0,0 +1,98 @@
package com.example.sso.client.auth.model;
import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.Data;
import java.io.Serializable;
import java.util.List;
import java.util.Set;
/**
* 申请认证参数
*
* @author 程序员小强
* @version ApplyAuthBO.java
*/
@Data
@JsonInclude(JsonInclude.Include.NON_NULL)
public class ApplyAuthVO implements Serializable {
private static final long serialVersionUID = -3755046680856409972L;
/**
* 认证结果
*/
private Boolean authResult;
/**
* 重定向url认证结果仅为false时返回
*/
private String redirectUrl;
/**
* 用户ID
*/
private Long userId;
/**
* 用户登录名
*/
private String username;
/**
* 用户昵称
*/
private String nickName;
/**
* 真实姓名
*/
private String realName;
/**
* 头像
*/
private String avatar;
/**
* 用户性别 0-;1-;2-未知
*/
private Integer sex;
/**
* 手机号码
*/
private String phone;
/**
* 邮箱
*/
private String email;
/**
* 系统编码
*/
private String sysCode;
/**
* 系统名称
*/
private String sysName;
/**
* 在当前系统下的-权限列表
*/
private Set<String> permissionList;
/**
* 在当前系统下的-角色标识
*/
private List<String> roleKeyList;
/**
* 在当前系统下的-菜单集
*/
private List<MenuTreeAuthVO> menuList;
}

@ -0,0 +1,67 @@
package com.example.sso.client.auth.model;
import lombok.Data;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
/**
* 菜单树-返回对象
*
* @author 程序员小强
*/
@Data
public class MenuTreeAuthVO implements Serializable {
private static final long serialVersionUID = 69788387641481670L;
/**
* 菜单ID
*/
private Long menuId;
/**
* 父菜单ID
*/
private Long menuParentId;
/**
* 菜单名称
*/
private String menuName;
/**
* 菜单类型M目录 C菜单 F按钮
*/
private String menuType;
/**
* 显示顺序
*/
private Integer sortNum;
/**
* 菜单路由地址
*/
private String path;
/**
* 菜单图标
*/
private String icon;
/**
* 组件路径
*/
private String component;
/**
* 是否显示 0-显示;1-隐藏
*/
private Integer visible;
private final List<MenuTreeAuthVO> children = new ArrayList<>();
}

@ -0,0 +1,94 @@
package com.example.sso.client.config;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
* 系统配置属性
*
* @author 程序员小强
*/
@Component
@ConfigurationProperties(prefix = "sys.config")
public class SysConfigProperty {
/**
* 我的系统编码
*/
private String mySysCode;
/**
* 认证中心申请认证开放网关地址
*/
private String ssoAuthGetWayUrl;
/**
* 认证中心秘钥
*/
private String ssoAuthSecret;
/**
* 本系统退出登录地址
*/
private String myLoginOutUrl;
/**
* 认证中心登录地址
*/
private static String ssoAuthLoginUrl;
/**
* 当前客户端web地址
*/
private static String clientWebUrl;
public String getMySysCode() {
return mySysCode;
}
public void setMySysCode(String mySysCode) {
this.mySysCode = mySysCode;
}
public String getSsoAuthGetWayUrl() {
return ssoAuthGetWayUrl;
}
public void setSsoAuthGetWayUrl(String ssoAuthGetWayUrl) {
this.ssoAuthGetWayUrl = ssoAuthGetWayUrl;
}
public String getSsoAuthSecret() {
return ssoAuthSecret;
}
public void setSsoAuthSecret(String ssoAuthSecret) {
this.ssoAuthSecret = ssoAuthSecret;
}
public String getMyLoginOutUrl() {
return myLoginOutUrl;
}
public void setMyLoginOutUrl(String myLoginOutUrl) {
this.myLoginOutUrl = myLoginOutUrl;
}
public static String getSsoAuthLoginUrl() {
return ssoAuthLoginUrl;
}
public void setSsoAuthLoginUrl(String ssoAuthLoginUrl) {
SysConfigProperty.ssoAuthLoginUrl = ssoAuthLoginUrl;
}
public static String getClientWebUrl() {
return clientWebUrl;
}
public void setClientWebUrl(String clientWebUrl) {
SysConfigProperty.clientWebUrl = clientWebUrl;
}
}

@ -0,0 +1,23 @@
package com.example.sso.client.constant;
public class SsoConstant {
/**
* 令牌统一参数名
*/
public static final String TOKEN_NAME = "ssoToken";
/**
* 申请认证接口
*/
public static final String METHOD_APPLY_AUTH = "com.sso.applyAuth";
/**
* 认证中心-退出登录地址
*/
public static final String CLIENT_LOGOUT_URL = "/logOut";
}

@ -0,0 +1,35 @@
package com.example.sso.client.controller;
import com.example.sso.client.utils.SSOClientHelper;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
/**
* 页面跳转
*
* @author 程序员小强
*/
@Controller
public class IndexController {
@RequestMapping({"/", "/index"})
protected String index(HttpServletRequest request, Model model) {
HttpSession session = request.getSession();
model.addAttribute("logOutUrl", SSOClientHelper.getClientLogOutUrl());
Object userInfo = session.getAttribute("userInfo");
if (null != userInfo) {
model.addAttribute("userInfo", userInfo);
}
return "index";
}
@RequestMapping("/welcome")
protected String welcome(Model model) {
return "welcome";
}
}

@ -0,0 +1,74 @@
package com.example.sso.client.controller;
import com.example.sso.client.config.SysConfigProperty;
import com.example.sso.client.model.ResultModel;
import com.example.sso.client.utils.SessionContext;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
@Slf4j
@RestController
public class LoginController {
@Autowired
private SysConfigProperty sysConfigProperty;
/**
* 从局部会话-缓存中
* <p>
* 获取登录用户的信息
*
* @param request
*/
@RequestMapping(value = "/getUserInfo", produces = {"application/json;charset=UTF-8"})
protected Object getUserInfo(HttpServletRequest request) {
HttpSession session = request.getSession();
Object userInfo = session.getAttribute("userInfo");
if (null != userInfo) {
return ResultModel.success(userInfo);
}
return ResultModel.success();
}
/**
* 退出当前系统
*
* @param request
* @param response
*/
@RequestMapping("/logOut")
protected void logOut(HttpServletRequest request, HttpServletResponse response) throws IOException {
//销毁局部会话
SessionContext.getInstance().delSession(request.getSession());
//跳转到认证中心登录页
response.sendRedirect(SysConfigProperty.getSsoAuthLoginUrl());
}
/**
* 认证中心统-调用-退出该系统接口
*
* @param request
* @param response
*/
@RequestMapping("/logOutBySsoAuth")
protected void logOutBySsoAuth(HttpServletRequest request, HttpServletResponse response) {
//获取参数sessionId
String sessionId = request.getParameter("sessionId");
log.info("[ 退出子系统 ] >> start sessionId:{}", sessionId);
HttpSession session = SessionContext.getInstance().getSession(sessionId);
if (null == session) {
log.error("[ 退出子系统 ] >> session不存在 sessionId:{}", sessionId);
return;
}
SessionContext.getInstance().delSession(session);
log.info("[ 退出子系统 ] >> end sessionId:{}", sessionId);
}
}

@ -0,0 +1,68 @@
package com.example.sso.client.interceptor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.web.servlet.config.annotation.ContentNegotiationConfigurer;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
import java.nio.charset.StandardCharsets;
@Slf4j
@Configuration
public class WebConfig extends WebMvcConfigurationSupport {
/**
* 创建拦截器
*/
@Bean
WebInterceptor webInterceptor() {
return new WebInterceptor();
}
/**
* 添加拦截器-进行拦截
* addPathPatterns 添加拦截
* excludePathPatterns 排除拦截
**/
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(this.webInterceptor())
.addPathPatterns("/**")
.excludePathPatterns("/static/**")
.excludePathPatterns("/logOut")
.excludePathPatterns("/logOutBySsoAuth");
super.addInterceptors(registry);
}
/**
* 返回值-编码 UTF-8
*/
@Bean
public HttpMessageConverter<String> responseBodyConverter() {
return new StringHttpMessageConverter(StandardCharsets.UTF_8);
}
@Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
configurer.favorPathExtension(false);
}
/**
* 资源处理器-资源路径 映射
*
* @param registry
*/
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/static/**").addResourceLocations("classpath:/static/");
registry.addResourceHandler("/webjars/**")
.addResourceLocations("classpath:/META-INF/resources/webjars/");
}
}

@ -0,0 +1,89 @@
package com.example.sso.client.interceptor;
import com.alibaba.fastjson.JSON;
import com.example.sso.client.auth.AuthService;
import com.example.sso.client.auth.model.ApplyAuthVO;
import com.example.sso.client.constant.SsoConstant;
import com.example.sso.client.utils.CookieUtil;
import com.example.sso.client.utils.SSOClientHelper;
import com.example.sso.client.utils.SessionContext;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
/**
* 创建拦截器-拦截需要安全访问的请求
* 方法说明
* 1.preHandle():前置处理回调方法返回true继续执行返回false中断流程不会继续调用其它拦截器
* 2.postHandle():后置处理回调方法但在渲染视图之前
* 3.afterCompletion():全部后置处理之后整个请求处理完毕后回调
*
* @author 程序员小强
*/
@Slf4j
public class WebInterceptor implements HandlerInterceptor {
@Autowired
protected AuthService authService;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws IOException {
//判断是否有局部会话
HttpSession httpSession = request.getSession();
//获取session
HttpSession session = SessionContext.getInstance().getSession(httpSession.getId());
if (null != session) {
Object isLogin = session.getAttribute("isLogin");
if (isLogin != null && (Boolean) isLogin) {
log.debug("[ 请求拦截器 ] >> 已登录状态 , 有局部会话 sessionId:{}", httpSession.getId());
return true;
}
}
log.info("[ 请求拦截器 ] >> 未登录需要登录 >> sessionId:{} requestUrl:{} ", httpSession.getId(), request.getRequestURI());
//获取令牌ssoToken
String token = SSOClientHelper.getSsoToken(request);
//无令牌
if (StringUtils.isBlank(token)) {
log.info("[ 请求拦截器 ] >> 未获取到token , 将跳转认证中心 >> sessionId:{} ", httpSession.getId());
//跳转到认证中心登录
SSOClientHelper.ssoLogin(request, response);
return true;
}
//有令牌-则请求认证中心校验令牌是否有效
ApplyAuthVO applyAuth = authService.authToken(token, httpSession.getId());
//令牌无效
if (null == applyAuth) {
log.debug("[ 请求拦截器 ] >> 令牌无效 , 将跳转认证中心进行认证 requestUrl:{}, token:{}", request.getRequestURI(), token);
//跳转到认证中心登录
SSOClientHelper.ssoLogin(request, response);
return true;
}
//令牌无效
if (!applyAuth.getAuthResult() && StringUtils.isNoneBlank(applyAuth.getRedirectUrl())) {
log.debug("[ 请求拦截器 ] >> 令牌无效 , 将跳转认证中心进行认证 requestUrl:{}, token:{}", applyAuth.getRedirectUrl(), token);
//跳转到认证中心登录
response.sendRedirect(applyAuth.getRedirectUrl());
return true;
}
//token有效,创建局部会话设置登录状态,并放行
httpSession.setAttribute("isLogin", true);
httpSession.setAttribute("userInfo", JSON.toJSONString(applyAuth));
//设置session失效时间-单位秒
httpSession.setMaxInactiveInterval(1800);
//添加session
SessionContext.getInstance().addSession(httpSession);
//设置本域cookie
CookieUtil.setCookie(response, SsoConstant.TOKEN_NAME, token, 1800);
log.debug("[ 请求拦截器 ] >> 令牌有效,创建局部会话成功 sessionId:{},requestUrl:{}, token:{}", httpSession.getId(), request.getRequestURI(), token);
return true;
}
}

@ -0,0 +1,54 @@
package com.example.sso.client.model;
import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.Getter;
import lombok.Setter;
import java.io.Serializable;
/**
* 返回统一对象
*
* @author 程序员小强
*/
@Getter
@Setter
@JsonInclude(JsonInclude.Include.NON_NULL)
public class ResultModel implements Serializable {
private static final long serialVersionUID = -4588600204128331404L;
/**
* 状态码
*/
private Integer code;
/**
* 返回的的数据
*/
private Object data;
/**
* 描述
*/
private String msg;
/**
* 成功请求
* code : 200
* msg : 默认 ""
*/
public static ResultModel success(Object data) {
return new ResultModel(200, data, "操作成功");
}
public static ResultModel success() {
return success("");
}
public ResultModel(Integer code, Object data, String msg) {
this.code = code;
this.data = data;
this.msg = msg;
}
}

@ -0,0 +1,56 @@
package com.example.sso.client.utils;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* @author 程序员小强
* @date 2020-07-11
*/
public class CookieUtil {
/**
* 根据key名称-获取cookie值
*
* @param request
* @param cookieName
*/
public static String getCookie(HttpServletRequest request, String cookieName) {
Cookie[] cookies = request.getCookies();
if (null == cookies) {
return null;
}
for (Cookie cookie : cookies) {
if (cookie.getName().equals(cookieName)) {
return cookie.getValue();
}
}
return null;
}
/**
* 设置cookie
*
* @param response
* @param cookieName
* @param value
* @param cookieMaxAge
*/
public static void setCookie(HttpServletResponse response, String cookieName, String value, int cookieMaxAge) {
Cookie cookie = new Cookie(cookieName, value);
cookie.setPath("/");
cookie.setMaxAge(cookieMaxAge);
response.addCookie(cookie);
}
/**
* 删除cookie
*
* @param response
* @param cookieName
*/
public static void deleteCookie(HttpServletResponse response, String cookieName) {
setCookie(response, cookieName, null, 0);
}
}

@ -0,0 +1,71 @@
package com.example.sso.client.utils;
import com.example.sso.client.config.SysConfigProperty;
import com.example.sso.client.constant.SsoConstant;
import org.springframework.util.StringUtils;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* @author 程序员小强
* @date 2021-01-26
*/
public class SSOClientHelper {
/**
* 请求认证中心-校验登录或首次登录
* <p>
* 1.若存在全局会话则携带令牌重定向回客户端已经登录
* 2.若无全局会话则返回统一登录页面进行登录首次登录
*
* @param request
* @param response
*/
public static void ssoLogin(HttpServletRequest request, HttpServletResponse response) throws IOException {
StringBuilder url = new StringBuilder();
//获取当前客户端在访问的地址
String redirectUrl = getCurrentServletPath(request);
url.append(SysConfigProperty.getSsoAuthLoginUrl())
.append("?redirectUrl=")
.append(redirectUrl);
response.sendRedirect(url.toString());
}
/**
* 获取 ssoToken
* 1.优先从请求参数中获取
* 2.二次尝试从cookie中获取
*/
public static String getSsoToken(HttpServletRequest request) {
//从参数中获取
String token = request.getParameter(SsoConstant.TOKEN_NAME);
//若参数未携带-尝试从cookie获取
if (StringUtils.isEmpty(token)) {
token = CookieUtil.getCookie(request, SsoConstant.TOKEN_NAME);
}
return token;
}
/**
* 获取当前访问地址
* 示例http://localhost:8088/index
*
* @param request
*/
public static String getCurrentServletPath(HttpServletRequest request) {
return SysConfigProperty.getClientWebUrl() + request.getServletPath();
}
/**
* 获取客户端的登出地址
* 示例http://localhost:8088/logOut
*/
public static String getClientLogOutUrl() {
return SysConfigProperty.getClientWebUrl() + SsoConstant.CLIENT_LOGOUT_URL;
}
}

@ -0,0 +1,46 @@
package com.example.sso.client.utils;
import javax.servlet.http.HttpSession;
import java.util.HashMap;
import java.util.Map;
/**
* session 上下文管理
*
* @author 程序员小强
*/
public class SessionContext {
private static SessionContext instance;
private Map<String, HttpSession> sessionMap;
private SessionContext() {
sessionMap = new HashMap<String, HttpSession>();
}
public static SessionContext getInstance() {
if (instance == null) {
instance = new SessionContext();
}
return instance;
}
public synchronized void addSession(HttpSession session) {
if (session != null) {
sessionMap.put(session.getId(), session);
}
}
public synchronized void delSession(HttpSession session) {
if (session != null) {
sessionMap.remove(session.getId());
}
}
public synchronized HttpSession getSession(String sessionId) {
if (sessionId == null) {
return null;
}
return sessionMap.get(sessionId);
}
}

@ -0,0 +1,40 @@
package com.example.sso.client.utils;
import lombok.extern.slf4j.Slf4j;
import javax.servlet.annotation.WebListener;
import javax.servlet.http.HttpSession;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;
/**
* session 监听
*/
@Slf4j
@WebListener
public class SessionListener implements HttpSessionListener {
private SessionContext context = SessionContext.getInstance();
/**
* session 创建
*
* @param sessionEvent
*/
@Override
public void sessionCreated(HttpSessionEvent sessionEvent) {
context.addSession(sessionEvent.getSession());
}
/**
* session 销毁
*
* @param sessionEvent
*/
@Override
public void sessionDestroyed(HttpSessionEvent sessionEvent) {
HttpSession session = sessionEvent.getSession();
context.delSession(session);
}
}

@ -0,0 +1,84 @@
package com.example.sso.client.utils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.StringUtils;
import java.util.*;
/**
* 统一认证中心 签名工具
*
* @author 程序员小强
*/
@Slf4j
public class SsoSignUtil {
/**
* 生成Md5签名
* 规则
* 1.按参数名字母顺序升序区分大小写
* 2.移除不需要加入签名参数
* 3.待签名串&符逐个拼接参数名=参数值而形成
* 4.拼接秘钥 格式&secret= xxxx
* 5.MD5加密
*
* @param paramMap 参数Map
* @param removeKeys 需要移除的参数名
* @param secret 秘钥值
*/
public static String getMd5Sign(Map<String, Object> paramMap, List<String> removeKeys, String secret) {
try {
//按参数名字母顺序(升序、区分大小写)
TreeMap<String, String> signParamMap = getTreeMap(paramMap);
//移除签名参数
removeKeys.forEach(signParamMap::remove);
//待签名串(以&符逐个拼接参数名=参数值而形成)
String content = getSignContent(signParamMap);
//MD5 方式拼接 &secret
content = content + "&secret=" + secret;
log.info("[ 加签参数 ] content:{}", content);
//MD5加签
return DigestUtils.md5Hex(content);
} catch (Exception e) {
throw new RuntimeException("MD5加签异常", e);
}
}
/**
* &符逐个拼接参数名=参数值而形成
*
* @param sortedParams
*/
public static String getSignContent(Map<String, String> sortedParams) {
StringBuilder content = new StringBuilder();
ArrayList<String> keys = new ArrayList<>(sortedParams.keySet());
Collections.sort(keys);
int index = 0;
for (Object o : keys) {
String key = (String) o;
String value = sortedParams.get(key);
if (StringUtils.isNotBlank(key) && StringUtils.isNotBlank(value)) {
content.append(index == 0 ? "" : "&").append(key).append("=").append(value);
++index;
}
}
return content.toString();
}
/**
* 按参数名字母顺序升序区分大小写
*
* @param paramMap
*/
public static TreeMap<String, String> getTreeMap(Map<String, Object> paramMap) {
TreeMap<String, String> signMap = new TreeMap<>();
for (Map.Entry<String, Object> entry : paramMap.entrySet()) {
signMap.put(entry.getKey(), entry.getValue().toString());
}
return signMap;
}
}

@ -0,0 +1,18 @@
server.port=8801
logging.level.com.example=debug
#系统编码
sys.config.mySysCode=sys-client01
#MD5签名秘钥(与认证中心-平台详情-添加的秘钥一致)
sys.config.ssoAuthSecret=1234567890
#认证中心登录地址
sys.config.ssoAuthLoginUrl=http://www.myauth.com:9528/login
#当前客户端web地址
sys.config.clientWebUrl=http://www.sysclient1.com:8801
#本系统退出登录url
sys.config.myLoginOutUrl=http://www.sysclient1.com:8801/logOutBySsoAuth
#认证中心开放接口地址
sys.config.ssoAuthGetWayUrl=http://localhost:9901/api/open/gateway

@ -0,0 +1,67 @@
/**
* Minified by jsDelivr using clean-css v4.2.1.
* Original file: /npm/jquery.json-viewer@1.4.0/json-viewer/jquery.json-viewer.css
*
* Do NOT use SRI with dynamically generated files! More information: https://www.jsdelivr.com/using-sri-with-dynamic-files
*/
.json-document {
padding: 1em 2em
}
ol.json-array, ul.json-dict {
list-style-type: none;
margin: 0 0 0 1px;
border-left: 1px dotted #ccc;
padding-left: 2em
}
.json-string {
color: #0b7500
}
.json-literal {
color: #1a01cc;
font-weight: 700
}
a.json-toggle {
position: relative;
color: inherit;
text-decoration: none
}
a.json-toggle:focus {
outline: 0
}
a.json-toggle:before {
font-size: 1.1em;
color: silver;
content: "\25BC";
position: absolute;
display: inline-block;
width: 1em;
text-align: center;
line-height: 1em;
left: -1.2em
}
a.json-toggle:hover:before {
color: #aaa
}
a.json-toggle.collapsed:before {
transform: rotate(-90deg)
}
a.json-placeholder {
color: #aaa;
padding: 0 1em;
text-decoration: none
}
a.json-placeholder:hover {
text-decoration: underline
}
/*# sourceMappingURL=/sm/f0dabc51886cc1a4c93835b8de136abfa1435343760848f28d0861c7843c1617.map */

File diff suppressed because it is too large Load Diff

@ -0,0 +1,51 @@
/**
* Minified by jsDelivr using Terser v3.14.1.
* Original file: /npm/jquery.json-viewer@1.4.0/json-viewer/jquery.json-viewer.js
*
* Do NOT use SRI with dynamically generated files! More information: https://www.jsdelivr.com/using-sri-with-dynamic-files
*/
!function (s) {
function e(s) {
return s instanceof Object && Object.keys(s).length > 0
}
s.fn.jsonViewer = function (l, a) {
return a = Object.assign({}, {
collapsed: !1,
rootCollapsable: !0,
withQuotes: !1,
withLinks: !0
}, a), this.each(function () {
var t = function s(l, a) {
var t = "";
if ("string" == typeof l) l = l.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/'/g, "&apos;").replace(/"/g, "&quot;"), a.withLinks && /^(https?:\/\/|ftps?:\/\/)?([a-z0-9%-]+\.){1,}([a-z0-9-]+)?(:(\d{1,5}))?(\/([a-z0-9\-._~:\/?#[\]@!$&'()*+,;=%]+)?)?$/i.test(l) ? t += '<a href="' + l + '" class="json-string" target="_blank">' + l + "</a>" : t += '<span class="json-string">"' + (l = l.replace(/&quot;/g, "\\&quot;")) + '"</span>'; else if ("number" == typeof l) t += '<span class="json-literal">' + l + "</span>"; else if ("boolean" == typeof l) t += '<span class="json-literal">' + l + "</span>"; else if (null === l) t += '<span class="json-literal">null</span>'; else if (l instanceof Array) if (l.length > 0) {
t += '[<ol class="json-array">';
for (var n = 0; n < l.length; ++n) t += "<li>", e(l[n]) && (t += '<a href class="json-toggle"></a>'), t += s(l[n], a), n < l.length - 1 && (t += ","), t += "</li>";
t += "</ol>]"
} else t += "[]"; else if ("object" == typeof l) {
var o = Object.keys(l).length;
if (o > 0) {
for (var i in t += '{<ul class="json-dict">', l) if (Object.prototype.hasOwnProperty.call(l, i)) {
t += "<li>";
var r = a.withQuotes ? '<span class="json-string">"' + i + '"</span>' : i;
e(l[i]) ? t += '<a href class="json-toggle">' + r + "</a>" : t += r, t += ": " + s(l[i], a), --o > 0 && (t += ","), t += "</li>"
}
t += "</ul>}"
} else t += "{}"
}
return t
}(l, a);
a.rootCollapsable && e(l) && (t = '<a href class="json-toggle"></a>' + t), s(this).html(t), s(this).addClass("json-document"), s(this).off("click"), s(this).on("click", "a.json-toggle", function () {
var e = s(this).toggleClass("collapsed").siblings("ul.json-dict, ol.json-array");
if (e.toggle(), e.is(":visible")) e.siblings(".json-placeholder").remove(); else {
var l = e.children("li").length, a = l + (l > 1 ? " items" : " item");
e.after('<a href class="json-placeholder">' + a + "</a>")
}
return !1
}), s(this).on("click", "a.json-placeholder", function () {
return s(this).siblings("a.json-toggle").click(), !1
}), 1 == a.collapsed && s(this).find("a.json-toggle").click()
})
}
}(jQuery);
//# sourceMappingURL=/sm/2eabfda485458aa3aa101e518dd23623b568b299b2883e2315ffe59e7b2e718b.map

@ -0,0 +1,49 @@
<!DOCTYPE html>
<html lang="zh" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>SSO-client1</title>
</head>
<body>
<script type="text/javascript" th:src="@{/static/js/jquery.min.js}"></script>
<script type="text/javascript" th:src="@{/static/js/json-viewer.min.js}"></script>
<link rel="stylesheet" th:href="@{/static/css/json-viewer.min.css}"/>
<h1>SSO-1号测试子系统</h1>
<h2>欢迎访问 SSO Demo 之 " 1号-测试子系统 "。。。。。。我是MD5加签与认证中心交互-示例 </h2>
<p><a href="/welcome">welcome.html</a></p>
<p><a th:href="${logOutUrl}">退出当前系统</a></p>
<h4 id="token"> </h4>
<h3> 用户信息如下:</h3>
<pre id="user"></pre>
<script>
$(document).ready(function () {
$.ajax({
url: "/getUserInfo",
success: function (result) {
$('#user').jsonViewer(JSON.parse(result.data));
}
});
$('#token').html(" ssoToken值: "+getCookie("ssoToken"));
});
function getCookie(name) {
const prefix = name + "=";
const start = document.cookie.indexOf(prefix);
if (start === -1) {
return null;
}
let end = document.cookie.indexOf(";", start + prefix.length);
if (end === -1) {
end = document.cookie.length;
}
const value = document.cookie.substring(start + prefix.length, end);
return unescape(value);
}
</script>
</body>
</html>

@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>SSO-client1</title>
</head>
<body>
<h1>SSO-1号测试子系统</h1>
<h2>欢迎访问 SSO Demo 之 " 1号-测试子系统 "。。。。。。我是MD5加签与认证中心交互-示例 </h2>
<p><a href="/index">回到首页</a></p>
</body>
</html>

@ -0,0 +1,83 @@
<?xml version="1.0" encoding="UTF-8"?>
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.0.RELEASE</version>
<relativePath/>
</parent>
<groupId>com.example</groupId>
<artifactId>sso-sys-client2</artifactId>
<version>1.0-SNAPSHOT</version>
<name>sso-sys-client2</name>
<description>sso单点登录客户端2</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.4</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.4.3</version>
</dependency>
<!-- fastjson -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.73</version>
</dependency>
<!-- io -->
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.6</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.9</version>
</dependency>
<dependency>
<groupId>org.apache.directory.studio</groupId>
<artifactId>org.apache.commons.codec</artifactId>
<version>1.8</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

@ -0,0 +1,17 @@
package com.example.sso.client;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* 统一认证中心-系统2-DEMO
*
* @author 程序员小强
*/
@SpringBootApplication
public class SsoSysClient02Application {
public static void main(String[] args) {
SpringApplication.run(SsoSysClient02Application.class, args);
}
}

@ -0,0 +1,118 @@
package com.example.sso.client.auth;
import cn.hutool.http.HttpUtil;
import com.alibaba.fastjson.JSON;
import com.example.sso.client.auth.model.ApplyAuthVO;
import com.example.sso.client.config.SysConfigProperty;
import com.example.sso.client.constant.SsoConstant;
import com.example.sso.client.model.ResultModel;
import com.example.sso.client.utils.sign.RSASignUtils;
import com.example.sso.client.utils.sign.SignContentUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
@Slf4j
@Service
public class AuthService {
private static final Integer SUCCESS_CODE = 200;
@Autowired
private SysConfigProperty sysConfigProperty;
/**
* 请求认证中心-申请认证
*
* @param token
* @return
*/
public ApplyAuthVO authToken(String token, String sessionId) {
//构建参数
Map<String, Object> paramMap = this.buildParamMap(token, sessionId);
long start = System.currentTimeMillis();
log.info("[ 请求认证中心-申请认证 ] start >> paramMap:{}", JSON.toJSONString(paramMap));
String response = HttpUtil.post(sysConfigProperty.getSsoAuthGetWayUrl(), paramMap);
log.info("[ 请求认证中心-申请认证 ] end >> times:{} ms, response:{}", (System.currentTimeMillis() - start), response);
//正式环境需对异常统一除以,这里只做了模拟默认抛出了 RuntimeException
if (StringUtils.isBlank(response)) {
throw new RuntimeException("认证中心-无返回");
}
ResultModel resultModel = JSON.parseObject(response, ResultModel.class);
if (null == resultModel || StringUtils.isBlank(resultModel.getData().toString())) {
throw new RuntimeException("认证中心-无返回");
}
if (!resultModel.getCode().equals(SUCCESS_CODE)) {
throw new RuntimeException("认证中心调用失败," + resultModel.getMsg());
}
return JSON.parseObject(resultModel.getData().toString(), ApplyAuthVO.class);
}
/**
* 构建参数
*
* @param token
* @param sessionId
*/
private Map<String, Object> buildParamMap(String token, String sessionId) {
Map<String, Object> paramMap = new HashMap<>(16);
//系统编码
paramMap.put("sysCode", sysConfigProperty.getMySysCode());
//申请认证接口地址 (com.sso.applyAuth)
paramMap.put("method", SsoConstant.METHOD_APPLY_AUTH);
//版本号
paramMap.put("version", "1.0");
//请求唯一标识
paramMap.put("apiRequestId", UUID.randomUUID());
//签名类型 1-MD5;2-RSA;3-RSA2
paramMap.put("signType", "2");
//当前时间戳
paramMap.put("timestamp", System.currentTimeMillis());
//业务参数
Map<String, Object> contentMap = this.buildContentMap(token, sessionId);
//业务参数JSON 值
paramMap.put("content", JSON.toJSONString(contentMap));
//生成签名
String sign = this.getRsaSign(paramMap);
paramMap.put("sign", sign);
return paramMap;
}
private Map<String, Object> buildContentMap(String token, String sessionId) {
Map<String, Object> contentMap = new HashMap<>(4);
//退出当前子系统地址 示例:http://www.sysclient1.com:8801/logOutBySessionId?sessionId=xxx
contentMap.put("loginOutUrl", String.format("%s?sessionId=%s", sysConfigProperty.getMyLoginOutUrl(), sessionId));
//返回菜单类型 1-普通列表 2-树型结构列表
contentMap.put("menuType", 2);
contentMap.put("ssoToken", token);
return contentMap;
}
/**
* 加签
*
* @param paramMap
*/
private String getRsaSign(Map<String, Object> paramMap) {
try {
String signContent = SignContentUtil.getSignContent(paramMap, Arrays.asList("sign", "signType"));
return RSASignUtils.sign(signContent, sysConfigProperty.getPrivateKey());
} catch (Exception e) {
throw new RuntimeException("加签异常", e);
}
}
}

@ -0,0 +1,98 @@
package com.example.sso.client.auth.model;
import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.Data;
import java.io.Serializable;
import java.util.List;
import java.util.Set;
/**
* 申请认证参数
*
* @author 程序员小强
* @version ApplyAuthBO.java
*/
@Data
@JsonInclude(JsonInclude.Include.NON_NULL)
public class ApplyAuthVO implements Serializable {
private static final long serialVersionUID = -3755046680856409972L;
/**
* 认证结果
*/
private Boolean authResult;
/**
* 重定向url认证结果仅为false时返回
*/
private String redirectUrl;
/**
* 用户ID
*/
private Long userId;
/**
* 用户登录名
*/
private String username;
/**
* 用户昵称
*/
private String nickName;
/**
* 真实姓名
*/
private String realName;
/**
* 头像
*/
private String avatar;
/**
* 用户性别 0-;1-;2-未知
*/
private Integer sex;
/**
* 手机号码
*/
private String phone;
/**
* 邮箱
*/
private String email;
/**
* 系统编码
*/
private String sysCode;
/**
* 系统名称
*/
private String sysName;
/**
* 在当前系统下的-权限列表
*/
private Set<String> permissionList;
/**
* 在当前系统下的-角色标识
*/
private List<String> roleKeyList;
/**
* 在当前系统下的-菜单集
*/
private List<MenuTreeAuthVO> menuList;
}

@ -0,0 +1,67 @@
package com.example.sso.client.auth.model;
import lombok.Data;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
/**
* 菜单树-返回对象
*
* @author 程序员小强
*/
@Data
public class MenuTreeAuthVO implements Serializable {
private static final long serialVersionUID = 69788387641481670L;
/**
* 菜单ID
*/
private Long menuId;
/**
* 父菜单ID
*/
private Long menuParentId;
/**
* 菜单名称
*/
private String menuName;
/**
* 菜单类型M目录 C菜单 F按钮
*/
private String menuType;
/**
* 显示顺序
*/
private Integer sortNum;
/**
* 菜单路由地址
*/
private String path;
/**
* 菜单图标
*/
private String icon;
/**
* 组件路径
*/
private String component;
/**
* 是否显示 0-显示;1-隐藏
*/
private Integer visible;
private final List<MenuTreeAuthVO> children = new ArrayList<>();
}

@ -0,0 +1,95 @@
package com.example.sso.client.config;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
* 系统配置属性
*
* @author 程序员小强
*/
@Component
@ConfigurationProperties(prefix = "sys.config")
public class SysConfigProperty {
/**
* 我的系统编码
*/
private String mySysCode;
/**
* 认证中心申请认证开放网关地址
*/
private String ssoAuthGetWayUrl;
/**
* RSA 加签私钥
*/
private String privateKey;
/**
* 本系统退出登录地址
*/
private String myLoginOutUrl;
/**
* 认证中心登录地址
*/
private static String ssoAuthLoginUrl;
/**
* 当前客户端web地址
*/
private static String clientWebUrl;
public String getMySysCode() {
return mySysCode;
}
public void setMySysCode(String mySysCode) {
this.mySysCode = mySysCode;
}
public String getSsoAuthGetWayUrl() {
return ssoAuthGetWayUrl;
}
public void setSsoAuthGetWayUrl(String ssoAuthGetWayUrl) {
this.ssoAuthGetWayUrl = ssoAuthGetWayUrl;
}
public String getPrivateKey() {
return privateKey;
}
public SysConfigProperty setPrivateKey(String privateKey) {
this.privateKey = privateKey;
return this;
}
public String getMyLoginOutUrl() {
return myLoginOutUrl;
}
public void setMyLoginOutUrl(String myLoginOutUrl) {
this.myLoginOutUrl = myLoginOutUrl;
}
public static String getSsoAuthLoginUrl() {
return ssoAuthLoginUrl;
}
public void setSsoAuthLoginUrl(String ssoAuthLoginUrl) {
SysConfigProperty.ssoAuthLoginUrl = ssoAuthLoginUrl;
}
public static String getClientWebUrl() {
return clientWebUrl;
}
public void setClientWebUrl(String clientWebUrl) {
SysConfigProperty.clientWebUrl = clientWebUrl;
}
}

@ -0,0 +1,23 @@
package com.example.sso.client.constant;
public class SsoConstant {
/**
* 令牌统一参数名
*/
public static final String TOKEN_NAME = "ssoToken";
/**
* 申请认证接口
*/
public static final String METHOD_APPLY_AUTH = "com.sso.applyAuth";
/**
* 认证中心-退出登录地址
*/
public static final String CLIENT_LOGOUT_URL = "/logOut";
}

@ -0,0 +1,35 @@
package com.example.sso.client.controller;
import com.example.sso.client.utils.SSOClientHelper;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
/**
* 页面跳转
*
* @author 程序员小强
*/
@Controller
public class IndexController {
@RequestMapping({"/", "/index"})
protected String index(HttpServletRequest request, Model model) {
HttpSession session = request.getSession();
model.addAttribute("logOutUrl", SSOClientHelper.getClientLogOutUrl());
Object userInfo = session.getAttribute("userInfo");
if (null != userInfo) {
model.addAttribute("userInfo", userInfo);
}
return "index";
}
@RequestMapping("/welcome")
protected String welcome(Model model) {
return "welcome";
}
}

@ -0,0 +1,74 @@
package com.example.sso.client.controller;
import com.example.sso.client.config.SysConfigProperty;
import com.example.sso.client.model.ResultModel;
import com.example.sso.client.utils.SessionContext;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
@Slf4j
@RestController
public class LoginController {
@Autowired
private SysConfigProperty sysConfigProperty;
/**
* 从局部会话-缓存中
* <p>
* 获取登录用户的信息
*
* @param request
*/
@RequestMapping(value = "/getUserInfo", produces = {"application/json;charset=UTF-8"})
protected Object getUserInfo(HttpServletRequest request) {
HttpSession session = request.getSession();
Object userInfo = session.getAttribute("userInfo");
if (null != userInfo) {
return ResultModel.success(userInfo);
}
return ResultModel.success();
}
/**
* 退出当前系统
*
* @param request
* @param response
*/
@RequestMapping("/logOut")
protected void logOut(HttpServletRequest request, HttpServletResponse response) throws IOException {
//销毁局部会话
SessionContext.getInstance().delSession(request.getSession());
//跳转到认证中心登录页
response.sendRedirect(SysConfigProperty.getSsoAuthLoginUrl());
}
/**
* 认证中心统-调用-退出该系统接口
*
* @param request
* @param response
*/
@RequestMapping("/logOutBySsoAuth")
protected void logOutBySsoAuth(HttpServletRequest request, HttpServletResponse response) {
//获取参数sessionId
String sessionId = request.getParameter("sessionId");
log.info("[ 退出子系统 ] >> start sessionId:{}", sessionId);
HttpSession session = SessionContext.getInstance().getSession(sessionId);
if (null == session) {
log.error("[ 退出子系统 ] >> session不存在 sessionId:{}", sessionId);
return;
}
SessionContext.getInstance().delSession(session);
log.info("[ 退出子系统 ] >> end sessionId:{}", sessionId);
}
}

@ -0,0 +1,68 @@
package com.example.sso.client.interceptor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.web.servlet.config.annotation.ContentNegotiationConfigurer;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
import java.nio.charset.StandardCharsets;
@Slf4j
@Configuration
public class WebConfig extends WebMvcConfigurationSupport {
/**
* 创建拦截器
*/
@Bean
WebInterceptor webInterceptor() {
return new WebInterceptor();
}
/**
* 添加拦截器-进行拦截
* addPathPatterns 添加拦截
* excludePathPatterns 排除拦截
**/
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(this.webInterceptor())
.addPathPatterns("/**")
.excludePathPatterns("/static/**")
.excludePathPatterns("/logOut")
.excludePathPatterns("/logOutBySsoAuth");
super.addInterceptors(registry);
}
/**
* 返回值-编码 UTF-8
*/
@Bean
public HttpMessageConverter<String> responseBodyConverter() {
return new StringHttpMessageConverter(StandardCharsets.UTF_8);
}
@Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
configurer.favorPathExtension(false);
}
/**
* 资源处理器-资源路径 映射
*
* @param registry
*/
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/static/**").addResourceLocations("classpath:/static/");
registry.addResourceHandler("/webjars/**")
.addResourceLocations("classpath:/META-INF/resources/webjars/");
}
}

@ -0,0 +1,93 @@
package com.example.sso.client.interceptor;
import com.alibaba.fastjson.JSON;
import com.example.sso.client.auth.AuthService;
import com.example.sso.client.auth.model.ApplyAuthVO;
import com.example.sso.client.constant.SsoConstant;
import com.example.sso.client.utils.CookieUtil;
import com.example.sso.client.utils.SSOClientHelper;
import com.example.sso.client.utils.SessionContext;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
/**
* 创建拦截器-拦截需要安全访问的请求
* 方法说明
* 1.preHandle():前置处理回调方法返回true继续执行返回false中断流程不会继续调用其它拦截器
* 2.postHandle():后置处理回调方法但在渲染视图之前
* 3.afterCompletion():全部后置处理之后整个请求处理完毕后回调
*
* @author 程序员小强
*/
@Slf4j
public class WebInterceptor implements HandlerInterceptor {
@Autowired
protected AuthService authService;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws IOException {
//判断是否有局部会话
HttpSession httpSession = request.getSession();
//获取session
HttpSession session = SessionContext.getInstance().getSession(httpSession.getId());
if (null != session) {
Object isLogin = session.getAttribute("isLogin");
if (isLogin != null && (Boolean) isLogin) {
log.debug("[ 请求拦截器 ] >> 已登录状态 , 有局部会话 sessionId:{}", httpSession.getId());
return true;
}
}
log.info("[ 请求拦截器 ] >> 未登录需要登录 >> sessionId:{} requestUrl:{} ", httpSession.getId(), request.getRequestURI());
//获取令牌ssoToken
String token = SSOClientHelper.getSsoToken(request);
//无令牌
if (StringUtils.isBlank(token)) {
log.info("[ 请求拦截器 ] >> 未获取到token , 将跳转认证中心 >> sessionId:{} ", httpSession.getId());
//跳转到认证中心登录
SSOClientHelper.ssoLogin(request, response);
return true;
}
//有令牌-则请求认证中心校验令牌是否有效
ApplyAuthVO applyAuth = authService.authToken(token, httpSession.getId());
//令牌无效
if (null == applyAuth) {
log.debug("[ 请求拦截器 ] >> 令牌无效 , 将跳转认证中心进行认证 requestUrl:{}, token:{}", request.getRequestURI(), token);
//跳转到认证中心登录
SSOClientHelper.ssoLogin(request, response);
return true;
}
//令牌无效
if (!applyAuth.getAuthResult() && org.apache.commons.lang3.StringUtils.isNoneBlank(applyAuth.getRedirectUrl())) {
log.debug("[ 请求拦截器 ] >> 令牌无效 , 将跳转认证中心进行认证 requestUrl:{}, token:{}", applyAuth.getRedirectUrl(), token);
//跳转到认证中心登录
response.sendRedirect(applyAuth.getRedirectUrl());
return true;
}
//token有效,创建局部会话设置登录状态,并放行
httpSession.setAttribute("isLogin", true);
httpSession.setAttribute("userInfo", JSON.toJSONString(applyAuth));
//设置session失效时间-单位秒
httpSession.setMaxInactiveInterval(1800);
//添加session
SessionContext.getInstance().addSession(httpSession);
//设置本域cookie
CookieUtil.setCookie(response, SsoConstant.TOKEN_NAME, token, 1800);
log.debug("[ 请求拦截器 ] >> 令牌有效,创建局部会话成功 sessionId:{},requestUrl:{}, token:{}", httpSession.getId(), request.getRequestURI(), token);
return true;
}
}

@ -0,0 +1,54 @@
package com.example.sso.client.model;
import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.Getter;
import lombok.Setter;
import java.io.Serializable;
/**
* 返回统一对象
*
* @author 程序员小强
*/
@Getter
@Setter
@JsonInclude(JsonInclude.Include.NON_NULL)
public class ResultModel implements Serializable {
private static final long serialVersionUID = -4588600204128331404L;
/**
* 状态码
*/
private Integer code;
/**
* 返回的的数据
*/
private Object data;
/**
* 描述
*/
private String msg;
/**
* 成功请求
* code : 200
* msg : 默认 ""
*/
public static ResultModel success(Object data) {
return new ResultModel(200, data, "操作成功");
}
public static ResultModel success() {
return success("");
}
public ResultModel(Integer code, Object data, String msg) {
this.code = code;
this.data = data;
this.msg = msg;
}
}

@ -0,0 +1,56 @@
package com.example.sso.client.utils;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* @author 程序员小强
* @date 2020-07-11
*/
public class CookieUtil {
/**
* 根据key名称-获取cookie值
*
* @param request
* @param cookieName
*/
public static String getCookie(HttpServletRequest request, String cookieName) {
Cookie[] cookies = request.getCookies();
if (null == cookies) {
return null;
}
for (Cookie cookie : cookies) {
if (cookie.getName().equals(cookieName)) {
return cookie.getValue();
}
}
return null;
}
/**
* 设置cookie
*
* @param response
* @param cookieName
* @param value
* @param cookieMaxAge
*/
public static void setCookie(HttpServletResponse response, String cookieName, String value, int cookieMaxAge) {
Cookie cookie = new Cookie(cookieName, value);
cookie.setPath("/");
cookie.setMaxAge(cookieMaxAge);
response.addCookie(cookie);
}
/**
* 删除cookie
*
* @param response
* @param cookieName
*/
public static void deleteCookie(HttpServletResponse response, String cookieName) {
setCookie(response, cookieName, null, 0);
}
}

@ -0,0 +1,71 @@
package com.example.sso.client.utils;
import com.example.sso.client.config.SysConfigProperty;
import com.example.sso.client.constant.SsoConstant;
import org.springframework.util.StringUtils;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* @author 程序员小强
* @date 2021-01-26
*/
public class SSOClientHelper {
/**
* 请求认证中心-校验登录或首次登录
* <p>
* 1.若存在全局会话则携带令牌重定向回客户端已经登录
* 2.若无全局会话则返回统一登录页面进行登录首次登录
*
* @param request
* @param response
*/
public static void ssoLogin(HttpServletRequest request, HttpServletResponse response) throws IOException {
StringBuilder url = new StringBuilder();
//获取当前客户端在访问的地址
String redirectUrl = getCurrentServletPath(request);
url.append(SysConfigProperty.getSsoAuthLoginUrl())
.append("?redirectUrl=")
.append(redirectUrl);
response.sendRedirect(url.toString());
}
/**
* 获取 ssoToken
* 1.优先从请求参数中获取
* 2.二次尝试从cookie中获取
*/
public static String getSsoToken(HttpServletRequest request) {
//从参数中获取
String token = request.getParameter(SsoConstant.TOKEN_NAME);
//若参数未携带-尝试从cookie获取
if (StringUtils.isEmpty(token)) {
token = CookieUtil.getCookie(request, SsoConstant.TOKEN_NAME);
}
return token;
}
/**
* 获取当前访问地址
* 示例http://localhost:8088/index
*
* @param request
*/
public static String getCurrentServletPath(HttpServletRequest request) {
return SysConfigProperty.getClientWebUrl() + request.getServletPath();
}
/**
* 获取客户端的登出地址
* 示例http://localhost:8088/logOut
*/
public static String getClientLogOutUrl() {
return SysConfigProperty.getClientWebUrl() + SsoConstant.CLIENT_LOGOUT_URL;
}
}

@ -0,0 +1,46 @@
package com.example.sso.client.utils;
import javax.servlet.http.HttpSession;
import java.util.HashMap;
import java.util.Map;
/**
* session 上下文管理
*
* @author 程序员小强
*/
public class SessionContext {
private static SessionContext instance;
private Map<String, HttpSession> sessionMap;
private SessionContext() {
sessionMap = new HashMap<String, HttpSession>();
}
public static SessionContext getInstance() {
if (instance == null) {
instance = new SessionContext();
}
return instance;
}
public synchronized void addSession(HttpSession session) {
if (session != null) {
sessionMap.put(session.getId(), session);
}
}
public synchronized void delSession(HttpSession session) {
if (session != null) {
sessionMap.remove(session.getId());
}
}
public synchronized HttpSession getSession(String sessionId) {
if (sessionId == null) {
return null;
}
return sessionMap.get(sessionId);
}
}

@ -0,0 +1,40 @@
package com.example.sso.client.utils;
import lombok.extern.slf4j.Slf4j;
import javax.servlet.annotation.WebListener;
import javax.servlet.http.HttpSession;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;
/**
* session 监听
*/
@Slf4j
@WebListener
public class SessionListener implements HttpSessionListener {
private SessionContext context = SessionContext.getInstance();
/**
* session 创建
*
* @param sessionEvent
*/
@Override
public void sessionCreated(HttpSessionEvent sessionEvent) {
context.addSession(sessionEvent.getSession());
}
/**
* session 销毁
*
* @param sessionEvent
*/
@Override
public void sessionDestroyed(HttpSessionEvent sessionEvent) {
HttpSession session = sessionEvent.getSession();
context.delSession(session);
}
}

@ -0,0 +1,84 @@
package com.example.sso.client.utils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.StringUtils;
import java.util.*;
/**
* 统一认证中心 签名工具
*
* @author 程序员小强
*/
@Slf4j
public class SsoSignUtil {
/**
* 生成Md5签名
* 规则
* 1.按参数名字母顺序升序区分大小写
* 2.移除不需要加入签名参数
* 3.待签名串&符逐个拼接参数名=参数值而形成
* 4.拼接秘钥 格式&secret= xxxx
* 5.MD5加密
*
* @param paramMap 参数Map
* @param removeKeys 需要移除的参数名
* @param secret 秘钥值
*/
public static String getMd5Sign(Map<String, Object> paramMap, List<String> removeKeys, String secret) {
try {
//按参数名字母顺序(升序、区分大小写)
TreeMap<String, String> signParamMap = getTreeMap(paramMap);
//移除签名参数
removeKeys.forEach(signParamMap::remove);
//待签名串(以&符逐个拼接参数名=参数值而形成)
String content = getSignContent(signParamMap);
//MD5 方式拼接 &secret
content = content + "&secret=" + secret;
log.info("[ 加签参数 ] content:{}", content);
//MD5加签
return DigestUtils.md5Hex(content);
} catch (Exception e) {
throw new RuntimeException("MD5加签异常", e);
}
}
/**
* &符逐个拼接参数名=参数值而形成
*
* @param sortedParams
*/
public static String getSignContent(Map<String, String> sortedParams) {
StringBuilder content = new StringBuilder();
ArrayList<String> keys = new ArrayList<>(sortedParams.keySet());
Collections.sort(keys);
int index = 0;
for (Object o : keys) {
String key = (String) o;
String value = sortedParams.get(key);
if (StringUtils.isNotBlank(key) && StringUtils.isNotBlank(value)) {
content.append(index == 0 ? "" : "&").append(key).append("=").append(value);
++index;
}
}
return content.toString();
}
/**
* 按参数名字母顺序升序区分大小写
*
* @param paramMap
*/
public static TreeMap<String, String> getTreeMap(Map<String, Object> paramMap) {
TreeMap<String, String> signMap = new TreeMap<>();
for (Map.Entry<String, Object> entry : paramMap.entrySet()) {
signMap.put(entry.getKey(), entry.getValue().toString());
}
return signMap;
}
}

@ -0,0 +1,133 @@
package com.example.sso.client.utils.sign;
import sun.misc.BASE64Decoder;
import sun.misc.BASE64Encoder;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
/**
* RSA 签名工具类
*
* @author 程序员小强
*/
public class RSASignUtils {
/**
* 加密算法RSA
*/
private static final String KEY_ALGORITHM = "RSA";
/**
* 签名算法
* SHA1WithRSA | MD5withRSA
*/
private static final String SIGNATURE_ALGORITHM = "SHA1WithRSA";
/**
* 用私钥对信息生成数字签名
*
* @param data 数据
* @param privateKey 私钥(BASE64编码)
* @return
* @throws Exception
*/
public static String sign(Object data, String privateKey) throws Exception {
return sign(data.toString().getBytes(), privateKey);
}
/**
* 用私钥对信息生成数字签名
*
* @param data 需要加签数据
* @param privateKey 私钥(BASE64编码)
* @return
* @throws Exception
*/
public static String sign(byte[] data, String privateKey) throws Exception {
//私钥 key BASE64解密
byte[] keyBytes = decryptBASE64(privateKey);
// 转换私钥
PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes);
// 实例化密钥工厂
KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
// 取私钥匙对象
PrivateKey privateK = keyFactory.generatePrivate(pkcs8KeySpec);
// 实例化Signature
Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);
// 初始化Signature
signature.initSign(privateK);
// 更新
signature.update(data);
// 生成签名
return encryptBASE64(signature.sign());
}
/**
* 校验数字签名
*
* @param data 已加签的数据
* @param publicKey 公钥(BASE64编码)
* @param sign 数字签名
* @return
* @throws Exception
*/
public static boolean verify(Object data, String publicKey, String sign)
throws Exception {
return verify(data.toString().getBytes(), publicKey, sign);
}
/**
* 校验数字签名
*
* @param data 已加签的数据
* @param publicKey 公钥(BASE64编码)
* @param sign 数字签名
* @return
* @throws Exception
*/
public static boolean verify(byte[] data, String publicKey, String sign)
throws Exception {
//私钥 key BASE64解密
byte[] keyBytes = decryptBASE64(publicKey);
// 转换公钥材料
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
// 实例化密钥工厂
KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
// 生成公钥
PublicKey publicK = keyFactory.generatePublic(keySpec);
// 实例化Signature
Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);
// 初始化Signature
signature.initVerify(publicK);
// 更新
signature.update(data);
// 验证签名
return signature.verify(decryptBASE64(sign));
}
/**
* BASE64解密
*
* @param key
* @throws Exception
*/
public static byte[] decryptBASE64(String key) throws Exception {
return (new BASE64Decoder()).decodeBuffer(key);
}
/**
* BASE64加密
*
* @param key
* @throws Exception
*/
public static String encryptBASE64(byte[] key) throws Exception {
return (new BASE64Encoder()).encodeBuffer(key);
}
}

@ -0,0 +1,67 @@
package com.example.sso.client.utils.sign;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import java.util.*;
/**
* 统一认证中心 签名工具
*
* @author 程序员小强
*/
@Slf4j
public class SignContentUtil {
/**
* 生成要加签的参数
*
* @param paramMap
* @param removeKeys
* @return 生成要加签的参数
*/
public static String getSignContent(Map<String, Object> paramMap, List<String> removeKeys) {
//按参数名字母顺序(升序、区分大小写)
TreeMap<String, String> signParamMap = getTreeMap(paramMap);
//移除签名参数
removeKeys.forEach(signParamMap::remove);
//待签名串(以&符逐个拼接参数名=参数值而形成)
return getSignContent(signParamMap);
}
/**
* &符逐个拼接参数名=参数值而形成
*
* @param sortedParams
*/
private static String getSignContent(Map<String, String> sortedParams) {
StringBuilder content = new StringBuilder();
ArrayList<String> keys = new ArrayList<>(sortedParams.keySet());
Collections.sort(keys);
int index = 0;
for (Object o : keys) {
String key = (String) o;
String value = sortedParams.get(key);
if (StringUtils.isNotBlank(key) && StringUtils.isNotBlank(value)) {
content.append(index == 0 ? "" : "&").append(key).append("=").append(value);
++index;
}
}
return content.toString();
}
/**
* 按参数名字母顺序升序区分大小写
*
* @param paramMap
*/
private static TreeMap<String, String> getTreeMap(Map<String, Object> paramMap) {
TreeMap<String, String> signMap = new TreeMap<>();
for (Map.Entry<String, Object> entry : paramMap.entrySet()) {
signMap.put(entry.getKey(), entry.getValue().toString());
}
return signMap;
}
}

@ -0,0 +1,17 @@
server.port=8802
logging.level.com.example=debug
#系统编码
sys.config.mySysCode=sys-client02
#RSA 签名秘钥
sys.config.privateKey= MIICeAIBADANBgkqhkiG9w0BAQEFAASCAmIwggJeAgEAAoGBAJwe1qwFYVOwayAJOGYZNnmosexFHb1xXuQah0yZIegJSp531nbegE4qpnDgn1ssctOkQa1nRoGOIuXSNBDOuJZC3gwLk8jYEhKrDuFM3HRza1gmPH3X8Qqv2OELh320xZy3k04woGCJkaVVV44NCyllBRsX5zGDwY3tGpSxm0vjAgMBAAECgYAgjM11UCnaqQ2swD9iCh+xfdqayE5LETgXlvBeqA7JsiY8o/+zCPD3Wy0Ym7yd5caLGdQXs1Cf8mKUSB9S4cbaObkFz10KBzCizBHuOOf/DSWOmRB59VNpDqKyAhi/2rnMs33tqhDmGjw+tOaSN+s2Q+ZsyJyAISyghK1ADujQAQJBANgYdCrkCSrF2+GIJn9LsIySJQywE09HrV/b+0Lg3kEX9RO5DsQp7V7oDrVWodWu5vjAyLFtxdXMaxzJoFgQ9qMCQQC48yqbFmR4ChQTetiTmBw8fsAD+5KX/7ozlvcUlv+ckLz5YutW6A7WFaLk2dHdepHgo1W3PLREM3SgGIKCvenBAkEAiB6PPl06MQlFQkGDDnhdE48Ta3SWFUBvQ4zMLwp6tcIjjHLrjEFk1n2SlkOl2XY848B8Ktec4NnNNusC57Z3xQJBAKzjau2E1jA8Q54eFPyLkcGfPsG6VsPN4vK86YWOza0+w9hgdtxLmyrXGOnTEFJ4dItIRCVBEsY6T4gkwUOUYkECQQClWV5NcFtkNvuBDDaDtu28+X5hJxwNuw3DAEX7tEwpejS+tu+BQsOyuKe0BoxXS4ILTR3istJYNvuU1um9otAH
#认证中心登录地址
sys.config.ssoAuthLoginUrl=http://www.myauth.com:9528/login
#当前客户端web地址
sys.config.clientWebUrl=http://www.sysclient2.com:8802
#本系统退出登录url
sys.config.myLoginOutUrl=http://www.sysclient2.com:8802/logOutBySsoAuth
#认证中心开放接口地址
sys.config.ssoAuthGetWayUrl=http://localhost:9901/api/open/gateway

@ -0,0 +1,67 @@
/**
* Minified by jsDelivr using clean-css v4.2.1.
* Original file: /npm/jquery.json-viewer@1.4.0/json-viewer/jquery.json-viewer.css
*
* Do NOT use SRI with dynamically generated files! More information: https://www.jsdelivr.com/using-sri-with-dynamic-files
*/
.json-document {
padding: 1em 2em
}
ol.json-array, ul.json-dict {
list-style-type: none;
margin: 0 0 0 1px;
border-left: 1px dotted #ccc;
padding-left: 2em
}
.json-string {
color: #0b7500
}
.json-literal {
color: #1a01cc;
font-weight: 700
}
a.json-toggle {
position: relative;
color: inherit;
text-decoration: none
}
a.json-toggle:focus {
outline: 0
}
a.json-toggle:before {
font-size: 1.1em;
color: silver;
content: "\25BC";
position: absolute;
display: inline-block;
width: 1em;
text-align: center;
line-height: 1em;
left: -1.2em
}
a.json-toggle:hover:before {
color: #aaa
}
a.json-toggle.collapsed:before {
transform: rotate(-90deg)
}
a.json-placeholder {
color: #aaa;
padding: 0 1em;
text-decoration: none
}
a.json-placeholder:hover {
text-decoration: underline
}
/*# sourceMappingURL=/sm/f0dabc51886cc1a4c93835b8de136abfa1435343760848f28d0861c7843c1617.map */

File diff suppressed because it is too large Load Diff

@ -0,0 +1,51 @@
/**
* Minified by jsDelivr using Terser v3.14.1.
* Original file: /npm/jquery.json-viewer@1.4.0/json-viewer/jquery.json-viewer.js
*
* Do NOT use SRI with dynamically generated files! More information: https://www.jsdelivr.com/using-sri-with-dynamic-files
*/
!function (s) {
function e(s) {
return s instanceof Object && Object.keys(s).length > 0
}
s.fn.jsonViewer = function (l, a) {
return a = Object.assign({}, {
collapsed: !1,
rootCollapsable: !0,
withQuotes: !1,
withLinks: !0
}, a), this.each(function () {
var t = function s(l, a) {
var t = "";
if ("string" == typeof l) l = l.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/'/g, "&apos;").replace(/"/g, "&quot;"), a.withLinks && /^(https?:\/\/|ftps?:\/\/)?([a-z0-9%-]+\.){1,}([a-z0-9-]+)?(:(\d{1,5}))?(\/([a-z0-9\-._~:\/?#[\]@!$&'()*+,;=%]+)?)?$/i.test(l) ? t += '<a href="' + l + '" class="json-string" target="_blank">' + l + "</a>" : t += '<span class="json-string">"' + (l = l.replace(/&quot;/g, "\\&quot;")) + '"</span>'; else if ("number" == typeof l) t += '<span class="json-literal">' + l + "</span>"; else if ("boolean" == typeof l) t += '<span class="json-literal">' + l + "</span>"; else if (null === l) t += '<span class="json-literal">null</span>'; else if (l instanceof Array) if (l.length > 0) {
t += '[<ol class="json-array">';
for (var n = 0; n < l.length; ++n) t += "<li>", e(l[n]) && (t += '<a href class="json-toggle"></a>'), t += s(l[n], a), n < l.length - 1 && (t += ","), t += "</li>";
t += "</ol>]"
} else t += "[]"; else if ("object" == typeof l) {
var o = Object.keys(l).length;
if (o > 0) {
for (var i in t += '{<ul class="json-dict">', l) if (Object.prototype.hasOwnProperty.call(l, i)) {
t += "<li>";
var r = a.withQuotes ? '<span class="json-string">"' + i + '"</span>' : i;
e(l[i]) ? t += '<a href class="json-toggle">' + r + "</a>" : t += r, t += ": " + s(l[i], a), --o > 0 && (t += ","), t += "</li>"
}
t += "</ul>}"
} else t += "{}"
}
return t
}(l, a);
a.rootCollapsable && e(l) && (t = '<a href class="json-toggle"></a>' + t), s(this).html(t), s(this).addClass("json-document"), s(this).off("click"), s(this).on("click", "a.json-toggle", function () {
var e = s(this).toggleClass("collapsed").siblings("ul.json-dict, ol.json-array");
if (e.toggle(), e.is(":visible")) e.siblings(".json-placeholder").remove(); else {
var l = e.children("li").length, a = l + (l > 1 ? " items" : " item");
e.after('<a href class="json-placeholder">' + a + "</a>")
}
return !1
}), s(this).on("click", "a.json-placeholder", function () {
return s(this).siblings("a.json-toggle").click(), !1
}), 1 == a.collapsed && s(this).find("a.json-toggle").click()
})
}
}(jQuery);
//# sourceMappingURL=/sm/2eabfda485458aa3aa101e518dd23623b568b299b2883e2315ffe59e7b2e718b.map

@ -0,0 +1,49 @@
<!DOCTYPE html>
<html lang="zh" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>SSO-client2</title>
</head>
<body>
<script type="text/javascript" th:src="@{/static/js/jquery.min.js}"></script>
<script type="text/javascript" th:src="@{/static/js/json-viewer.min.js}"></script>
<link rel="stylesheet" th:href="@{/static/css/json-viewer.min.css}"/>
<h1>SSO-2号测试子系统</h1>
<h2>欢迎访问 SSO Demo 之 " 2号-测试子系统 "。。。。。。我是RSA加签与认证中心交互-示例 </h2>
<p><a href="/welcome">welcome.html</a></p>
<p><a th:href="${logOutUrl}">退出当前系统</a></p>
<h4 id="token"> </h4>
<h3> 用户信息如下:</h3>
<pre id="user"></pre>
<script>
$(document).ready(function () {
$.ajax({
url: "/getUserInfo",
success: function (result) {
$('#user').jsonViewer(JSON.parse(result.data));
}
});
$('#token').html(" ssoToken值: "+getCookie("ssoToken"));
});
function getCookie(name) {
const prefix = name + "=";
const start = document.cookie.indexOf(prefix);
if (start === -1) {
return null;
}
let end = document.cookie.indexOf(";", start + prefix.length);
if (end === -1) {
end = document.cookie.length;
}
const value = document.cookie.substring(start + prefix.length, end);
return unescape(value);
}
</script>
</body>
</html>

@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>SSO-client2</title>
</head>
<body>
<h1>SSO-2号测试子系统</h1>
<h2>欢迎访问 SSO Demo 之 " 2号-测试子系统 "。。。。。。我是RSA加签与认证中心交互-示例 </h2>
<p><a href="/index">回到首页</a></p>
</body>
</html>
Loading…
Cancel
Save