Cas 5.2.x版本使用 —— 实现SSO单点登录(九)

在开展项目之前,需要一些基本的CAS知识扫盲工作,请仔细理解这几篇文章对后期项目很有帮助,具体重复内容这里不在多说,看文章即可,

项目架构

统一使用 SpringBoot+Meven 构建

项目 地址 说明
cas-overlay-template-master https://cas.server.com:8443/cas cas-server 服务端
cas-app1 http://app1.com:8181 cas-client 客户端1
cas-app2 http://app2.com:8282 cas-client 客户端2

接下来,老夫就要开始飙车了。

一、配置hosts文件

# SSO单点登录Demo
127.0.0.1    cas.server.com
127.0.0.1    app1.com
127.0.0.1    app2.com

二、配置cas服务端

1、配置json服务注册

1、在src/main/resources/目录下创建services文件夹

2、在war包中,找到HTTPSandIMAPS-10000001.json文件,并copy到services文件夹中

3、将HTTPSandIMAPS-10000001.json文件内容修改如下。

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

默认cas支持https,不支持http客户端站点来登录,所以需要手动进行配置兼容。

如果不做此操作就会出现如下图:未认证授权的服务,所以还是乖乖的听话,跟着我的飙车轨道,别跑丢了。

具体json文件中代表什么含义,查看文章:Cas 5.2.x版本使用 —— Service配置介绍(十一)

2、pom.xml 配置

导入json服务的依赖。经过尝试发现下面不导入也是可以的,但是官网说要加,有点蒙

<!--json服务注册-->
<dependency>
    <groupId>org.apereo.cas</groupId>
    <artifactId>cas-server-support-json-service-registry</artifactId>
    <version>${cas.version}</version>
</dependency>

3、application.properties 配置

在application.properties文件中,增加配置项cas.serviceRegistry.initFromJson=true表示开启了json注册服务。

另外为了Demo尽可能的简单方便理解,我这里启用静态账号密码方式认证(非JDBC和Rest)

##
# CAS Authentication Credentials
#
cas.authn.accept.users=tingfeng::tingfeng

##
# 开启json服务注册
#
cas.serviceRegistry.initFromJson=true

##
# 登出后允许跳转到指定页面
#
cas.logout.followServiceRedirects=true

三、配置客户端

客户端 app1 和 app2 的项目逻辑相同,不多说。

1、pom.xml 配置

添加 cas-client 包依赖,我用的 3.5.0 版本

<!--cas的客户端 -->
<dependency>
   <groupId>org.jasig.cas.client</groupId>
   <artifactId>cas-client-core</artifactId>
   <version>${java.cas.client.version}</version>
</dependency>

2、项目结构

➜  src tree
.
└── main
    ├── java
    │   └── com
    │       └── tingfeng
    │           ├── AppRun.java (程序入口)
    │           ├── cas
    │           │   ├── auth
    │           │   │   └── SimpleUrlPatternMatcherStrategy.java (设置忽略,不拦截地址)
    │           │   └── config
    │           │       └── CasConfig.java (常用cas配置)
    │           └── controller
    │               ├── BookController.java  (受限接口)
    │               ├── IndexController.java
    │               └── UserController.java  (用户登出)
    └── resources
        └── application.yml

3、 AppRun.java

SpringBoot程序入口,包含cas-client.jar的过滤器配置信息

package com.tingfeng;

import com.tingfeng.cas.config.CasConfig;
import org.jasig.cas.client.authentication.AuthenticationFilter;
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.Cas30ProxyReceivingTicketValidationFilter;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
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.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;

import java.util.EventListener;
import java.util.HashMap;
import java.util.Map;

/**
 * 过滤器配置介绍Doc:
 * http://doc.okbase.net/234390216/archive/105696.html
 * http://blog.csdn.net/u010475041/article/details/78094251
 */
@SpringBootApplication
public class AppRun {

    /*************************************   SSO配置-开始   ************************************************/

    /**
     * SingleSignOutFilter 登出过滤器
     * 该过滤器用于实现单点登出功能,可选配置
     *
     * @return
     */
    @Bean
    public FilterRegistrationBean filterSingleRegistration() {
        FilterRegistrationBean registration = new FilterRegistrationBean();
        registration.setFilter(new SingleSignOutFilter());
        // 设定匹配的路径
        registration.addUrlPatterns("/*");
        Map<String, String> initParameters = new HashMap();
        initParameters.put("casServerUrlPrefix", CasConfig.CAS_SERVER_LOGIN_PATH);
        registration.setInitParameters(initParameters);
        // 设定加载的顺序
        registration.setOrder(1);
        return registration;
    }

