shiro框架下实现cas单点登录

单点登录

官方解释:单点登录(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下的单点登录还是只有单点登录只是验证的复杂度不同罢了,还是要先走通登录的流程,看一步步的怎么走,才能根据逻辑具体的修改代码喔!

# sso-shiro-cas spring下使用shiro+cas配置单点登录,多个系统之间的访问,每次只需要登录一次 ## 系统模块说明 1. cas单点登录模块,这里直接拿的是cas的项目改了点样式而已 2. doc: 文档目录,里面有数据库生成语句,采用的是MySQL5.0,数据库名为db_test 3. spring-node-1: 应用1 4. spring-node-2: 应用2 其中node1跟node2都是采用spring + springMVC + mybatis 框架,使用maven做项目管理 ## cas集成说明 1.首先采用的是查数据库的方式来校验用户身份的,在cas/WEB-INF/deployerConfigContext.xml中第135行构建了这个类型 ``` xml ``` 其中QueryDatabaseAuthenticationHandler这个类是自定义构建的,在cas/WEB-INF/lib/cas-jdbc-1.0.0.jar里面,有兴趣的同学可以发编译看下,关于几个属性的说明 1. dataSource: 数据源,配置MySQL的连接信息 2. passwordEncoder: 加密方式,这里用的是MD5 3. sql: sql查询语句,这个语句就是根据用户输入的账号查询其密码 #### 以上就是单点登录管理的主要配置 ## 应用系统的配置node1 1. 应用系统采用shiro做权限控制,并且跟cas集成 2. 在/spring-node-1/src/main/resources/conf/shiro.properties 文件中 ``` properties shiro.loginUrl=http://127.0.0.1:8080/cas/login?service=http://127.0.0.1:8081/node1/shiro-cas shiro.logoutUrl=http://127.0.0.1:8080/cas/logout?service=http://127.0.0.1:8081/node1/shiro-cas shiro.cas.serverUrlPrefix=http://127.0.0.1:8080/cas shiro.cas.service=http://127.0.0.1:8081/node1/shiro-cas shiro.failureUrl=/users/loginSuccess shiro.successUrl=/users/loginSuccess ``` 其中shiro.loginUrl 跟 shiro.logoutUrl的前面是cas验证的地址,后面的是我们应用系统的地址,这样配置的方式是为了在访问我们的应用系统的时候,先到cas进行验证,如果验证成功了,cas将重定向到shiro.successUrl 所表示的地址 3.在/spring-node-1/src/main/resources/conf/shiro.xml 文件中 ``` xml /shiro-cas = casFilter /logout = logoutFilter /users/** = user ``` > 其中shiroFilter这个类主要用于需要拦截的url请求,需要注意的是这个是shiro的拦截,我们还需要配置cas的过滤配置casFilter > casRealm这个类是需要我们自己实现的,主要用于shiro的权限验证,里面的属性说明如下 1. defaultRoles: 默认的角色 2. casServerUrlPrefix: cas地址 3. casService: 系统应用地址 最后我们还需要在/spring-node-1/src/main/webapp/WEB-INF/web.xml 文件中配置相关的过滤器拦截全部请求 ``` xml shiroFilter org.springframework.web.filter.DelegatingFilterProxy targetFilterLifecycle true shiroFilter /* ``` ## 系统运行 1. 端口说明,cas:8080,node1:8081,node2:8082,大家可以采用maven提供的tomcat7插件,配置如下: ``` xml org.apache.tomcat.maven tomcat7-maven-plugin 2.1 8081 UTF-8 tomcat7 /node1 ``` 这样的配置,我们甚至都不需要配置tomcat服务器了,建议这种方式 2.各个模块的访问地址 > cas:http://127.0.0.1:8080/cas > node1:http://127.0.0.1:8081/node1 > node2:http://127.0.0.1:8082/node2 3.访问系统 > 输入 http://127.0.0.1:8081/node1/shiro-cas ,进入cas验证 > 输入用户名 admin,密码 admin@2015,验证成功后将会重定向到http://127.0.0.1:8081/node1//users/loginSuccess ,也就是node1系统的主页,里面的节点2代表的是node2系统的主页,你会发现我们不需要登录到node2系统就能访问其中的系统了
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

草莓味少女vv

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值