背景与需求
有A,B两个系统,要实现在登录A系统后,中点击B系统的地址链接直接免密跳转到B系统。即单点登录,按照我个人理解来说就是用户只需要登录一次就可以访问相互关联但又独立的系统。实现单点登录的方案有很多,在这里的话采用的是cas来实现单点登录。采用cas构建单点登录的原理的详述,大家可参考https://www.cnblogs.com/shanyou/archive/2009/08/30/1556659.html,读了之后很是受益,向大神学习。
具体实施过程
前提:已经有了cas服务端提供的登录接口地址https://cas部署的ip:cas部署的port/cas/login?service=xxx,同时还有cas服务端提供的退出接口地址https://cas部署的ip:cas部署的port/cas/logout?service=xxx。B系统已经实现了在cas上的统一认证,并且提供了实现用户数据同步的接口:http://B系统部署ip:B系统部署port/user/synchroUsers。(小贴士:只有A系统中存在B系统的用户,这样才能通过A系统中的某个链接跳转到B系统中,实现免密登录)。
总体思路:在还未登录A系统时就调用B系统同步用户数据的接口,然后访问cas提供的登录接口地址:https://cas部署的ip:cas部署的port/cas/login?service=http://A系统部署的ip:A系统部署的port/sso/portal(其中service的具体值是A系统的后端接口地址),访问这个地址之后会弹出cas的登录页面,在该页面实现登录之后会重定向(这个重定向是在请求cas登录的那个接口时已经就实现的,不需要我们来操心)到A系统的后端接口地址,同时会携带ticket,也就是说重定向的地址是:https://A系统部署的ip:A系统的port/sso/portal?ticket=xxxx,在这个接口里面我们会解析ticket拿到用户的账号,然后参考A系统的登录逻辑模拟生成登录A系统所用的token等信息,将这些信息添加到Cookie中,带着这些Cookie进行重定向到A系统内部的首页,这样就实现了模拟登录A系统。
详细实现:
实现用户数据同步
---------------------------------------------------------auth微服务中---------------------------------------------------
TokenSSOController:
/**
* 同步用户数据
* @return
*/
@GetMapping("/synchroUsers")
@ResponseBody
public R<Map<String, Object>> synchroUsers(){
return tokenSSOService.synchroUsers();
}
TokenSSOService:
/**
* 同步用户数据
* @return
*/
public R<Map<String, Object>> synchroUsers() {
return remoteUserService.synchroUsers(SecurityConstants.INNER);
}
Feign客户端RemoteService中同步用户数据的接口:
@GetMapping("/user/synchroUsers")
public R<Map<String, Object>> synchroUsers(@RequestHeader(SecurityConstants.FROM_SOURCE) String source);
如果远程调用(因为在两个不同的微服务中,因此需要通过feign来实现远程调用)该接口时失败,进行用户降级处理:
@Override
public R<Map<String, Object>> synchroUsers(String source) {
return R.fail("同步用户失败:" + throwable.getMessage());
}
---------------------------------------------------------system微服务中--------------------------------------------------
SysUserController,SysUserService,SysUserServiceImpl:
/*SysUserController:同步B系统的用户数据到A系统*/
@GetMapping("synchroUsers")
public R<Map<String, Object>> synchroUsers() {
//向绿洲系统提供获取全量用户的接口发送https请求
String url = "https://B系统部署的ip:B系统部署的port/system-management/cache/users";
String response = null;
try {
response = HttpClient4Util.doGet(url);
} catch (Exception e) {
e.printStackTrace();
}
String msg = "";
List data=new ArrayList<>();
//由于response有可能为null,因此!response.equals()会出现空指针。使用Objects.equals()比较好
if (!Objects.equals(response,null)) {
//解析response获取到存放用户信息的data数组
JSONObject jsonObject = JSONObject.parseObject(response);
data = (List) jsonObject.get("data");
ArrayList<SysUser> userList = new ArrayList<>();
if (!data.isEmpty()) {
for (int i = 0; i < data.size(); i++) {
String user = data.get(i).toString();
JSONObject jsonObjectUser = JSONObject.parseObject(user);
SysUser sysUser = new SysUser();
//将解析得到的待同步用户根据A系统的需求进行适当编辑后统一添加到集合userList中
userList.add(userService.editSynchroUsers(sysUser,jsonObjectUser));
}
}
//将请求得到的用户数据同步到驾驶舱数据库中的用户表中
msg = userService.synchroUser(userList);
} else {
msg = "从response中解析得到的data为"+data;
}
return R.ok(null, msg);
}
/*SysUserService:编辑获取到的待同步的用户接口*/
SysUser editSynchroUsers(SysUser sysUser, JSONObject jsonObjectUser);
/*SysUserServiceImpl:编辑获取到的待同步的用户具体实现,可根据自己系统存储用户字段的需求进行编辑*/
@Override
public SysUser editSynchroUsers(SysUser sysUser, JSONObject jsonObjectUser) {
//获取用户账号
String account = (String) jsonObjectUser.get("account");
sysUser.setUserName(account);
//获取部门id
String deptId = (String) jsonObjectUser.get("deptId");
sysUser.setDeptId(deptId);
//获取用户id
String userId = (String) jsonObjectUser.get("id");
sysUser.setUserId(userId);
//获取用户真实姓名
String realName = (String) jsonObjectUser.get("realName");
if (realName.equals("")) {
sysUser.setRealName(null);
}
sysUser.setRealName(realName);
//获取用户昵称
String nickName = (String) jsonObjectUser.get("name");
sysUser.setNickName(nickName);
//设置最后登录时间默认值
sysUser.setLoginDate(null);
//获取创建时间
String createTime = (String) jsonObjectUser.get("createTime");
sysUser.setCreateTime(DateUtils.parseDate(createTime));
//获取更新时间
String updateTime = (String) jsonObjectUser.get("updateTime");
sysUser.setCreateTime(DateUtils.parseDate(updateTime));
//设置备注默认值
sysUser.setRemark(null);
//设置入职时间默认值
sysUser.setInductionDate(null);
//设置员工状态默认值
sysUser.setPersonnelStatus(null);
//获取生日
String birthday = (String) jsonObjectUser.get("birthday");
sysUser.setBirthDay(birthday);
//设置婚姻状态
sysUser.setMaritalStatus(null);
//获取电话
String phone = (String) jsonObjectUser.get("phone");
sysUser.setPhonenumber(phone);
//设置岗位编号默认值
sysUser.setPostCode(null);
//设置身份证号默认值
sysUser.setCertificateNumber(null);
//设置用户编码默认值
sysUser.setUserCode(null);
//设置默认删除标志
sysUser.setDelFlag("0");
return sysUser;
}
/*SysUserService同步用户接口*/
String synchroUser(ArrayList<SysUser> accountList);
/*SysUserServiceImpl同步用户具体实现*/
@Override
public String synchroUser(ArrayList<SysUser> sysUserListAfter) {
logger.info("-------同步用户数据---------开始");
String msg="同步用户数据成功!";
try {
List<SysUser> synchroUserList=new ArrayList<>();
//先查询A系统用户列表
List<SysUser> sysUserListBefore = selectUsers();
//这个嵌套的for循环就是比较A系统用户列表和待同步用户的列表,把A系统中不存在的用户过滤出来,整体添加到一个集合中
loop:for (SysUser sysUserAfter : sysUserListAfter) {
String userAfterUserName = sysUserAfter.getUserName();
for (SysUser sysUserBefore : sysUserListBefore) {
String userBeforeUserName = sysUserBefore.getUserName();
if (!userAfterUserName.equals(userBeforeUserName)){
continue;
}else {
continue loop;
}
}
synchroUserList.add(sysUserAfter);
}
//把过滤出来的真正需要同步的用户批量添加到A系统的用户表中
if(!synchroUserList.isEmpty()){
userMapper.insertBatchSomeColumn(synchroUserList);
}
} catch (Exception e) {
msg="同步用户数据失败!";
}
logger.info("-------同步用户数据---------结束");
return msg;
}
//查询A系统中原有的用户信息
public List<SysUser> selectUsers(){
return userMapper.selectSysUserListBefore();
}
实现模拟登录
TokenSSOController:
/** TokenSSOController中:
*单点登录--模拟登录
* @param ticket 在cas登录页登录后获取到的票据
*/
@ResponseBody
@GetMapping("/portal")
public R<Map<String, Object>> ssoPortal(@RequestParam String ticket,HttpServletResponse response){
String casServerUrl="cas的校验地址";//用来校验ticket是否有效
String casServiceUrl="生成ticket的地址";
//2、解析ticket,拿到用户的账户
String username=tokenSSOService.getAccountNameFromTicket(ticket,casServerUrl,casServiceUrl);
log.info("-------账户为"+username+"的用户模拟登录---------开始");
Map<String, Object> map = null;
if(!Objects.equals(username,null)){
//3、使用用户的账户生成登录A所需要的token并使用token实现模拟登录
map = tokenSSOService.generateJscToken(username);
}
//解析map,得到sso_token
String sso_token = (String) map.get("access_token");
String expires_in = String.valueOf(map.get("expires_in"));
String sidebarStatus="1";
// 构建重定向URL
String redirectUrl = "A系统内部首页url";
try {
// 设置Cookie的值到Header中
Cookie expiresInCookie = new Cookie("Admin-Expires-In", expires_in);
Cookie sidebarStatusCookie = new Cookie("sidebarStatus", sidebarStatus);
Cookie ssoTokenCookie = new Cookie("Admin-Token", sso_token);
tokenSSOService.addCookie(expiresInCookie,response);
tokenSSOService.addCookie(sidebarStatusCookie,response);
tokenSSOService.addCookie(ssoTokenCookie,response);
// 重定向到指定URL
response.sendRedirect(redirectUrl);
} catch (Exception e) {
e.printStackTrace();
}
log.info("-------账户为"+username+"的用户模拟登录---------结束");
return R.ok(map);
}
TokenSSOService:
/**
* 登录cas之后解析cas返回的ticket拿到用户的账号
*
* @param ticket cas票据
* @param casServiceUrl casService地址
* @param casServerUrl casServer地址
* @return
*/
public String getAccountNameFromTicket(String ticket, String casServerUrl, String casServiceUrl) {
String account = null;
try {
//使用Cas30ServiceTicketValidator前提:
//引入cas客户端依赖
//<dependency>
// <groupId>org.jasig.cas.client</groupId>
// <artifactId>cas-client-core</artifactId>
// <version>3.5.0</version>
//</dependency>
Cas30ServiceTicketValidator ticketValidator
= new Cas30ServiceTicketValidator(casServerUrl);
//因为生成ticket是根据service地址生成的,因此解析ticket时也需要该地址进行解析
Assertion assertion = ticketValidator.validate(ticket, casServiceUrl);
AttributePrincipal principal = assertion.getPrincipal();
account = principal.getName();
} catch (TicketValidationException e) {
e.printStackTrace();
}
return account;
}
/**
* 生成登录A系统的的token
* @param userName
* @return
*/
@Autowired
private RemoteUserService remoteUserService;
@Autowired
private TokenService tokenService;
public Map<String, Object> generateJscToken(String username) {
//采用工具类得到userId
//查询用户信息,SecurityConstants.INNER为访问权限字段
R<LoginUser> userResult = remoteUserService.getUserInfo(username, SecurityConstants.INNER);
LoginUser userInfo = userResult.getData();
//直接调用的A系统生成token的方法
return tokenService.createToken(userInfo);
}
/**
* 实现重定向中添加Cookie
* @param cookie
* @param response
* @return
*/
public R<Map<String,Object>> addCookie(Cookie cookie, HttpServletResponse response){
//设置重定向到哪个ip时使用这些cookie的属性
cookie.setDomain("A系统部署的ip");
//设置cookie的根路径
cookie.setPath("/");
//设置cookie的存活时间为3600秒
cookie.setMaxAge(3600);
response.addCookie(cookie);
return R.ok();
}
今天分享就到这里啦~~~,欢迎大家批评指正,共同进步哟~~~

被折叠的 条评论
为什么被折叠?



