系统中两种单点登录实现方式:
1、 直接登录单点登录认证系统。
2、 登录业务系统,如用户此时为首次登录,则跳转得到单点登录认证界面。如用户已通过认证,则直接登录到业务系统。
【补充说明】
单点登录认证服务器地址:http://192.168.61.124:8080/SSOAuth_v1.0.0/
业务系统服务器地址:http://192.168.61.134:8080/sepmis_v1.3.0/
直接登录单点登录认证系统流程:
1、 打开单点登录认证服务器登录页面,输入用户名密码。
2、 如果通过认证,此时页面跳转到系统主界面main.jsp。这个时候在加载main.jsp页面同时,页面后端通过ajax中jsonp方式,向单点登录认证服务器后台Action发送hello请求。需要传递参数,当前用户代码。
main.jsp页面核心代码如下:
<%
UserSession userSession = (UserSession)session.getAttribute(AppConst.USER_SESSION_ID);
String userCode = "";
String userName = "";
if(null != userSession){
userCode = userSession.getUserCode();
userName = userSession.getUserType();
}
%>
<script type="text/javascript">
$.ajax({
type: "get",
contentType: "application/json",
url: "http://192.168.61.124:8080/SSOAuth_v1.0.0/hello.do?usercode=<%=userCode%>&callback=?",
crossDomain: true,
success: function(data) {
$.each(data.msg, function () {
$.getJSON(this);
});
}
});
</script>
3、 后台接受hello请求,通过传递的参数用户代码,在SysRealuser表中判断用户所能够访问到的业务系统对象。拿到业务系统对象中url参数,组织各个业务系统setCookie Url。然后通过json异步传回前台。
Url格式例如:(红色字体处为变量)
http://192.168.61.134:8080/sepmis_v1.3.0/setCookie.do?userCode=xxx&&callback=?
代码如下:
/**
* 当用户成功通过单点登录系统认证后,用户进入系统操作界面,同时页面异步发送此action请求
* 获取创建json对象,对象内容为页面使用ajax异步访问各个业务系统的 setcookie action方法
* @return result
*/
public String hello(){
try {
List<SysInfo> tmp = getUserSysRealSys();
UserSession userSession = AuthorityUtil.getSysUserSession();
JSONObject jsonObject = new JSONObject();
JSONArray jsonArray = new JSONArray();
if(tmp!=null && tmp.size()>0){
for(int i=0; i<tmp.size(); i++){
jsonArray.put(tmp.get(i).getSysUrl() + "/setCookie.do?userCode="+userSession.getUserCode()+"&callback=?");
}
}
jsonObject.put("msg", jsonArray);
response.setContentType("application/json");
response.setCharacterEncoding("UTF-8");
response.setHeader("Cache-Control", "no-cache");
response.getWriter().write(jsonObject.toString());
response.getWriter().flush();
//释放资源
tmp = null;
userSession = null;
//打印日志
String info = LoggerUtil.getInfoMsg("AJAX异步访问,用户权限范围内各业务系统的 setCookie方法");
log.info(info);
return null;
} catch (IOException ie) {
this.exceptionMessage.setError(ie.toString());
this.exceptionMessage.setClassName(this.getClass().getName());
this.exceptionMessage.setMessage("AJAX异步访问出现IO异常!");
log.error(ie.toString());
return ERROR;
} catch (Exception ex) {
this.exceptionMessage.setError(ex.toString());
this.exceptionMessage.setClassName(this.getClass().getName());
this.exceptionMessage.setMessage("AJAX异步访问出现异常!");
log.error(ex.toString());
return ERROR;
}
}
4、 前台main.jsp页面这时拿到传回的URL参数,此时执行Success代码段中的函数
success: function(data) {
$.each(data.msg, function () {
$.getJSON(this);
});
此时function中参数data就是传回的URL参数,然后遍历执行Jquery中getJSON方法。此时通过AJAX技术异步又向各个业务系统发送setCookie请求,此操作可以视为一种透明的登录操作,用户是不能够感知到。
5、 业务系统接受setCookie action请求。
代码如下:
public String setCookie(){
try{
// - 查找用户岗位集合
StringBuilder hql = new StringBuilder(300);
userCode = request.getParameter("userCode");
hql.append("FROM SysUser WHERE userCode ='").append(userCode).append("'");
List<SysUser> sysUserList = sysUserService.findByHql(hql.toString());
if(sysUserList.size() > 0){
Cookie cookie = new Cookie("sso", userCode);
cookie.setPath("/");
//设置cookie的有效期,单位是秒;
//如果不使用这个方法或者参数为负数的话,当浏览器关闭的时候,这个cookie就失效了
//cookie一年内有效60*60*24*365
cookie.setMaxAge(60*60);
response.addCookie(cookie);
}
}catch(Exception ex){
//返回异常信息
this.exceptionMessage.setError(ex.toString());
this.exceptionMessage.setClassName(this.getClass().getName());
this.exceptionMessage.setMessage("用户登录出现异常!");
log.info(ex.toString());
return ERROR;
}
return null;
}
业务系统通过setCookie方法,将此用户在业务系统中Cookie存储到用户本地。
6、 输入业务系统连接。
业务系统同后台通过filter过滤所有请求,获取用户在当前服务器域下Cookie值,进行校验,通过校验进入系统,未通过校验返回单点登录认证页面。
SSOFilter.java核心代码如下
/**
* servlet filter 过滤方法
* @param ServletRequest
* @param ServletResponse
* @param FilterChain
* @throws IOException,ServletException
*/
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
// 用户本地cookie值
String cookieValue = "";
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
String url = request.getRequestURL().toString();
// 返回结果
String result = null;
// 根据上下文根截取URL
String[] urlArray = url.split(request.getContextPath());
//如果是业务service地址,跳转到业务系统单点登录action
if (urlArray.length == 2 && ("/").equals(urlArray[1])) {
response.sendRedirect(url+"loginSSO.do");
return;
}else {
for(int i=0; i<urlArray.length; i++){
if("/setCookie.do".equals(urlArray[i])){
result = "setCookie";
break;
}
}
}
// 检查HTTP请求的head是否有需要的cookie
Cookie[] diskCookies = request.getCookies();
if (diskCookies != null) {
log.info("【权限系统-用户认证过滤器】找到COOKIES");
for (int i = 0; i < diskCookies.length; i++) {
cookieValue = diskCookies[i].getValue();
log.info("【权限系统-用户认证过滤器】COOKIES中找到用户标识:" + diskCookies[i].getName() + "=" + cookieValue);
if (SSO_AUTH.equals(diskCookies[i].getName())) {
result = SSOService(cookieValue);
if ("success".equals(result)) {
log.info("【通过单点登录服务器认证】");
} else {
log.info("【未通过单点登录服务器认证】COOKIES认证失败");
response.sendRedirect(ssoLoginPage);
}
} else if ("sso".equals(diskCookies[i].getName())) {
HttpSession session = request.getSession();
session.setAttribute("sso", cookieValue);
result = "success";
}
}
} else {
log.info("【权限系统-用户认证过滤器】未找到COOKIES");
}
// COOKIES效验,转到单点登录认证页面
if (result.equals("failed")) {
log.info("【权限系统-用户认证过滤器】COOKIES认证失败");
response.sendRedirect(ssoLoginPage);
// 效验成功并退出服务
} else if (result.equals("logout")) {
log.info("【权限系统-用户认证过滤器】退出登录");
//logoutService(cookieValue);
response.sendRedirect(ssoLoginPage);
// 效验成功
} else {
log.info("【权限系统-用户认证过滤器】认证成功");
Throwable problem = null;
try {
chain.doFilter(req, res);
} catch (Throwable t) {
problem = t;
t.printStackTrace();
}
if (problem != null) {
if (problem instanceof ServletException) {
throw ((ServletException) problem);
}
if (problem instanceof IOException) {
throw ((IOException) problem);
}
sendProcessingError(problem, res);
}
}
}
/**
* 使用httpclient调用身份认证服务(SSOAuth)校验cookie
*
* @param cookievalue
* @return String
* @throws IOException
*/
private String SSOService(String cookievalue) throws IOException {
String result = "failed";
String authAction = "/sso/authcookie.do?cookieName=";
String tmp = this.ssoServiceURL + authAction + cookievalue;
// 通过http协议来调用身份认证服务(SSOAuth)使用httpclient实现
HttpClient httpclient = new HttpClient();
GetMethod httpget = new GetMethod(tmp);
httpget.getParams().setParameter(HttpMethodParams.RETRY_HANDLER, new DefaultHttpMethodRetryHandler());
try {
// 执行getMethod
int statusCode = httpclient.executeMethod(httpget);
if (statusCode != HttpStatus.SC_OK) {
log.error("HttpClient Method failed: " + httpget.getStatusLine());
}
// 获取返回内容
result = httpget.getResponseHeader("result").getValue();
} catch (HttpException e) {
// 发生致命的异常,可能是协议不对或者返回的内容有问题
log.error("Please check your HttpClient address!");
log.error(e.toString());
} catch (IOException e) {
// 发生网络异常
log.error(e.toString());
} finally {
httpget.releaseConnection();
}
log.info("【权限系统-用户认证过滤器】校验返回result=" + result);
return result;
}
执行通过业务系统fileter将,根据不同的请求进行不同的操作。获取业务系统Cookie后同构使用HttpClient类方法调用单点认证中的authcookie方法,验证用户Cookie的合法性。
RealSysLoginAction.java类authcookie方法代码:
/**
* 验证用户本地cookie值,如果通过验证则登录到业务系统总,如果没有通过验证,
* 则跳转到单点登录系统认证页面。
* @return result
*/
public String authcookie(){
try{
String info = null;
SysUser tmpUser = sysUserService.getSysUserById(getCookieName());
if( tmpUser == null){
response.setHeader("result", "failed");
info = LoggerUtil.getInfoMsg("未通过验证用户本地cookie值。");
}else{
response.setHeader("result", "success");
info = LoggerUtil.getInfoMsg("通过验证用户本地cookie值。");
}
//释放资源
tmpUser = null;
//打印日志
log.info(info);
return null;
}catch(Exception ex){
this.exceptionMessage.setError(ex.toString());
this.exceptionMessage.setClassName(this.getClass().getName());
this.exceptionMessage.setMessage("登录业务系统出现异常!");
log.error(ex.toString());
return ERROR;
}
}
此种登录方法要修改原有业务系统中的登录action方法。
业务系统LoginSSOAction.java类,loginSSO登录方法
public String loginSSO(){
try {
//获取认证系统传过来的用户名密码
String sysUserCode = ActionContext.getContext().getSession().get("sso").toString();
log.info("【权限系统】获取用户登录信息:[sysUserCode="+sysUserCode+"]");
SysUser sysUser_ = this.loginService.findByPk(sysUserCode);
//判断输入用户名是否正确
if (sysUser_ != null) {
//判断输入密码是否正确
// - 查找用户岗位集合
StringBuilder hql = new StringBuilder(300);
hql.append("SELECT sg.sysRole FROM SysUserGroup su, SysGroupRole sg WHERE su.id.userCode = '").append(sysUserCode)
.append("' AND su.id.groupCode = sg.id.groupCode AND sg.sysRole.roleType = '").append(AppConst.ROLE_TYPE_01).append("'");
List<SysRole> rolesList = this.sysRoleService.findSysRoleList(hql.toString());
// - 将功能性的岗位添加至集合,并排除不同岗位中的相同功能
// - 查找用户所有功能集合
Map<String, SysRole> funcPowersMap = new HashMap<String, SysRole>();
for (SysRole role : rolesList) {
funcPowersMap.put(role.getRoleCode(), role);
}
// - 组织UserSession
UserSession userSession = new UserSession();
userSession.setUserCode(sysUserCode);
userSession.setUserName(sysUser_.getUserName());
userSession.setComCode(sysUser_.getSysCompany().getComCode());
userSession.setComCname(sysUser_.getSysCompany().getComCname());
userSession.setAreaCode(sysUser_.getSysCompany().getSysArea().getAreaCode());
userSession.setComType(sysUser_.getSysCompany().getComType());
userSession.setComLevel(sysUser_.getSysCompany().getComLevel());
userSession.setFuncPowers(funcPowersMap);
//用户信息放入session
ActionContext.getContext().getSession().put(AppConst.USER_SESSION_ID, userSession);
//释放map引用
funcPowersMap = null;
//打印日志
StringBuffer infoBuffer = new StringBuffer();
infoBuffer.append("【用户登录】 机构代码: ");
infoBuffer.append(userSession.getComCode());
infoBuffer.append(" 用户代码: ");
infoBuffer.append(userSession.getUserCode());
infoBuffer.append(" (");
infoBuffer.append(userSession.getUserName());
infoBuffer.append(") 登录时间: ");
infoBuffer.append(new DateTime(DateTime.current(), DateTime.YEAR_TO_SECOND));
infoBuffer.append(" 登录成功!");
log.info(infoBuffer.toString());
return SUCCESS;
} else {
this.exceptionMessage.setClassName(this.getClass().getName());
this.exceptionMessage.setMessage("密码不正确!");
log.info("密码不正确!");
return ERROR;
}
} catch (Exception e) {
//返回异常信息
this.exceptionMessage.setError(e.toString());
this.exceptionMessage.setClassName(this.getClass().getName());
this.exceptionMessage.setMessage("用户登录出现异常!");
log.info(e.toString());
return ERROR;
}
}
业务系统总filter 在web.xml文件中配置方法:
<!-- 实现单点登录Fileter start-->
<filter>
<filter-name>SSOFilter</filter-name>
<filter-class>SSO.SSOFilter</filter-class>
<init-param>
<param-name>cookieName</param-name>
<param-value>hzpCookies</param-value>
</init-param>
<init-param>
<param-name>ssoServiceURL</param-name>
<param-value>http://192.168.61.124:8080/SSOAuth_v1.0.0/</param-value>
</init-param>
<init-param>
<param-name>ssoLoginPage</param-name>
<param-value>http://192.168.61.124:8080/SSOAuth_v1.0.0/login.jsp</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>SSOFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- 实现单点登录Fileter end-->
未通过认证,登录业务系统,跳转到单点登录认证。
1、 输入业务系统服务地址
2、 系统自动跳转到单点登录服务器,下边过程同上“直接登录单点登录认证系统流程”
其原理是通过filter实现类中dofilter方法进行过滤,判断用没有Cookie,则进行跳转到单点登录认证界面。
过滤代码如下:
// 检查HTTP请求的head是否有需要的cookie
Cookie[] diskCookies = request.getCookies();
if (diskCookies != null) {
log.info("【权限系统-用户认证过滤器】找到COOKIES");
for (int i = 0; i < diskCookies.length; i++) {
cookieValue = diskCookies[i].getValue();
log.info("【权限系统-用户认证过滤器】COOKIES中找到用户标识:" + diskCookies[i].getName() + "=" + cookieValue);
if (SSO_AUTH.equals(diskCookies[i].getName())) {
result = SSOService(cookieValue);
if ("success".equals(result)) {
log.info("【通过单点登录服务器认证】");
} else {
log.info("【未通过单点登录服务器认证】COOKIES认证失败");
response.sendRedirect(ssoLoginPage);
}
} else if ("sso".equals(diskCookies[i].getName())) {
HttpSession session = request.getSession();
session.setAttribute("sso", cookieValue);
result = "success";
}
}
} else {
log.info("【权限系统-用户认证过滤器】未找到COOKIES");
}
// COOKIES效验,转到单点登录认证页面
if (result.equals("failed")) {
log.info("【权限系统-用户认证过滤器】COOKIES认证失败");
response.sendRedirect(ssoLoginPage);
// 效验成功并退出服务
} else if (result.equals("logout")) {
log.info("【权限系统-用户认证过滤器】退出登录");
//logoutService(cookieValue);
response.sendRedirect(ssoLoginPage);
// 效验成功
} else {
log.info("【权限系统-用户认证过滤器】认证成功");
Throwable problem = null;
try {
chain.doFilter(req, res);
} catch (Throwable t) {
problem = t;
t.printStackTrace();
}
if (problem != null) {
if (problem instanceof ServletException) {
throw ((ServletException) problem);
}
if (problem instanceof IOException) {
throw ((IOException) problem);
}
sendProcessingError(problem, res);
}
}