新开的项目,果断使用  spring boot  最新版本  2.0.3 ,免得后期升级坑太多,前期把雷先排了。

由于对 shiro 比较熟,故使用 shiro 来做权限控制。同时已经存在了 cas 认证中心, shiro 官方在 1.2 中就表明已经弃用了 CasFilter ,建议使用 buji-pac4j ,故使用 pac4j 来做单点登录的控制。

废话不说,代码如下:

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>

复制代码

复制代码

<span>import</span><span> io.buji.pac4j.filter.LogoutFilter;
</span><span>import</span><span> io.buji.pac4j.filter.SecurityFilter;
</span><span>import</span><span> io.buji.pac4j.subject.Pac4jSubjectFactory;
</span><span>import</span><span> org.apache.shiro.session.mgt.SessionManager;
</span><span>import</span><span> org.apache.shiro.session.mgt.eis.MemorySessionDAO;
</span><span>import</span><span> org.apache.shiro.session.mgt.eis.SessionDAO;
</span><span>import</span><span> org.apache.shiro.spring.LifecycleBeanPostProcessor;
</span><span>import</span><span> org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
</span><span>import</span><span> org.apache.shiro.spring.web.ShiroFilterFactoryBean;
</span><span>import</span><span> org.apache.shiro.web.mgt.DefaultWebSecurityManager;
</span><span>import</span><span> org.apache.shiro.web.servlet.SimpleCookie;
</span><span>import</span><span> org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
</span><span>import</span><span> org.pac4j.core.config.Config;
</span><span>import</span><span> org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
</span><span>import</span><span> org.springframework.beans.factory.annotation.Value;
</span><span>import</span><span> org.springframework.boot.web.servlet.FilterRegistrationBean;
</span><span>import</span><span> org.springframework.context.annotation.Bean;
</span><span>import</span><span> org.springframework.context.annotation.Configuration;
</span><span>import</span><span> org.springframework.context.annotation.DependsOn;
</span><span>import</span><span> org.springframework.web.filter.DelegatingFilterProxy;<br>import org.jasig.cas.client.session.SingleSignOutFilter;
</span><span>import</span><span> javax.servlet.DispatcherType;
</span><span>import</span><span> javax.servlet.Filter;
</span><span>import</span><span> java.util.HashMap;
</span><span>import</span><span> java.util.LinkedHashMap;
</span><span>import</span><span> java.util.Map;

</span><span>/**</span><span>
 * </span><span>@author</span><span> gongtao
 * </span><span>@version</span><span> 2018-03-30 10:49
 * @update 2018-08-29 升级 pac4j 版本到 4.0.0
 *</span><span>*/</span><span>