    /**
     * SingleSignOutHttpSessionListener 添加监听器
     * 用于单点退出,该过滤器用于实现单点登出功能,可选配置
     *
     * @return
     */
    @Bean
    public ServletListenerRegistrationBean<EventListener> singleSignOutListenerRegistration() {
        ServletListenerRegistrationBean<EventListener> registrationBean = new ServletListenerRegistrationBean<EventListener>();
        registrationBean.setListener(new SingleSignOutHttpSessionListener());
        registrationBean.setOrder(1);
        return registrationBean;
    }

    /**
     * Cas30ProxyReceivingTicketValidationFilter 验证过滤器
     * 该过滤器负责对Ticket的校验工作,必须启用它
     *
     * @return
     */
    @Bean
    public FilterRegistrationBean filterValidationRegistration() {
        FilterRegistrationBean registration = new FilterRegistrationBean();
        registration.setFilter(new Cas30ProxyReceivingTicketValidationFilter());

        // 设定匹配的路径
        registration.addUrlPatterns("/*");
        Map<String, String> initParameters = new HashMap();
        initParameters.put("casServerUrlPrefix", CasConfig.CAS_SERVER_PATH);
        initParameters.put("serverName", CasConfig.SERVER_NAME);

        // 是否对serviceUrl进行编码,默认true:设置false可以在302对URL跳转时取消显示;jsessionid=xxx的字符串
        // 观察CommonUtils.constructServiceUrl方法可以看到
        initParameters.put("encodeServiceUrl", "false");

        registration.setInitParameters(initParameters);
        // 设定加载的顺序
        registration.setOrder(1);
        return registration;
    }

    /**
     * AuthenticationFilter 授权过滤器
     *
     * @return
     */
    @Bean
    public FilterRegistrationBean filterAuthenticationRegistration() {

        FilterRegistrationBean registration = new FilterRegistrationBean();
        Map<String, String> initParameters = new HashMap();

        registration.setFilter(new AuthenticationFilter());
        registration.addUrlPatterns("/*");
        initParameters.put("casServerLoginUrl", CasConfig.CAS_SERVER_LOGIN_PATH);
        initParameters.put("serverName", CasConfig.SERVER_NAME);

        // 不拦截的请求 .* 有后缀的文件
        initParameters.put("ignorePattern", ".*");

        // 表示过滤所有
        initParameters.put("ignoreUrlPatternType", "com.tingfeng.cas.auth.SimpleUrlPatternMatcherStrategy");

        registration.setInitParameters(initParameters);
        // 设定加载的顺序
        registration.setOrder(1);
        return registration;
    }

    /**
     * AssertionThreadLocalFilter
     *
     * 该过滤器使得开发者可以通过org.jasig.cas.client.util.AssertionHolder来获取用户的登录名。
     * 比如AssertionHolder.getAssertion().getPrincipal().getName()。
     *
     * @return
     */
    @Bean
    public FilterRegistrationBean filterAssertionThreadLocalRegistration() {
        FilterRegistrationBean registration = new FilterRegistrationBean();
        registration.setFilter(new AssertionThreadLocalFilter());
        // 设定匹配的路径
        registration.addUrlPatterns("/*");
        // 设定加载的顺序
        registration.setOrder(1);
        return registration;
    }

    /*
     * HttpServletRequestWrapperFilter wraper过滤器
     * 该过滤器负责实现HttpServletRequest请求的包裹,
     * 比如允许开发者通过HttpServletRequest的getRemoteUser()方法获得SSO登录用户的登录名,可选配置。
     *
     * @return
     */
    @Bean
    public FilterRegistrationBean filterWrapperRegistration() {
        FilterRegistrationBean registration = new FilterRegistrationBean();
        registration.setFilter(new HttpServletRequestWrapperFilter());
        // 设定匹配的路径
        registration.addUrlPatterns("/*");
        // 设定加载的顺序
        registration.setOrder(1);
        return registration;
    }

    /*************************************   SSO配置-结束   ************************************************/

    /**
     * 设定首页
     */
    @Configuration
    public class DefaultView extends WebMvcConfigurerAdapter {

        @Override
        public void addViewControllers(ViewControllerRegistry registry) {
            //设定首页为index
            registry.addViewController("/").setViewName("forward:/index");

            //设定匹配的优先级
            registry.setOrder(Ordered.HIGHEST_PRECEDENCE);

            //添加视图控制类
            super.addViewControllers(registry);
        }
    }

    public static void main(String[] args) {
        SpringApplication.run(AppRun.class, args);
    }
}

