美文网首页javaWeb学习权限管理spring boot
springboot 整合shiro + cas 实现单点登录权

springboot 整合shiro + cas 实现单点登录权

作者: 程序猿TT | 来源:发表于2019-07-18 20:49 被阅读243次

    简介

    • 本项目将单点登录和权限认证结合在一起,实现了用户登录与授权的基础框架,后续可以很好的在此框架上进行二次开发
    • 在原理和配置上也有很多不清楚的地方,希望大家留言讨论~
    • 后续将会进行spring cloud系列工程的搭建,并将shiro-cas尝试接入spring cloud中
    • spring cloud系列直达地址 spring cloud

    源码地址


    准备:cas服务器搭建


    单点登录流程

    1. 客户端请求目标服务器
    2. 目标服务器重定向到cas服务器
    3. cas服务器进行验证,通过则请求目标服务器,将ticket传给目标服务器
    4. 目标服务器根据ticket,请求cas服务器,获取用户登录信息
    5. cas服务器返回验证消息给目标服务器

    项目实现

    1、导入依赖包

            <!--Apache Shiro -->
            <dependency>
                <groupId>org.apache.shiro</groupId>
                <artifactId>shiro-spring</artifactId>
                <version>${shiro.version}</version>
            </dependency>
            <dependency>
                <groupId>org.apache.shiro</groupId>
                <artifactId>shiro-cas</artifactId>
                <version>${shiro.version}</version>
            </dependency>
    

    2、 编辑配置文件

    • 以下为shiro-cas相关部分
    # shiro - cas 配置
    shiro:
      # 在访问cas服务器登录之后,会返回一个ticket。由该地址接收
      casFilterUrlPattern: /shiro-cas
      # cas服务前缀
      casServerUrlPrefix: http://127.0.0.1:8181/cas
      # shiro服务前缀
      shiroServerUrlPrefix: http://127.0.0.1:${server.port}${server.servlet.context-path}
      # 登录地址
      loginUrl: ${shiro.casServerUrlPrefix}/login?service=${shiro.shiroServerUrlPrefix}${shiro.casFilterUrlPattern}
      # 登出地址
      logoutUrl: ${shiro.casServerUrlPrefix}/logout?service=${shiro.shiroServerUrlPrefix}${shiro.casFilterUrlPattern}
    
    • 以下为全量配置
    # Tomcat
    server:
      tomcat:
        uri-encoding: UTF-8
        max-threads: 1000
        min-spare-threads: 30
      port: 40301
      connection-timeout: 5000ms
      servlet:
        context-path: /shiro
        session:
          cookie:
            http-only: true
    
    spring:
      application:
        name: service-auth
    
    # shiro - cas 配置
    shiro:
      # 在访问cas服务器登录之后,会返回一个ticket。由该地址接收
      casFilterUrlPattern: /shiro-cas
      # cas服务前缀
      casServerUrlPrefix: http://127.0.0.1:8181/cas
      # shiro服务前缀
      shiroServerUrlPrefix: http://127.0.0.1:${server.port}${server.servlet.context-path}
      # 登录地址
      loginUrl: ${shiro.casServerUrlPrefix}/login?service=${shiro.shiroServerUrlPrefix}${shiro.casFilterUrlPattern}
      # 登出地址
      logoutUrl: ${shiro.casServerUrlPrefix}/logout?service=${shiro.shiroServerUrlPrefix}${shiro.casFilterUrlPattern}
    

    3、自定义配置casFileter

    import org.apache.shiro.authc.AuthenticationToken;
    import org.apache.shiro.cas.CasFilter;
    import org.apache.shiro.cas.CasToken;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.util.StringUtils;
    import org.springframework.web.bind.annotation.RequestMethod;
    
    import javax.servlet.ServletRequest;
    import javax.servlet.ServletResponse;
    import javax.servlet.http.HttpServletRequest;
    
    /**
     * @author : xingsongtan@qq.com
     * @date : 14:17 2019/7/17
     */
    public class MyCasFilter extends CasFilter {
        private static       Logger logger           = LoggerFactory.getLogger(MyCasFilter.class);
        private static final String TICKET_PARAMETER = "ticket";
    
        public MyCasFilter() {
        }
    
        @Override
        public AuthenticationToken createToken(ServletRequest request, ServletResponse response) throws Exception {
            // 获取请求的ticket
            HttpServletRequest httpRequest = (HttpServletRequest) request;
            String ticket = getRequestTicket(httpRequest);
            if (StringUtils.isEmpty(ticket)) {
                logger.debug("票证获取失败,票证为空!");
                return null;
            }
            return new CasToken(ticket);
        }
    
        /**
         * 拒绝除了option以外的所有请求
         **/
        @Override
        public boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
            if (((HttpServletRequest) request).getMethod().equals(RequestMethod.OPTIONS.name())) {
                return true;
            }
            return false;
        }
    
        @Override
        public boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
            // 获取ticket,如果不存在,直接返回false
            String ticket = getRequestTicket((HttpServletRequest) request);
            if (StringUtils.isEmpty(ticket)) {
                return false;
            }
            return this.executeLogin(request, response);
        }
    
        /**
         * 获取请求的ticket
         */
        private String getRequestTicket(HttpServletRequest httpRequest) {
            // 从参数中获取ticket
            String ticket = httpRequest.getParameter(TICKET_PARAMETER);
            if (StringUtils.isEmpty(ticket)) {
                // 如果为空的话,则从header中获取参数
                ticket = httpRequest.getHeader(TICKET_PARAMETER);
            }
            return ticket;
        }
    
    }
    

    4、自定义casRealm

    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 java.util.HashSet;
    import java.util.Set;
    
    /**
     * @author : xingsongtan@qq.com
     * @date : 12:00 2019/7/17
     */
    public class MyCasRealm extends CasRealm {
        private static Logger log = LoggerFactory.getLogger(MyCasRealm.class);
    
        /**
         * 在调用subject.login()时,首先调用此接口
         */
        @Override
        public AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
            // 调用父类的方法,然后授权用户
            AuthenticationInfo authc = super.doGetAuthenticationInfo(token);
            // 获得用户名
            String username = (String) authc.getPrincipals().getPrimaryPrincipal();
            // TODO:这里应该从数据库中获取用户信息
    
            return authc;
        }
    
        /**
         * 进行权限验证的时候,调用方法,将用户的权限信息写进SimpleAuthorizationInfo
         */
        @Override
        public AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
            // 用户名称
            log.info("进入了权限认证");
            Object username = principals.getPrimaryPrincipal();
            SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
            // TODO: 这里应该从数据库获取用户权限
            Set<String> permission = new HashSet<>();
            permission.add("sys:dept:list");
            info.setStringPermissions(permission);
            return info;
        }
    }
    

    6、进行shiroconfig配置

    import com.ttcode.shiro.security.MyCasFilter;
    import com.ttcode.shiro.security.MyCasRealm;
    import org.apache.shiro.authc.pam.AtLeastOneSuccessfulStrategy;
    import org.apache.shiro.authc.pam.ModularRealmAuthenticator;
    import org.apache.shiro.cache.MemoryConstrainedCacheManager;
    import org.apache.shiro.cas.CasSubjectFactory;
    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.filter.authc.LogoutFilter;
    import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
    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.web.filter.DelegatingFilterProxy;
    
    import javax.servlet.Filter;
    import java.util.HashMap;
    import java.util.LinkedHashMap;
    import java.util.Map;
    
    /**
     * Shiro的配置文件
     *
     * @author : xingsongtan@qq.com
     * @date : 20:51 2019/7/18
     */
    
    @Configuration
    public class ShiroCasConfiguration {
        /**
         * 添加shiro的filter
         */
        @Bean
        public FilterRegistrationBean filterRegistrationBean() {
            FilterRegistrationBean filterRegistration = new FilterRegistrationBean();
            filterRegistration.setFilter(new DelegatingFilterProxy("shiroFilter"));
            filterRegistration.addInitParameter("targetFilterLifecycle", "true");
            filterRegistration.setEnabled(true);
            filterRegistration.addUrlPatterns("/*");
            return filterRegistration;
        }
    
        /**
         * 保证了shiro内部lifecycle函数bean的执行
         */
        @Bean(name = "lifecycleBeanPostProcessor")
        public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
            return new LifecycleBeanPostProcessor();
        }
    
        /**
         * 配置授权策略
         */
        @Bean(name = "authenticator")
        public ModularRealmAuthenticator modularRealmAuthenticator() {
            ModularRealmAuthenticator authenticator = new ModularRealmAuthenticator();
            authenticator.setAuthenticationStrategy(new AtLeastOneSuccessfulStrategy());
            return authenticator;
        }
    
        @Bean(name = "casRealm")
        public MyCasRealm casRealm(@Value("${shiro.casServerUrlPrefix}") String casServerUrlPrefix,
                                   @Value("${shiro.shiroServerUrlPrefix}") String shiroServerUrlPrefix,
                                   @Value("${shiro.casFilterUrlPattern}") String casFilterUrlPattern) {
            MyCasRealm casRealm = new MyCasRealm();
            // 认证通过后的默认角色
            casRealm.setDefaultRoles("ROLE_USER");
            // cas服务端地址前缀
            casRealm.setCasServerUrlPrefix(casServerUrlPrefix);
            // 应用服务地址,用来接收cas服务端票证
            casRealm.setCasService(shiroServerUrlPrefix + casFilterUrlPattern);
            return casRealm;
        }
    
        /**
         * 配置安全管理器
         **/
        @Bean(name = "securityManager")
        public DefaultWebSecurityManager defaultWebSecurityManager(ModularRealmAuthenticator authenticator,
                                                                   MyCasRealm casRealm) {
            DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
            // 设置授权策略,此步骤必须在设置realm的前面,不然会报错realm未配置
            securityManager.setAuthenticator(authenticator);
            securityManager.setSubjectFactory(new CasSubjectFactory());
            // 缓存管理器
            securityManager.setCacheManager(new MemoryConstrainedCacheManager());
            // 设置自定义验证策略
            securityManager.setRealm(casRealm);
            return securityManager;
        }
    
    
        /**
         * 配置登录过滤器
         */
        @Bean(name = "casFilter")
        public MyCasFilter casFilter(@Value("${shiro.loginUrl}") String loginUrl) {
            MyCasFilter casFilter = new MyCasFilter();
            casFilter.setName("casFilter");
            casFilter.setEnabled(true);
            casFilter.setFailureUrl(loginUrl);
            return casFilter;
        }
    
        /**
         * shiro 过滤器
         */
        @Bean(name = "shiroFilter")
        public ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager securityManager,
                                                                MyCasFilter casFilter,
                                                                @Value("${shiro.logoutUrl}") String logoutUrl,
                                                                @Value("${shiro.loginUrl}") String loginUrl,
                                                                @Value("${shiro.casFilterUrlPattern}") String casFilterUrlPattern) {
            ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
            // 设置安全管理器
            shiroFilterFactoryBean.setSecurityManager(securityManager);
            // 设置登录地址
            shiroFilterFactoryBean.setLoginUrl(loginUrl);
            // 设置登录成功地址
            shiroFilterFactoryBean.setSuccessUrl("/");
            // 配置拦截地址
            Map<String, Filter> filters = new HashMap<>();
            filters.put("casFilter", casFilter);
            LogoutFilter logoutFilter = new LogoutFilter();
            // 配置登出地址
            logoutFilter.setRedirectUrl(logoutUrl);
            filters.put("logout", logoutFilter);
            shiroFilterFactoryBean.setFilters(filters);
            // 设置访问用户页面需要授权的操作
            loadShiroFilterChain(shiroFilterFactoryBean, casFilterUrlPattern);
            // 将设置的权限设置到shiroFilterFactoryBean
            return shiroFilterFactoryBean;
        }
    
        /**
         * 1、当我们第一次访问客户端时,先去cas进行认证,成功后会返回一个ticket
         * 2、返回的ticket地址在casRealm已经进行了配置,shiroServerUrlPrefix + casFilterUrlPattern
         * 3、即地址为/shiro-cas,对该地址进行casFilter拦截
         */
        private void loadShiroFilterChain(ShiroFilterFactoryBean shiroFilterFactoryBean, String casFilterUrlPattern) {
            Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
    
            filterChainDefinitionMap.put(casFilterUrlPattern, "casFilter");
            filterChainDefinitionMap.put("/logout", "logout");
            filterChainDefinitionMap.put("/**", "authc");
            shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
        }
    
        /**
         * 开启Shiro的注解(如@RequiresPermissions)
         * 需借助SpringAOP扫描使用Shiro注解的类
         * 配置以下两个bean(DefaultAdvisorAutoProxyCreator和AuthorizationAttributeSourceAdvisor)即可实现此功能
         *
         * @return
         */
        @Bean
        public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator() {
            DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
            advisorAutoProxyCreator.setProxyTargetClass(true);
            return advisorAutoProxyCreator;
        }
    
        /**
         * 开启aop注解支持
         *
         * @param securityManager
         * @return
         */
        @Bean
        public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(DefaultWebSecurityManager securityManager) {
            AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
            authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
            return authorizationAttributeSourceAdvisor;
        }
    
    }
    

    结尾:以上为核心配置

    相关文章

      网友评论

        本文标题:springboot 整合shiro + cas 实现单点登录权

        本文链接:https://www.haomeiwen.com/subject/yfbgkctx.html