@Configuration
</span><span>public</span> <span>class</span><span> ShiroConfig {


    </span><span>/**</span><span> 项目工程路径 </span><span>*/</span><span>
    @Value(</span>"${cas.project.url}"<span>)
    </span><span>private</span><span> String projectUrl;

    </span><span>/**</span><span> 项目cas服务路径 </span><span>*/</span><span>
    @Value(</span>"${cas.server.url}"<span>)
    </span><span>private</span><span> String casServerUrl;

    </span><span>/**</span><span> 客户端名称 </span><span>*/</span><span>
    @Value(</span>"${cas.client-name}"<span>)
    </span><span>private</span><span> String clientName;


    @Bean(</span>"securityManager"<span>)
    </span><span>public</span><span> DefaultWebSecurityManager securityManager(Pac4jSubjectFactory subjectFactory, SessionManager sessionManager, CasRealm casRealm){
        DefaultWebSecurityManager manager </span>= <span>new</span><span> DefaultWebSecurityManager();
        manager.setRealm(casRealm);
        manager.setSubjectFactory(subjectFactory);
        manager.setSessionManager(sessionManager);
        </span><span>return</span><span> manager;
    }

    @Bean
    </span><span>public</span><span> CasRealm casRealm(){
        CasRealm realm </span>= <span>new</span><span> CasRealm();
        </span><span>//</span><span> 使用自定义的realm</span>
<span>        realm.setClientName(clientName);
        realm.setCachingEnabled(</span><span>false</span><span>);
        </span><span>//</span><span>暂时不使用缓存</span>
        realm.setAuthenticationCachingEnabled(<span>false</span><span>);
        realm.setAuthorizationCachingEnabled(</span><span>false</span><span>);
        </span><span>//</span><span>realm.setAuthenticationCacheName("authenticationCache");
        </span><span>//</span><span>realm.setAuthorizationCacheName("authorizationCache");</span>
        <span>return</span><span> realm;
    }

    </span><span>/**</span><span>
     * 使用 pac4j 的 subjectFactory
     * </span><span>@return</span>
     <span>*/</span><span>
    @Bean
    </span><span>public</span><span> Pac4jSubjectFactory subjectFactory(){
        </span><span>return</span> <span>new</span><span> Pac4jSubjectFactory();
    }

    @Bean
    </span><span>public</span><span> FilterRegistrationBean filterRegistrationBean() {
        FilterRegistrationBean filterRegistration </span>= <span>new</span><span> FilterRegistrationBean();
        filterRegistration.setFilter(</span><span>new</span> DelegatingFilterProxy("shiroFilter"<span>));
        </span><span>//</span><span>  该值缺省为false,表示生命周期由SpringApplicationContext管理,设置为true则表示由ServletContainer管理</span>
        filterRegistration.addInitParameter("targetFilterLifecycle", "true"<span>);
        filterRegistration.setEnabled(</span><span>true</span><span>);
        filterRegistration.addUrlPatterns(</span>"/*"<span>);
        filterRegistration.setDispatcherTypes(DispatcherType.REQUEST, DispatcherType.FORWARD);
        </span><span>return</span><span> filterRegistration;
    }

    </span><span>/**</span><span>
     * 加载shiroFilter权限控制规则(从数据库读取然后配置)
     * </span><span>@param</span><span> shiroFilterFactoryBean
     </span><span>*/</span>
    <span>private</span> <span>void</span><span> loadShiroFilterChain(ShiroFilterFactoryBean shiroFilterFactoryBean){
        </span><span>/*</span><span>下面这些规则配置最好配置到配置文件中 </span><span>*/</span><span>
        Map</span>&lt;String, String&gt; filterChainDefinitionMap = <span>new</span> LinkedHashMap&lt;&gt;<span>();
        filterChainDefinitionMap.put(</span>"/", "securityFilter"<span>);
        filterChainDefinitionMap.put(</span>"/application/**", "securityFilter"<span>);
        filterChainDefinitionMap.put(</span>"/index", "securityFilter"<span>);
        filterChainDefinitionMap.put(</span>"/callback", "callbackFilter"<span>);
        filterChainDefinitionMap.put(</span>"/logout", "logout"<span>);
        filterChainDefinitionMap.put(</span>"/**","anon"<span>);
        </span><span>//</span><span> filterChainDefinitionMap.put("/user/edit/**", "authc,perms[user:edit]");</span>
<span>        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
    }


    </span><span>/**</span><span>
     * shiroFilter
     * </span><span>@param</span><span> securityManager
     * </span><span>@param</span><span> config
     * </span><span>@return</span>
     <span>*/</span><span>
    @Bean(</span>"shiroFilter"<span>)
    </span><span>public</span><span> ShiroFilterFactoryBean factory(DefaultWebSecurityManager securityManager, Config config) {
        ShiroFilterFactoryBean shiroFilterFactoryBean </span>= <span>new</span><span> ShiroFilterFactoryBean();
        </span><span>//</span><span> 必须设置 SecurityManager</span>
<span>        shiroFilterFactoryBean.setSecurityManager(securityManager);
        </span><span>//</span><span>shiroFilterFactoryBean.setUnauthorizedUrl("/403");
        </span><span>//</span><span> 添加casFilter到shiroFilter中</span>
<span>        loadShiroFilterChain(shiroFilterFactoryBean);
        Map</span>&lt;String, Filter&gt; filters = <span>new</span> HashMap&lt;&gt;(3<span>);
        </span><span>//</span><span>cas 资源认证拦截器</span>
        SecurityFilter securityFilter = <span>new</span><span> SecurityFilter();
        securityFilter.setConfig(config);
        securityFilter.setClients(clientName);
        filters.put(</span>"securityFilter"<span>, securityFilter);
        </span><span>//</span><span>cas 认证后回调拦截器</span>
        CallbackFilter callbackFilter = <span>new</span><span> CallbackFilter();
        callbackFilter.setConfig(config);
        callbackFilter.setDefaultUrl(projectUrl);
        filters.put(</span>"callbackFilter"<span>, callbackFilter);
        </span><span>//</span><span> 注销 拦截器</span>
        LogoutFilter logoutFilter = <span>new</span><span> LogoutFilter();
        logoutFilter.setConfig(config);
        logoutFilter.setCentralLogout(</span><span>true</span><span>);
        logoutFilter.setLocalLogout(</span><span>true</span><span>);
        logoutFilter.setDefaultUrl(projectUrl </span>+ "/callback?client_name=" +<span> clientName);
        filters.put(</span>"logout"<span>,logoutFilter);
        shiroFilterFactoryBean.setFilters(filters);
        </span><span>return</span><span> shiroFilterFactoryBean;
    }

    @Bean
    </span><span>public</span><span> SessionDAO sessionDAO(){
        </span><span>return</span> <span>new</span><span> MemorySessionDAO();
    }

    </span><span>/**</span><span>
     * 自定义cookie名称
     * </span><span>@return</span>
     <span>*/</span><span>
    @Bean
    </span><span>public</span><span> SimpleCookie sessionIdCookie(){
        SimpleCookie cookie </span>= <span>new</span> SimpleCookie("sid"<span>);
        cookie.setMaxAge(</span>-1<span>);
        cookie.setPath(</span>"/"<span>);
        cookie.setHttpOnly(</span><span>false</span><span>);
        </span><span>return</span><span> cookie;
    }

    @Bean
    </span><span>public</span><span> DefaultWebSessionManager sessionManager(SimpleCookie sessionIdCookie, SessionDAO sessionDAO){
        DefaultWebSessionManager sessionManager </span>= <span>new</span><span> DefaultWebSessionManager();
        sessionManager.setSessionIdCookie(sessionIdCookie);
        sessionManager.setSessionIdCookieEnabled(</span><span>true</span><span>);
        </span><span>//</span><span>30分钟</span>
        sessionManager.setGlobalSessionTimeout(180000<span>);
        sessionManager.setSessionDAO(sessionDAO);
        sessionManager.setDeleteInvalidSessions(</span><span>true</span><span>);
        sessionManager.setSessionValidationSchedulerEnabled(</span><span>true</span><span>);
        </span><span>return</span><span> sessionManager;
    }

    </span><span>/**</span><span>
     * 下面的代码是添加注解支持
     </span><span>*/</span><span>
    @Bean
    @DependsOn(</span>"lifecycleBeanPostProcessor"<span>)
    </span><span>public</span><span> DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
        DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator </span>= <span>new</span><span> DefaultAdvisorAutoProxyCreator();
        </span><span>//</span><span> 强制使用cglib,防止重复代理和可能引起代理出错的问题
        </span><span>//</span> <span>https://zhuanlan.zhihu.com/p/29161098</span>
        defaultAdvisorAutoProxyCreator.setProxyTargetClass(<span>true</span><span>);
        </span><span>return</span><span> defaultAdvisorAutoProxyCreator;
    }

    @Bean
    </span><span>public</span> <span>static</span><span> LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
        </span><span>return</span> <span>new</span><span> LifecycleBeanPostProcessor();
    }

    @Bean
    </span><span>public</span><span> AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(DefaultWebSecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor advisor </span>= <span>new</span><span> AuthorizationAttributeSourceAdvisor();
        advisor.setSecurityManager(securityManager);
        </span><span>return</span><span> advisor;
    }<br>  <br>  </span>
   @Bean<br>   public FilterRegistrationBean singleSignOutFilter() {<br>       FilterRegistrationBean bean = new FilterRegistrationBean();<br>       bean.setName("singleSignOutFilter");<br>       SingleSignOutFilter singleSignOutFilter = new SingleSignOutFilter();<br>       singleSignOutFilter.setCasServerUrlPrefix(casServerUrl);<br>       singleSignOutFilter.setIgnoreInitConfiguration(true);<br>       bean.setFilter(singleSignOutFilter);<br>       bean.addUrlPatterns("/*");<br>       bean.setEnabled(true);<br>    bean.setOrder(Ordered.HIGHEST_PERCEDENCE);<br>       return bean;<br>   }