过滤器的设置,类似于在 web.xml 中配置的过滤器,关于web.xml配置,参考底部文档链接。

有关上面几个过滤器和各个参数的含义,我打算在后面单独做几篇文章进行详细介绍

SimpleUrlPatternMatcherStrategy.java

机能概要:过滤掉一些不需要授权登录的URL

package com.tingfeng.cas.auth;

import org.jasig.cas.client.authentication.UrlPatternMatcherStrategy;

import java.util.Arrays;
import java.util.List;

/**
 * 机能概要:过滤掉一些不需要授权登录的URL
 */
public class SimpleUrlPatternMatcherStrategy implements UrlPatternMatcherStrategy {

    /**
     * 机能概要: 判断是否匹配这个字符串
     *
     * @param url 用户请求的连接
     * @return true : 不拦截
     * false :必须得登录了
     */
    @Override
    public boolean matches(String url) {

        if(url.contains("/logout")){
            return true;
        }

        List<String> list = Arrays.asList(
                "/",
                "/index",
                "/favicon.ico"
        );

        String name = url.substring(url.lastIndexOf("/"));
        if (name.indexOf("?") != -1) {
            name = name.substring(0, name.indexOf("?"));
        }

        System.out.println("name:" + name);
        boolean result = list.contains(name);
        if (!result) {
            System.out.println("拦截URL:" + url);
        }
        return result;
    }

    /**
     * 正则表达式的规则,这个地方可以是web传递过来的
     */
    @Override
    public void setPattern(String pattern) {

    }
}

UserController.java

用于登出

package com.tingfeng.controller;

import com.tingfeng.cas.config.CasConfig;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@Controller
@RequestMapping("/user")
public class UserController {
    /**
     * 用户登出
     *
     * @param request
     * @return
     */
    @RequestMapping("/logout")
    public String logout(HttpServletRequest request) {
        // session失效
        request.getSession().invalidate();
        return "redirect:" + CasConfig.CAS_SERVER_LOGOUT_PATH;
    }
    /**
     * 用户登出,并重定向回来
     *
     * @param request
     * @return
     */
    @RequestMapping("/logout2")
    public String logout2(HttpServletRequest request) {
        // session失效
        request.getSession().invalidate();
        return "redirect:" + CasConfig.CAS_SERVER_LOGOUT_PATH + "?service="+CasConfig.APP_LOGOUT_PATH;
    }
    /**
     * 登出成功回调
     * @return
     */
    @ResponseBody
    @RequestMapping("/logout/success")
    public String logoutPage(){
        return "登出成功,跳转登出页面";
    }
}

四、视频演示地址

https://v.qq.com/x/page/d063304k06a.html

五、我的源码

https://github.com/X-rapido/CAS_SSO_Record/tree/master/simple-sso

六、错误集合

java.lang.RuntimeException: javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed:

导致这个问题的原因就是,客户端,没有导入证书,报的错,客户端的jdk,也是需要导入证书的,而且必须和服务端的证书一致。

java.lang.RuntimeException: javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
   org.jasig.cas.client.util.CommonUtils.getResponseFromServer(CommonUtils.java:403)
   org.jasig.cas.client.validation.AbstractCasProtocolUrlBasedTicketValidator.retrieveResponseFromServer(AbstractCasProtocolUrlBasedTicketValidator.java:41)
   org.jasig.cas.client.validation.AbstractUrlBasedTicketValidator.validate(AbstractUrlBasedTicketValidator.java:193)
   org.jasig.cas.client.validation.AbstractTicketValidationFilter.doFilter(AbstractTicketValidationFilter.java:204)
   org.jasig.cas.client.session.SingleSignOutFilter.doFilter(SingleSignOutFilter.java:97)
   org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:88)
   org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)

org.apereo.cas.services.RegisteredServiceAccessStrategyUtils.ensurePrincipalAccessIsAllowedForService(Lorg/apereo/cas/authentication/principal/Service;Lorg/apereo/cas/services/RegisteredService;Lorg/apereo/cas/ticket/TicketGrantingTicket;)V

检查JDK证书是否导入成功

七、总结

本章讲解了cas-client如何使用,但我们实际上远远比这要复杂,当然我们这个demo也作为入门了解阶段学习,但目标很明确,要了解cas的整个转发过程,在测试过程,请多注意使用浏览器的Debug功能进行观察转发和重定向的过程。

八、参考文档

https://github.com/cas-projects/cas-sample-java-webapp

https://github.com/apereo/java-cas-client


赞(52) 打赏
未经允许不得转载:优客志 » JAVA开发
分享到:

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