SpringBoot集成CAS实现SSO(single sign on)单点登录

什么是单点登录

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

CAS(中央认证服务)

CAS是Central Authentication Service的缩写,中央认证服务,一种独立开放指令协议。CAS 是 耶鲁大学(Yale University)发起的一个开源项目,旨在为 Web 应用系统提供一种可靠的单点登录方法。

协议特点

  • 开源的企业级单点登录解决方案。
  • CAS Server 需要独立部署的 Web 应用。
  • CAS Client 支持非常多的客户端(这里指单点登录系统中的各个 Web 应用),包括 Java, .Net, PHP, Perl, Apache, uPortal, Ruby 等语言编写的各种web应用。
  • CAS属于Apache 2.0许可证,允许代码修改,再发布(作为开源或商业软件)。

原理和协议

从结构上看,CAS 包含两个部分: CAS Server 和 CAS Client。CAS Server 需要独立部署,主要负责对用户的认证工作;CAS Client 负责处理对客户端受保护资源的访问请求,需要登录时,重定向到 CAS Server。下图是 CAS 最基本的协议过程:

  1. 访问服务:sso客户端发送请求访问应用系统提供的服务资源。
  2. 定向认证:sso客户端会重定向用户请求到sso服务器。
  3. 用户认证:用户身份认证。
  4. 发放票据:sso服务器会产生一个随机的service ticket。
  5. 验证票据:sso服务器验证票据service ticket的合法性,验证通过后,允许客户端访问服务。
  6. 传输用户信息:sso服务器验证票据通过后,传输用户认证结果信息给客户端。

CAS服务端

基本部署

1、git地址:https://github.com/apereo/cas-overlay-template/tree/5.3

2、解压下载的zip压缩包,我放到:E:\wspace\cas-overlay-template-5.3

3、解压后使用maven命令打包:mvn package(该命令执行时间比较长,耐心等待)

4、把target下生成的war包重命名为cas.war放到tomcat下

5、启动tomcat

6、修改配置文件

1)由于cas默认使用的是基于https协议,需要改为兼容使用http协议,打开对应你的目录文件:${tomcat目录}\webapps\cas\web-inf\classes\application.properties

修改application.properties文件,添加下面配置,使用http

# Service Registry(服务注册)
# 开启识别Json文件,默认false,这里就是开启识别 services 中的json文件的
# 注意属性是驼峰的,网上有些文章是小写的,误人子弟
cas.serviceRegistry.initFromJson=true
# 保存tgc,去掉https协议,使用http协议。
cas.tgc.secure=false
 
#由于https协议默认使用的端口为8443,还需我们修改为tomcat的8080端口
server.port=8080

2)修改HTTPSandIMAPS-10000001.json文件:${tomcat目录}\webapps\cas\web-inf\classes\services目录下的HTTPSandIMAPS-10000001.json

把原来的serviceid内容改成如下:

"serviceId" : "^(https|http|imaps)://.*",

兼容http修改完毕。

3)修改配置中的登录用户名密码(application.properties)

cas.authn.accept.users=lhw::123456

cas服务器端搭建完毕,重启tomcat 进行测试,在浏览器中输入下面地址,进行访问:http://localhost:8080/cas/login

输出我们刚才配置的用户名和密码(lhw/123456)

服务端就已经搭好了,并且可以通过登录退出了。

CAS客户端

客户端搭建

在新建的springboot项目的pom.xml添加如下依赖(匹配对应的版本):

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

在resources下新建application.yml

server:
  port: 9999

# cas配置
cas:
  #cas服务端的地址
  server-url-prefix: http://localhost:8080/cas
  #cas服务端的登录地址
  server-login-url: http://localhost:8080/cas/login
  #当前服务器的地址(客户端)
  client-host-url: http://localhost:${server.port}
  #ticket校验器使用cas30proxyreceivingticketvalidationfilter
  validation-type: cas3

application启动类上添加注解

import net.unicon.cas.client.configuration.EnableCasClient;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

//启用cas
@EnableCasClient
@SpringBootApplication
public class CasOneApp {
    public static void main(String[] args) {
        SpringApplication.run(CasOneApp.class, args);
    }
}

第一个客户端的controller

import org.jasig.cas.client.authentication.AttributePrincipal;
import org.jasig.cas.client.validation.Assertion;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpSession;

import static org.jasig.cas.client.util.AbstractCasFilter.CONST_CAS_ASSERTION;

@RestController
public class SsoClientOneController {

    @RequestMapping("/sso-test1")
    public String test1(HttpSession session) {

        Assertion assertion = (Assertion) session.getAttribute(CONST_CAS_ASSERTION);
        AttributePrincipal principal = assertion.getPrincipal();
        String loginName = principal.getName();
        
        return "sso-test1,当前登录账户" + loginName;
    }
}

一个客户端就添加好了,我们再添加另一个客户端,除端口其他基本也是一样。

第二个客户端的controller

import org.jasig.cas.client.authentication.AttributePrincipal;
import org.jasig.cas.client.validation.Assertion;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpSession;

import static org.jasig.cas.client.util.AbstractCasFilter.CONST_CAS_ASSERTION;

