• 基于SpringbootShiro实现的CAS单点登录


    概述

    单点登录(Single Sign On,SSO)是一种登录管理机制,主要用于多系统集成,即在多个系统中,用户只需要到一个中央服务器登录一次即可访问这些系统中的任何一个,无须多次登录。常见的例子就是,当我们在淘宝网上登录了之后,如果再访问天猫网的话是不需要再次登录的。

    详细

    一、运行效果

    image.png

    image.png

    二、实现过程

    ①、cas单点登录流程图

    CAS是SSO的一种实现方式,CAS系统分为服务端与客户端,服务端提供登录认证的功能,客户端需要跳转到服务端进行登录认证,大体流程如下

    image.png

    ②、cas 服务端搭建

    CAS 服务端登录需要使用https,在本地我们可以使用 JDK 自带的 keytool 工具生成数字证书,具体流程和配置如下:

    a 修改hosts文件,将要配置的CAS服务端域名映射到本地:

    127.0.0.1       www.xiaoti.com

    b 生成密钥库文件,这里设置密钥库口令,名字与姓氏处填写单点登录服务器的域名(这里是 www.xiaoti.com),其余项可随便填写。654321,设置密钥口令:654321,两者须相同:

    1. c 查看生成的密钥库,输入密钥库密码654321
    2. keytool -list -keystore localhost.keystore
    3. d 生成crt证书文件,输入密钥库密码654321
    4. keytool -export -alias localhost -keystore localhost.keystore -file localhost.crt
    5. e 客户端信任证书,这里需要输入的密码是changeit:
    6. keytool -import -keystore "D:\Program Files\Java\jdk1.8.0_66\jre\lib\security\cacerts" -file localhost.crt -alias localhost
    7. f tomcat配置,server.xml配置:
    8. <Connector port="8443" protocol="org.apache.coyote.http11.Http11Protocol"
    9. maxThreads="200"
    10. SSLEnabled="true"
    11. scheme="https"
    12. secure="true"
    13. clientAuth="false"
    14. sslProtocol="TLS"
    15. keystoreFile="D:\dev\localhost.keystore"keystorePass="654321" />

     

    ③、 新建cas服务端项目

    CAS已经提供了服务端的基本war包实现,我们只需在其基础上作修改即可,使用maven 的oerlay配置可以通过覆盖的方式来进行修改。

    a 新建Maven webapp项目,这里命名为cas-server-demo,引入依赖:

    1. <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    2. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
    3. <modelVersion>4.0.0</modelVersion>
    4. <groupId>gdou.laixiaoming</groupId>
    5. <artifactId>cas-server-demo</artifactId>
    6. <packaging>war</packaging>
    7. <version>0.0.1-SNAPSHOT</version>
    8. <name>cas-server-demo Maven Webapp</name>
    9. <url>http://maven.apache.org</url>
    10. <properties>
    11. <cas.version>5.2.5</cas.version>
    12. </properties>
    13. <dependencies>
    14. <dependency>
    15. <groupId>org.apereo.cas</groupId>
    16. <artifactId>cas-server-webapp</artifactId>
    17. <version>${cas.version}</version>
    18. <type>war</type>
    19. <scope>runtime</scope>
    20. </dependency>
    21. </dependencies>
    22. <build>
    23. <finalName>cas</finalName>
    24. <plugins>
    25. <plugin>
    26. <groupId>org.apache.maven.plugins</groupId>
    27. <artifactId>maven-compiler-plugin</artifactId>
    28. <version>3.1</version>
    29. <configuration>
    30. <source>1.8</source>
    31. <target>1.8</target>
    32. </configuration>
    33. </plugin>
    34. <plugin>
    35. <groupId>org.apache.maven.plugins</groupId>
    36. <artifactId>maven-war-plugin</artifactId>
    37. <configuration>
    38. <failOnMissingWebXml>false</failOnMissingWebXml>
    39. <warName>cas</warName>
    40. <overlays>
    41. <overlay>
    42. <groupId>org.apereo.cas</groupId>
    43. <artifactId>cas-server-webapp</artifactId>
    44. </overlay>
    45. </overlays>
    46. </configuration>
    47. </plugin>
    48. </plugins>
    49. </build>
    50. </project>

    b 打包,选择上面我们配置的tomcat,启动:
    浏览器打开https://www.xiaoti.com:8443/cas/login,默认登录名和密码是casuserMellon,输入后可成功登录

    ④、配置数据库认证

    上面我们成功搭建了CAS Server,但是只能使用默认的用户名和密码进行登录,如果我们的用户名和密码是存储在数据库的话,需要引入 JDBC的支持并进行配置:

    a 首先,pom.xml配置文件中增加依赖:

    1. <dependency>
    2. <groupId>org.apereo.cas</groupId>
    3. <artifactId>cas-server-support-jdbc</artifactId>
    4. <version>${cas.version}</version>
    5. </dependency>
    6. <dependency>
    7. <groupId>org.apereo.cas</groupId>
    8. <artifactId>cas-server-support-jdbc-drivers</artifactId>
    9. <version>${cas.version}</version>
    10. </dependency>
    11. <dependency>
    12. <groupId>mysql</groupId>
    13. <artifactId>mysql-connector-java</artifactId>
    14. <version>5.1.36</version>
    15. </dependency>

    b 创建用户密码表:

    CREATE TABLE `user` (

      `id` bigint(20) NOT NULL AUTO_INCREMENT,

      `userName` varchar(15) DEFAULT NULL,

      `password` char(32) DEFAULT NULL,

      PRIMARY KEY (`id`)

    ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4;

    c 将war包中的的application.properties复制出来:

    image.png

    放到项目如下位置,这里的路径需要对应(确保部署后的application.properties可以对原文件进行覆盖),才能实现更新:

    image.png

    d 修改application.properties,关于JDBC这里的更详细的配置,可以访问官网 database-authentication部分:

    #数据库连接配置

    cas.authn.jdbc.query[0].user=root

    cas.authn.jdbc.query[0].password=mysql

    cas.authn.jdbc.query[0].driverClass=com.mysql.jdbc.Driver

    cas.authn.jdbc.query[0].url=jdbc:mysql://localhost:3306/cas_auth

    cas.authn.jdbc.query[0].dialect=org.hibernate.dialect.MySQLDialect

    ##从数据库中获取用户名和密码进行匹配登录

    cas.authn.jdbc.query[0].sql=SELECT * FROM `user` WHERE userName=?

    cas.authn.jdbc.query[0].fieldPassword=password

    e 重新构建项目并启动,输入数据库中存在的用户名和密码,是可以成功登录的;

    ⑤、cas服务端搭建(整合Shiro和SpringBoot)

    新建maven项目,引入相关依赖

    1. <?xml version="1.0" encoding="UTF-8"?>
    2. <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    3. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    4. <modelVersion>4.0.0</modelVersion>
    5. <parent>
    6. <groupId>org.springframework.boot</groupId>
    7. <artifactId>spring-boot-starter-parent</artifactId>
    8. <version>2.1.3.RELEASE</version>
    9. <relativePath/> <!-- lookup parent from repository -->
    10. </parent>
    11. <groupId>gdou.laixiaoming</groupId>
    12. <artifactId>cas-client-a</artifactId>
    13. <version>0.0.1-SNAPSHOT</version>
    14. <name>cas-client-a</name>
    15. <description>CAS Client Demo.</description>
    16. <properties>
    17. <java.version>1.8</java.version>
    18. <shiro.version>1.4.0</shiro.version>
    19. </properties>
    20. <dependencies>
    21. <dependency>
    22. <groupId>org.springframework.boot</groupId>
    23. <artifactId>spring-boot-starter-thymeleaf</artifactId>
    24. </dependency>
    25. <dependency>
    26. <groupId>org.springframework.boot</groupId>
    27. <artifactId>spring-boot-starter-web</artifactId>
    28. </dependency>
    29. <!-- shiro -->
    30. <dependency>
    31. <groupId>org.apache.shiro</groupId>
    32. <artifactId>shiro-spring</artifactId>
    33. <version>${shiro.version}</version>
    34. </dependency>
    35. <dependency>
    36. <groupId>org.apache.shiro</groupId>
    37. <artifactId>shiro-core</artifactId>
    38. <version>${shiro.version}</version>
    39. </dependency>
    40. <dependency>
    41. <groupId>org.apache.shiro</groupId>
    42. <artifactId>shiro-cas</artifactId>
    43. <version>${shiro.version}</version>
    44. </dependency>
    45. <dependency>
    46. <groupId>org.apache.shiro</groupId>
    47. <artifactId>shiro-aspectj</artifactId>
    48. <version>${shiro.version}</version>
    49. </dependency>
    50. <dependency>
    51. <groupId>org.springframework.boot</groupId>
    52. <artifactId>spring-boot-starter-test</artifactId>
    53. <scope>test</scope>
    54. </dependency>
    55. </dependencies>
    56. <build>
    57. <plugins>
    58. <plugin>
    59. <groupId>org.springframework.boot</groupId>
    60. <artifactId>spring-boot-maven-plugin</artifactId>
    61. </plugin>
    62. </plugins>
    63. </build>
    64. </project>

    shiro配置

    1. 扩展CasRealm,在登录成功后,可以获取到登录的用户名,在客户端这边再把用户拥有的角色和权限查询出来并保存:
    2. public class MyCasRealm extends CasRealm{
    3. @Override
    4. protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
    5. //获取用户名
    6. String username = (String)principals.getPrimaryPrincipal();
    7. SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
    8. // authorizationInfo.setRoles(new HashSet<>(Arrays.asList("admin")));
    9. // authorizationInfo.setStringPermissions(new HashSet<>(Arrays.asList("admin")));
    10. return authorizationInfo;
    11. }
    12. }
    13. Shiro配置:
    14. @Configuration
    15. public class ShiroConfig {
    16. @Bean
    17. public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) {
    18. ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
    19. shiroFilterFactoryBean.setSecurityManager(securityManager);
    20. //配置自定义casFilter
    21. Map<String, Filter> filters = new LinkedHashMap<>();
    22. CasFilter casFilter = new CasFilter();
    23. casFilter.setFailureUrl("/casFailure");
    24. filters.put("cas", casFilter);
    25. shiroFilterFactoryBean.setFilters(filters);
    26. //配置filter调用链
    27. Map<String,String> filterChainDefinitionMap = new LinkedHashMap<>();
    28. //anon为匿名,不拦截
    29. filterChainDefinitionMap.put("/static/**", "anon");
    30. filterChainDefinitionMap.put("/casFailure", "anon");
    31. //拦截CAS Server返回的ticket
    32. filterChainDefinitionMap.put("/cas", "cas");
    33. //退出登录
    34. filterChainDefinitionMap.put("/logout", "anon");
    35. filterChainDefinitionMap.put("/logouttips", "anon");
    36. //需要登录访问的页面
    37. filterChainDefinitionMap.put("/**", "user");
    38. shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
    39. //service指定了登录成功后的回调地址,回调/cas将被CasFilter拦截,获取服务端返回的Service Ticket进行登录
    40. shiroFilterFactoryBean.setLoginUrl("https://www.xiaoti.com:8443/cas/login?service=http://www.localhost1.com:9090/cas");
    41. //登录成功后要跳转的链接
    42. shiroFilterFactoryBean.setSuccessUrl("/");
    43. //未授权跳转页面
    44. shiroFilterFactoryBean.setUnauthorizedUrl("/403");
    45. return shiroFilterFactoryBean;
    46. }
    47. @Bean
    48. public MyCasRealm casRealm(){
    49. //使用自定义Realm
    50. MyCasRealm casRealm = new MyCasRealm();
    51. casRealm.setCachingEnabled(true);
    52. casRealm.setAuthenticationCachingEnabled(true);
    53. casRealm.setAuthenticationCacheName("authenticationCache");
    54. casRealm.setAuthorizationCachingEnabled(true);
    55. casRealm.setAuthorizationCacheName("authorizationCache");
    56. //指定CAS服务端地址
    57. casRealm.setCasServerUrlPrefix("https://www.xiaoti.com:8443/cas");
    58. //当前应用的CAS服务URL,用于接收和处理CAS服务端的Ticket
    59. casRealm.setCasService("http://www.localhost1.com:9090/cas");
    60. return casRealm;
    61. }
    62. @Bean
    63. public SecurityManager securityManager(){
    64. DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
    65. securityManager.setRealm(casRealm());
    66. return securityManager;
    67. }
    68. /**
    69. * Shiro生命周期处理器
    70. */
    71. @Bean
    72. public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
    73. return new LifecycleBeanPostProcessor();
    74. }
    75. @Bean
    76. @DependsOn({"lifecycleBeanPostProcessor"})
    77. public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator() {
    78. DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
    79. advisorAutoProxyCreator.setProxyTargetClass(true);
    80. return advisorAutoProxyCreator;
    81. }
    82. /**
    83. * 开启Shiro AOP的注解支持(如@RequiresRoles,@RequiresPermissions)
    84. */
    85. @Bean
    86. public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager){
    87. AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
    88. authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
    89. return authorizationAttributeSourceAdvisor;
    90. }
    91. }
    ⑥、cas单点登出配置

    退出登录时,将页面重定向到CAS的退出页面,service参数的设置指定了退出登录后的回调地址:

    1. @GetMapping("/logout")
    2. public String logout(){
    3. SecurityUtils.getSubject().logout();
    4. return "redirect:https://www.xiaoti.com:8443/cas/logout?service=https://www.xiaoti.com:9090/logouttips";
    5. }

    service参数名可以在CAS服务端通过修改application.properties进行配置,关于登出的更多配置,可见官网

    #单点登出

    #配置允许登出后跳转到指定页面

    cas.logout.followServiceRedirects=true

    #跳转到指定页面需要的参数名为 service

    cas.logout.redirectParameter=service

    在CAS服务端退出后,会向注册的每个服务发送登出请求,该请求可以由SingleSignOutFilter进行拦截并销毁当前会话:

    1. @Configuration
    2. public class CasConfig {
    3. @Bean
    4. public FilterRegistrationBean singleSignOutFilterRegistration() {
    5. FilterRegistrationBean registration = new FilterRegistrationBean(new SingleSignOutFilter());
    6. registration.addUrlPatterns("/*");
    7. return registration;
    8. }
    9. @Bean
    10. public ServletListenerRegistrationBean<SingleSignOutHttpSessionListener> singleSignOutListenerRegistration() {
    11. ServletListenerRegistrationBean<SingleSignOutHttpSessionListener> registration =
    12. new ServletListenerRegistrationBean<>(new SingleSignOutHttpSessionListener());
    13. return registration;
    14. }
    15. }
    ⑥、cas单点登出配置

    退出登录时,将页面重定向到CAS的退出页面,service参数的设置指定了退出登录后的回调地址:

    @GetMapping("/logout")

             public String logout(){

                       SecurityUtils.getSubject().logout();

                       return "redirect:https://www.xiaoti.com:8443/cas/logout?service=https://www.xiaoti.com:9090/logouttips";

             }

    #单点登出

    #配置允许登出后跳转到指定页面

    cas.logout.followServiceRedirects=true

    #跳转到指定页面需要的参数名为 service

    cas.logout.redirectParameter=service

    在CAS服务端退出后,会向注册的每个服务发送登出请求,该请求可以由SingleSignOutFilter进行拦截并销毁当前会话:

    @Configuration

    public class CasConfig {

      

        @Bean

        public FilterRegistrationBean singleSignOutFilterRegistration() {

            FilterRegistrationBean registration = new FilterRegistrationBean(new SingleSignOutFilter());

            registration.addUrlPatterns("/*");

            return registration;

        }

      

        @Bean

        public ServletListenerRegistrationBean singleSignOutListenerRegistration() {

            ServletListenerRegistrationBean registration =

                    new ServletListenerRegistrationBean<>(new SingleSignOutHttpSessionListener());

            return registration;

        }

    }

    ⑦、cas服务端配置

    客户端搭建好后,需要在CAS服务端上配置哪些服务可以注册到CAS服务端上,服务的管理也有许多方式,这里使用的JSON的方式,需要先在服务端引入相关依赖(更多说明见官网):

    <dependency>

                                <groupId>org.apereo.casgroupId>

                                <artifactId>cas-server-support-json-service-registryartifactId>

                                <version>${cas.version}version>

                       dependency>

    然后,在已经生成的war包中找到文件:

    image.png

    将文件复制到这个位置:

    image.png

    修改,serviceId指定允许注册的客户端,更多配置见官网

    {

      "@class" : "org.apereo.cas.services.RegexRegisteredService",

      "serviceId" : "^(https|imaps|http)://(www\\.localhost1\\.com:9090|www\\.localhost2\\.com:9091)/.*",

      "name" : "HTTPS and IMAPS",

      "id" : 10000001,

      "description" : "This service definition authorizes all application urls that support HTTPS and IMAPS protocols.",

      "evaluationOrder" : 10000

    }

    同时在application.properties指定配置文件位置:

    #客户端服务注册

    cas.serviceRegistry.json.location=classpath:/services

    至此,CAS客户端搭建完成了

    三、项目结构图

    image.png

    四、补充

    CAS全称为Central Authentication Service即中央认证服务,是一个企业多语言单点登录的解决方案,并努力去成为一个身份验证和授权需求的综合平台。

    CAS是由Yale大学发起的一个企业级的、开源的项目,旨在为Web应用系统提供一种可靠的单点登录解决方法(属于 Web SSO )。

    CAS协议至少涉及三方:客户端Web浏览器,请求身份验证的Web应用程序和CAS服务器。 它也可能涉及后端服务,如数据库服务器,它没有自己的HTTP接口,但与Web应用程序进行通信。

  • 相关阅读:
    Windows环境下使用Tomcat配置Jenkins 调用Python脚本
    Git小乌龟(TortoiseGit) 简单提交代码到github
    JavaScript流程控制语法
    【开源】基于Vue.js的中小学教师课程排课系统
    http缓存
    短信验证码接口风险分析
    【面经】Thoughtworks 大数据开发面经
    Python学习打卡:day11
    工地安全带穿戴识别系统
    数据结构小记【Python/C++版】——树与二叉树篇
  • 原文地址:https://blog.csdn.net/hanjiepo/article/details/132927708