单点登录
官方解释:单点登录(Single Sign On),简称为 SSO,是目前比较流行的企业业务整合的解决方案之一。SSO的定义是在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统。
举个通俗的例子就是:比如你登录了qq,当你又想听音乐的时候,打开qq音乐,不用再输入用户密码登录,直接就可以进入qq音乐的主页,这就是单点登录,减少了用户频繁登录各个相联系的系统。
单点登录的实现过程是什么样的呢?
shiro
shiro是什么呢?
官方解释:Apache Shiro是一个强大且易用的Java安全框架,执行身份验证、授权、密码和会话管理。使用Shiro的易于理解的API,您可以快速、轻松地获得任何应用程序,从最小的移动应用程序到最大的网络和企业应用程序。
再举一个通俗一点的例子:当你在配置了shiro框架之后,非正常登录进入主页,所有的功能都不可用。就算配置了url到主页,任何功能你也是用不了的,可以用一句俗话来解释:强扭的瓜不甜。
shiro使用的流程?
shiro下的单点登录如何实现
(具体配置如下)
1.配置web.xml。添加filter,使得系统运行时先跳转到单点登录端输入用户名和密码,再跳转到应用系统端(即我的系统)。
配置如下:
<filter>
<filter-name>CAS Single Sign Out Filter</filter-name><!-- 这个必须是第一个Filter -->
<filter-class>org.jasig.cas.client.session.SingleSignOutFilter</filter-class>
<init-param>
<param-name>casServerUrlPrefix</param-name>
<param-value>http://10.18.0.50:8086/platform-sso-server</param-value><!-- 这个是单点登录的地址 -->
</init-param>
</filter>
<listener>
<listener-class>org.jasig.cas.client.session.SingleSignOutHttpSessionListener</listener-class>
</listener>
<filter>
<filter-name>CAS Authentication Filter</filter-name>
<!--<filter-class>org.jasig.cas.client.authentication.Saml11AuthenticationFilter</filter-class>-->
<filter-class>org.jasig.cas.client.authentication.AuthenticationFilter</filter-class>
<init-param>
<param-name>casServerLoginUrl</param-name>
<param-value>http://10.18.0.50:8086/platform-sso-server/login</param-value><!-- 这个是单点登录的地址 -->
</init-param>
<init-param>
<param-name>serverName</param-name>
<param-value>http://10.18.0.152:8080/oa</param-value><!-- 这个是接入应用的地址 -->
</init-param>
<init-param>
<param-name>ignorePattern</param-name>
<param-value>^.*[.](js|css|png|gif|jpeg|bmp)$</param-value><!-- 这个是不需要单点登录过滤的地址是一个正则表达式 -->
</init-param>
</filter>
<filter>
<filter-name>CAS Validation Filter</filter-name>
<!--<filter-class>org.jasig.cas.client.validation.Saml11TicketValidationFilter</filter-class>-->
<filter-class>org.jasig.cas.client.validation.Cas30ProxyReceivingTicketValidationFilter</filter-class>
<init-param>
<param-name>casServerUrlPrefix</param-name>
<param-value>http://10.18.0.50:8086/platform-sso-server</param-value><!-- 这个是单点登录的地址,这个地址必须服务器之间互通 -->
</init-param>
<init-param>
<param-name>serverName</param-name>
<param-value>http://10.18.0.152:8080/oa</param-value><!-- 这个是接入应用地址 -->
</init-param>
<init-param>
<param-name>redirectAfterValidation</param-name>
<param-value>true</param-value>
</init-param>
<init-param>
<param-name>useSession</param-name>
<param-value>true</param-value>
</init-param>
<init-param>
<param-name>authn_method</param-name>
<param-value>mfa-duo</param-value>
</init-param>
</filter>
<filter>
<filter-name>CAS HttpServletRequest Wrapper Filter</filter-name>
<filter-class>org.jasig.cas.client.util.HttpServletRequestWrapperFilter</filter-class>
</filter>
<filter>
<filter-name>InitFilter</filter-name>
<filter-class>com.powerrich.sso.client.filter.SysInitFilter</filter-class>
<init-param>
<param-name>initSystemImpl</param-name>
<param-value>com.oa.manager.system.service.impl.InitSystemImpl</param-value><!-- 这个类必须实现com.powerrich.sso.client.init
.InitSystem这个接口 -->
</init-param>
</filter>
<filter-mapping>
<filter-name>CAS Single Sign Out Filter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>CAS Validation Filter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>CAS Authentication Filter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>CAS HttpServletRequest Wrapper Filter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>InitFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<listener>
<listener-class>com.oa.commons.listener.MyServletContextListener</listener-class>
</listener>
<listener>
<listener-class>com.oa.commons.listener.MyHttpSessionListener</listener-class>
</listener>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<filter>
<filter-name>encoding</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>encoding</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter>
<filter-name>shiroFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>shiroFilter</filter-name>
<url-pattern>*.do</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>shiroFilter</filter-name>
<url-pattern>*.mo</url-pattern>
</filter-mapping>
<filter>
<filter-name>loginFilter</filter-name>
<filter-class>com.oa.commons.filter.LoginFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>loginFilter</filter-name>
<url-pattern>*.do</url-pattern>
</filter-mapping>
<filter>
<filter-name>loginFilterAndroid</filter-name>
<filter-class>com.oa.commons.filter.LoginFilterAndroid</filter-class>
</filter>
<filter-mapping>
<filter-name>loginFilterAndroid</filter-name>
<url-pattern>*.mo</url-pattern>
</filter-mapping>
2.自己添加initSystemipml实现类,继承initSystem接口,并且重写init方法。方法里对用户名及用户的信息初始化,便于以后的验证。
单点登录端会将用户输入的用户名传入这个类里,接下来需要我们系统做的就是根据获得的用户名去自己的数据库查询出这个用户其他的信息比如部门id,用户id,并保存在session和token里。
package com.oa.manager.system.service.impl;
import com.oa.manager.system.service.ILoginService;
import com.powerrich.sso.client.init.InitSystem;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class InitSystemImpl implements InitSystem {
@Autowired
private ILoginService service;
@Override
public boolean init(HttpServletRequest request, HttpServletResponse response, String name) {
// 在此方法内部实现初始化登录用户username的功能,如读取其授权信息、在session中记录已登录用户
// 名等等.初始化成功则返回true.
//LoginServiceImpl login = new LoginServiceImpl();
//手动强转获取
System.out.println("名字==="+name);
//service.initsso(request,response);
if(StringUtils.isNotBlank(name)){
request.getSession().setAttribute("name",name);
return true;
}
return false;
}
}
3.写具体的初始化方法
public boolean initsso(HttpServletRequest request, HttpServletResponse response) {
// 在此方法内部实现初始化登录用户username的功能,如读取其授权信息、在session中记录已登录用户
// 名等等.初始化成功则返回true.
Object username = request.getSession().getAttribute("name");
System.out.println("名字是"+username);
String name=username.toString();
if(name!=null&& name!=""){
Timestamp loginTime=DateUtil.currentTimestamp();
HttpSession session=request.getSession();
System.out.println("走这里这里");
//查询数据库中是否有这个用户
SyUsers u=(SyUsers)dao.findOne("from SyUsers where userName = ?",name);
//判断用户是否存在,如果存在则直接走下边的步骤,如果不存在则调用全量接口同步用户数据。
if(u.getUserName()==null){
String str ="http://192.168.121.21:8082/usercenter-ws/services/syncAllDataService?wsdl";
HttpUtils httpUtils =HttpUtils.get(str);
ResponseWrap execute = httpUtils.execute();
System.out.println(execute.getString());
}else{
// 登录成功 保持一些用户信息到session中
Member me=new Member();//需要放入当前session 的用户信息
//放置token信息
Subject currentUser =SecurityUtils.getSubject();
UsernamePasswordToken token =new UsernamePasswordToken(u.getId(), u.getUserPassword());
//登录认证 记录登陆信息
currentUser.login(token);
me.setId(u.getId());
System.out.println("id===="+u.getId());
me.setLoginTime(loginTime);
me.setDeptId(u.getDeptId());
session.setAttribute("minfo",me); //将当前用户信息存入session中
if(name.equals(BaseConfig.getInstance().getDevName())){
session.setAttribute("dev", true);//当前登陆用户是开发者,拥有所有权限
}else{
session.setAttribute("dev", true);//之前为false
}
if(name.equals(BaseConfig.getInstance().getSaName())){
session.setAttribute("sa", true);//当前登陆用户是系统超级管理员
}else{
session.setAttribute("sa", true);
}
Map<String,OnLineUser> usersMap=ServletUtil.getOnLineUsers();
OnLineUser onmy=usersMap.get(u.getId());
if(onmy==null){
onmy=new OnLineUser();
}
onmy.setId(u.getId());
onmy.setTrueName(u.getTrueName());
onmy.setDeptId(u.getDeptId());
onmy.setSex(u.getUserSex());
Map<String,LoginInfo> loginInfos=onmy.getLoginInfos();
if(loginInfos==null){
loginInfos=new HashMap<String,LoginInfo>();
}
LoginInfo loginInfo=new LoginInfo();
loginInfo.setId(FileUtils.getUUID());
loginInfo.setLoginType(2);
loginInfo.setLoginTime(loginTime);
loginInfos.put(session.getId(),loginInfo );
onmy.setLoginInfos(loginInfos);
usersMap.put(u.getId(), onmy);//将当前用户信息放入在线用户列表
session.setAttribute("loginType", 2);//标记此session登陆方式 用于退出时使用
}
return true;
}
else{
return false;
}
}
4.验证在线用户信息
/**
* @Title UserAuthenticationFilter.java
* @date 2013-12-4 上午10:46:12
* @Copyright: 2013
*/
package com.oa.commons.security;
import java.util.Map;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import org.apache.shiro.subject.Subject;
import com.oa.commons.model.LoginInfo;
import com.oa.commons.model.OnLineUser;
import com.oa.commons.util.ServletUtil;
/**
* 验证用户是否在线
* 用户已验证通过,并且在全局在线用户列表,否定登陆超时
* @author LiuJincheng
* @version 1.0
*/
public class UserAuthenticationFilter extends org.apache.shiro.web.filter.authc.UserFilter{
@Override
protected boolean isAccessAllowed(ServletRequest request,ServletResponse response, Object mappedValue) {
HttpServletRequest req=(HttpServletRequest)request;
System.out.println("验证登陆信息=="+req.getRequestURI());
System.out.println("sessionId=="+req.getSession().getId());
if (isLoginRequest(request, response)) {
System.out.println("走进if");
return true;
} else {
Subject subject = getSubject(request, response);
// 验证是否有登陆用户
if(subject.getPrincipal() != null&&subject.isAuthenticated()){
//判断用户是否在全局在线列表
String userId=(String)subject.getPrincipal();//获取用户id
Map<String,OnLineUser> usersMap =ServletUtil.getOnLineUsers();
OnLineUser om=usersMap.get(userId);//获取在线人员列表
//判断当前用户是否在在线人员列表里面,如果不在,不能继续访问
if(om==null){
System.out.println("om===="+om);
return false;//之前是false
}else{
Map<String,LoginInfo> loginInfos=om.getLoginInfos();
if(loginInfos.containsKey(((HttpServletRequest)request).getSession().getId())){
return true;
}else{
return false;//之前是false
}
}
}else{
return false;//之前是false
}
}
}
}
5.shiro验证并授权
/**
* @Title ShiroAuthRealm.java
* @date 2013-11-2 下午3:52:21
* @Copyright: 2013
*/
package com.oa.commons.security;
import java.util.Collection;
import java.util.Map;
import com.oa.commons.base.IBaseDao;
import com.oa.manager.system.bean.SyUsers;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.springframework.beans.factory.annotation.Autowired;
import com.oa.manager.system.service.IUserService;
/**
*
* @author LiuJincheng
* @version 1.0
*/
public class ShiroAuthRealm extends AuthorizingRealm{
@Autowired
private IUserService userService;
@Autowired
protected IBaseDao dao;
/**
* 认证回调函数,登录时调用.
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authcToken) throws AuthenticationException {
UsernamePasswordToken token = (UsernamePasswordToken) authcToken;
System.out.println("登录认证"+getName());
//验证通过返回一组身份信息
return new SimpleAuthenticationInfo(token.getUsername(), new String(token.getPassword()), getName());
}
//
/**
* 授权查询回调函数, 进行鉴权但缓存中无用户的授权信息时调用.
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
System.out.println("开始授权查询");
String userId=(String)principals.getPrimaryPrincipal();
Map<String,Collection<String>> map=userService.selectRolesPowers(userId);
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
info.addRoles(map.get("roleIds"));
info.addStringPermissions(map.get("powers"));
return info;
//
}
}
(运行时,4,5方法会交替走,可详细看方法)
总结
总的来说shiro下的单点登录还是只有单点登录只是验证的复杂度不同罢了,还是要先走通登录的流程,看一步步的怎么走,才能根据逻辑具体的修改代码喔!