spring boot 2.0 集成 shiro 和 pac4j cas单点登录

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

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

废话不说,代码如下:

首先是 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>2.2.1</version>
        </dependency>
        <dependency>
            <groupId>io.buji</groupId>
            <artifactId>buji-pac4j</artifactId>
            <version>3.1.0</version>
            <exclusions>
                <exclusion>
                    <artifactId>shiro-web</artifactId>
                    <groupId>org.apache.shiro</groupId>
                </exclusion>
            </exclusions>
        </dependency>
/**
 * @author gongtao
 * @version 2018-03-30 10:49
 **/
@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(false);
        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,防止重复代理和可能引起代理出错的问题
        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;
    }
}

上面是  shiro 的配置。

/**
 * @author gongtao
 * @version 2018-07-06 9:35
 **/
@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);
        casClient.setIncludeClientNameInCallbackUrl(false);
        return casClient;
    }

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

    /**
     * shiro登出处理器,销毁session及登录状态等
     */
    @Bean
    public ShiroCasLogoutHandler casLogoutHandler(){
        ShiroCasLogoutHandler casLogoutHandler = new ShiroCasLogoutHandler();
        casLogoutHandler.setDestroySession(true);
        return casLogoutHandler;
    }

}
/**
 * @author gongtao
 * @version 2018-07-06 9:50
 **/
public class ShiroCasLogoutHandler<C extends WebContext> extends DefaultCasLogoutHandler<C> {

    public ShiroCasLogoutHandler() {
        super();
    }

    public ShiroCasLogoutHandler(Store<String, Object> store) {
        super(store);
    }

    @Override
    protected void destroy(C context, SessionStore sessionStore, String channel) {
        // remove profiles
        final ShiroProfileManager manager = new ShiroProfileManager(context);
        manager.logout();//shiro登出操作
        logger.debug("destroy the user profiles");
        // and optionally the web session
        if (isDestroySession()) {
            logger.debug("destroy the whole session");
            @SuppressWarnings("unchecked") final boolean invalidated = sessionStore.destroySession(context);
            if (!invalidated) {
                logger.error("The session has not been invalidated for {} channel logout", channel);
            }
        }
    }

}
/**
 * @author gongtao
 * @version 2018-07-06 9:58
 **/
public class ShiroProvidedSessionStore extends ShiroSessionStore {

    /**存储的TrackableSession,往后要操作时用这个session操作*/
    private Session session;

    public ShiroProvidedSessionStore(Session session) {
        this.session = session;
    }
    @Override
    protected Session getSession(final boolean createSession) {
        return session;
    }
}
@Slf4j
public class ShiroSessionStore implements SessionStore<J2EContext> {

    /**
     * 获取shiro session
     * @param createSession
     * @return
     */
    protected Session getSession(final boolean createSession) {
        return SecurityUtils.getSubject().getSession(createSession);
    }

    /**
     * 获取 shiro 的 sessionid
     * @param j2EContext
     * @return
     */
    @Override
    public String getOrCreateSessionId(J2EContext j2EContext) {
//        final Session session = getSession(false);
//        if (session != null) {
//            return session.getId().toString();
//        }
//        return null;
        Session session = getSession(true);
        return session.getId().toString();
    }

    /**
     * 获取shiro session中的属性
     * @param j2EContext
     * @param key
     * @return
     */
    @Override
    public Object get(J2EContext j2EContext, String key) {
        final Session session = getSession(false);
        if (session != null) {
            return session.getAttribute(key);
        }
        return null;
    }

    /**
     *  设置session属性
     * @param j2EContext
     * @param key
     * @param value
     */
    @Override
    public void set(J2EContext j2EContext, String key, Object value) {
        final Session session = getSession(true);
        if (session != null) {
            try {
                session.setAttribute(key, value);
            } catch (final UnavailableSecurityManagerException e) {
                log.warn("Should happen just once at startup in some specific case of Shiro Spring configuration", e);
            }
        }
    }

    /**
     * 销毁session
     * @param j2EContext
     * @return
     */
    @Override
    public boolean destroySession(J2EContext j2EContext) {
        getSession(true).stop();
        return true;
    }

    /**
     * 获取shiro session并缓存用于单点登出
     * @param j2EContext
     * @return
     */
    @Override
    public Object getTrackableSession(J2EContext j2EContext) {
        return getSession(true);
    }

    /**
     * 从 getTrackableSession 中获取的session来构建SessionStore
     * @param j2EContext
     * @param trackableSession
     * @return
     */
    @Override
    public SessionStore<J2EContext> buildFromTrackableSession(J2EContext j2EContext, Object trackableSession) {
        if(trackableSession != null) {
            return new ShiroProvidedSessionStore((Session) trackableSession);
        }
        return null;
    }

    /**
     * 刷新session属性,这里暂返回false,实际应用中需实现
     * @param j2EContext
     * @return
     */
    @Override
    public boolean renewSession(J2EContext j2EContext) {
        return false;
    }
}
/**
 * @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 LinkedHashMap<String,CommonProfile> userProfilesMap = pac4jToken.getProfiles();
        final CommonProfile commonProfile = userProfilesMap.get(clientName);
        System.out.println("单点登录返回的信息" + commonProfile.toString());
        //todo 
        final Pac4jPrincipal principal = new Pac4jPrincipal(userProfilesMap, getPrincipalNameAttribute());
        final PrincipalCollection principalCollection = new SimplePrincipalCollection(principal, getName());
        return new SimpleAuthenticationInfo(principalCollection, userProfilesMap.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

猜你喜欢

转载自www.cnblogs.com/suiyueqiannian/p/9359597.html