@RestController
public class SsoClientTwoController {

    @RequestMapping("/sso-test2")
    public String test1(HttpSession session) {

        Assertion assertion = (Assertion) session.getAttribute(CONST_CAS_ASSERTION);
        AttributePrincipal principal = assertion.getPrincipal();
        String loginName = principal.getName();

        return "test222222,当前登录账户" + loginName;
    }
}

在没有登录的情况下访问 http://localhost:9999/sso-test1

直接跳到了登录界面,并且把回调地址也带上了。

访问第二个客户端 http://localhost:8888/sso-test2

跟第一个也是一样,这次我们随便登录一个。

登录后,执行了回调接口,刷新一下另外一个客户端的地址

也登录成功了。

配置统一登出

添加登出接口。

@Controller
public class LogoutController {
    /**
     * 退出 后自动重定向自定义接口
     *
     * @param request
     * @return
     */
    @RequestMapping("/system/logout1")
    public String logout1(HttpServletRequest request) {
        HttpSession session = request.getSession();
        session.invalidate();
        //http://localhost:8080/cas/logout是cas的登出链接
        //http://localhost:9999/system/logOutSuccess 自己的客户端的退出请求链接
        return "redirect:http://localhost:8080/cas/logout?service=http://localhost:9999/system/logOutSuccess";

    }

    /**
     * 退出成功页
     *
     * @return
     */
    @RequestMapping("/system/logOutSuccess")
    @ResponseBody
    public String logOutSuccess() {
        return "test1成功退出!";
    }
}

设置cas认证中心允许重定向跳转

打开你的cas认证中心里的 application.properties 文件,添加如下配置:

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

新建Java配置文件:

@Configuration
public class CasConfig {

    @Autowired
    private Environment environment;

    /**
     * description: 登录过滤器
     *
     * @param: []
     * @return: org.springframework.boot.web.servlet.FilterRegistrationBean
     */
    @Bean
    public FilterRegistrationBean filterSingleRegistrationBean() {
        FilterRegistrationBean registration = new FilterRegistrationBean();
        registration.setFilter(new SingleSignOutFilter());
        // 设定匹配的路径
        registration.addUrlPatterns("/*");
        Map<String, String> initParameters = new HashMap<String, String>();
        initParameters.put("casServerUrlPrefix", environment.getProperty("cas.server-url-prefix"));
        registration.setInitParameters(initParameters);
        // 设定加载的顺序
        registration.setOrder(1);
        return registration;
    }


    /**
     * description:过滤验证器
     * * @param: []
     *
     * @return: org.springframework.boot.web.servlet.FilterRegistrationBean
     */
    @Bean
    public FilterRegistrationBean filterValidationRegistration() {
        FilterRegistrationBean registration = new FilterRegistrationBean();
        registration.setFilter(new Cas30ProxyReceivingTicketValidationFilter());
        // 设定匹配的路径
        registration.addUrlPatterns("/*");
        Map<String, String> initParameters = new HashMap<String, String>();
        initParameters.put("casServerUrlPrefix", environment.getProperty("cas.server-url-prefix"));
        initParameters.put("serverName", environment.getProperty("cas.client-host-url"));
        initParameters.put("useSession", "true");
        registration.setInitParameters(initParameters);
        // 设定加载的顺序
        registration.setOrder(1);
        return registration;
    }


    /**
     * description:授权过滤器
     *
     * @param: []
     * @return: org.springframework.boot.web.servlet.FilterRegistrationBean
     */
    @Bean
    public FilterRegistrationBean filterAuthenticationRegistration() {
        FilterRegistrationBean registration = new FilterRegistrationBean();
        registration.setFilter(new AuthenticationFilter());
        // 设定匹配的路径
        registration.addUrlPatterns("/*");

        Map<String, String> initParameters = new HashMap<String, String>();
        initParameters.put("casServerUrlPrefix", environment.getProperty("cas.server-url-prefix"));
        initParameters.put("serverName", environment.getProperty("cas.client-host-url"));


        //设置忽略  退出登录不用登录
        initParameters.put("ignorePattern", "/system/*");

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


    /**
     * wrapper过滤器
     *
     * @return
     */
    @Bean
    public FilterRegistrationBean filterWrapperRegistration() {
        FilterRegistrationBean registration = new FilterRegistrationBean();
        registration.setFilter(new HttpServletRequestWrapperFilter());
        // 设定匹配的路径
        registration.addUrlPatterns("/*");
        // 设定加载的顺序
        registration.setOrder(1);
        return registration;
    }

    /**
     * 添加监听器
     *
     * @return
     */
    @Bean
    public ServletListenerRegistrationBean<EventListener> singleSignOutListenerRegistration() {
        ServletListenerRegistrationBean<EventListener> registrationBean = new ServletListenerRegistrationBean<EventListener>();
        registrationBean.setListener(new SingleSignOutHttpSessionListener());
        registrationBean.setOrder(1);
        return registrationBean;
    }
}

客户端2跟客户端1的大似相同,这样就可以实现登出一个系统,所有系统全部登出。

 

posted @ 2022-04-25 21:45  残城碎梦  阅读(3109)  评论(1编辑  收藏  举报