shiro添加CAS单点登录

目的

已有一个通过shiro控制的系统,现在希望系统接入CAS单点登录。

  1. 单点登录使用CAS认证协议,CAS服务端已经存在,该系统作为CAS客户端接入
  2. 仍然保留原系统登陆页面,只有当通过一个特定单点登录链接访问时,才走单点登录流程
    • 这里有点像很多网站都提供的通过XXX登录的按钮。所不同的是,我这里不在页面展现,只是一个URL
  3. 如果单点登录的用户在系统中不存在,则直接跳转到系统默认的登陆页面。

修改分析

其实shiro有默认的CAS接入包,提供了CAS接入的默认实现,只想用默认的实现,参考之篇文章就够了(地址在这),不过这里我的需求和默认实现有些不同。

主要体现在

  1. 登录链接,默认的CasFilter只处理CAS ticket回调。我这里即处理ticket回调,同时也判断如果请求不带ticket,则跳转到单点登录页。
  2. 登录失败梳理,默认的CasFilter会判断该用户之前是否已经登录,我这里只要失败,一律跳转到默认登录页
  3. 默认CasRealm是通过一些属性生成最终的AuthenticationToken,由于我这里的系统之前对token格式进行了改造,所以我需要自己扩展CasRealm填充Authentication和Authorization信息。

共三个地方进行修改:

  1. 新增一个CAS的Filter扩展自自带的CasFilter,拦截处理单点登录地址的请求
  2. 新增一个CAS的Realm扩展自自带的CasRealm,用来解析返回的ticket,生成自定义的Authentication和Authorization信息
  3. 修改配置文件,添加新增的Filter和Realm。

修改代码

Filter

public class CustomCasFilter extends CasFilter {

    private Logger logger = LoggerFactory
            .getLogger(CustomCasFilter.class);

    private String casServerLoginUrl;

    // the url where the application is redirected if the CAS service ticket validation failed (example : /mycontextpatch/cas_error.jsp)
    private String failureUrl;

    /**
     * Custom session key used for other purpose
     */
    public static String CUSTOM_SESSION_KEY = "custom_session_key";

    @Override
    public boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        String ticket = httpRequest.getParameter("ticket");
        logger.info("CAS login with ticket: {}.", ticket);

        if (StringUtils.isEmpty(ticket)) {
            logger.info("SSO ticket not provided, redirect to CAS server.");
            //redirect to SSO server if ticket not provided
            WebUtils.issueRedirect(request, response, casServerLoginUrl);
            return false;
        }

        return super.onPreHandle(request, response, mappedValue);
    }

    @Override
    protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException ae, ServletRequest request, ServletResponse response) {
        //Go to login page if CAS not verified

        Subject subject = getSubject(request, response);
        if (subject.isAuthenticated()) {
            subject.logout();
        }
        try {
            WebUtils.issueRedirect(request, response, failureUrl);
        } catch (IOException e) {
            logger.error("Cannot redirect to failure url : {}", failureUrl, e);
        }
        return false;
    }

    @Override
    protected boolean onLoginSuccess(AuthenticationToken token,
                                     Subject subject, ServletRequest req, ServletResponse resp)
            throws Exception {
        //use custom key to store user info in session
        UserMessage user = UserUtils.getCurrentUserMessage();
        Session session = SecurityUtils.getSubject().getSession();
        String sessionValue = user.getId()+"_"+user.getUsername();
        session.setAttribute(CUSTOM_SESSION_KEY,sessionValue);
        logger.info("User [{}}] login success!", sessionValue);

        HttpServletRequest request = (HttpServletRequest) req;
        HttpServletResponse response = (HttpServletResponse) resp;
        WebUtils.issueRedirect(request, response, getSuccessUrl());
        return false;
    }

    public String getCasServerLoginUrl() {
        return casServerLoginUrl;
    }

    public void setCasServerLoginUrl(String casServerLoginUrl) {
        this.casServerLoginUrl = casServerLoginUrl;
    }

    public void setFailureUrl(String failureUrl) {
        this.failureUrl = failureUrl;
    }
}

Realm

public class CustomCasRealm extends CasRealm {

    private Logger log = LoggerFactory.getLogger(CustomCasRealm.class);

    @Resource
    protected IAccountService accountService;

