spring boot + mybatis + shiro + cas 单点登录

该项目是在shiro(三)的基础上进行修改的,建议首先将shiro的部分了解一下。

CAS的知识点:

单点登录:简称SSO,SSO使得在多个应用系统中,用户只需要登录一次就可以访问所有互相信任的应用系统

CAS框架:CAS是实现单点登录的框架。

结构:cas分为两个部分,CAS SERVER 和 CAS Client 需要独立部署,主要负责对用户的认证工作,Cas Client 负责对客户端保护资源的请求,需要登录的时候,重定向到CAS Server

盗用一张图:(深深的吸引了我)

执行流程:

1:Web Browser去访问CAS Client 的时候,cas客户端会向Web Browser索要session和令牌,如果没有则执行第二步

2:当发现令牌和session的时候,会重定向到cas服务端(重点),在重定向的时候,地址需要按照一定的格式:

    登录:认证中心的地址加"/login"+"service="+你本地的项目发布的地址+你shiro的地址(filterUrlPattern)

    原因:service前面的地址是认证中心的地址+"/login",后面的是认证完成之后,需要跳会的地址,在调回的地址中需要设置

successUrl 和 failureUrl,登录成功和失败的路径。

    登出:认证中心的地址加"/logout"+"service="+shiro的地址(你本地项目的地址)

3:如果认证中心发现当前还没有登录,此时会跳转到登录页面,进行登录

4:登录成功之后,认证中心会生成一个Ticket  ,就是唯一的票据

   格式:http://a:8080/?ticket=ST-XXXX-XXX

5:然后客户端会再去向服务器端去认证票据,

6:认证成功之后返回用户信息,认证完成之后,会建立一个全局的session

登出的流程:

1)、客户端向客户端发送登出Logout请求。 
2)、客户端取消本地会话,同时通知认证中心,用户已登出。 
3)、客户端返回客户端登出请求。 
4)、认证中心通知所有用户登录访问的应用,用户已登出。

引用一张图:图片地址,https://blog.csdn.net/javaloveiphone/article/details/52439613?utm_source=blogxgwz0

   写的小的案例:

shiro在1.2版本的时候就整合了cas,在使用cas的时候,就是将CasFilter添加到shiro的Filter中

一些常用常量

  //    logger
    private static final Logger logger = LoggerFactory.getLogger(MyShiroCasConfig.class);

    //    cas 的server地址
    public static final String casServerUrlPrefix = "http://127.0.0.1";
    //    cas 登录页面的地址
    public static final String casLoginUrl = casServerUrlPrefix + "/login";
    //    cas 登出页面地址
    public static final String casLogoutUrl = casServerUrlPrefix + "/logout";
    //    当前功臣对外提供的服务地址
    public static final String shiroServerUrlPrefix = "http://127.0.1.28:8080";
    //    casFilter cas 拦截的地址
     public static final String casFilterUrlPattern = "/shiro-cas";
    //    登录的地址
    public static final String loginUrl = casLoginUrl + "?service=" + shiroServerUrlPrefix + casFilterUrlPattern;
    //    退出的地址
    public static final String logoutUrl = casLogoutUrl + "?service=" + casLogoutUrl;
    //    登录成功的地址
    public static final String loginSuccessUrl = "/index";
    //    失败的地址
    public static final String unauthorizedUrl = "/403";

设置 SecurityManager --- shiro的核心处理器

   @Bean
    public SecurityManager securityManager() {
        DefaultWebSecurityManager defaultWebSecurityManager =
                new DefaultWebSecurityManager();
//        才采用缓存
        defaultWebSecurityManager.setCacheManager(getEhCacheManager());
//        指定shiro
        defaultWebSecurityManager.setRealm(myShiroCasRealm());
//        指定subjectFactory,如果实现的cas的remember me(免登录) 的功能,
        defaultWebSecurityManager.setSubjectFactory(new CasSubjectFactory());
        return defaultWebSecurityManager;
    }

该类是shiro 的核心处理类,将Realm和用户的数据进行数据的比较,new CasSubjectFactory()的作用是如果要使用面登录的功能,需要添加该类。

设置Ehcache缓存的Bean

    /**
     * 【配置的ehcache进行数据的缓存
     *
     * @return
     */
    @Bean
    public EhCacheManager getEhCacheManager() {
        EhCacheManager ehCacheManager = new EhCacheManager();
        ehCacheManager.setCacheManagerConfigFile("classpath:ehcache-shiro.xml");
        return ehCacheManager;
    }

