zoukankan      html  css  js  c++  java
  • 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>
    

  • 相关阅读:
    jQuery中的promise实例
    你可能不需要单页面应用
    单页面和多页面应用场景总结
    ES6的模块暴露与模块引入
    export default 和 export的区别
    Android中获取网页表单中的数据实现思路及代码
    Pojo和JavaBean的区别(转载)
    MyEclipse默认编码为GBK,修改为UTF8的方法
    JSP中getParameter和getAttribute区别
    内部跳转(请求转发)和外部跳转(重定向)的区别?
  • 原文地址:https://www.cnblogs.com/mosakashaka/p/13913212.html
Copyright © 2011-2022 走看看