单点登陆sso实现

时间:2022-10-20 16:38:02

需求:

多个bs业务系统,在某个业务系统登陆后,访问其他bs应用系统无需重复登陆.

制约:必须同一浏览器.

解决方案:

关键词:cookie,跨域,sso

环境

l  Passport.com 登陆认证服务

l pis.com 病理业务系统

l lis.com 检验业务系统

 

l  login 拦截器:验证请求是否有令牌,令牌是否合法()

l  令牌 ticket

括号内为增强功能

l  用户访问pis.com,拦截器发现无令牌或令牌无效,跳转至passport.com的登陆页面(防止恶意测试密码,服务器生成登陆令牌到passport.com的cookie)

l  用户输入用户名,密码,发起请求(并携带登陆令牌,合法请求)到passport.com验证,验证通过,生成令牌,返回令牌-set cookie和需要sso的所有域名.

l  ajax使用令牌发起跨域请求pis.com,lis.com传送令牌到各个业务系统,成功后重定向到pis.com/home页面

l  拦截器验证令牌合法性.不合法跳转到passport.com,合法返回页面给用户

l  用户继续访问lis.com,因为令牌已经设置完成,所以请求可以通过,无需登陆.

 

所有业务的Gateway与微服务都通过redis共享存储验证请求的合法性.实现业务系统的单点登录.

拦截器代码

单点登陆sso实现单点登陆sso实现
    @Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object arg2) throws Exception {
if(request.getServletPath().equals("/sso"))
return true;
this.getCache();
Cookie[] cookies
= request.getCookies();
boolean checkFlag = false;
if (cookies == null) {
logger.error(
"请求的cookie为空,请启用cookie");
checkFlag
= false;
}
else {

for (Cookie cookie : cookies) {
if (cookie.getName().equals(Constants.TICKET)) {
String ticket
= cookie.getValue();
checkFlag
= true;

}
}
}

if (!checkFlag) {// 验证不通过
String requestType = request.getHeader("x-requested-with");
if ("XMLHttpRequest".equals(requestType)) {
String contentType
= "application/octet-stream";
ServletOutputStream out
= response.getOutputStream();
response.setContentType(contentType);
response.setHeader(
"sessionstatus", "timeout");
out.print(
"<script>");
out.print(
"windows.location.href='" + Constants.PASSPORT_LOGIN);
out.print(
"</script>");
out.flush();
}
else {
response.sendRedirect(Constants.PASSPORT_LOGIN);
}
}
return true;
}
View Code

登陆验证

单点登陆sso实现单点登陆sso实现
@RestController
@RequestMapping(
"/login")
public class LoginContronller {

@Autowired
DictCacheHelper cache;

@Autowired
SSOHelper sso;

String[] servers
= new String[] { "my.pis.com", "my.lis.com" };

/**
* 登陆验证 ,验证通过生成tictiket,并已tictiket为key保存用户权限信息,基本信息到redis.
*
*
@param inParam
* :userId,password,returnurl(验证通过后前往的页面,如果为空返回到用户有权访问的第一个系统)
*
@return 成功返回: ticket 要跳转到的系统 需要跨域的域名列表 验证不通过抛出异常.
*
@throws Exception
*/
@RequestMapping(
"login")
@ResponseBody
public Map<String, Object> login(@RequestParam Map<String, Object> inParam
,HttpServletRequest request, HttpServletResponse response)
throws Exception {
// 此处应调用后台服务获取用户权限信息.
String userId= MapUtil.getValue("userId", inParam);
if(!userId.equals("1"))
throw new Exception(String.format("用户 %s 登陆验证失败,请输入正确的用户名密码", userId));
String ticket
= String.valueOf(System.currentTimeMillis()); // UUID.randomUUID();
List<Map<String,Object>> userFuncs= new ArrayList<>();
userFuncs.add(
new MyMap().put("funcCode", "1001").getMap());
userFuncs.add(
new MyMap().put("funcCode", "1002").getMap());
cache.setItem(
"ticket", ticket, new MyMap().put("userFuncs", userFuncs).getMap());
Cookie cookie
= new Cookie("ticket", ticket);
// cookie.setDomain(request.getParameter("server"));
cookie.setPath("/");
// cookie.setMaxAge(10000000);
response.addCookie(cookie);
return new MyMap().put("ticket", ticket).put("servers", servers).getMap();
}

}
View Code

登陆js,登陆成功后会发起跨域请求,让需要sso的域名设置正确的cookie

var passport = {
init :
function() {
$(
"#btnLogin").on(
"click",
function() {
$.ajax(
"login/login", {
data : $(
'#loginForm').serialize(),
error:
function (XMLHttpRequest, textStatus, errorThrown) {
$.messager.alert(
"操作失败",XMLHttpRequest.responseText);
},
dataType:
"json",
dataFilter:
function (json, type) {
var data = JSON.parse(json);
for (var i = 0; i < data.servers.length; i++) {
passport.crossAjax(
"http://" + data.servers[i]
+ ':81/pis/sso', {
'ticket' : data.ticket,
'server' : data.servers[i]
});
}
;
window.location.href
= "http://" + data.servers[0]
+ ':81/pis';
},
sucess :
function(data) {
alert(data);
for (var i = 0; i < data.servers.length; i++) {
passport.crossAjax(
"http://" + data.servers[i]
+ ':81/pis/sso', {
'ticket' : data.ticket,
'server' : data.servers[i]
});
}
;
window.location.href
= "http://" + data.servers[0]
+ ':81/pis';

}
});

})
},
crossAjax :
function(pageUrl, json, redirectFlag) {
$.ajax({
url : pageUrl,
data : json,
xhrFields : {
withCredentials :
true
},
crossDomain :
true,
async :
false,
timeout :
20 * 1000,

})
}
}

$(document).ready(
function() {
passport.init();
});

 

业务系统收到sso请求

    @RequestMapping(value = "/sso")
public void doSSO(HttpServletRequest request, HttpServletResponse response) throws IOException {
Cookie cookie
= new Cookie("ticket", request.getParameter("ticket"));
cookie.setDomain(request.getParameter(
"server"));
cookie.setPath(
"/");
// cookie.setMaxAge(10000000);
response.addCookie(cookie);
response.setContentType(
"text/plain");
response.addHeader(
"P3P", "CP=CAO PSA OUR");
response.getWriter().write(request.getParameter(
"server"));
}

至此已实现单点登陆,主要思路参考京东单点登陆业务逻辑.