Ehcache的配置

<ehcache
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"
        updateCheck="false">
    <diskStore path="d:/ehcache"/>
    <defaultCache
            maxElementsInMemory="20000"
            eternal="false"
            timeToIdleSeconds="120"
            timeToLiveSeconds="120"
            overflowToDisk="true"
            maxElementsOnDisk="10000000"
            diskPersistent="false"
            diskExpiryThreadIntervalSeconds="120"
            memoryStoreEvictionPolicy="LRU"
    />
    <cache name="eternalCache"
           maxElementsInMemory="20000"
           eternal="true"
           overflowToDisk="true"
           diskPersistent="false"
           timeToLiveSeconds="0"
           diskExpiryThreadIntervalSeconds="120"
    />
</ehcache>

设置自定义的CasRealm的类

    @Bean
    public MyShiroCasRealm myShiroCasRealm() {
        MyShiroCasRealm myShiroCasRealm = new MyShiroCasRealm();
//        设置cas登录服务器地址的前缀
        myShiroCasRealm.setCasServerUrlPrefix(MyShiroCasConfig.casServerUrlPrefix);
//        客户端回调地址,登录成功后的跳转的地址(自己的服务器)
        myShiroCasRealm.setCasService(MyShiroCasConfig.shiroServerUrlPrefix + MyShiroCasConfig.casFilterUrlPattern);
        return myShiroCasRealm;
    }
setCasServerUrlPrefix:表示的你访问的认证中心的地址 ,setCasService:表示当你认真中心认证完成之后需要访问的service地址

MyShiroCasRealm的类

package com.sgqing.demo.config;

import com.sgqing.demo.entity.SysPermission;
import com.sgqing.demo.entity.SysRole;
import com.sgqing.demo.entity.UserInfo;
import com.sgqing.demo.sevice.UserInfoService;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.cas.CasRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;

/**
 * shiro集成cas之后,进行的配置
 */
public class MyShiroCasRealm extends CasRealm {
    @Autowired
    private UserInfoService userInfoService;
    private static final Logger logger = LoggerFactory.getLogger(MyShiroCasRealm.class);

    /**
     * 用于授权
     *
     * @param principalCollection
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        System.out.println("进行授权 -------->");
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        String name = (String) super.getAvailablePrincipal(principalCollection);
        UserInfo byUsername = userInfoService.findByUsername(name);

        for (SysRole role : byUsername.getRoleList()) {
            info.addRole(role.getRole());
            for (SysPermission permission : role.getPermissions()) {
                info.addStringPermission(permission.getPermission());
            }
        }
        return info;
    }

    /**
     * 用于认证
     *
     * @param authenticationToken
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
//        调用父类的认证,父类认证已经完成了
        AuthenticationInfo authenticationInfo = super.doGetAuthenticationInfo(authenticationToken);
        String name = (String) authenticationInfo.getPrincipals().getPrimaryPrincipal();
        SecurityUtils.getSubject().getSession().setAttribute("no", name);

        return authenticationInfo;
    }
}

上面的类作用:直接继承CasRealm类,然后CasRealm已经完成了数据的认证工作,我们直接调用父类的功能即可

设置单点退出的监听器

 // 设置单点登出的监听器
    /*
     *SingleSignOutFilter需要配置在所有Filter之前,当Cas Client通过Cas Server登录成功,
     * Cas Server会携带生成的Service Ticket回调Cas Client,
     * 此时SingleSignOutFilter会将Service Ticket与当前的Session绑定在一起。
     * 当Cas Server在进行logout后回调Cas Client应用时也会携带该Service Ticket,
     * 此时Cas Client配置的SingleSignOutFilter将会使对应的Session失效,进而达到登出的目的。
     * 
     *  SingleSignOutHttpSessionListener用于在Cas Client应用中的Session过期时将其从对应的映射关系中移除。
     * */
    @Bean
    @Order(Ordered.HIGHEST_PRECEDENCE)
    public ServletListenerRegistrationBean servletListenerRegistrationBean() {
        ServletListenerRegistrationBean bean = new ServletListenerRegistrationBean();
        bean.setListener(new SingleSignOutHttpSessionListener());
        bean.setEnabled(true);
        return bean;
    }

作用是将所有的过期的session将其从对应的映射关系中移除

设置单点退出的拦截器

