zoukankan      html  css  js  c++  java
  • 七、spring boot 1.5.4 集成shiro+cas,实现单点登录和权限控制

    1.安装cas-server-3.5.2

    官网:https://github.com/apereo/cas/releases/tag/v3.5.2

    下载地址:cas-server-3.5.2-release.zip

    安装参考文章:http://blog.csdn.net/xuxuchuan/article/details/54924933

    注意:

    输入 <tomcat_key> 的密钥口令
    (如果和密钥库口令相同, 按回车) ,这里直接回车,也采用keystore密码changeit,否则tomcat启动报错!
     

    2.配置ehcache缓存

    <?xml version="1.0" encoding="UTF-8"?>
    <ehcache updateCheck="false" name="shiroCache">
    
        <defaultCache
                maxElementsInMemory="10000"
                eternal="false"
                timeToIdleSeconds="120"
                timeToLiveSeconds="120"
                overflowToDisk="false"
                diskPersistent="false"
                diskExpiryThreadIntervalSeconds="120"
        />
    </ehcache>

    3.添加maven依赖

            <dependency>
                <groupId>org.apache.shiro</groupId>
                <artifactId>shiro-spring</artifactId>
                <version>1.2.4</version>
            </dependency>
            <dependency>
                <groupId>org.apache.shiro</groupId>
                <artifactId>shiro-ehcache</artifactId>
                <version>1.2.4</version>
            </dependency>
            <dependency>
                <groupId>org.apache.shiro</groupId>
                <artifactId>shiro-cas</artifactId>
                <version>1.2.4</version>
            </dependency>        

    4.启动类添加@ServletComponentScan注解

    @ServletComponentScan
    @SpringBootApplication
    public class Application {
    public static void main(String[] args){ SpringApplication.run(Application.class,args); } }

    5.配置shiro+cas

    package com.hdwang.config.shiroCas;
    
    import com.hdwang.dao.UserDao;
    import org.apache.shiro.cache.ehcache.EhCacheManager;
    import org.apache.shiro.cas.CasFilter;
    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.jasig.cas.client.session.SingleSignOutFilter;
    import org.jasig.cas.client.session.SingleSignOutHttpSessionListener;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
    import org.springframework.beans.factory.annotation.Qualifier;
    import org.springframework.boot.web.servlet.FilterRegistrationBean;
    import org.springframework.boot.web.servlet.ServletListenerRegistrationBean;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.core.Ordered;
    import org.springframework.web.filter.DelegatingFilterProxy;
    
    import javax.servlet.Filter;
    import javax.servlet.annotation.WebListener;
    import java.util.HashMap;
    import java.util.LinkedHashMap;
    import java.util.Map;
    
    /**
     * Created by hdwang on 2017/6/20.
     * shiro+cas 配置
     */
    @Configuration
    public class ShiroCasConfiguration {
    
        private static final Logger logger = LoggerFactory.getLogger(ShiroCasConfiguration.class);
    
        // cas server地址
        public static final String casServerUrlPrefix = "https://localhost:8443/cas";
        // Cas登录页面地址
        public static final String casLoginUrl = casServerUrlPrefix + "/login";
        // Cas登出页面地址
        public static final String casLogoutUrl = casServerUrlPrefix + "/logout";
        // 当前工程对外提供的服务地址
        public static final String shiroServerUrlPrefix = "http://localhost:8081";
        // casFilter UrlPattern
        public static final String casFilterUrlPattern = "/cas";
        // 登录地址
        public static final String loginUrl = casLoginUrl + "?service=" + shiroServerUrlPrefix + casFilterUrlPattern;
        // 登出地址(casserver启用service跳转功能,需在webappscasWEB-INFcas.properties文件中启用cas.logout.followServiceRedirects=true)
        public static final String logoutUrl = casLogoutUrl+"?service="+shiroServerUrlPrefix;
        // 登录成功地址
        public static final String loginSuccessUrl = "/home";
        // 权限认证失败跳转地址
        public static final String unauthorizedUrl = "/error/403.html";
    
    
        @Bean
        public EhCacheManager getEhCacheManager() {
            EhCacheManager em = new EhCacheManager();
            em.setCacheManagerConfigFile("classpath:ehcache-shiro.xml");
            return em;
        }
    
        @Bean(name = "myShiroCasRealm")
        public MyShiroCasRealm myShiroCasRealm(EhCacheManager cacheManager) {
            MyShiroCasRealm realm = new MyShiroCasRealm();
            realm.setCacheManager(cacheManager);
            //realm.setCasServerUrlPrefix(ShiroCasConfiguration.casServerUrlPrefix);
            // 客户端回调地址
            //realm.setCasService(ShiroCasConfiguration.shiroServerUrlPrefix + ShiroCasConfiguration.casFilterUrlPattern);
            return realm;
        }
    
        /**
         * 注册单点登出listener
         * @return
         */
        @Bean
        public ServletListenerRegistrationBean singleSignOutHttpSessionListener(){
            ServletListenerRegistrationBean bean = new ServletListenerRegistrationBean();
            bean.setListener(new SingleSignOutHttpSessionListener());
    //        bean.setName(""); //默认为bean name
            bean.setEnabled(true);
            //bean.setOrder(Ordered.HIGHEST_PRECEDENCE); //设置优先级
            return bean;
        }
    
        /**
         * 注册单点登出filter
         * @return
         */
        @Bean
        public FilterRegistrationBean singleSignOutFilter(){
            FilterRegistrationBean bean = new FilterRegistrationBean();
            bean.setName("singleSignOutFilter");
            bean.setFilter(new SingleSignOutFilter());
            bean.addUrlPatterns("/*");
            bean.setEnabled(true);
            //bean.setOrder(Ordered.HIGHEST_PRECEDENCE);
            return bean;
        }
    
    
    
        /**
         * 注册DelegatingFilterProxy(Shiro)
         *
         * @return
         * @author SHANHY
         * @create  2016年1月13日
         */
        @Bean
        public FilterRegistrationBean delegatingFilterProxy() {
            FilterRegistrationBean filterRegistration = new FilterRegistrationBean();
            filterRegistration.setFilter(new DelegatingFilterProxy("shiroFilter"));
            //  该值缺省为false,表示生命周期由SpringApplicationContext管理,设置为true则表示由ServletContainer管理
            filterRegistration.addInitParameter("targetFilterLifecycle", "true");
            filterRegistration.setEnabled(true);
            filterRegistration.addUrlPatterns("/*");
            return filterRegistration;
        }
    
    
        @Bean(name = "lifecycleBeanPostProcessor")
        public LifecycleBeanPostProcessor getLifecycleBeanPostProcessor() {
            return new LifecycleBeanPostProcessor();
        }
    
        @Bean
        public DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator() {
            DefaultAdvisorAutoProxyCreator daap = new DefaultAdvisorAutoProxyCreator();
            daap.setProxyTargetClass(true);
            return daap;
        }
    
        @Bean(name = "securityManager")
        public DefaultWebSecurityManager getDefaultWebSecurityManager(MyShiroCasRealm myShiroCasRealm) {
            DefaultWebSecurityManager dwsm = new DefaultWebSecurityManager();
            dwsm.setRealm(myShiroCasRealm);
    //      <!-- 用户授权/认证信息Cache, 采用EhCache 缓存 -->
            dwsm.setCacheManager(getEhCacheManager());
            // 指定 SubjectFactory
            dwsm.setSubjectFactory(new CasSubjectFactory());
            return dwsm;
        }
    
        @Bean
        public AuthorizationAttributeSourceAdvisor getAuthorizationAttributeSourceAdvisor(DefaultWebSecurityManager securityManager) {
            AuthorizationAttributeSourceAdvisor aasa = new AuthorizationAttributeSourceAdvisor();
            aasa.setSecurityManager(securityManager);
            return aasa;
        }
    
    
        /**
         * CAS过滤器
         *
         * @return
         * @author SHANHY
         * @create  2016年1月17日
         */
        @Bean(name = "casFilter")
        public CasFilter getCasFilter() {
            CasFilter casFilter = new CasFilter();
            casFilter.setName("casFilter");
            casFilter.setEnabled(true);
            // 登录失败后跳转的URL,也就是 Shiro 执行 CasRealm 的 doGetAuthenticationInfo 方法向CasServer验证tiket
            casFilter.setFailureUrl(loginUrl);// 我们选择认证失败后再打开登录页面
            return casFilter;
        }
    
        /**
         * ShiroFilter<br/>
         * 注意这里参数中的 StudentService 和 IScoreDao 只是一个例子,因为我们在这里可以用这样的方式获取到相关访问数据库的对象,
         * 然后读取数据库相关配置,配置到 shiroFilterFactoryBean 的访问规则中。实际项目中,请使用自己的Service来处理业务逻辑。
         *
         * @param securityManager
         * @param casFilter
         * @param userDao
         * @return
         * @author SHANHY
         * @create  2016年1月14日
         */
        @Bean(name = "shiroFilter")
        public ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager securityManager, CasFilter casFilter, UserDao userDao) {
            ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
            // 必须设置 SecurityManager
            shiroFilterFactoryBean.setSecurityManager(securityManager);
            // 如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面
            shiroFilterFactoryBean.setLoginUrl(loginUrl);
            // 登录成功后要跳转的连接
            shiroFilterFactoryBean.setSuccessUrl(loginSuccessUrl);
            shiroFilterFactoryBean.setUnauthorizedUrl(unauthorizedUrl);
            // 添加casFilter到shiroFilter中
            Map<String, Filter> filters = new HashMap<>();
            filters.put("casFilter", casFilter);
           // filters.put("logout",logoutFilter());
            shiroFilterFactoryBean.setFilters(filters);
    
            loadShiroFilterChain(shiroFilterFactoryBean, userDao);
            return shiroFilterFactoryBean;
        }
    
        /**
         * 加载shiroFilter权限控制规则(从数据库读取然后配置),角色/权限信息由MyShiroCasRealm对象提供doGetAuthorizationInfo实现获取来的
         *
         * @author SHANHY
         * @create  2016年1月14日
         */
        private void loadShiroFilterChain(ShiroFilterFactoryBean shiroFilterFactoryBean, UserDao userDao){
            /////////////////////// 下面这些规则配置最好配置到配置文件中 ///////////////////////
            Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
    
            // authc:该过滤器下的页面必须登录后才能访问,它是Shiro内置的一个拦截器org.apache.shiro.web.filter.authc.FormAuthenticationFilter
            // anon: 可以理解为不拦截
            // user: 登录了就不拦截
            // roles["admin"] 用户拥有admin角色
            // perms["permission1"] 用户拥有permission1权限
            // filter顺序按照定义顺序匹配,匹配到就验证,验证完毕结束。
            // url匹配通配符支持:? * **,分别表示匹配1个,匹配0-n个(不含子路径),匹配下级所有路径
    
            //1.shiro集成cas后,首先添加该规则
            filterChainDefinitionMap.put(casFilterUrlPattern, "casFilter");
            //filterChainDefinitionMap.put("/logout","logout"); //logut请求采用logout filter
    
            //2.不拦截的请求
            filterChainDefinitionMap.put("/css/**","anon");
            filterChainDefinitionMap.put("/js/**","anon");
            filterChainDefinitionMap.put("/login", "anon");
            filterChainDefinitionMap.put("/logout","anon");
            filterChainDefinitionMap.put("/error","anon");
            //3.拦截的请求(从本地数据库获取或者从casserver获取(webservice,http等远程方式),看你的角色权限配置在哪里)
            filterChainDefinitionMap.put("/user", "authc"); //需要登录
            filterChainDefinitionMap.put("/user/add/**", "authc,roles[admin]"); //需要登录,且用户角色为admin
            filterChainDefinitionMap.put("/user/delete/**", "authc,perms["user:delete"]"); //需要登录,且用户有权限为user:delete
    
            //4.登录过的不拦截
            filterChainDefinitionMap.put("/**", "user");
    
            shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
        }
    
    }
    package com.hdwang.config.shiroCas;
    
    import javax.annotation.PostConstruct;
    
    import com.hdwang.dao.UserDao;
    import com.hdwang.entity.User;
    import org.apache.shiro.SecurityUtils;
    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;
    
    import java.util.HashSet;
    import java.util.Set;
    
    /**
     * Created by hdwang on 2017/6/20.
     * 安全数据源
     */
    public class MyShiroCasRealm extends CasRealm{
    
        private static final Logger logger = LoggerFactory.getLogger(MyShiroCasRealm.class);
    
        @Autowired
        private UserDao userDao;
    
        @PostConstruct
        public void initProperty(){
    //      setDefaultRoles("ROLE_USER");
            setCasServerUrlPrefix(ShiroCasConfiguration.casServerUrlPrefix);
            // 客户端回调地址
            setCasService(ShiroCasConfiguration.shiroServerUrlPrefix + ShiroCasConfiguration.casFilterUrlPattern);
        }
    
    //    /**
    //     * 1、CAS认证 ,验证用户身份
    //     * 2、将用户基本信息设置到会话中(不用了,随时可以获取的)
    //     */
    //    @Override
    //    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) {
    //
    //        AuthenticationInfo authc = super.doGetAuthenticationInfo(token);
    //
    //        String account = (String) authc.getPrincipals().getPrimaryPrincipal();
    //
    //        User user = userDao.getByName(account);
    //        //将用户信息存入session中
    //        SecurityUtils.getSubject().getSession().setAttribute("user", user);
    //
    //        return authc;
    //    }
    
        /**
         * 权限认证,为当前登录的Subject授予角色和权限
         * @see 经测试:本例中该方法的调用时机为需授权资源被访问时
         * @see 经测试:并且每次访问需授权资源时都会执行该方法中的逻辑,这表明本例中默认并未启用AuthorizationCache
         * @see 经测试:如果连续访问同一个URL(比如刷新),该方法不会被重复调用,Shiro有一个时间间隔(也就是cache时间,在ehcache-shiro.xml中配置),超过这个时间间隔再刷新页面,该方法会被执行
         */
        @Override
        protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
            logger.info("##################执行Shiro权限认证##################");
            //获取当前登录输入的用户名,等价于(String) principalCollection.fromRealm(getName()).iterator().next();
            String loginName = (String)super.getAvailablePrincipal(principalCollection);
    
            //到数据库查是否有此对象(1.本地查询 2.可以远程查询casserver 3.可以由casserver带过来角色/权限其它信息)
            User user=userDao.getByName(loginName);// 实际项目中,这里可以根据实际情况做缓存,如果不做,Shiro自己也是有时间间隔机制,2分钟内不会重复执行该方法
            if(user!=null){
                //权限信息对象info,用来存放查出的用户的所有的角色(role)及权限(permission)
                SimpleAuthorizationInfo info=new SimpleAuthorizationInfo();
                //给用户添加角色(让shiro去验证)
                Set<String> roleNames = new HashSet<>();
                if(user.getName().equals("boy5")){
                    roleNames.add("admin");
                }
                info.setRoles(roleNames);
    
                if(user.getName().equals("李四")){
                    //给用户添加权限(让shiro去验证)
                    info.addStringPermission("user:delete");
                }
    
    
                // 或者按下面这样添加
                //添加一个角色,不是配置意义上的添加,而是证明该用户拥有admin角色
    //            simpleAuthorInfo.addRole("admin");
                //添加权限
    //            simpleAuthorInfo.addStringPermission("admin:manage");
    //            logger.info("已为用户[mike]赋予了[admin]角色和[admin:manage]权限");
                return info;
            }
            // 返回null的话,就会导致任何用户访问被拦截的请求时,都会自动跳转到unauthorizedUrl指定的地址
            return null;
        }
    
    }
    package com.hdwang.controller;
    
    import com.hdwang.config.shiroCas.ShiroCasConfiguration;
    import com.hdwang.entity.User;
    import org.springframework.stereotype.Controller;
    import org.springframework.ui.Model;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RequestMethod;
    
    import javax.servlet.http.HttpSession;
    
    /**
     * Created by hdwang on 2017/6/21.
     * 跳转至cas server去登录(一个入口)
     */
    @Controller
    @RequestMapping("")
    public class CasLoginController {
    
        /**
         * 一般用不到
         * @param model
         * @return
         */
        @RequestMapping(value="/login",method= RequestMethod.GET)
        public String loginForm(Model model){
            model.addAttribute("user", new User());
    //      return "login";
            return "redirect:" + ShiroCasConfiguration.loginUrl;
        }
    
    
        @RequestMapping(value = "logout", method = { RequestMethod.GET,
                RequestMethod.POST })
        public String loginout(HttpSession session)
        {
            return "redirect:"+ShiroCasConfiguration.logoutUrl;
        }
    }
    package com.hdwang.controller;
    
    import com.alibaba.fastjson.JSONObject;
    import com.hdwang.entity.User;
    import com.hdwang.service.datajpa.UserService;
    import org.apache.shiro.SecurityUtils;
    import org.apache.shiro.mgt.SecurityManager;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Controller;
    import org.springframework.ui.ModelMap;
    import org.springframework.web.bind.annotation.RequestMapping;
    
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpSession;
    
    /**
     * Created by hdwang on 2017/6/19.
     */
    @Controller
    @RequestMapping("/home")
    public class HomeController {
    
        @Autowired
        UserService userService;
    
        @RequestMapping("")
        public String index(HttpSession session, ModelMap map, HttpServletRequest request){
    //        User user = (User) session.getAttribute("user");
    
            System.out.println(request.getUserPrincipal().getName());
            System.out.println(SecurityUtils.getSubject().getPrincipal());
    
            User loginUser = userService.getLoginUser();
            System.out.println(JSONObject.toJSONString(loginUser));
    
            map.put("user",loginUser);
            return "home";
        }
    
    
    
    }

    6.运行验证

    登录 

    访问:http://localhost:8081/home

    跳转至:https://localhost:8443/cas/login?service=http://localhost:8081/cas

    输入正确用户名密码登录跳转回:http://localhost:8081/cas?ticket=ST-203-GUheN64mOZec9IWZSH1B-cas01.example.org

    最终跳回:http://localhost:8081/home

    登出

    访问:http://localhost:8081/logout

    跳转至:https://localhost:8443/cas/logout?service=http://localhost:8081

    由于未登录,又执行登录步骤,所以最终返回https://localhost:8443/cas/login?service=http://localhost:8081/cas

    这次登录成功后返回:http://localhost:8081/

    cas server端登出(也行)

    访问:https://localhost:8443/cas/logout

    再访问:http://localhost:8081/home 会跳转至登录页,perfect!

    7.项目源码:https://github.com/hdwang123/springboottest_onedb

    参考文章

    1. Spring Boot 集成Shiro和CAS

    2. Cas Server 与Cas Client 的配置与部署

    3.第一章 Shiro简介——《跟我学Shiro》

    4.shiro-cas 单点退出

    5. CAS Shiro做单点登录不能退出的问题

  • 相关阅读:
    NOIp2018集训test-9-4
    「THUSC 2016」成绩单 & 方块消除 (区间dp)
    NOIp2018集训test-9-2(pm)
    NOIp2018集训test-9-2(am)
    NOIp2018集训test-9-1(pm)
    NOIp2018集训test-9-1(am)
    暑假集训test-8-31(pm)
    暑假集训test-8-31(am)
    暑假集训test-8-30
    day22 笔记
  • 原文地址:https://www.cnblogs.com/hdwang/p/7064492.html
Copyright © 2011-2022 走看看