精华内容
下载资源
问答
  • 那就意味着做单点登录咯,至于不知道什么是单点登录的同学,建议去找一下万能的度娘。 刚接到这个需求的时候,老夫心里便不屑的认为:区区登录何足挂齿,但是,开发的过程狠狠的打了我一巴掌(火辣辣的一巴掌)。。...
  • 前后端分离实现Cas单点登录

    千次阅读 热门讨论 2019-09-17 09:47:29
    前后端分离实现单点登录环境介绍会遇到的问题问题解决需要注意的点 环境介绍 前端vue单独部署 后端springboot单独部署 会遇到的问题 跨域 cas认证失败无法重定向,前端302无法捕捉。 问题解决 1、跨域 直接上代码。 ...

    环境介绍

    前端vue单独部署 后端springboot单独部署

    会遇到的问题

    跨域
    cas认证失败无法重定向,前端302无法捕捉。

    问题解决

    1、跨域
    直接上代码。

    import org.springframework.context.annotation.Configuration;
    import org.springframework.core.annotation.Order;
    import org.springframework.web.servlet.config.annotation.CorsRegistry;
    import org.springframework.web.servlet.config.annotation.EnableWebMvc;
    import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
    
    @Configuration
    @EnableWebMvc
    public class CorsConfig implements WebMvcConfigurer {
    
        @Override
        @Order(0)
        public void addCorsMappings(CorsRegistry registry) {
            //设置允许跨域的路径
            registry.addMapping("/**")
                    //设置允许跨域请求的域名
                    .allowedOrigins("*")
                    //这里:是否允许证书 不再默认开启
                    .allowCredentials(true)
                    //设置允许的方法
                    .allowedMethods("*")
                    //跨域允许时间
                    .maxAge(3600);
        }
        }
    

    2、cas认证失败无法重定向,前端302无法捕捉
    这里就比较复杂了,我的方案就是CAS源码竟然是无法认证直接重定向,而ajax请求又不能直接重定向,导致前端302,而302vue response拦截器是拦截不到的。所以就想到不让cas给我重定向,给我返回状态码,告诉前端认证失败,让前端直接跳转cas服务器登录地址。上代码

    修改cas源码过滤器,复制源码AuthenticationFilter这个过滤器,重写他,其实这里只改了重定向的代码其他都一样。
    在这里插入图片描述
    这个类复制出来把源码复制进去,修改图里面的位置,再把原来使用AuthenticationFilter的地方换成你新的类,这样认证失败就返回的是状态码前端可以拦截。

    这里还是贴上我cas client的一些代码

    这个是我复制源码AuthenticationFilter新建的类

    
    package com.nascent.daren.filter;
    
    import com.alibaba.fastjson.JSON;
    import com.nascent.utils.R;
    import org.jasig.cas.client.Protocol;
    import org.jasig.cas.client.authentication.AuthenticationRedirectStrategy;
    import org.jasig.cas.client.authentication.ContainsPatternUrlPatternMatcherStrategy;
    import org.jasig.cas.client.authentication.DefaultAuthenticationRedirectStrategy;
    import org.jasig.cas.client.authentication.DefaultGatewayResolverImpl;
    import org.jasig.cas.client.authentication.ExactUrlPatternMatcherStrategy;
    import org.jasig.cas.client.authentication.GatewayResolver;
    import org.jasig.cas.client.authentication.RegexUrlPatternMatcherStrategy;
    import org.jasig.cas.client.authentication.UrlPatternMatcherStrategy;
    import org.jasig.cas.client.configuration.ConfigurationKeys;
    import org.jasig.cas.client.util.AbstractCasFilter;
    import org.jasig.cas.client.util.CommonUtils;
    import org.jasig.cas.client.util.ReflectUtils;
    import org.jasig.cas.client.validation.Assertion;
    
    import javax.servlet.FilterChain;
    import javax.servlet.FilterConfig;
    import javax.servlet.ServletException;
    import javax.servlet.ServletRequest;
    import javax.servlet.ServletResponse;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import javax.servlet.http.HttpSession;
    import java.io.IOException;
    import java.io.PrintWriter;
    import java.util.HashMap;
    import java.util.Map;
    
    public class DaRenAuthenticationFilter extends AbstractCasFilter {
        /**
         * The URL to the CAS Server login.
         */
        private String casServerLoginUrl;
    
        /**
         * Whether to send the renew request or not.
         */
        private boolean renew = false;
    
        /**
         * Whether to send the gateway request or not.
         */
        private boolean gateway = false;
    
        private GatewayResolver gatewayStorage = new DefaultGatewayResolverImpl();
    
        private AuthenticationRedirectStrategy authenticationRedirectStrategy = new DefaultAuthenticationRedirectStrategy();
    
        private UrlPatternMatcherStrategy ignoreUrlPatternMatcherStrategyClass = null;
    
        private static final Map<String, Class<? extends UrlPatternMatcherStrategy>> PATTERN_MATCHER_TYPES =
                new HashMap<String, Class<? extends UrlPatternMatcherStrategy>>();
    
        static {
            PATTERN_MATCHER_TYPES.put("CONTAINS", ContainsPatternUrlPatternMatcherStrategy.class);
            PATTERN_MATCHER_TYPES.put("REGEX", RegexUrlPatternMatcherStrategy.class);
            PATTERN_MATCHER_TYPES.put("EXACT", ExactUrlPatternMatcherStrategy.class);
        }
    
        public DaRenAuthenticationFilter() {
            this(Protocol.CAS2);
        }
    
        protected DaRenAuthenticationFilter(final Protocol protocol) {
            super(protocol);
        }
    
        protected void initInternal(final FilterConfig filterConfig) throws ServletException {
            if (!isIgnoreInitConfiguration()) {
                super.initInternal(filterConfig);
                setCasServerLoginUrl(getString(ConfigurationKeys.CAS_SERVER_LOGIN_URL));
                setRenew(getBoolean(ConfigurationKeys.RENEW));
                setGateway(getBoolean(ConfigurationKeys.GATEWAY));
    
                final String ignorePattern = getString(ConfigurationKeys.IGNORE_PATTERN);
                final String ignoreUrlPatternType = getString(ConfigurationKeys.IGNORE_URL_PATTERN_TYPE);
    
                if (ignorePattern != null) {
                    final Class<? extends UrlPatternMatcherStrategy> ignoreUrlMatcherClass = PATTERN_MATCHER_TYPES.get(ignoreUrlPatternType);
                    if (ignoreUrlMatcherClass != null) {
                        this.ignoreUrlPatternMatcherStrategyClass = ReflectUtils.newInstance(ignoreUrlMatcherClass.getName());
                    } else {
                        try {
                            logger.trace("Assuming {} is a qualified class name...", ignoreUrlPatternType);
                            this.ignoreUrlPatternMatcherStrategyClass = ReflectUtils.newInstance(ignoreUrlPatternType);
                        } catch (final IllegalArgumentException e) {
                            logger.error("Could not instantiate class [{}]", ignoreUrlPatternType, e);
                        }
                    }
                    if (this.ignoreUrlPatternMatcherStrategyClass != null) {
                        this.ignoreUrlPatternMatcherStrategyClass.setPattern(ignorePattern);
                    }
                }
    
                final Class<? extends GatewayResolver> gatewayStorageClass = getClass(ConfigurationKeys.GATEWAY_STORAGE_CLASS);
    
                if (gatewayStorageClass != null) {
                    setGatewayStorage(ReflectUtils.newInstance(gatewayStorageClass));
                }
    
                final Class<? extends AuthenticationRedirectStrategy> authenticationRedirectStrategyClass = getClass(ConfigurationKeys.AUTHENTICATION_REDIRECT_STRATEGY_CLASS);
    
                if (authenticationRedirectStrategyClass != null) {
                    this.authenticationRedirectStrategy = ReflectUtils.newInstance(authenticationRedirectStrategyClass);
                }
            }
        }
    
        public void init() {
            super.init();
            CommonUtils.assertNotNull(this.casServerLoginUrl, "casServerLoginUrl cannot be null.");
        }
    
        public final void doFilter(final ServletRequest servletRequest, final ServletResponse servletResponse,
                                   final FilterChain filterChain) throws IOException, ServletException {
    
            final HttpServletRequest request = (HttpServletRequest) servletRequest;
            final HttpServletResponse response = (HttpServletResponse) servletResponse;
    
            if (isRequestUrlExcluded(request)) {
                logger.debug("Request is ignored.");
                filterChain.doFilter(request, response);
                return;
            }
    
            final HttpSession session = request.getSession(false);
            final Assertion assertion = session != null ? (Assertion) session.getAttribute(CONST_CAS_ASSERTION) : null;
    
            if (assertion != null) {
                filterChain.doFilter(request, response);
                return;
            }
    
            final String serviceUrl = constructServiceUrl(request, response);
            final String ticket = retrieveTicketFromRequest(request);
            final boolean wasGatewayed = this.gateway && this.gatewayStorage.hasGatewayedAlready(request, serviceUrl);
    
            if (CommonUtils.isNotBlank(ticket) || wasGatewayed) {
                filterChain.doFilter(request, response);
                return;
            }
    
            final String modifiedServiceUrl;
    
            logger.debug("no ticket and no assertion found");
            if (this.gateway) {
                logger.debug("setting gateway attribute in session");
                modifiedServiceUrl = this.gatewayStorage.storeGatewayInformation(request, serviceUrl);
            } else {
                modifiedServiceUrl = serviceUrl;
            }
    
            logger.debug("Constructed service url: {}", modifiedServiceUrl);
    
            final String urlToRedirectTo = CommonUtils.constructRedirectUrl(this.casServerLoginUrl,
                    getProtocol().getServiceParameterName(), modifiedServiceUrl, this.renew, this.gateway);
    
            logger.debug("redirecting to \"{}\"", urlToRedirectTo);
            PrintWriter out = response.getWriter();
    
            response.setContentType("application/json; charset=UTF-8");
            out.println(JSON.toJSONString(R.error(401,"登陆出错")));
            //this.authenticationRedirectStrategy.redirect(request, response, urlToRedirectTo);
        }
    
        public final void setRenew(final boolean renew) {
            this.renew = renew;
        }
    
        public final void setGateway(final boolean gateway) {
            this.gateway = gateway;
        }
    
        public final void setCasServerLoginUrl(final String casServerLoginUrl) {
            this.casServerLoginUrl = casServerLoginUrl;
        }
    
        public final void setGatewayStorage(final GatewayResolver gatewayStorage) {
            this.gatewayStorage = gatewayStorage;
        }
    
        private boolean isRequestUrlExcluded(final HttpServletRequest request) {
            if (this.ignoreUrlPatternMatcherStrategyClass == null) {
                return false;
            }
    
            final StringBuffer urlBuffer = request.getRequestURL();
            if (request.getQueryString() != null) {
                urlBuffer.append("?").append(request.getQueryString());
            }
            final String requestUri = urlBuffer.toString();
            return this.ignoreUrlPatternMatcherStrategyClass.matches(requestUri);
        }
    }
    

    这个是springboot配置的cas类

    import org.jasig.cas.client.session.SingleSignOutFilter;
    import org.jasig.cas.client.session.SingleSignOutHttpSessionListener;
    import org.jasig.cas.client.util.AssertionThreadLocalFilter;
    import org.jasig.cas.client.util.HttpServletRequestWrapperFilter;
    import org.jasig.cas.client.validation.Cas20ProxyReceivingTicketValidationFilter;
    import org.springframework.beans.factory.annotation.Value;
    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.security.web.authentication.logout.LogoutFilter;
    import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler;
    import org.springframework.stereotype.Component;
    
    import java.util.ArrayList;
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;
    
    @Configuration
    @Component
    public class CasConfigure {
        /**
         * cas服务端地址
         */
       // private String  casServerLoginUrl="";
        private String  casServerLoginUrl="";
        /**lo
         * 当前应用地址
         */
        @Value("${env.serverName}")
        private String serverName;
    
    
    
    
        /**
         * 该监听器用于实现单点登出功能,session失效监听器
         */
        @Bean
        public ServletListenerRegistrationBean<SingleSignOutHttpSessionListener> singleSignOutHttpSessionListener() {
            ServletListenerRegistrationBean<SingleSignOutHttpSessionListener> listener = new ServletListenerRegistrationBean<>();
            listener.setEnabled(true);
            listener.setListener(new SingleSignOutHttpSessionListener());
            listener.setOrder(1);
            return listener;
        }
    
        /**
         * 该过滤器用于实现单点登出功能,单点退出配置,一定要放在其他filter之前
         *  当调用当前应用的/logout时,该拉截器将会重定向到cas服务端的/logout请求
         */
        @Bean
        public FilterRegistrationBean logOutFilter() {
            FilterRegistrationBean filterRegistration = new FilterRegistrationBean();//new SecurityContextLogoutHandler()
            LogoutFilter logoutFilter = new LogoutFilter(casServerLoginUrl + "/logout?service=" + serverName,new SecurityContextLogoutHandler());
            filterRegistration.setFilter(logoutFilter);
            filterRegistration.setEnabled(true);
            filterRegistration.addUrlPatterns("/logout");
            filterRegistration.addInitParameter("casServerUrlPrefix", casServerLoginUrl);
            filterRegistration.addInitParameter("serverName", serverName);
            filterRegistration.setOrder(2);
            return filterRegistration;
        }
    
    
        /**
         * 该过滤器用于实现单点登出功能,当一个系统登出时,cas服务端会通知,各个应
         * 用进行进行退出操作,该过滤器就是用来接收cas回调的请求,如果是前后端分离
         * 应用,需要重写SingleSignOutFilter过滤器,按自已的业务规则去处理
         */
        @Bean
        public FilterRegistrationBean singleSignOutFilter() {
            FilterRegistrationBean filterRegistration = new FilterRegistrationBean();
            filterRegistration.setFilter(new SingleSignOutFilter());
            filterRegistration.setEnabled(true);
            filterRegistration.addUrlPatterns("/*");
            filterRegistration.addInitParameter("casServerUrlPrefix", casServerLoginUrl);
            filterRegistration.addInitParameter("serverName", serverName);
            filterRegistration.setOrder(3);
            return filterRegistration;
        }
    
        /**
         * 该过滤器负责单点登录功能,用户登录的认证工作
         * @return
         */
        @Bean
        public FilterRegistrationBean authenticationFilterRegistrationBean() {
            FilterRegistrationBean authenticationFilter = new FilterRegistrationBean();
            authenticationFilter.setFilter(new DaRenAuthenticationFilter()); //这里就是被换的类
            Map<String, String> initParameters = new HashMap<String, String>();
            initParameters.put("casServerLoginUrl", casServerLoginUrl);
           initParameters.put("ignorePattern", "/api/");
            initParameters.put("serverName",serverName);
            authenticationFilter.setInitParameters(initParameters);
            authenticationFilter.setOrder(4);
            List<String> urlPatterns = new ArrayList<String>();
            urlPatterns.add("/*");
            authenticationFilter.setUrlPatterns(urlPatterns);
            return authenticationFilter;
        }
    
        /**
         * 该过滤器用于单点登录功能,负责对Ticket的校验工作
         * @return
         */
        @Bean
        public FilterRegistrationBean ValidationFilterRegistrationBean(){
            FilterRegistrationBean authenticationFilter = new FilterRegistrationBean();
            authenticationFilter.setOrder(5);
            authenticationFilter.setFilter(new Cas20ProxyReceivingTicketValidationFilter());
    
            Map<String, String> initParameters = new HashMap<>();
            initParameters.put("casServerUrlPrefix", casServerLoginUrl);
            initParameters.put("serverName", serverName);
            authenticationFilter.setInitParameters(initParameters);
    
            List<String> urlPatterns = new ArrayList<String>();
            urlPatterns.add("/*");
            authenticationFilter.setUrlPatterns(urlPatterns);
            return authenticationFilter;
        }
    
    
        /**
         * 该过滤器用于单点登录功能 ,对HttpServletRequest请求包装, 可通过HttpServletRequest的getRemoteUser()方法获得登录用户的登录名
         * @return
         */
    
        @Bean
        public FilterRegistrationBean casHttpServletRequestWrapperFilter(){
            FilterRegistrationBean authenticationFilter = new FilterRegistrationBean();
            authenticationFilter.setFilter(new HttpServletRequestWrapperFilter());
            authenticationFilter.setOrder(6);
            List<String> urlPatterns = new ArrayList<String>();
            urlPatterns.add("/*");
            authenticationFilter.setUrlPatterns(urlPatterns);
            return authenticationFilter;
        }
    
        /**
         * 该过滤器使得可以通过org.jasig.cas.client.util.AssertionHolder来获取用户的登录名。
         比如AssertionHolder.getAssertion().getPrincipal().getName()。
         这个类把Assertion信息放在ThreadLocal变量中,这样应用程序不在web层也能够获取到当前登录信息
         * @return
         */
        @Bean
        public FilterRegistrationBean casAssertionThreadLocalFilter(){
            FilterRegistrationBean authenticationFilter = new FilterRegistrationBean();
            authenticationFilter.setFilter(new AssertionThreadLocalFilter());
            authenticationFilter.setOrder(7);
            List<String> urlPatterns = new ArrayList<>();
            urlPatterns.add("/*");
            authenticationFilter.setUrlPatterns(urlPatterns);
            return authenticationFilter;
        }
    }
    

    到这里前端就可以捕捉跳转cas服务器的登录地址。
    这里一定要注意前端跳转指定Cas回调地址必须是后端地址,因为cas服务器返回的票据还需要后端验证。这样session cookie才会正确。那么后台还得提供一个ValidateController里面在跳转前端服务器地址。
    上代码
    前端拦截器

    axiosInstance.interceptors.response.use(response => {
        let status = response.data.code;
        let url =  "http://cas服务登录地址/?service=回调后端控制器/daren/checkToken";
        if(status ===401){
            window.location.href = url;
            return
        }
        }
    

    后台控制器

    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.RequestMapping;
    
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;
    
    @Controller
    @RequestMapping("/daren")
    public class ValidateController {
        @RequestMapping("/checkToken")
        public void index(HttpServletRequest request, HttpServletResponse response) throws IOException {
            response.sendRedirect(”前端服务器地址");//
        }
    }
    

    好了整个流程就可以走通了。

    需要注意的点

    1、所有前后端地址,如果是本地调试,需要统一,都是用ip那么地址都配置成ip,是localhost就都是localhost
    2、重要点还是在改掉原来的cas重定向的逻辑。理解了就知道怎么弄了。

    展开全文
  • Springboot前后端分离实现CAS单点登录

    千次阅读 热门讨论 2020-03-20 19:40:14
    springboot前后端分离实现cas单点登录1.客户端构建1.1pom依赖1.2yml配置1.3后端代码1.4页面2.问题记录2.1在前后端分离情况下,AuthenticationFilter重定向问题,导致前端发生跨域2.2AuthenticationFilter自定义...

    1.CAS服务端构建

    1.1war包部署

    cas5.3版本
    https://github.com/apereo/cas-overlay-template
    构建完成后将war包部署到tomcat即可
    在这里插入图片描述

    1.2配置文件修改

    支持http协议
    修改apache-tomcat-8.5.53\webapps\cas\WEB-INF\classes\services目录下的HTTPSandIMAPS-10000001.json,在serviceId中添加http即可

    {
      "@class" : "org.apereo.cas.services.RegexRegisteredService",
      "serviceId" : "^(https|http|imaps)://.*",
      "name" : "HTTPS and IMAPS",
      "id" : 10000001,
      "description" : "This service definition authorizes all application urls that support HTTPS and IMAPS protocols.",
      "evaluationOrder" : 10000
    }
    

    apache-tomcat-8.5.53\webapps\cas\WEB-INF\classesapplication.properties添加配置

    cas.tgc.secure=false
    cas.serviceRegistry.initFromJson=true
    

    配置默认登录用户名密码及登出重定向
    修改apache-tomcat-8.5.53\webapps\cas\WEB-INF\classesapplication.properties配置

    cas.authn.accept.users=admin::admin
    
    #配置允许登出后跳转到指定页面
    cas.logout.followServiceRedirects=true
    

    1.3启动

    在这里插入图片描述
    在这里插入图片描述

    1.客户端构建

    1.1pom依赖

    <dependency>
        <groupId>net.unicon.cas</groupId>
        <artifactId>cas-client-autoconfig-support</artifactId>
        <version>2.3.0-GA</version>
    </dependency>
    

    1.2yml配置

    client-host-url配置的地址和前端ajax调用的地址必须一致,统一使用ip:port或hostname:port;如果本地后端配置localhost,前端使用ip,会造成Ticket验证失败

    cas:
      server-url-prefix: http://172.19.25.113:8080/cas
      server-login-url: http://172.19.25.113:8080/cas/login
      client-host-url: http://172.19.25.113:1010
      validation-type: cas
      use-session: true
      authentication-url-patterns:
        /auth
    

    1.3后端代码

    启动类添加@EnableCasClient注解

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

    自定义AuthenticationFilter重定向策略

    public class CustomAuthRedirectStrategy implements AuthenticationRedirectStrategy {
    
        @Override
        public void redirect(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, String s) throws IOException {
            httpServletResponse.setCharacterEncoding("utf-8");
            httpServletResponse.setContentType("application/json; charset=utf-8");
            PrintWriter out = httpServletResponse.getWriter();
            out.write("401");
        }
    }
    

    Cors及CasClient相关filter初始化参数配置

    @Configuration
    public class CasAuthConfig extends CasClientConfigurerAdapter {
    
        @Override
        public void configureAuthenticationFilter(FilterRegistrationBean authenticationFilter) {
            Map<String, String> initParameters = authenticationFilter.getInitParameters();
            initParameters.put("authenticationRedirectStrategyClass", "cc.jasonwang.springbootcasdemo.config.CustomAuthRedirectStrategy");
        }
    
        @Override
        public void configureValidationFilter(FilterRegistrationBean validationFilter) {
            Map<String, String> initParameters = validationFilter.getInitParameters();
            initParameters.put("encodeServiceUrl", "false");
        }
    
        @Bean
        public FilterRegistrationBean corsFilter() {
            UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
            CorsConfiguration config = new CorsConfiguration();
            config.setAllowCredentials(true);
            config.addAllowedOrigin("*");
            config.addAllowedHeader("*");
            config.addAllowedMethod("*");
            source.registerCorsConfiguration("/**", config);
            FilterRegistrationBean<CorsFilter> registrationBean = new FilterRegistrationBean<>();
            registrationBean.setFilter(new CorsFilter(source));
            registrationBean.setOrder(-2147483648);
            return registrationBean;
        }
    }
    

    Controller

    @RestController
    public class HelloController {
    
        @Value("${cas.server-url-prefix}")
        private String casServerUrlPrefix;
    
        @GetMapping("/auth")
        public void auth(HttpServletRequest request, HttpServletResponse response, HttpSession session) {
            Assertion assertion = (Assertion) session.getAttribute("_const_cas_assertion_");
            response.setHeader("Content-type", "application/json;charset=UTF-8");
            response.setCharacterEncoding("utf-8");
            response.setStatus(200);
            if (assertion != null) {
                String redirectUrl= request.getParameter("redirectUrl");
                try {
                    response.setHeader("Content-type", "text/html;charset=UTF-8");
                    response.sendRedirect(redirectUrl);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            } else {
                try {
                    response.getWriter().print("401");
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    
        @GetMapping("/logout")
        public RedirectView logout(HttpServletRequest request, HttpServletResponse response, HttpSession session) {
            session.invalidate();
            String indexPageUrl = "http://127.0.0.1";
            return new RedirectView( casServerUrlPrefix + "/logout?service=" + indexPageUrl);
        }
    }
    

    1.4页面

    <!DOCTYPE html>
    <html lang="en" dir="ltr">
      <head>
        <meta charset="utf-8">
        <title></title>
      </head>
      <body>
          <span>单点地址:</span><input class="url" type="text"/><br>
          <button type="button" class="button">登录</button><br>
          <div class="response" style="width: 200px;height:200px;border: 1px solid #3333;"></div>
         <script src="https://code.jquery.com/jquery-3.1.1.min.js"></script>
          <script type="text/javascript">
            $(".button").click(function(){
              $.get("http://172.19.25.113:1010/auth", function(data){
                $(".response").text(data)
                if(data == 401){
                  window.location.href = "http://localhost:8080/cas/login?service=http://172.19.25.113:1010/auth?redirectUrl=http://127.0.0.1"
                }
              })
            })
          </script>
      </body>
    </html>
    

    在这里插入图片描述
    在这里插入图片描述
    这里只是验证前后端分离下页面url跳转问题,页面没有放在nginx服务上
    在这里插入图片描述

    2.问题记录

    2.1在前后端分离情况下,AuthenticationFilter重定向问题,导致前端发生跨域

    一个关于单点登录框架CAS在前后端分离下的解决方案

    (1)描述
    cas前后端不分离的情况下是能够直接跳转的,然而前后端分离后,前端ajax访问后端在经过AuthenticationFilter时,验证未登录会重定向到CAS登录,导致前端发生跨域问题

    (2)解决思路
    在AuthenticationFilter中不进行重定向,验证未登录就直接返回一个错误状态码;由前端获取到状态码后进行判断,再跳转到CAS登录地址
    AuthenticationFilter

    public final void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest)servletRequest;
        HttpServletResponse response = (HttpServletResponse)servletResponse;
        if (this.isRequestUrlExcluded(request)) {
            this.logger.debug("Request is ignored.");
            filterChain.doFilter(request, response);
        } else {
        	// 获取Assertion 验证是否登录
            HttpSession session = request.getSession(false);
            Assertion assertion = session != null ? (Assertion)session.getAttribute("_const_cas_assertion_") : null;
            if (assertion != null) {
                filterChain.doFilter(request, response);
            } else {
                String serviceUrl = this.constructServiceUrl(request, response);
                String ticket = this.retrieveTicketFromRequest(request);
                boolean wasGatewayed = this.gateway && this.gatewayStorage.hasGatewayedAlready(request, serviceUrl);
                if (!CommonUtils.isNotBlank(ticket) && !wasGatewayed) {
                    this.logger.debug("no ticket and no assertion found");
                    String modifiedServiceUrl;
                    if (this.gateway) {
                        this.logger.debug("setting gateway attribute in session");
                        modifiedServiceUrl = this.gatewayStorage.storeGatewayInformation(request, serviceUrl);
                    } else {
                        modifiedServiceUrl = serviceUrl;
                    }
    
                    this.logger.debug("Constructed service url: {}", modifiedServiceUrl);
                    String urlToRedirectTo = CommonUtils.constructRedirectUrl(this.casServerLoginUrl, this.getProtocol().getServiceParameterName(), modifiedServiceUrl, this.renew, this.gateway);
                    this.logger.debug("redirecting to \"{}\"", urlToRedirectTo);
                    // 通过这个方法进行重定向
                    this.authenticationRedirectStrategy.redirect(request, response, urlToRedirectTo);
                } else {
                    filterChain.doFilter(request, response);
                }
            }
        }
    }
    

    DefaultAuthenticationRedirectStrategy

    public final class DefaultAuthenticationRedirectStrategy implements AuthenticationRedirectStrategy {
        public DefaultAuthenticationRedirectStrategy() {
        }
    
        public void redirect(HttpServletRequest request, HttpServletResponse response, String potentialRedirectUrl) throws IOException {
        	//response重定向
            response.sendRedirect(potentialRedirectUrl);
        }
    }
    

    (3)实现
    自定义重定向策略,将DefaultAuthenticationRedirectStrategy替换掉
    CustomAuthRedirectStrategy

    public class CustomAuthRedirectStrategy implements AuthenticationRedirectStrategy {
        @Override
        public void redirect(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, String s) throws IOException {
            httpServletResponse.setCharacterEncoding("utf-8");
            httpServletResponse.setContentType("application/json; charset=utf-8");
            PrintWriter out = httpServletResponse.getWriter();
            out.write("401");
        }
    }
    
    @Configuration
    public class CasAuthConfig extends CasClientConfigurerAdapter {
    
        @Override
        public void configureAuthenticationFilter(FilterRegistrationBean authenticationFilter) {
            Map<String, String> initParameters = authenticationFilter.getInitParameters();
            initParameters.put("authenticationRedirectStrategyClass", "cc.jasonwang.springbootcasdemo.config.CustomAuthRedirectStrategy");
    	}
    }
    

    2.2AuthenticationFilter自定义重定向策略实现后,前端仍然发生跨域问题

    Spring 里那么多种 CORS 的配置方式,到底有什么区别

    (1)描述
    原使用WebMvcConfigurationSupport实现CORS,AuthenticationFilter输出状态码后,前端仍然发生跨域问题

    @Configuration
    public class CorsConfig extends WebMvcConfigurationSupport {
    
        @Override
        public void addCorsMappings(CorsRegistry registry) {
            registry.addMapping("/**")
                    .allowedOrigins("*")
                    .allowedHeaders("*")
                    .allowedMethods("*")
                    .maxAge(3600)
                    .allowCredentials(true);
        }
    }
    

    (2)解决思路
    通过查找资料发现:

    • 实现 WebMvcConfigurationSupport .addCorsMappings 方法来进行的 CORS 配置,最后会在 Spring 的 Interceptor 或 Handler 中生效
    • 注入 CorsFilter 的方式会让 CORS 验证在 Filter 中生效
      在这里插入图片描述

    (3)实现
    修改CORS实现方式

    @Bean
    public FilterRegistrationBean corsFilter() {
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        CorsConfiguration config = new CorsConfiguration();
        config.setAllowCredentials(true);
        config.addAllowedOrigin("*");
        config.addAllowedHeader("*");
        config.addAllowedMethod("*");
        source.registerCorsConfiguration("/**", config);
        FilterRegistrationBean<CorsFilter> registrationBean = new FilterRegistrationBean<>();
        registrationBean.setFilter(new CorsFilter(source));
        registrationBean.setOrder(-2147483648);
        return registrationBean;
    }
    

    2.3前端跳转CAS登录并传递redirectUrl参数,Ticket票据验证问题

    (1)原因
    Cas20ProxyReceivingTicketValidationFilter在进行Ticket验证时,CAS重定向的service地址进行了URLEncoder编码,而CAS使用Ticket获取到存储的service地址未进行编码,导致两个service不一致,造成Ticket票据验证失败
    在这里插入图片描述
    (2)debug定位问题

    AbstractTicketValidationFilter
    在这里插入图片描述
    AbstractUrlBasedTicketValidator
    在这里插入图片描述

    找到CAS服务器接口地址后,便想到在CAS服务器端看下接口是怎么实现的,下面就是在CAS服务器debug后的结果

    CAS Server
    在web.xml中找到了servlet映射
    在这里插入图片描述
    定位到SafeDispatcherServlet,根据目录结构和类文件名称找到了ServiceValidateController
    在这里插入图片描述

    ServiceValidateController
    在这里插入图片描述
    CentralAuthenticationServiceImpl
    在这里插入图片描述
    ServiceTicketImpl
    在这里插入图片描述
    AbstractWebApplicationService
    在这里插入图片描述
    (3)实现
    对Cas20ProxyReceivingTicketValidationFilter添加encodeServiceUrl=false初始化参数

    @Configuration
    public class CasAuthConfig extends CasClientConfigurerAdapter {
    
        @Override
        public void configureAuthenticationFilter(FilterRegistrationBean authenticationFilter) {
            Map<String, String> initParameters = authenticationFilter.getInitParameters();
            initParameters.put("authenticationRedirectStrategyClass", "cc.jasonwang.springbootcasdemo.config.CustomAuthRedirectStrategy");
        }
    
        @Override
        public void configureValidationFilter(FilterRegistrationBean validationFilter) {
            Map<String, String> initParameters = validationFilter.getInitParameters();
            initParameters.put("encodeServiceUrl", "false");
        }
    }
    

    3.参考

    展开全文
  • 前后端分离模式下 CAS 单点登录实现方案前言知识点前后端分离单点登录CAS用户登录登录用户访问其他资源CAS ServerCAS ClientTicket Grangting Ticket(TGT)Ticket-granting cookie(TGC)Service ticket(ST)...

    前言

    前段时间一直在调研的前后端分离单点登录认证方案终于以 Demo 的形式实现了,期间如其他前后端开发者们一样——踩过不少坑。虽然目前的方案不能说是尽善尽美,也算勉强达到了项目要求,故在此处记录一下相关情况以备日后查阅。

    知识点

    开始之前需要先了解一些相关的知识与概念,包括单点登录,CAS,前后端分离等。

    前后端分离

    前后端分离已在互联网项目开发业界进行了广泛应用,通过前端应用与后端服务的分布式部署可以有效进行解耦,将数据与展现彻底分离,既保证了数据安全,也给了前端开发充分的自由。

    前后端分离最常见的实现方式之一是前端 HTML 页面通过 AJAX 调用后端的 RESTFUL API 接口并使用 JSON 数据进行交互(这种方式也为单点登录方案的实现挖了个大坑)。

    单点登录

    单点登录(Single Sign On),简称为 SSO,是目前比较流行的企业业务整合的解决方案之一。SSO的定义是在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统。

    CAS

    在这里插入图片描述

    用户登录

    1. 用户通过浏览器发送请求访问 CAS Client 资源
    2. CAS Client 发现用户请求中未包含 ST 票据,将浏览器重定向到 CAS Server,此时 URL 中会携带名为 service 的参数,参数值是用户要访问的客户端资源地址
    3. CAS Server 对访问的用户是否携带 TGC 进行验证,若未携带则跳转到 CAS 统一的登录页面
    4. 用户登录后,CAS Server 将浏览器重定向到之前 service 参数值指向的客户端地址(URL 的最后会增加 st 参数,CAS Client 可将 ST 保存起来),同时生成 TGC 写入浏览器中
    5. 由于此次重定向携带了 ST,CAS Client 会向 CAS Server 发送验证请求
    6. CAS Server 验证通过,用户可以正常访问资源
    7. 此时浏览器已与 CAS Client 建立会话,若 CAS Client 保存了 ST,后续请求通过会话即可调取 ST 并与 CAS Server 进行验证

    已登录用户访问其他资源

    1. 用户访问未建立会话的 CAS Client 资源
    2. CAS Client 需要 ST 进行验证,将浏览器重定向到 CAS Server
    3. 用户访问 CAS Server,CAS Server 发现用户有 TGT,签发一个 ST,返回给用户浏览器并重定向到 CAS Client
    4. CAS Client 发现有 ST 去 CAS Server(CAS Client 可将 ST 保存起来) 验证,验证通过后,允许用户访问资源
    5. 此时浏览器已与 CAS Client 建立会话,若 CAS Client 保存了 ST,后续请求通过会话即可调取 ST 并与 CAS Server 进行验证

    CAS Server

    CAS Server(CAS服务端)负责完成对用户的认证工作,完成与浏览器端的用户认证和CAS客户端的票据验证。

    CAS Client

    CAS Client(CAS客户端)负责处理对受保护资源的访问请求,需要对请求方进行身份认证时,重定向到 CAS Server 进行认证。 CAS Client 与受保护的客户端应用部署在一起,以 Filter 方式保护受保护的资源。

    Ticket Grangting Ticket(TGT)

    TGT 是 CAS 为用户签发的登录票据,拥有了 TGT,用户就可以证明自己在CAS成功登录过。 TGT 封装了 Cookie 值以及此 Cookie 值对应的用户信息。用户在 CAS 认证成功后,CAS 生成 cookie(叫TGC),写入浏览器,同时生成一个 TGT 对象,放入自己的缓存,TGT 对象的 ID 就是 cookie 的值。 当 HTTP 再次请求到来时,如果传过来的有 CAS 生成的 cookie,则 CAS 以此 cookie 值为 key 查询缓存中有无 TGT,如果有,说明用户之前登录过,如果没有,则用户需要重新登录。

    Ticket-granting cookie(TGC)

    存放用户身份认证凭证的 cookie,在浏览器和 CAS Server 间通讯时使用,并且只能基于安全通道传输(Https),是 CAS Server 用来明确用户身份的凭证。

    Service ticket(ST)

    服务票据,服务的惟一标识码 , 由 CAS Server 发出( Http 传送),用户访问 Service 时,Service 发现用户没有 ST,则要求用户去 CAS 获取 ST。

    存在的问题

    四方认证与Ajax

    前文已经介绍了 CAS 认证的过程,可以看出 CAS 的认证基于会话(即浏览器与服务器之间的 Session),因此终端、CAS 客户端与 CAS 服务端会组成一个三方的认证系统。登录之后的浏览器会在 CAS Server 的域名下存放 cookie,用于浏览器和 CAS Server 之间验证是否登录;而在访问 CAS Client 资源时则会在 Client 的域名下存放一个 cookie,用于下次访问资源时调取 ST 与 CAS Server 进行验证。

    现在问题出现了。当前端与后端分离时,原本的 CAS Client 就不再是一方了,而是变成了两方,于是三方认证也成了四方认证。 如果是单纯的变成了两方也并没有离开 CAS 的认证框架,无非是多一个 CAS Client 罢了,然而前端常用的 Ajax 请求恰好无法处理 CAS 中最常见的重定向操作。这样一来,包括首次登录、登录成功后返回 ST、认证登录等一系列的逻辑似乎都没有办法继续进行了。

    微服务架构下的认证

    另一个尚未解决的问题就是微服务架构带来的认证问题。对于前端访问多个 CAS Client 时需要携带 ST 的需求,目前尚未设计出较好的解决方案,此问题还有待后续研究。

    解决方案

    前面的问题网上出现了众多解决办法,在此处仅记录我自己实现的确实可行的一个设计方案。

    后端(CAS Client 客户端)

    跳转页面的 Controller

    首先后端需要增加一个专门用来跳转页面的 Controller,只需能实现根据传入的参数(要跳转的URL)跳转到对应的页面即可。这个跳转的作用主要在于认证通过后返回前端页面,并建立会话,同时需要将会话的 JSESSIONID 放在 URL 中。

    重定向改为返回 JSON

    在尽量减少侵入的原则下,不对 CAS 本身的代码进行修改,而是在认证过滤之前增加一个自定义的过滤器,将原有的返回 302 重定向状态改为返回 JSON 数据。返回的数据应包括 CAS Client 中已定义的跳转 Controller 地址,用于认证通过后返回到跳转页面的方法并跳回前端页面。

    前端

    处理未登录状态码

    前端可以封装一个发送请求并接收返回值的组件,用于拦截所有的返回结果。与后端约定判断返回的状态码,若需要跳转 CAS Server,则保存当前浏览器地址,向 CAS Server 发送 service 参数,其值为 Client 中跳转页面用的 Controller 地址并向其传入返回前端页面的参数:url。

    以 axios 封装的组件为例:

    service.interceptors.response.use(
      response => {
        if (String(response.data.returnCode) === '401') {
          // 获取当前浏览器地址,用于后端回调
          const href = window.location.href
          // returnData 存放了用于跳转页面的 Controller 地址,最后 url 参数中是要返回的前端地址
          window.location.href = 'http://cas.server.com:8443/cas/login?service=' + encodeURIComponent(response.data.returnData) + '?url=' + encodeURIComponent(encodeURIComponent(href))
        }
        return response.data
      },
      error => {
        NProgress.done()
        return Promise.reject(error)
      }
    )
    

    获取并保存 Client 返回的 JSESSIONID

    登录认证成功后,Client 返回前端时在浏览器地址中会携带 JSESSIONID 参数,前端获取后需要手动存放在 cookie 中,下次请求 Client 资源时将自动携带,Client 获取到 JSESSIONID 后即可取得会话并进行认证。 以 vue-router 的导航钩子为例:

    router.beforeEach((to, from, next) => {
      // 路由导航钩子函数中可进行处理
      if (to.query && to.query.JSESSIONID) {
        Cookies.set('JSESSIONID', to.query.JSESSIONID)
        // 去掉浏览器地址栏中的 JSESSIONID 参数
        delete to.query.JSESSIONID
        next(to)
      }
    })
    

    整体流程设计

    下面记录一下整体的设计流程:

    1. 浏览器访问子系统 A 前端页面,前端向 Client 获取用户信息
    2. Client 发现请求中没有会话ID,返回 401 及用于跳转页面的 Controller 的地址
    3. 前端发现 401,将浏览器重定向到 CAS Server 的登录页,后面带上 service 参数,service 即为 Client 回传的 Controller 地址,同时向其中传入一个 url 参数,用于返回前端页面
    4. CAS Server 登录,登录成功后根据 service 参数返回 Client 中的 Controller
    5. Client 接收 url 参数,同时将 JSESSIONID 拼在 url 最后,通过跳转回传给前端
    6. 前端接收 JSESSIONID 并存放在其自身域的 cookie 中,后续请求均携带 cookie
    7. 另一子系统 B 前端访问 Client2 时,Client2 发现无会话,同样返回 401
    8. 前端跳转 CAS Server 进行登录认证(携带 service 及 url 两个参数),CAS 发现已登录,直接跳转回到 service 中
    9. Client2 根据 url 跳转回前端 B,并在地址栏增加 JSESSIONID 参数
    10. 前端 B 接收 JSESSIONID 并存放在 B 域的 cookie 中,后续请求均携带 cookie

    实施

    在开发过程中也存在一些具体的坑,此处只记录遇到并解决的坑以及留下的坑。

    遇到的问题

    每次访问 CAS Server 生成不同的 JSESSIONID

    开发期间发现了 CAS Server 登录后无法保存登录状态的问题,即下次访问 CAS Server 时仍然会被认为未登录,而跳转登录页。经仔细排查发现每次访问 CAS Server 时都会生成不同的 JSESSIONID,即每次访问都会创建新的会话。 此时应排查 Request 中是否携带了 cookie(其中包含 JSESSIONID),若未携带,如果使用axios封装了请求组件,可以加上配置:

    axios.defaults.withCredentials = true
    

    若发现 Request 中已携带了 cookie,而 JSESSIONID 仍然会变,可尝试为 CAS Server 设置一个域名解决问题。

    多个窗口访问不同子系统

    对于同一个平台下的多个子系统,如果都采用前后端分离的方式,打通了 Client1 的登录认证之后,如何让 Client2 不需要登录直接访问呢?

    此处常见的解决办法之一是前端通过 iframe 手动将 JSESSIONID 写入每个子系统域下的 cookie 中。子系统每次调用接口时只需将自己保存的 JSESSIONID 带上,即可保证认证通过。

    存在的缺陷

    前端只能访问单一的 CAS Client

    由于采用了会话机制,目前实现的版本一个前端只能访问对应的一个 CAS Client 资源,对于在同一个前端访问微服务架构多个服务的情况尚未有解决方案。因此只能设计成每个前端访问自己对应的后端。

    总结

    通过此次调研,基本可以认为基于 CAS 的单点登录认证不适用于前后端分离架构,因为基于会话的单点登录对前后端分离架构天生不友好。大部分前后端分离架构最终实现单点登录都是通过共享 session,这在严格意义上说或许不能算是完善的单点登录解决方案。

    参考资料

    CAS原理

    前后端分离

    CAS Restful

    OAuth

    展开全文
  • 一、搭建CAS5.2服务端 1.1、cas服务端搭建 可以参考文档【别人整理好的不必重复整理】:https://blog.csdn.net/oumuv/article/details/84306361 但是需要注意的是连接数据库时需要在cas5.2服务端的pom.xml文件中...

    一、搭建CAS5.2服务端


    1.1、cas服务端搭建

    可以参考文档【别人整理好的不必重复整理】:https://blog.csdn.net/oumuv/article/details/84306361
    但是需要注意的是连接数据库时需要在cas5.2服务端的pom.xml文件中导入如下两个依赖:

    	<dependencies>
            <dependency>
                <groupId>org.apereo.cas</groupId>
                <artifactId>cas-server-support-jdbc</artifactId>
                <version>5.2.0</version>
            </dependency>
    
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <version>5.1.24</version>
            </dependency>
        </dependencies>
    

    重新maven clean package
    在windows环境下build.cmd run 运行cas服务端

    二、后端配置


    2.1导入cas客户端依赖

    在springboot项目pom.xml文件加入依赖

    <dependency>
        <groupId>net.unicon.cas</groupId>
        <artifactId>cas-client-autoconfig-support</artifactId>
        <version>2.3.0-GA</version>
    </dependency>
    
    

    在resources目录下的yml文件中添加配置

    cas:
      server-url-prefix: http://172.19.25.113:8080/cas
      server-login-url: http://172.19.25.113:8080/cas/login
      client-host-url: http://172.19.25.113:1010
      validation-type: cas
      use-session: true
      authentication-url-patterns:
        /auth
    
    

    记录我们项目中的配置

    cas:
      server-url-prefix: "http://cas.server.com:8443/cas"
      server-login-url: "http://cas.server.com:8443/cas/login"
      client-host-url: "http://cas.client1.com:8080"
      validation-type: cas
      use-session: true
      validation-type:cas
    casClientLogoutUrl: "http://cas.server.com:8443/cas/logout?service=http"//cas.client1.com:8081/logout/sucess"
    
    

    而前端请求的cas登录地址是

    http://cas.server.com:8443/cas/login?service=http://cas.client1.com:8080/caslogin
    

    这里配置了nginx代理

    server{
    	listen 8080;
    	location /restful{
    		proxy_pass http:127.0.0.1:8081;后端请求地址
    		client_max_body_size 40m;
    	}
    	location /resource{
    		proxy_pass http:127.0.0.1:8081/resource;
    	}
    	location / {
    		proxy_pass http:127.0.0.1:8079;前端请求地址
    		client_max_body_size 40m;
    	}
    	location /caslogin{
    		proxy_pass http:127.0.0.1:8081/caslogin;
    	}
    	location /casjump{
    		proxy_pass http:127.0.0.1:8081/casjump;
    		
    	}
    }
    

    sprinbgoot后端配置,添加开启客户端注解

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

    之所以项目中采用client-host-url端口号是8080而springboot服务端是8081是因为nginx配置了代理
    相当于前端请求http://cas.server.com:8443/cas/login?service=http://cas.client1.com:8080/caslogin会代理到
    http://cas.server.com:8443/cas/login?service=http://cas.client1.com:8081/caslogin而cas认证成功之后
    请求controller /caslogin进行重定向到前端页面http:/127.0.0.1:8079/#/jump,然后前端发送请求请求 /casjump执行shiro的认证流程

    @Controller
    public class CASController extends AbstractRestController{
    	@Autowired
    	private RoleService roleService;
    	@Autowired
    	private PermissionService permissionService;
    	@Autowired
    	private ISysOrganizationRepository sysOrganizationRepository;
    	@Autowired
    	private ISysUserRepository sysUserRepository;
    
    	@Value("${casClientLogoutUrl}")
    	private String cilentLogoutUrl;
    
    	@RequestMapping("/caslogin")
    	public String UserLogin(HttpServletRequest request ,Model model){
    		sout(request.getUserPrincipal().getName());//拿到登录的用户名
    		return "redirect:http://cas.client1.com:8080/#/jump";
    	}
    
    	@RequestMapping("/casjump")
    	public @ResponseBody ResponseEntity<GetLoginWebResponse> UserJump(HttpServletRequest request){
    		String policeNo = request.getUserPrincipal().getName();
    		//查询数据库获取用户信息,此处代表CAS已经认证通过
    		Subject subject = SecurityUtils.getSubject();
    		UsernamePasswordToken token = new UsernamePasswordToken ("用户名","密码");
    		//执行shiro认证
    		subject.login(token)
    		.......
    	}
    
    
    
    	
    }
    
    
    @Configuration
    public class CASAutoConfig{
    	@Value("${cas.server-url-prefix}")
    	private String serverUrlPrefix;
    	@Value("${cas.server-login-url}")
    	private String serverLoginUrl;
    	@Value("${cas.client-host-url}")
    	private String clientHostUrl;
    
    	//授权过滤器
    	@Bean
    	public FilterRegistrationBean filterAuthenticationRegistration(){
    		FilterRegistrationBean reg = new FilterRegistrationBean ();
    		reg.setFilter(new AuthenticationFilter());
    		//设定匹配路径
    		reg.addUrlPatterns("/*");
    		Map<String,String> init = new HashMap<~>();
    		init.put("casServerLoginUrl",serverUrlPrefix);
    		init.put("serverName",clientHostUrl);
    		//忽略url,"|"分隔多个url
    		init.put("ignorePattern","/logout/success|/index|/12345|/restful/*");
    		reg.setInitParameters(init);
    		//设定加载顺序
    		reg.setOrder(1);
    		return reg;
    
    
    	}
    
    
    }
    

    总结:最后效果是既能用shiro认证登录又可以用CAS统一登录

    展开全文
  • 由于前后端分离的项目自己需要维护会话,故不能通过官方提供的配置实现,本文将聊聊具体如何实现前后端分离项目使用CAS单点登录认证。 CAS的简单了解 票据 TGC Ticket Granting Cookie,TGC与TGT相当于sessionId与...
  • 主要为大家详细介绍了Shiro+Cas微服务化及前后端完全分离,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
  • CAS单点登录前后端分离方案

    千次阅读 2020-09-19 01:28:30
    目录1 cas原理及概念2 cas服务搭建(cas5.3.2)2.1 骨架搭建2.1.1 下载cas2.1.2 添加域名映射2.1.3 tomcat的https访问2.1.4 启动CAS服务2.1.5 使用Overlay生成真正有用的服务端2.2 记住我2.2 自定义Credential、...
  • 目前越来越多的系统采用前后端分离技术来进行构建,这类系统需要接入CAS单点登录系统,是否有很好的解决方案呢? 前后端分离之后,前端如何保存数据呢?一般前端的本地存储方式有:localStorage、sessionStorage、...
  • CAS单点登录(十三)——客户端前后端分离接入

    万次阅读 多人点赞 2019-07-07 09:02:14
    最近在工作上遇到CAS前后端分离接入,后端是使用的GO,前端则是Ant Design,...前面一些列的文章介绍CAS,具体原理我就住在这里复述了,如果读者还不太熟悉原理,可以去翻翻前面的文章——CAS单点登录(一)——初识S...
  • 以前我们前后端分离,使用shiro对页面进行权限验证做跳转处理 现在我们使用vue + springboot 前后端分离 所有权限验证都交给前台了,那么后台还需要权限框架做处理吗? 后台是不是只管做数据处理...
  • 要实现单点登录的重点其实就是将 账号密码的验证 给独立出来当做一个独立的项目,只需要在此项目上一次通过验证即可。 而此项目不需要自己从头来写,已经有很多开源的实现。这里使用的是CAS 使用 CAS+Shiro 之前...
  • SSO(Single Sing On)单点登录是一种架构,一种思想。 CAS(Center Authentication Server)中心授权服务 是一个开源的协议,是SSO的一种具体的实现。当然SSO还有其他的实现,比如Cookier同域名的场景。 Auth是一种...
  • springboot+vue+cas前后端分离实现单点登录 提示:cas服务已经搭建完毕 文章目录前言一、CAS是什么?二、搭建客户端系统1.引入CAS2.客户端后端搭建总结 前言 什么是单点登录单点登录全称Single Sign On(以下...
  • 需求 根据领导要求集成cas 客户端到我的服务... ... 原理 官方cas单点登陆时序图 思路 实现 基础变量 public String casServerUrl = "https://xxx/cas/login"; public String casServerLogoutUrl = "https://xxx/cas/
  • shiro+cas 前后端分离 单点登录 解决方案写在前面主要的重点cas服务cas客户端 写在前面 在设计开发自己的博客系统时,选择cas+shiro做用户和鉴权的框架。 曾经在徐州客户现场被cas坑过,一直想把cas啃下来。原谅我是...
  • 前后端分离(Vue + SpringBoot)整合CAS单点登录

    千次阅读 热门讨论 2019-12-17 16:18:00
    前方高能→前端配合修改小哥吐血鼎力支持,博客:http://www.musheng.art/blogs/tech/sso/CAS认证前后端分离单点登录调研.html#实施 公司项目都是前端Vue 后端SpringBoot的前后端分离架构,尝试过SpringBoot整合CAS...
  • cas + shiro 前后端分离单点登录后从定向问题问题描述 问题描述 再未认证状态下,请求需要认证的资源 当接口为get请求时,再cas登录后会重定向到get请求的接口,类似浏览器直接访问get请求接口 如图 当改为post请求...
  • XMLHttpRequest Level 2 使用指南 跨源通信 :描述前端跨域的各种方法; 什么是SSO? 单点登录(SSO)的设计
  • 【Django基础】7、django+vue前后端分离项目,接入cas单点登录
  • ),并且其官方的实例和网上的实例教程都是基于传统的项目(也就是非前后端分离项目),因此对于前后端分离的项目集成CAS的可参考的资料就比较少,于是就有了下面的前后端分离项目基于CAS单点登录单点登出流程图...
  • 前后端分离单点登录

    千次阅读 2019-04-02 18:50:38
    单点登录基于 Apereo CAS实现,不是此次记录的重点。 登陆过程中,需要重定向至CAS Server,前端Vue+axios,需要从单页面跳转至login页面,后端如果使用response.sendRedirect(),返回302,axios并不能拦截到302...
  • 原文来自 © 呆萌钟 vue+springboot前后端分离实现单点登录跨域问题处理 最近在做一个后台管理系统,前端是用时下火热的vue.js,后台是基于springboot的。因为后台系统没有登录功能,但是公司要求统一登录登录...
  • 一个简单的集成了shiro+cas+pac4j的springboot项目,实现单点登录单点退出。 包括一个cas-server服务器和一个demo客户端
  • --单点登录--> <dependency> <groupId>org.pac4j</groupId> <artifactId>pac4j-cas</artifactId> <version>3.0.2</version> </dep...
  • 如题,使用spring-security-oauth2实现单点登录后,我有一个前端服务器,通过JS到认证服务器通过认证后能正常访问其它后端服务器接口。但是如果使用ajax就访问不了。 ``` crossDomain:true, xhrFields:{ ...
  • ('APP_USER')" />//因为是伪前后端分离 这2个url拦截是为了用户直接刷新页面时触发未登录情况跳转cas登录页使用。 index.html是我的主页 **" access="hasRole('APP_USER')" /> --5.0不用带ROLE_开头 ...
  • 文章目录前言一、整合前后端分离遇到的问题二、CAS整合前后端分离项目实战第一步、引入CAS客户端依赖第二步、编写Cas经典拦截器的配置类第三步、编写一个CasController,专门负责Cas的业务第四步、进行完整测试单点...
  • 1.首先在a(www.a.com)网站下创建文件夹a.vue,通过iframe引入B网站登录页面 <iframe ref="iframe" src="http://www.b.com/#/login" style="height: 400px; width: 1200px; margin-bottom: 100px; display: ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 3,521
精华内容 1,408
关键字:

cas单点登录前后端分离