  //    设置单点登出的拦截器

//    FilterRegistrationBean 表示的是设置拦截器的优先级

    /*SingleSignOutFilter需要配置在所有Filter之前,当Cas Client通过Cas Server登录成功,
     * Cas Server会携带生成的Service Ticket回调Cas Client,
     * 此时SingleSignOutFilter会将Service Ticket与当前的Session绑定在一起。
     * 当Cas Server在进行logout后回调Cas Client应用时也会携带该Service Ticket,
     * 此时Cas Client配置的SingleSignOutFilter将会使对应的Session失效,进而达到登出的目的。
     * */
    @Bean
    public FilterRegistrationBean registrationBean() {
        FilterRegistrationBean registrationBean = new FilterRegistrationBean();
        registrationBean.setName("registrationBean");
        registrationBean.setFilter(new SingleSignOutFilter());
        registrationBean.addUrlPatterns("/*");//拦截所有的请求
        registrationBean.setEnabled(true);
        registrationBean.setOrder(10);//设置优先级
        return registrationBean;
    }

上面的作用是说,在登录的时候,客户端会去服务端进行认证,此时认证成功之后,服务端会将地址和ST返回给客户端,而在此时该拦截器会将session跟ST绑定在一起,如果访问退出的时候,此时服务端也会将服务地址和ST返回,此时的监听器会将所有的session全部变为失效。

设置配置的触发的地方;

 //    注册DelegatingFilterProxy(shiro)    是一个代理类,用于管理拦截器的生命周期,  所有的请求都会拦截
    /*
     *   先在filter中加入DelegatingFilterProxy类,"targetFilterLifecycle"指明作用于filter的所有生命周期。
     *   原理是,DelegatingFilterProxy类是一个代理类,所有的请求都会首先发到这个filter代理,然后再按照"filter-name"委派到spring中的这个bean。
     *   在Spring中配置的bean的name要和web.xml中的<filter-name>一样.
        此外,spring bean实现了Filter接口,但默认情况下,是由spring容器来管理其生命周期的(不是由tomcat这种服务器容器来管理)。
        如果设置"targetFilterLifecycle"为True,则spring来管理Filter.init()和Filter.destroy();若为false,则这两个方法失效!!

    该步只是将当前的的生命周期交给了spring管理,具体的管理还是需要下面的LifecycleBeanPostProcessor的对象去进行操作


 bean.addUrlPatterns("/*"); :表示的是拦截所有的请求
     * */

    @Bean
    public FilterRegistrationBean filterRegistrationBean() {
        FilterRegistrationBean bean = new FilterRegistrationBean();
        bean.setFilter(new DelegatingFilterProxy("shiroFilter")); //设置的shiro的拦截器 ShiroFilterFactoryBean
        bean.addInitParameter("targetFilterLifecycle", "true");
        bean.setEnabled(true);
        bean.addUrlPatterns("/*");
        return bean;
    }

作用:用于设置shiro的拦截器,和将每一个拦截器的生命周期交给spring去管理

上面设置了声明周期,下面进行设置生命周期的自动化:

  //    设置方法的自动初始化和销毁,init和destory方法被自动调用。注意,如果使用了该类,则不需要手动初始化方法和销毁方法,否则出错
    @Bean(name = "lifecycleBeanPostProcessor")
    public LifecycleBeanPostProcessor getLifecycleBeanPostProcessor() {
        return new LifecycleBeanPostProcessor();
    }

开启注解声明

 //    开启shiro aop 的注解支持,使用代理的方式,所以需要开启代码的支持
    @Bean
    public DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator() {
        DefaultAdvisorAutoProxyCreator proxyCreator = new DefaultAdvisorAutoProxyCreator();
//        设置代理方式,true是cglib的代理方式,false是普通的jdk代理方式
        proxyCreator.setProxyTargetClass(true);
        return proxyCreator;
    }

    //    开启注解
    @Bean
    public AuthorizationAttributeSourceAdvisor attributeSourceAdvisor(SecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
        advisor.setSecurityManager(securityManager);
        return advisor;
    }

设置shiro的拦截器工厂类

//  使用工厂模式,创建并初始化ShiroFilter