<span>}</span>

复制代码

上面是  shiro 的配置。

复制代码

<span>import</span><span> io.buji.pac4j.context.ShiroSessionStore;
</span><span>import</span><span> org.pac4j.cas.config.CasConfiguration;
</span><span>import</span><span> org.pac4j.cas.config.CasProtocol;
</span><span>import</span><span> org.pac4j.core.config.Config;
</span><span>import</span><span> org.springframework.beans.factory.annotation.Value;
</span><span>import</span><span> org.springframework.context.annotation.Bean;
</span><span>import</span><span> org.springframework.context.annotation.Configuration;

</span><span>/**</span><span>
 * </span><span>@author</span><span> gongtao
 * </span><span>@version</span><span> 2018-07-06 9:35
 * @update 2018-08-29 升级 pac4j 版本到 4.0.0
 *</span><span>*/</span><span>
@Configuration
</span><span>public</span> <span>class</span><span> Pac4jConfig {

    </span><span>/**</span><span> 地址为:cas地址 </span><span>*/</span><span>
    @Value(</span>"${cas.server.url}"<span>)
    </span><span>private</span><span> String casServerUrl;

    </span><span>/**</span><span> 地址为:验证返回后的项目地址:</span><span>http://localhost</span><span>:8081 </span><span>*/</span><span>
    @Value(</span>"${cas.project.url}"<span>)
    </span><span>private</span><span> String projectUrl;

    </span><span>/**</span><span> 相当于一个标志,可以随意 </span><span>*/</span><span>
    @Value(</span>"${cas.client-name}"<span>)
    </span><span>private</span><span> String clientName;


    </span><span>/**</span><span>
     *  pac4j配置
     * </span><span>@param</span><span> casClient
     * </span><span>@param</span><span> shiroSessionStore
     * </span><span>@return</span>
     <span>*/</span><span>
    @Bean(</span>"authcConfig"<span>)
    </span><span>public</span><span> Config config(CasClient casClient, ShiroSessionStore shiroSessionStore) {
        Config config </span>= <span>new</span><span> Config(casClient);
        config.setSessionStore(shiroSessionStore);
        </span><span>return</span><span> config;
    }

    </span><span>/**</span><span>
     * 自定义存储
     * </span><span>@return</span>
     <span>*/</span><span>
    @Bean
    </span><span>public</span><span> ShiroSessionStore shiroSessionStore(){
        </span><span>return</span> <span>new</span><span> ShiroSessionStore();
    }

    </span><span>/**</span><span>
     * cas 客户端配置
     * </span><span>@param</span><span> casConfig
     * </span><span>@return</span>
     <span>*/</span><span>
    @Bean
    </span><span>public</span><span> CasClient casClient(CasConfiguration casConfig){
        CasClient casClient </span>= <span>new</span><span> CasClient(casConfig);
        </span><span>//</span><span>客户端回调地址</span>
        casClient.setCallbackUrl(projectUrl + "/callback?client_name=" +<span> clientName);
        casClient.setName(clientName);
        </span><span>return</span><span> casClient;
    }

    </span><span>/**</span><span>
     * 请求cas服务端配置
     * </span><span>@param</span><span> casLogoutHandler
     </span><span>*/</span><span>
    @Bean
    </span><span>public</span><span> CasConfiguration casConfig(){
        </span><span>final</span> CasConfiguration configuration = <span>new</span><span> CasConfiguration();
        </span><span>//</span><span>CAS server登录地址</span>
        configuration.setLoginUrl(casServerUrl + "/login"<span>);
        </span><span>//</span><span>CAS 版本,默认为 CAS30,我们使用的是 CAS20</span>
<span>        configuration.setProtocol(CasProtocol.CAS20);
        configuration.setAcceptAnyProxy(</span><span>true</span><span>);
        configuration.setPrefixUrl(casServerUrl </span>+ "/"<span>);
        </span><span>return</span><span> configuration;
    }
</span><span>

}</span>