    @Override
    protected void onInit() {
        super.onInit();
        //SSO doesn't need to check password
        this.setCredentialsMatcher(new AllowAllCredentialsMatcher());
    }


    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(
            PrincipalCollection principals) {
        //UserMessage here is a customized principal object
        UserMessage user = (UserMessage) principals.getPrimaryPrincipal();
        Integer userId = user.getId();
        String userType = user.getTypeOfUser();
        SimpleAuthorizationInfo auth = new SimpleAuthorizationInfo();
        if (userId != null) {
            //get and add authorization infos
            //auth.addRole(xxxxxxxxxxxxxx);
            //auth.addStringPermission(xxxxxxxx);
        }
        return auth;
    }

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        //Call super method to retrieve CAS user
        AuthenticationInfo info = super.doGetAuthenticationInfo(token);
        log.info("CAS realm login");
        if (null == info) {
            return null;
        }
        PrincipalCollection principals = info.getPrincipals();
        //CasRealm store username as principal
        String username = principals.getPrimaryPrincipal().toString();
        log.info("CAS realm login account:{}", username);

        //add custom data
        CasToken authToken = (CasToken) token;
        SysAccount account = accountService.getByUsername(username, typeValue); 
        if (null == account) {
            return null;
        }
        SimpleAuthenticationInfo simpleAuthenticationInfo = usersLogin(account);
        return simpleAuthenticationInfo;

    }


    private SimpleAuthenticationInfo usersLogin(Object user) {
        UserMessage commonUser = new UserMessage();
        //get data from db and add to this `UserMessage` class;
        return new SimpleAuthenticationInfo(commonUser, commonUser.getPassword(),
                    commonUser.getSalt(), getName());
    }

}

shiro配置修改

省略掉了一些不重要的配置,主要的修改点做了注释,共有5处。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	   xmlns:context="http://www.springframework.org/schema/context" xmlns:jdbc="http://www.springframework.org/schema/jdbc"
	   xmlns:jee="http://www.springframework.org/schema/jee" xmlns:tx="http://www.springframework.org/schema/tx"
	   xmlns:util="http://www.springframework.org/schema/util" xmlns:task="http://www.springframework.org/schema/task" xsi:schemaLocation="
		http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
		http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd
		http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc-4.0.xsd
		http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-4.0.xsd
		http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd
		http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.0.xsd
		http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-4.0.xsd"
	   default-lazy-init="true">
	<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
		<property name="securityManager" ref="securityManager" />
		<property name="loginUrl" value="/admin/login" />
		<property name="successUrl" value="/admin/sys/index" />
		<property name="filters">
			<util:map>
				<entry key="authc" value-ref="authcFilter" />
				<entry key="user" value-ref="userFilter" />
				<entry key="logout" value-ref="logoutFilter" />

	                        <!-- 修改点1:增加CAS的Filter注册到shiro -->
				<entry key="cas" value-ref="casFilter" />
			</util:map>
		</property>
		<property name="filterChainDefinitions">
			<value>
				/ = anon
	                        <!-- 修改点2:增加CAS Filter对应的URL -->
				/admin/cas = cas
				/admin/login = authc
				/statics/* = anon
				/admin/logout = logout
				/admin/**/* = authc,user
			</value>
		</property>
	</bean>
	<!-- Shiro Filter -->
	<bean id="authcFilter" class="com.my.portal.web.sys.shiro.SysAuthenticationFilter">
	</bean>
	<bean id="userFilter" class="com.my.portal.web.sys.shiro.SysUserFilter"/>
	<bean id="logoutFilter" class="com.my.portal.web.sys.shiro.SysLogoutFilter"/>
	<!-- 修改点3:注册casFilter对应的实现 -->
	<bean id="casFilter" class="com.my.portal.web.sys.shiro.cas.CustomCasFilter">
		<property name="casServerLoginUrl" value="http://单点登录服务地址/cas/login?service=http://127.0.0.1:8080/portal-web/admin/cas"/>
		<property name="failureUrl" value="/admin/login.jsp"/>
	</bean>

	<!-- 修改点4:注册casRealm对应实现 -->
	<bean id="casRealm" class="com.my.portal.web.sys.shiro.cas.CustomCasRealm">
		<property name="casServerUrlPrefix" value="http://单点登录服务地址/cas"/>
		<property name="casService" value="http://127.0.0.1:8080/portal-web/admin/cas"/>
	</bean>

	<!-- Shiro's main business-tier object for web-enabled applications -->
	<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
		<property name="realm" ref="shiroDbRealm" />
	        <!-- 修改点5:增加casRealm注册到SecurityManager中,SecurityManager支持单个Realm或者多个Realm,多个需要用realms -->
		<property name="realms">
			<list>
				<ref bean="shiroDbRealm" />
				<ref bean="casRealm" />
			</list>
		</property>
		<property name="sessionManager" ref="sessionManager"></property>
		<property name="cacheManager" ref="shiroEhcacheManager" />
	</bean>


</beans>

posted @ 2020-11-02 10:32  mosakashaka  阅读(1423)  评论(0编辑  收藏  举报