zoukankan      html  css  js  c++  java
  • springboot+shiro+cas 前后端分离 知识点汇总以及遇到的坑

    1.项目要接入cas服务,记录下这周的过程以及遇到的坑

        1.配置CasRealem和AuthorizingRealm的区别 

        由于上个服务 自己用的springboot+shiro 而没有整合cas,上个服务是登录后直接去库里面查询,那么何时去加载这个Ream。 我刚开始是实现了AuthorizingRealm而不是CasRealem,之后交给spring管理,结果发现怎么都进入不到自己的ream里面。最后发现是配置的问题

    package com.sq.unionmanage.gateway.api;
    
    
    import com.sq.unionmanage.gateway.service.common.datasource.DataSourceConfig;
    import com.sq.unionmanage.gateway.service.shiro.PlatformShiroFilterFactoryBean;
    import com.sq.unionmanage.gateway.service.shiro.cache.RedisCacheManager;
    import com.sq.unionmanage.gateway.service.shiro.filter.ShiroFormAuthenticationFilter;
    import com.sq.unionmanage.gateway.service.shiro.filter.SqUserFilter;
    import com.sq.unionmanage.gateway.service.shiro.realm.ShiroRealm;
    import com.sq.unionmanage.gateway.service.shiro.session.RedisSessionDAO;
    import com.sq.unionmanage.gateway.service.shiro.session.UuIdSessionIdGenerator;
    import com.sq.unionmanage.gateway.service.util.DESUtil;
    import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
    import org.apache.shiro.web.mgt.CookieRememberMeManager;
    import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
    import org.apache.shiro.web.servlet.SimpleCookie;
    import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
    import org.jasig.cas.client.session.SingleSignOutFilter;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.boot.autoconfigure.AutoConfigureAfter;
    import org.springframework.boot.web.servlet.FilterRegistrationBean;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.data.redis.core.RedisTemplate;
    import org.springframework.web.filter.DelegatingFilterProxy;
    
    import javax.annotation.Resource;
    import javax.servlet.Filter;
    import java.util.Base64;
    import java.util.HashMap;
    import java.util.LinkedHashMap;
    import java.util.Map;
    
    /**
     * @Author:
     * @Date:2020/03/21
     * @Description:
     */
    
    @Configuration
    @AutoConfigureAfter(DataSourceConfig.class)
    public class ShiroConfiguration {
    
        @Value(value = "${cms.login.url}")
        private String cmsLoginUrl;
    
        @Value(value = "${homepage.url}")
        private String homePageUrl;
    
        @Value(value = "${service.des.secret}")
        private String serviceDesSecret;
    
        @Value(value = "${sso.server.url}")
        private String ssoServerUrl;
    
        @Value(value = "${sso.login.url}")
        private String ssoLoginUrl;
    
        @Value(value = "${cms.server.url}")
        private String cmsServerUrl;
    
        //
        @Resource(name="scosSerRedisTemplate")
        private RedisTemplate scosSerRedisTemplate;
    
    
        @Value("${cms.login.url}")
        private String localLoginUrl;  //本地客户端的认证回调地址
        @Value("${service.des.secret}")
        private String desSecret;       //本地客户端的认证回调地址  的DES加密密钥
    
        @Bean
        public ShiroRealm shiroRealm(){
            ShiroRealm shiroRealm = new ShiroRealm();
    //      /*        shiroRealm.setDefaultRoles("ROLE_USER");
            shiroRealm.setCasServerUrlPrefix(ssoServerUrl);
            //casServic的作用是 登录成功后向客户端回调
            shiroRealm.setCasService(cmsLoginUrl);
            return shiroRealm;
        }
    
        @Bean(name ="sessionIdGenerator")
        public UuIdSessionIdGenerator sessionIdGenerator(){
            UuIdSessionIdGenerator sessionIdGenerator = new UuIdSessionIdGenerator();
            return sessionIdGenerator;
        }
    
        @Bean(name = "sessionDAO")
        public RedisSessionDAO  sessionDAO(UuIdSessionIdGenerator sessionIdGenerator){
            RedisSessionDAO sessionDAO = new RedisSessionDAO();
            sessionDAO.setActiveSessionsCacheName("shiro-activeSessionCache");
            sessionDAO.setSessionIdGenerator(sessionIdGenerator);
            sessionDAO.setRedisTemplate(scosSerRedisTemplate);
            return sessionDAO;
        }
    
        @Bean(name = "sessionIdCookie")
        public SimpleCookie sessionIdCookie(){
            SimpleCookie sessionIdCookie = new SimpleCookie("unsid");
            sessionIdCookie.setHttpOnly(true);
            sessionIdCookie.setMaxAge(-1);
            return sessionIdCookie;
        }
    
        @Bean("sessionManager")
        public DefaultWebSessionManager sessionManager(RedisSessionDAO  sessionDAO, SimpleCookie sessionIdCookie){
            DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
            sessionManager.setGlobalSessionTimeout(1800000);
            sessionManager.setDeleteInvalidSessions(true);
            sessionManager.setSessionValidationSchedulerEnabled(true);
            //sessionManager.setSessionValidationScheduler(sessionValidationScheduler);
            sessionManager.setSessionDAO(sessionDAO);
            sessionManager.setSessionIdCookieEnabled(true);
            sessionManager.setSessionIdCookie(sessionIdCookie);
            return sessionManager;
        }
    
        //<!-- 会话过期校验调度器 -->
        //<bean id="sessionValidationScheduler" class="org.apache.shiro.session.mgt.quartz.QuartzSessionValidationScheduler">
        //    <property name="sessionValidationInterval" value="900000" />
        //    <property name="sessionManager" ref="sessionManager" />
        //</bean>
    
        //@Bean(name ="sessionValidationScheduler")
        //public QuartzSessionValidationScheduler sessionValidationScheduler(){
        //    QuartzSessionValidationScheduler sessionValidationScheduler = new QuartzSessionValidationScheduler();
        //    sessionValidationScheduler.setSessionValidationInterval(900000);
        //    sessionValidationScheduler.setSessionManager(sessionManager);
        //    return sessionValidationScheduler;
        //}
    
    
    
    
        @Bean(name="shiroCacheManager")
        public RedisCacheManager shiroCacheManager(){
            RedisCacheManager shiroCacheManager = new RedisCacheManager();
            shiroCacheManager.setRedisTemplate(scosSerRedisTemplate);
            shiroCacheManager.setExpireSeconds(1800);
            return shiroCacheManager;
        }
    
    
    
        @Bean(name="rememberMeManager")
        public CookieRememberMeManager rememberMeManager(SimpleCookie rememberMeCookie){
            CookieRememberMeManager rememberMeManager = new CookieRememberMeManager();
            byte[] cipherKey =  Base64.getEncoder().encode("4AvVhmFLUs0KTA3Kprsdag==".getBytes());
            rememberMeManager.setCipherKey(cipherKey);
            rememberMeManager.setCookie(rememberMeCookie);
            return rememberMeManager;
        }
    
        @Bean(name ="rememberMeCookie")
        public SimpleCookie rememberMeCookie(){
            SimpleCookie simpleCookie = new SimpleCookie();
            simpleCookie.setName("unionRememberMe");
            simpleCookie.setHttpOnly(true);
            simpleCookie.setMaxAge(432000);
            return simpleCookie;
        }
    
        @Bean
        public DefaultWebSecurityManager securityManager(RedisCacheManager shiroCacheManager,
                                                         CookieRememberMeManager rememberMeManager,
                                                         ShiroRealm shiroRealm,
                                                         DefaultWebSessionManager sessionManager) {
            DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
            securityManager.setRealm(shiroRealm);
    
            securityManager.setCacheManager(shiroCacheManager);
            securityManager.setSessionManager(sessionManager);
            // 指定 SubjectFactory,如果要实现cas的remember me的功能,需要用到下面这个CasSubjectFactory,并设置到securityManager的subjectFactory中
            //securityManager.setSubjectFactory(new CasSubjectFactory());
            securityManager.setRememberMeManager(rememberMeManager);
            return securityManager;
        }
    
    
    
        @Bean("casSingleSignOutFilter")
        public SingleSignOutFilter casSingleSignOutFilter(){
            SingleSignOutFilter casSingleSignOutFilter = new SingleSignOutFilter();
            //casSingleSignOutFilter.setCasServerUrlPrefix(ssoServerUrl);
            casSingleSignOutFilter.setIgnoreInitConfiguration(true);
            return casSingleSignOutFilter;
        }
    
    
        @Bean
        public FilterRegistrationBean delegatingFilterProxy(){
            FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
            DelegatingFilterProxy proxy = new DelegatingFilterProxy();
            proxy.setTargetFilterLifecycle(true);
            proxy.setTargetBeanName("shiroFilter");
            filterRegistrationBean.setFilter(proxy);
            return filterRegistrationBean;
        }
    
        @Bean
        public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(DefaultWebSecurityManager securityManager){
            AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
            authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
            return authorizationAttributeSourceAdvisor;
        }
    
        @Bean(name = "casFilter")
        public ShiroFormAuthenticationFilter casFilter() throws Exception {
            ShiroFormAuthenticationFilter casFilter = new ShiroFormAuthenticationFilter();
             casFilter.setName("casFilter");
            casFilter.setEnabled(true);
            casFilter.setFailureUrl("/unauthorized");
            //casFilter.setLoginUrl(homePageUrl);
            return casFilter;
        }
    
        @Bean(name = "sqUserFilter")
        public SqUserFilter sqUserFilter() throws Exception {
            SqUserFilter sqUserFilter = new SqUserFilter();
            return sqUserFilter;
        }
    
    
        @Bean(name ="shiroFilter")
        public PlatformShiroFilterFactoryBean shiroFilter(DefaultWebSecurityManager securityManager,
                                                          ShiroFormAuthenticationFilter casFilter,
                                                          SqUserFilter sqUserFilter) throws Exception {
            PlatformShiroFilterFactoryBean shiroFilterFactoryBean = new PlatformShiroFilterFactoryBean();
            Map<String , Filter> filters = new HashMap<>();
            filters.put("casFilter", casFilter);
            filters.put("sqUserFilter", sqUserFilter);
            shiroFilterFactoryBean.setFilters(filters);
    
             shiroFilterFactoryBean.setSecurityManager(securityManager);
            shiroFilterFactoryBean.setLoginUrl("/unauthorized");
            //shiroFilterFactoryBean.setSuccessUrl(homePageUrl);
            shiroFilterFactoryBean.setUnauthorizedUrl("/unauthorized");
            //shiroFilterFactoryBean.setDesSecret(serviceDesSecret);
    
    
            //注意此处使用的是LinkedHashMapU,是有顺序的,shiro会按从上到下的顺序匹配验证,匹配了就不再继续验证
            //所以上面的url要苛刻,宽松的url要放在下面,尤其是"/**"要放到最下面,如果放前面的话其后的验证规则就没作用了。
            Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
            filterChainDefinitionMap.put("/login", "casFilter");
            //1.不拦截的请求
            filterChainDefinitionMap.put("/unauthorized", "anon");
    
            //filterChainDefinitionMap.put(homePageUrl,"anon");
            filterChainDefinitionMap.put("/nginx.html", "anon");
            filterChainDefinitionMap.put("/needlogin", "anon");
            filterChainDefinitionMap.put("/logout", "anon");
    
            //3.需要登录 authc:该过滤器下的页面必须登录后才能访问,它是Shiro内置的一个拦截器org.apache.shiro.web.filter.authc.FormAuthenticationFilter
            filterChainDefinitionMap.put("/mp/**", "authc");
    
            //4.登录过的不拦截
            filterChainDefinitionMap.put("/**", "user");
    
    
    
    
    
    
            shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
            return shiroFilterFactoryBean;
        }
    }

      

    2.shiroFilter 注入的几个问题

      

     3.302问题

       登录成功后,shiro会通过cas回调到我的服务器地址,这个时候一般是需要我们去配置一个首页进行跳转

      

    package com.sq.unionmanage.gateway.service.shiro.filter;
    
    import com.alibaba.fastjson.JSON;
    import com.alibaba.fastjson.JSONObject;
    import com.sq.unionmanage.gateway.service.common.ResponseResult;
    import org.apache.http.HttpStatus;
    import org.apache.shiro.authc.AuthenticationException;
    import org.apache.shiro.authc.AuthenticationToken;
    import org.apache.shiro.cas.CasFilter;
    import org.apache.shiro.subject.Subject;
    import org.springframework.beans.factory.annotation.Value;
    
    import javax.servlet.ServletRequest;
    import javax.servlet.ServletResponse;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.PrintWriter;
    
    /**
     * @Author:qxx
     * @Date:2019/9/6
     * @Description:
     */
    public class ShiroFormAuthenticationFilter extends CasFilter {
        @Value(value = "${homepage.url}")
        private String homePageUrl;
    
    
        @Value(value = "${sso.login.url}")
        private String ssoLoginUrl;    //cas sso 登录页面
        @Value("${cms.login.url}")
        private String localLoginUrl;  //本地客户端的认证回调地址
        @Value("${service.des.secret}")
        private String desSecret;       //本地客户端的认证回调地址  的DES加密密钥
    
        /*@Override
        protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
    
            return true;
    
        }*/
    
        @Override
        protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception {
            System.out.println("==========executeLogin==========");
            AuthenticationToken token = createToken(request, response);
            try {
                System.out.println("========= token 是否为空===" + token);
            } catch (Exception e) {
                e.printStackTrace();
            }
            if (token == null) {
                String msg = "createToken method implementation returned null. A valid non-null AuthenticationToken " +
                        "must be created in order to execute a login attempt.";
                throw new IllegalStateException(msg);
            }
            try {
                System.out.println("========是否登录成功===========");
                Subject subject = getSubject(request, response);
                subject.login(token);
                System.out.println("========登录成功===========");
                return onLoginSuccess(token, subject, request, response);
            } catch (AuthenticationException e) {
                return onLoginFailure(token, e, request, response);
            }
    
         }
    
    
    
    
        @Override
        protected boolean onLoginSuccess(AuthenticationToken token, Subject subject, ServletRequest request, ServletResponse response) throws Exception {
            HttpServletResponse rep = toHttp(response);
            System.out.println("=====onLoginSuccess=====");
            rep.setStatus(302);
            rep.setHeader("Location", homePageUrl);
            return true;
        }
    
    
    
        public  HttpServletRequest toHttp(ServletRequest request) {
            return (HttpServletRequest)request;
        }
    
        public  HttpServletResponse toHttp(ServletResponse response) {
            return (HttpServletResponse)response;
        }
    
    
    }
    

      

    4.跨域问题

       shiro登录成功后,因为源码里面有重定向,会导致header里面的信息都置为空,导致在前后端分时候出现跨域。解决办法:

       

    package com.sq.unionmanage.gateway.api.web.filter;
    
    import org.apache.http.HttpStatus;
    import org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.core.annotation.Order;
    import org.springframework.stereotype.Component;
    import org.springframework.web.bind.annotation.RequestMethod;
    
    import javax.servlet.ServletRequest;
    import javax.servlet.ServletResponse;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    
    /**
     * @Author fanht
     * @Description 解决shiro 未认证后cors 跨域同源问题
     * @Date 2020/3/11 下午7:12
     * @Version 1.0
     */
    @Component
    @Order(1)
    public class CORSFilter extends BasicHttpAuthenticationFilter{
    
        private static Logger logger = LoggerFactory.getLogger(AccessFilter.class);
    
        @Override
        protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
            HttpServletRequest req = (HttpServletRequest) request;
            HttpServletResponse res = (HttpServletResponse) response;
            if(req.getMethod().equals(RequestMethod.OPTIONS.name())){
                res.setHeader("Access-control-Allow-Origin",req.getHeader("Origin"));
                res.setHeader("Access-Control-Allow-Methods","GET,POST,OPTIONS,PUT,DELETE");
                // 响应首部 Access-Control-Allow-Headers 用于 preflight request (预检请求)中,列出了将会在正式请求的 Access-Control-Expose-Headers 字段中出现的首部信息。修改为请求首部
                res.setHeader("Access-Control-Allow-Headers",req.getHeader("Access-Control-Request-Headers"));
                //给option请求直接返回正常状态
                res.setStatus(HttpStatus.SC_OK);
                return false;
            }
            return super.preHandle(request, response);
        }
    }
    package com.sq.unionmanage.gateway.api.web.filter;
    
    import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
    import org.springframework.boot.web.servlet.FilterRegistrationBean;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.stereotype.Component;
    
    /**
     * @Author fanht
     * @Description
     * @Date 2020/3/24 下午8:06
     * @Version 1.0
     */
    @Component
    //@ConditionalOnProperty(name = "server.scheme.chanage.enabled", havingValue = "true") // 开启注解才会启动
    public class RedirectFilterConfig {
    
    
        @Bean
        public FilterRegistrationBean registFilter() {
            FilterRegistrationBean registration = new FilterRegistrationBean();
            registration.setFilter(new AbsoluteSendRedirectFilter());
            registration.addUrlPatterns("*");
            registration.setName("filterRegistrationBean");
            registration.setOrder(1);
            return registration;
        }
    
    }

    5.跨域问题解决后,会提示 http转https问题 解决办法

    package com.sq.unionmanage.gateway.api.web.filter;
    
    import org.apache.commons.lang.StringUtils;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.core.annotation.Order;
    import org.springframework.stereotype.Component;
    
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import javax.servlet.http.HttpServletResponseWrapper;
    import java.io.IOException;
    import java.net.URI;
    import java.net.URISyntaxException;
    
    /**
     * @program: union-manage-gateway
     * @description:
     * @author: zjw
     * @create: 2020-03-16 16:39
     **/
    @Component
    //@Order(0)
    public class HttpTransWrapper extends HttpServletResponseWrapper{
    
    
        private Logger logger = LoggerFactory.getLogger(this.getClass());
        private final HttpServletRequest request;
    
        /**
         * Constructs a response adaptor wrapping the given response.
         *
         * @param response The response to be wrapped
         * @throws IllegalArgumentException if the response is null
         */
        public HttpTransWrapper(final HttpServletRequest req, HttpServletResponse response) {
            super(response);
            this.request = req;
        }
    
        @Override
        public void sendRedirect(String location) throws IOException {
            if(StringUtils.isEmpty(location)){
                super.sendRedirect(location);
                return;
            }
    
            try {
                final URI uri = new URI(location);
                if(uri.getScheme() != null){
                    super.sendRedirect(location);
                    return;
                }
            } catch (URISyntaxException e) {
                logger.error("=======跳转异常========" + e);
                super.sendRedirect(location);
            }
    
            String finalUrl = "https://" + this.request.getServerName();
            if(request.getServerPort() != 80 && request.getServerPort() != 443 ){
                finalUrl += ":" + request.getServerPort();
            }
            finalUrl += location;
            logger.info("finalUrl:{}",finalUrl);
            if(finalUrl.indexOf("localhost") > 0){
                //todo 如果是本地测试 仍然用http的
                super.sendRedirect(location);
            }else {
                super.sendRedirect(finalUrl);
            }
        }
    }
    package com.sq.unionmanage.gateway.api.web.filter;
    
    import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
    import org.springframework.boot.web.servlet.FilterRegistrationBean;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.stereotype.Component;
    
    /**
     * @Author fanht
     * @Description
     * @Date 2020/3/24 下午8:06
     * @Version 1.0
     */
    @Component
    //@ConditionalOnProperty(name = "server.scheme.chanage.enabled", havingValue = "true") // 开启注解才会启动
    public class RedirectFilterConfig {
    
    
        @Bean
        public FilterRegistrationBean registFilter() {
            FilterRegistrationBean registration = new FilterRegistrationBean();
            registration.setFilter(new AbsoluteSendRedirectFilter());
            registration.addUrlPatterns("*");
            registration.setName("filterRegistrationBean");
            registration.setOrder(1);
            return registration;
        }
    
    }

    7.登录cas回调成功后,h5cookieId为空问题,我是在回调成功后将后端的sessionId给到了H5,让他们加到session里面,原来以为这样是可以的,结果还是不行。

    8.没有办法,最终还是通过运维来解决:大体思路是这样的: sso登录成功后,回调地址配置为前端的地址,然后当前端向后端请求时候,nginx做转发,转发到后端的ip上面,这样因为在sso转发时候cookie已经种在了前端的域名下,nginx绑定到了我们后端的ip上,相当于是 没有跨域了,也就不会出现跨域和cookie丢失的问题。然后测试发现,是可以的。 

    参考:https://segmentfault.com/a/1190000015235402

    https://blog.csdn.net/qq_21251983/article/details/87631991

  • 相关阅读:
    PL/SQL编辑数据"这些查询结果不可更新,请包括ROWID或使用SELECT...FOR UPDATE获得可更新结果"处理
    软件开发是什么、如何做
    HIS系统患者实体OO设计的一点思考
    Entity Framework for Oracle 基本配置
    PowerDesigner 15学习笔记:十大模型及五大分类
    手动触发dom节点事件代码
    JavaScript 继承代码中,B.prototype = new A(); 的含义是什么?[转自知乎]
    各类知识点文章收集
    偶尔遇到的“The request was aborted:Could not create SSL/TLS secure channel.”怎么解决?
    sqlserver 树结构递归(向上递归和向下递归)
  • 原文地址:https://www.cnblogs.com/thinkingandworkinghard/p/12596443.html
Copyright © 2011-2022 走看看