复制代码

以上为pac4j 配置

复制代码

<span>import<span> org.pac4j.cas.config.CasConfiguration;
<span>import<span> org.pac4j.core.context.Pac4jConstants;
<span>import<span> org.pac4j.core.context.WebContext;
<span>import<span> org.pac4j.core.context.session.SessionStore;
<span>import<span> org.pac4j.core.redirect.RedirectAction;
<span>import<span> org.pac4j.core.util.CommonHelper;

<span>/**<span>
 * <span>@author<span> gongtao
 * <span>@version<span> 2018-07-06 9:41
 * @update 2018-08-29 升级 pac4j 版本到 4.0.0
 *<span>*/
<span>public <span>class CasClient <span>extends<span> org.pac4j.cas.client.CasClient {
    <span>public<span> CasClient() {
        <span>super<span>();
    }

    <span>public<span> CasClient(CasConfiguration configuration) {
        <span>super<span>(configuration);
    }

    <span>/*<span>
     * (non-Javadoc)
     * @see org.pac4j.core.client.IndirectClient#getRedirectAction(org.pac4j.core.context.WebContext)
     <span>*/<span>

    @Override
    <span>public<span> RedirectAction getRedirectAction(WebContext context) {
        <span>this<span>.init();
        <span>if<span> (getAjaxRequestResolver().isAjax(context)) {
            <span>this.logger.info("AJAX request detected -&gt; returning the appropriate action"<span>);
            RedirectAction action =<span> getRedirectActionBuilder().redirect(context);
            <span>this<span>.cleanRequestedUrl(context);
            <span>return<span> getAjaxRequestResolver().buildAjaxResponse(action.getLocation(), context);
        } <span>else<span> {
            <span>final String attemptedAuth = (String)context.getSessionStore().get(context, <span>this.getName() +<span> ATTEMPTED_AUTHENTICATION_SUFFIX);
            <span>if<span> (CommonHelper.isNotBlank(attemptedAuth)) {
                <span>this<span>.cleanAttemptedAuthentication(context);
                <span>this<span>.cleanRequestedUrl(context);
                <span>//<span>这里按自己需求处理,默认是返回了401,我在这边改为跳转到cas登录页面
                <span>//<span>throw HttpAction.unauthorized(context);
                <span>return <span>this<span>.getRedirectActionBuilder().redirect(context);
            } <span>else<span> {
                <span>return <span>this<span>.getRedirectActionBuilder().redirect(context);
            }
        }
    }

    <span>private <span>void<span> cleanRequestedUrl(WebContext context) {
        SessionStore&lt;WebContext&gt; sessionStore =<span> context.getSessionStore();
        <span>if (sessionStore.get(context, Pac4jConstants.REQUESTED_URL) != <span>null<span>) {
            sessionStore.set(context, Pac4jConstants.REQUESTED_URL, ""<span>);
        }

    }

    <span>private <span>void<span> cleanAttemptedAuthentication(WebContext context) {
        SessionStore&lt;WebContext&gt; sessionStore =<span> context.getSessionStore();
        <span>if (sessionStore.get(context, <span>this.getName() + ATTEMPTED_AUTHENTICATION_SUFFIX) != <span>null<span>) {
            sessionStore.set(context, <span>this.getName() + ATTEMPTED_AUTHENTICATION_SUFFIX, ""<span>);
        }

    }


}</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span>