    @Bean(name = "shiroFilter")
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(SecurityManager securityManager, CasFilter casFilter) {
        ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();
        factoryBean.setSecurityManager(securityManager);
//        如果不设置,会自动寻找目录下的/login.jsp页面
        factoryBean.setLoginUrl(loginUrl);
//        设置无权限访问页面
        factoryBean.setUnauthorizedUrl(unauthorizedUrl);
//        添加casFilter中,注意,casFilter需要放到shiroFilter的前面
        Map<String, Filter> linkedHashMap = new LinkedHashMap<>();
        linkedHashMap.put("casFilter", casFilter);

        factoryBean.setFilters(linkedHashMap);
        loadShiroFilterChain(factoryBean);
        return factoryBean;
    }

说明:loginUrl表示的是:当没有登录的时候,客户端回去访问服务端,请求的参数格式是:认证中心的地址+"/login"+"service="+"当前项目的地址"+"shiro定义的返回的地址"

在设置拦截器的时候,需要先执行cas的拦截器,再执行shiro的拦截器

设置CasFilter

    //    定义cas的拦截器
    @Bean(name = "casFilter")
    public CasFilter getCasFilter() {
        CasFilter filter = new CasFilter();
//        自动注入拦截器的名称
        filter.setName("casFilter");
//        是否自动的将当前的拦截器进行注入
        filter.setEnabled(true);
//        在登录失败之后,也就是shiro执行CasRealm的doGetAuthenticationInfo  方法向CasServer验证tiker
        filter.setFailureUrl(loginUrl);//认证失败之后,重新登录
        filter.setLoginUrl(loginUrl);
        return filter;
    }

对shiroFilter所需要的拦截器

   /**
     * 加载shiroFilter权限控制规则(从数据库读取然后配置),角色/权限信息由MyShiroCasRealm对象提供doGetAuthorizationInfo实现获取来的
     * 生产中会将这部分规则放到数据库中
     *
     * @param shiroFilterFactoryBean
     */

    private void loadShiroFilterChain(ShiroFilterFactoryBean shiroFilterFactoryBean) {
        Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();

        filterChainDefinitionMap.put(casFilterUrlPattern, "casFilter");

        //2.不拦截的请求
        filterChainDefinitionMap.put("/css/**", "anon");
        filterChainDefinitionMap.put("/js/**", "anon");
        filterChainDefinitionMap.put("/login", "anon");
        // 此处将logout页面设置为anon,而不是logout,因为logout被单点处理,而不需要再被shiro的logoutFilter进行拦截
        filterChainDefinitionMap.put("/logout", "anon");
        filterChainDefinitionMap.put("/error", "anon");
        //3.拦截的请求(从本地数据库获取或者从casserver获取(webservice,http等远程方式),看你的角色权限配置在哪里)
        filterChainDefinitionMap.put("/user", "authc"); //需要登录

        //4.登录过的不拦截
        filterChainDefinitionMap.put("/**", "authc");

        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
    }

上面是根据Shiro和Cas整合之后操作的代码。 

说一下,执行的流程:

 首先在启动的时候会对所有的请求进行拦截 DelegatingFilterProxy,指定的是casFilter和shiroFilter两个拦截器,并同时将生命周期交给spring去管理,当然只是交给还不可以,需要设置一个@Bena,LifecycleBeanPostProcessor这个类用于管理声明周期,

在shiroFilter中首先设置的是 securityManager,在securityManager设置 EhCacheManager 缓存,在缓存中就只是读取缓存的一些配置 -> 同时设置自定义的CasRealm类,在CasRealm类中需要指定远程认证的地址和认证成功之后返回的地址,CasRealm类中去继承CasRealm类,然后覆盖认证和授权这两个类 ,(当设置完成登录的地址的时候,同时设置登出的拦截器(FilterRegistrationBean中有SingleSignOutFilter对象,该对象用于清除过时的session,不过该拦截器要优先于所有的拦截器进行执行)和监听器(ServletListenerRegistrationBean-->监听器中有SingleSignOutHttpSessionListener对象,用于清除session的映射(所谓的映射就是cas客户端和访问端)))- -> 并同时设置参数,修改登录的页面,需要修改成客户端去访问服务端的地址,如果不修改,会自动的去访问当前项目下面的login页面,就不会走cas认证中心的地址,还修改当没有权限访问的时候,请求的路径,同时还设置casFilter拦截器(该拦截器中设置的是如果认证失败之后跳转的位置(一般是登录的页面)和登录的页面)同时设置之前设置的shiro拦截器的一些设置(主要是对一些地址进行权限的设置),设置完成之后,再开启shiro的注解

猜你喜欢

转载自blog.csdn.net/weixin_38297879/article/details/83301997