parent
6cdeb15e5b
commit
f92d75a3ca
@ -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, "&").replace(/</g, "<").replace(/>/g, ">").replace(/'/g, "'").replace(/"/g, """), 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(/"/g, "\\"")) + '"</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, "&").replace(/</g, "<").replace(/>/g, ">").replace(/'/g, "'").replace(/"/g, """), 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(/"/g, "\\"")) + '"</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…
Reference in new issue