复制代码

复制代码

<span>/**</span><span>
 * </span><span>@author</span><span> gongtao
 * </span><span>@version</span><span> 2018-07-05 15:30
 *</span><span>*/</span>
<span>public</span> <span>class</span> CallbackFilter <span>extends</span><span> io.buji.pac4j.filter.CallbackFilter {

    @Override
    </span><span>public</span> <span>void</span> doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) <span>throws</span><span> IOException, ServletException {
        </span><span>super</span><span>.doFilter(servletRequest, servletResponse, filterChain);
    }
}</span>

复制代码

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

复制代码

<span>/**</span><span>
 * 认证与授权
 * </span><span>@author</span><span> gongtao
 * </span><span>@version</span><span> 2018-03-30 13:55
 *</span><span>*/</span>
<span>public</span> <span>class</span> CasRealm <span>extends</span><span> Pac4jRealm {

    </span><span>private</span><span> String clientName;
    

    </span><span>/**</span><span>
     * 认证
     * </span><span>@param</span><span> authenticationToken
     * </span><span>@return</span><span>
     * </span><span>@throws</span><span> AuthenticationException
     </span><span>*/</span><span>
    @Override
    </span><span>protected</span> AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) <span>throws</span><span> AuthenticationException {
        </span><span>final</span> Pac4jToken pac4jToken =<span> (Pac4jToken) authenticationToken;
        </span><span>final</span> List&lt;CommonProfile&gt; commonProfileList =<span> pac4jToken.getProfiles();
     </span><span>final</span> CommonProfile commonProfile = commonProfileList.get(0<span>); 
        System.out.println(</span>"单点登录返回的信息" +<span> commonProfile.toString());
        </span><span>//</span><span>todo </span>
        <span>final</span> Pac4jPrincipal principal = <span>new</span><span> Pac4jPrincipal(commonProfileList, getPrincipalNameAttribute());
        </span><span>final</span> PrincipalCollection principalCollection = <span>new</span><span> SimplePrincipalCollection(principal, getName());
        </span><span>return</span> <span>new</span><span> SimpleAuthenticationInfo(principalCollection, commonProfileList.hashCode());
    }

    </span><span>/**</span><span>
     * 授权/验权(todo 后续有权限在此增加)
     * </span><span>@param</span><span> principals
     * </span><span>@return</span>
     <span>*/</span><span>
    @Override
    </span><span>protected</span><span> AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        SimpleAuthorizationInfo authInfo </span>= <span>new</span><span> SimpleAuthorizationInfo();
        authInfo.addStringPermission(</span>"user"<span>);
        </span><span>return</span><span> authInfo;
    }
}</span>

复制代码

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

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

复制代码

<span>#cas配置
cas:
  client</span>-<span>name: mfgClient
  server:
    url: http:</span><span>//</span><span>127.0.0.1:8080/cas</span>
<span>  project:
    url: http:</span><span>//</span><span>127.0.0.1:8081</span>

复制代码

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

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

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