zoukankan      html  css  js  c++  java
  • SpringBoot-技术专区-集成 shiro 和 pac4j cas单点登录

      2018-08-29更新:由于pac4j 3.1 版本未支持单点登出,故升级到 4.0.0 版本,pac4j-cas 升级到 3.0.2版本,可以实现单点登出。

    首先是 maven 配置。

       <dependency>
                <groupId>org.apache.shiro</groupId>
                <artifactId>shiro-spring</artifactId>
                <version>1.4.0</version>
            </dependency>
    
            <dependency>
                <groupId>org.pac4j</groupId>
                <artifactId>pac4j-cas</artifactId>
                <version>3.0.2</version>
            </dependency>
            <dependency>
                <groupId>io.buji</groupId>
                <artifactId>buji-pac4j</artifactId>
                <version>4.0.0</version>
                <exclusions>
                    <exclusion>
                        <artifactId>shiro-web</artifactId>
                        <groupId>org.apache.shiro</groupId>
                    </exclusion>
                </exclusions>
       </dependency>
    import io.buji.pac4j.filter.LogoutFilter;
    import io.buji.pac4j.filter.SecurityFilter;
    import io.buji.pac4j.subject.Pac4jSubjectFactory;
    import org.apache.shiro.session.mgt.SessionManager;
    import org.apache.shiro.session.mgt.eis.MemorySessionDAO;
    import org.apache.shiro.session.mgt.eis.SessionDAO;
    import org.apache.shiro.spring.LifecycleBeanPostProcessor;
    import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
    import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
    import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
    import org.apache.shiro.web.servlet.SimpleCookie;
    import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
    import org.pac4j.core.config.Config;
    import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.boot.web.servlet.FilterRegistrationBean;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.context.annotation.DependsOn;
    import org.springframework.web.filter.DelegatingFilterProxy;
    import org.jasig.cas.client.session.SingleSignOutFilter; import javax.servlet.DispatcherType; import javax.servlet.Filter; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.Map; /** * @author gongtao * @version 2018-03-30 10:49 * @update 2018-08-29 升级 pac4j 版本到 4.0.0 **/ @Configuration public class ShiroConfig { /** 项目工程路径 */ @Value("${cas.project.url}") private String projectUrl; /** 项目cas服务路径 */ @Value("${cas.server.url}") private String casServerUrl; /** 客户端名称 */ @Value("${cas.client-name}") private String clientName; @Bean("securityManager") public DefaultWebSecurityManager securityManager(Pac4jSubjectFactory subjectFactory, SessionManager sessionManager, CasRealm casRealm){ DefaultWebSecurityManager manager = new DefaultWebSecurityManager(); manager.setRealm(casRealm); manager.setSubjectFactory(subjectFactory); manager.setSessionManager(sessionManager); return manager; } @Bean public CasRealm casRealm(){ CasRealm realm = new CasRealm(); // 使用自定义的realm realm.setClientName(clientName); realm.setCachingEnabled(false); //暂时不使用缓存 realm.setAuthenticationCachingEnabled(false); realm.setAuthorizationCachingEnabled(false); //realm.setAuthenticationCacheName("authenticationCache"); //realm.setAuthorizationCacheName("authorizationCache"); return realm; } /** * 使用 pac4j 的 subjectFactory * @return */ @Bean public Pac4jSubjectFactory subjectFactory(){ return new Pac4jSubjectFactory(); } @Bean public FilterRegistrationBean filterRegistrationBean() { FilterRegistrationBean filterRegistration = new FilterRegistrationBean(); filterRegistration.setFilter(new DelegatingFilterProxy("shiroFilter")); // 该值缺省为false,表示生命周期由SpringApplicationContext管理,设置为true则表示由ServletContainer管理 filterRegistration.addInitParameter("targetFilterLifecycle", "true"); filterRegistration.setEnabled(true); filterRegistration.addUrlPatterns("/*"); filterRegistration.setDispatcherTypes(DispatcherType.REQUEST, DispatcherType.FORWARD); return filterRegistration; } /** * 加载shiroFilter权限控制规则(从数据库读取然后配置) * @param shiroFilterFactoryBean */ private void loadShiroFilterChain(ShiroFilterFactoryBean shiroFilterFactoryBean){ /*下面这些规则配置最好配置到配置文件中 */ Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>(); filterChainDefinitionMap.put("/", "securityFilter"); filterChainDefinitionMap.put("/application/**", "securityFilter"); filterChainDefinitionMap.put("/index", "securityFilter"); filterChainDefinitionMap.put("/callback", "callbackFilter"); filterChainDefinitionMap.put("/logout", "logout"); filterChainDefinitionMap.put("/**","anon"); // filterChainDefinitionMap.put("/user/edit/**", "authc,perms[user:edit]"); shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap); } /** * shiroFilter * @param securityManager * @param config * @return */ @Bean("shiroFilter") public ShiroFilterFactoryBean factory(DefaultWebSecurityManager securityManager, Config config) { ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); // 必须设置 SecurityManager shiroFilterFactoryBean.setSecurityManager(securityManager); //shiroFilterFactoryBean.setUnauthorizedUrl("/403"); // 添加casFilter到shiroFilter中 loadShiroFilterChain(shiroFilterFactoryBean); Map<String, Filter> filters = new HashMap<>(3); //cas 资源认证拦截器 SecurityFilter securityFilter = new SecurityFilter(); securityFilter.setConfig(config); securityFilter.setClients(clientName); filters.put("securityFilter", securityFilter); //cas 认证后回调拦截器 CallbackFilter callbackFilter = new CallbackFilter(); callbackFilter.setConfig(config); callbackFilter.setDefaultUrl(projectUrl); filters.put("callbackFilter", callbackFilter); // 注销 拦截器 LogoutFilter logoutFilter = new LogoutFilter(); logoutFilter.setConfig(config); logoutFilter.setCentralLogout(true); logoutFilter.setLocalLogout(true); logoutFilter.setDefaultUrl(projectUrl + "/callback?client_name=" + clientName); filters.put("logout",logoutFilter); shiroFilterFactoryBean.setFilters(filters); return shiroFilterFactoryBean; } @Bean public SessionDAO sessionDAO(){ return new MemorySessionDAO(); } /** * 自定义cookie名称 * @return */ @Bean public SimpleCookie sessionIdCookie(){ SimpleCookie cookie = new SimpleCookie("sid"); cookie.setMaxAge(-1); cookie.setPath("/"); cookie.setHttpOnly(false); return cookie; } @Bean public DefaultWebSessionManager sessionManager(SimpleCookie sessionIdCookie, SessionDAO sessionDAO){ DefaultWebSessionManager sessionManager = new DefaultWebSessionManager(); sessionManager.setSessionIdCookie(sessionIdCookie); sessionManager.setSessionIdCookieEnabled(true); //30分钟 sessionManager.setGlobalSessionTimeout(180000); sessionManager.setSessionDAO(sessionDAO); sessionManager.setDeleteInvalidSessions(true); sessionManager.setSessionValidationSchedulerEnabled(true); return sessionManager; } /** * 下面的代码是添加注解支持 */ @Bean @DependsOn("lifecycleBeanPostProcessor") public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() { DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator(); // 强制使用cglib,防止重复代理和可能引起代理出错的问题 // https://zhuanlan.zhihu.com/p/29161098 defaultAdvisorAutoProxyCreator.setProxyTargetClass(true); return defaultAdvisorAutoProxyCreator; } @Bean public static LifecycleBeanPostProcessor lifecycleBeanPostProcessor() { return new LifecycleBeanPostProcessor(); } @Bean public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(DefaultWebSecurityManager securityManager) { AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor(); advisor.setSecurityManager(securityManager); return advisor; }
      
      
       @Bean
    public FilterRegistrationBean singleSignOutFilter() {
    FilterRegistrationBean bean = new FilterRegistrationBean();
    bean.setName("singleSignOutFilter");
    SingleSignOutFilter singleSignOutFilter = new SingleSignOutFilter();
    singleSignOutFilter.setCasServerUrlPrefix(casServerUrl);
    singleSignOutFilter.setIgnoreInitConfiguration(true);
    bean.setFilter(singleSignOutFilter);
    bean.addUrlPatterns("/*");
    bean.setEnabled(true);
        bean.setOrder(Ordered.HIGHEST_PERCEDENCE);
    return bean;
    }
    }

    上面是  shiro 的配置。

    import io.buji.pac4j.context.ShiroSessionStore;
    import org.pac4j.cas.config.CasConfiguration;
    import org.pac4j.cas.config.CasProtocol;
    import org.pac4j.core.config.Config;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    
    /**
     * @author gongtao
     * @version 2018-07-06 9:35
     * @update 2018-08-29 升级 pac4j 版本到 4.0.0
     **/
    @Configuration
    public class Pac4jConfig {
    
        /** 地址为:cas地址 */
        @Value("${cas.server.url}")
        private String casServerUrl;
    
        /** 地址为:验证返回后的项目地址:http://localhost:8081 */
        @Value("${cas.project.url}")
        private String projectUrl;
    
        /** 相当于一个标志,可以随意 */
        @Value("${cas.client-name}")
        private String clientName;
    
    
        /**
         *  pac4j配置
         * @param casClient
         * @param shiroSessionStore
         * @return
         */
        @Bean("authcConfig")
        public Config config(CasClient casClient, ShiroSessionStore shiroSessionStore) {
            Config config = new Config(casClient);
            config.setSessionStore(shiroSessionStore);
            return config;
        }
    
        /**
         * 自定义存储
         * @return
         */
        @Bean
        public ShiroSessionStore shiroSessionStore(){
            return new ShiroSessionStore();
        }
    
        /**
         * cas 客户端配置
         * @param casConfig
         * @return
         */
        @Bean
        public CasClient casClient(CasConfiguration casConfig){
            CasClient casClient = new CasClient(casConfig);
            //客户端回调地址
            casClient.setCallbackUrl(projectUrl + "/callback?client_name=" + clientName);
            casClient.setName(clientName);
            return casClient;
        }
    
        /**
         * 请求cas服务端配置
         * @param casLogoutHandler
         */
        @Bean
        public CasConfiguration casConfig(){
            final CasConfiguration configuration = new CasConfiguration();
            //CAS server登录地址
            configuration.setLoginUrl(casServerUrl + "/login");
            //CAS 版本,默认为 CAS30,我们使用的是 CAS20
            configuration.setProtocol(CasProtocol.CAS20);
            configuration.setAcceptAnyProxy(true);
            configuration.setPrefixUrl(casServerUrl + "/");
            return configuration;
        }
    
    
    }

    以上为pac4j 配置

    import org.pac4j.cas.config.CasConfiguration;
    import org.pac4j.core.context.Pac4jConstants;
    import org.pac4j.core.context.WebContext;
    import org.pac4j.core.context.session.SessionStore;
    import org.pac4j.core.redirect.RedirectAction;
    import org.pac4j.core.util.CommonHelper;
    
    /**
     * @author gongtao
     * @version 2018-07-06 9:41
     * @update 2018-08-29 升级 pac4j 版本到 4.0.0
     **/
    public class CasClient extends org.pac4j.cas.client.CasClient {
        public CasClient() {
            super();
        }
    
        public CasClient(CasConfiguration configuration) {
            super(configuration);
        }
    
        /*
         * (non-Javadoc)
         * @see org.pac4j.core.client.IndirectClient#getRedirectAction(org.pac4j.core.context.WebContext)
         */
    
        @Override
        public RedirectAction getRedirectAction(WebContext context) {
            this.init();
            if (getAjaxRequestResolver().isAjax(context)) {
                this.logger.info("AJAX request detected -> returning the appropriate action");
                RedirectAction action = getRedirectActionBuilder().redirect(context);
                this.cleanRequestedUrl(context);
                return getAjaxRequestResolver().buildAjaxResponse(action.getLocation(), context);
            } else {
                final String attemptedAuth = (String)context.getSessionStore().get(context, this.getName() + ATTEMPTED_AUTHENTICATION_SUFFIX);
                if (CommonHelper.isNotBlank(attemptedAuth)) {
                    this.cleanAttemptedAuthentication(context);
                    this.cleanRequestedUrl(context);
                    //这里按自己需求处理,默认是返回了401,我在这边改为跳转到cas登录页面
                    //throw HttpAction.unauthorized(context);
                    return this.getRedirectActionBuilder().redirect(context);
                } else {
                    return this.getRedirectActionBuilder().redirect(context);
                }
            }
        }
    
        private void cleanRequestedUrl(WebContext context) {
            SessionStore<WebContext> sessionStore = context.getSessionStore();
            if (sessionStore.get(context, Pac4jConstants.REQUESTED_URL) != null) {
                sessionStore.set(context, Pac4jConstants.REQUESTED_URL, "");
            }
    
        }
    
        private void cleanAttemptedAuthentication(WebContext context) {
            SessionStore<WebContext> sessionStore = context.getSessionStore();
            if (sessionStore.get(context, this.getName() + ATTEMPTED_AUTHENTICATION_SUFFIX) != null) {
                sessionStore.set(context, this.getName() + ATTEMPTED_AUTHENTICATION_SUFFIX, "");
            }
    
        }
    
    
    }
    复制代码
    复制代码
    /**
     * @author gongtao
     * @version 2018-07-05 15:30
     **/
    public class CallbackFilter extends io.buji.pac4j.filter.CallbackFilter {
    
        @Override
        public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
            super.doFilter(servletRequest, servletResponse, filterChain);
        }
    }
    复制代码

     CallbackFilter 是单点登录后回调使用的过滤器。

    复制代码
    /**
     * 认证与授权
     * @author gongtao
     * @version 2018-03-30 13:55
     **/
    public class CasRealm extends Pac4jRealm {
    
        private String clientName;
        
    
        /**
         * 认证
         * @param authenticationToken
         * @return
         * @throws AuthenticationException
         */
        @Override
        protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
            final Pac4jToken pac4jToken = (Pac4jToken) authenticationToken;
            final List<CommonProfile> commonProfileList = pac4jToken.getProfiles();
         final CommonProfile commonProfile = commonProfileList.get(0); 
            System.out.println("单点登录返回的信息" + commonProfile.toString());
            //todo 
            final Pac4jPrincipal principal = new Pac4jPrincipal(commonProfileList, getPrincipalNameAttribute());
            final PrincipalCollection principalCollection = new SimplePrincipalCollection(principal, getName());
            return new SimpleAuthenticationInfo(principalCollection, commonProfileList.hashCode());
        }
    
        /**
         * 授权/验权(todo 后续有权限在此增加)
         * @param principals
         * @return
         */
        @Override
        protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
            SimpleAuthorizationInfo authInfo = new SimpleAuthorizationInfo();
            authInfo.addStringPermission("user");
            return authInfo;
        }
    }
    复制代码

     CasRealm 这个就是和之前  shiro  的 CasRealm  一样了。

    最后就是  application.yml 的配置了。

    复制代码
    #cas配置
    cas:
      client-name: mfgClient
      server:
        url: http://127.0.0.1:8080/cas
      project:
        url: http://127.0.0.1:8081
    复制代码

    参考: https://blog.csdn.net/hxm_code/article/details/79226456

    参考: https://github.com/bujiio/buji-pac4j

    参考:https://github.com/pac4j/pac4j

  • 相关阅读:
    AjaxControlToolKit(整理)三.......(35个控件)简单介绍
    C#多线程学习
    《深入浅出WPF》视频列表
    【Redis】Redis功能及性能
    MySQL性能优化
    【Redis】Redis常用命令
    php项目相关资源
    JAVA基础知识总结:十一
    JAVA基础知识总结:九
    JAVA基础知识总结:十
  • 原文地址:https://www.cnblogs.com/liboware/p/12550754.html
Copyright © 2011-2022 走看看