CAS+Springboot单点登录

什么是单点登录

单点登录就是登录一次处处已登录。
单点登录就是假设我有两个系统 比如 淘宝和天猫,我登录了淘宝,当我访问天猫的时候不用在登录一次。

原文地址
原文地址

本文源码已上传到github

CAS原理

在这里插入图片描述

文字描述 假设我们用3个系统 系统A,系统B,和认证中心
1、访问系统A,第一次访问没登陆,系统A重定向用户认证中心(带上service)
2、用户访问认证中心没有带上TGC(还没登陆),认证中心返回登陆页面
3、用户输入账号密码,进行登陆
4、认证中心进行登陆逻辑校验,成功就向客户端写cookie(TGC),并生成TGT缓存在服务器本地,
用TGT签发ST
5、用户认证中心重定向到第一次访问的带上的service(带上ST)
6、系统A的拦截器收到请求后,拿出ST,向认证中心询问ST是否有效(这个步骤对用户透明,直接使用http访问)
7、认证中心回复有效,并返回用户名字,和一些其他属性
8、系统A收到回复,建立本地Session

当用户第二次访问系统A的时候,由于第一次已经建立了本地session,所以成功登陆

1、当用户访问系统B的时候,没有本地session,系统B将请求重定向到用户认证中心(带上service)
2、这个时候用于我们第一次访问系统A的时候,用户认证中心,已经写下TGC,所以访问的是会带上TGC,认证中心根据TGC找到TGT,说明已经登陆过了
3、认证中心重定向到service地址带上ST
4、系统B收到ST向认证中心询问,ST是否有效,(这个步骤对用户透明,直接使用http访问)
5、认证中心回复有效,并返回用户名字,和一些其他属性
6、系统B收到回复,建立本地Session
参考
https://blog.csdn.net/ban_tang/article/details/80015946
https://www.cnblogs.com/notDog/p/5252973.html

CAS Server搭建自定义登陆逻辑及页面

1、从github下载解压(我选择5.2分支)
https://github.com/apereo/cas-overlay-template

2、导入idea
在这里插入图片描述

目录结构如上图,我们自己建src/java/main、resource目录,并修改project structure
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZeSgtBrn-1589868767244)(/upload/微信图片_20200519112239.png)]

在这里插入图片描述

3、修改pom文件
由于我们使用overlay方式进行开发的,war引用的依赖我们在开发中是依赖不到的,所以我们如果使用到Cas的jar还是需要引用的,我们可以将生命周期设置为provided。

由于这里我要查询数据库和集成mybaits,所以也依赖了

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd ">
    <modelVersion>4.0.0</modelVersion>
    <groupId>org.apereo.cas</groupId>
    <artifactId>cas-overlay</artifactId>
    <packaging>war</packaging>
    <version>1.0</version>

    <!-- 使用overlay 方式是在war的基础上进行开发的,,war里面pom问题引用的我们在开发的时候是引用不到的, -->
    <!-- 所以我们如果需要使用cas的jar包,我们还需要在这个文件中进行引用,scope 为provided就行, -->
    <!-- dependencies 这部分是我们自己的依赖,,只是将仓库删除,不然下载不了包,, -->
    <dependencies>
        <dependency>
            <groupId>org.apereo.cas</groupId>
            <artifactId>cas-server-webapp${app.server}</artifactId>
            <version>${cas.version}</version>
            <type>war</type>
            <scope>runtime</scope>
        </dependency>

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

        <dependency>
            <groupId>org.apereo.cas</groupId>
            <artifactId>cas-server-core-authentication</artifactId>
            <version>${cas.version}</version>
            <scope>provided</scope>
        </dependency>

        <dependency>
            <groupId>org.apereo.cas</groupId>
            <artifactId>cas-server-core-authentication-api</artifactId>
            <version>${cas.version}</version>
            <scope>provided</scope>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.44</version>
        </dependency>

        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis-spring</artifactId>
            <version>2.0.3</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.4.6</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter -->
        <!--引入Spring SPI机制 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
            <version>1.5.2.RELEASE</version>
            <exclusions>
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-logging</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

        <dependency>
            <groupId>org.apereo.cas</groupId>
            <artifactId>cas-server-core-configuration</artifactId>
            <version>${cas.version}</version>
            <scope>provided</scope>
        </dependency>

        <dependency>
            <groupId>org.apereo.cas</groupId>
            <artifactId>cas-server-support-rest</artifactId>
            <version>${cas.version}</version>
            <scope>provided</scope>
        </dependency>

        <dependency>
            <groupId>org.apereo.cas</groupId>
            <artifactId>cas-server-core-webflow-api</artifactId>
            <version>${cas.version}</version>
            <scope>provided</scope>
        </dependency>

        <dependency>
            <groupId>org.apereo.cas</groupId>
            <artifactId>cas-server-core-webflow</artifactId>
            <version>${cas.version}</version>
            <scope>provided</scope>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.apereo.cas/cas-server-support-actions -->
        <dependency>
            <groupId>org.apereo.cas</groupId>
            <artifactId>cas-server-support-actions</artifactId>
            <version>${cas.version}</version>
            <scope>provided</scope>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.12.6</version>
            <scope>provided</scope>
        </dependency>

    </dependencies>

    <!--下面是复制war原本的  只是删除仓库-->
    <build>
        <plugins>
            <plugin>
                <groupId>com.rimerosolutions.maven.plugins</groupId>
                <artifactId>wrapper-maven-plugin</artifactId>
                <version>0.0.4</version>
                <configuration>
                    <verifyDownload>true</verifyDownload>
                    <checksumAlgorithm>MD5</checksumAlgorithm>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>${springboot.version}</version>
                <configuration>
                    <mainClass>${mainClassName}</mainClass>
                    <addResources>true</addResources>
                    <executable>${isExecutable}</executable>
                    <layout>WAR</layout>
                </configuration>
                <executions>
                    <execution>
                        <goals>
                            <goal>repackage</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-war-plugin</artifactId>
                <version>2.6</version>
                <configuration>
                    <warName>cas</warName>
                    <failOnMissingWebXml>false</failOnMissingWebXml>
                    <recompressZippedFiles>false</recompressZippedFiles>
                    <archive>
                        <compress>false</compress>
                        <manifestFile>${manifestFileToUse}</manifestFile>
                    </archive>
                    <overlays>
                        <overlay>
                            <groupId>org.apereo.cas</groupId>
                            <artifactId>cas-server-webapp${app.server}</artifactId>
                            <!--原有的服务不再初始化进去-->
                            <excludes>
<!--                                <exclude>WEB-INF/classes/services/*</exclude>-->
<!--                                <exclude>WEB-INF/classes/application.*</exclude>-->
<!--                                <exclude>WEB-INF/classes/log4j2.*</exclude>-->
                            </excludes>
                        </overlay>
                    </overlays>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.3</version>
            </plugin>
        </plugins>
        <finalName>cas</finalName>
    </build>

    <properties>
        <cas.version>5.2.6</cas.version>
        <springboot.version>1.5.12.RELEASE</springboot.version>
        <!-- app.server could be -jetty, -undertow, -tomcat, or blank if you plan to provide appserver -->
        <app.server>-tomcat</app.server>

        <mainClassName>org.springframework.boot.loader.WarLauncher</mainClassName>
        <isExecutable>false</isExecutable>
        <manifestFileToUse>${project.build.directory}/war/work/org.apereo.cas/cas-server-webapp${app.server}/META-INF/MANIFEST.MF</manifestFileToUse>

        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <profiles>
        <profile>
            <activation>
                <activeByDefault>true</activeByDefault>
            </activation>
            <id>default</id>
            <dependencies>
                <dependency>
                    <groupId>org.apereo.cas</groupId>
                    <artifactId>cas-server-webapp${app.server}</artifactId>
                    <version>${cas.version}</version>
                    <type>war</type>
                    <scope>runtime</scope>
                </dependency>
                <!--
                ...Additional dependencies may be placed here...
                -->
            </dependencies>
        </profile>

        <profile>
            <activation>
                <activeByDefault>false</activeByDefault>
            </activation>
            <id>exec</id>
            <properties>
                <mainClassName>org.apereo.cas.web.CasWebApplication</mainClassName>
                <isExecutable>true</isExecutable>
                <manifestFileToUse></manifestFileToUse>
            </properties>
            <build>
                <plugins>
                    <plugin>
                        <groupId>com.soebes.maven.plugins</groupId>
                        <artifactId>echo-maven-plugin</artifactId>
                        <version>0.3.0</version>
                        <executions>
                            <execution>
                                <phase>prepare-package</phase>
                                <goals>
                                    <goal>echo</goal>
                                </goals>
                            </execution>
                        </executions>
                        <configuration>
                            <echos>
                                <echo>Executable profile to make the generated CAS web application executable.</echo>
                            </echos>
                        </configuration>
                    </plugin>
                </plugins>
            </build>
        </profile>

        <profile>
            <activation>
                <activeByDefault>false</activeByDefault>
            </activation>
            <id>bootiful</id>
            <properties>
                <app.server>-tomcat</app.server>
                <isExecutable>false</isExecutable>
            </properties>
            <dependencies>
                <dependency>
                    <groupId>org.apereo.cas</groupId>
                    <artifactId>cas-server-webapp${app.server}</artifactId>
                    <version>${cas.version}</version>
                    <type>war</type>
                    <scope>runtime</scope>
                </dependency>
            </dependencies>
        </profile>

        <profile>
            <activation>
                <activeByDefault>false</activeByDefault>
            </activation>
            <id>pgp</id>
            <build>
                <plugins>
                    <plugin>
                        <groupId>com.github.s4u.plugins</groupId>
                        <artifactId>pgpverify-maven-plugin</artifactId>
                        <version>1.1.0</version>
                        <executions>
                            <execution>
                                <goals>
                                    <goal>check</goal>
                                </goals>
                            </execution>
                        </executions>
                        <configuration>
                            <pgpKeyServer>hkp://pool.sks-keyservers.net</pgpKeyServer>
                            <pgpKeysCachePath>${settings.localRepository}/pgpkeys-cache</pgpKeysCachePath>
                            <scope>test</scope>
                            <verifyPomFiles>true</verifyPomFiles>
                            <failNoSignature>false</failNoSignature>
                        </configuration>
                    </plugin>
                </plugins>
            </build>
        </profile>
    </profiles>
</project>

4、创建登陆处理类
这里我第一次先将账号密码写死,后面使用了jdbc去查询数据库,后面又集成了mybatis,所以这个比较乱,自行修改

package com.ding;

import org.apereo.cas.authentication.HandlerResult;
import org.apereo.cas.authentication.PreventedException;
import org.apereo.cas.authentication.UsernamePasswordCredential;
import org.apereo.cas.authentication.handler.support.AbstractUsernamePasswordAuthenticationHandler;
import org.apereo.cas.authentication.principal.PrincipalFactory;
import org.apereo.cas.services.ServicesManager;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

import javax.security.auth.login.FailedLoginException;
import java.security.GeneralSecurityException;
import java.util.HashMap;
import java.util.Map;


/**
 * 自定义登陆逻辑参考和页面
 * https://blog.csdn.net/u010588262/article/details/80014083
 * https://blog.csdn.net/weixin_37548740/article/details/104053834
 */
public class Login extends AbstractUsernamePasswordAuthenticationHandler {

    private SysUserMapper sysUserMapper;

    public Login(String name, ServicesManager servicesManager, PrincipalFactory principalFactory, Integer order,SysUserMapper sysUserMapper) {
        super(name, servicesManager, principalFactory, order);
        this.sysUserMapper = sysUserMapper;
    }

    @Override
    protected HandlerResult authenticateUsernamePasswordInternal(UsernamePasswordCredential usernamePasswordCredential, String s) throws GeneralSecurityException, PreventedException {
        //使用jdbc试一下 和使用mybatis试试
        DriverManagerDataSource d=new DriverManagerDataSource();
        d.setDriverClassName("com.mysql.jdbc.Driver");
        d.setUrl("jdbc:mysql://127.0.0.1:3306/blog");
        d.setUsername("root");
        d.setPassword("123456");
        JdbcTemplate template=new JdbcTemplate();
        template.setDataSource(d);

        String username=usernamePasswordCredential.getUsername();
        String pd=usernamePasswordCredential.getPassword();
        Map<String,Object> user = template.queryForMap("SELECT `password` FROM t_sys_user WHERE user_name = ?", username);
        String pad = sysUserMapper.findUserName(username);
        System.out.printf(pad);
        //查询数据库加密的的密码
        if(username==null || username.equals("admin1111")){
            throw new FailedLoginException("没有该用户");
        }

        //返回多属性
        Map<String, Object> map=new HashMap<>();
        map.put("email", "34865666@qq.com");
        map.put("phone", "18850588888");
        if(username.equals("admin") && pd.equals("123456")){
            return createHandlerResult(usernamePasswordCredential, principalFactory.createPrincipal(username, map), null);
        }
        throw new FailedLoginException("Sorry, login attemp failed.");
    }
}

5、新增登陆配置器

package com.ding;

import org.apereo.cas.authentication.AuthenticationEventExecutionPlan;
import org.apereo.cas.authentication.AuthenticationEventExecutionPlanConfigurer;
import org.apereo.cas.authentication.AuthenticationHandler;
import org.apereo.cas.authentication.principal.DefaultPrincipalFactory;
import org.apereo.cas.configuration.CasConfigurationProperties;
import org.apereo.cas.services.ServicesManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * 還需要在下面这个问题配置  不然spring不会管理这个文件
 * O:\cas\cas-overlay-template-5.2\cas-overlay-template-5.2\src\main\resource\META-INF\spring.factories
 */
@Configuration("CustomAuthConfig")
@EnableConfigurationProperties(CasConfigurationProperties.class)
public class CustomAuthConfig implements AuthenticationEventExecutionPlanConfigurer {
    @Autowired
    private CasConfigurationProperties casProperties;

    @Autowired
    @Qualifier("servicesManager")
    private ServicesManager servicesManager;

    @Autowired
    private SysUserMapper sysUserMapper;

    @Bean
    public AuthenticationHandler myAuthenticationHandler() {
        final Login handler = new Login(Login.class.getSimpleName(), servicesManager, new DefaultPrincipalFactory(), 10,sysUserMapper);
        return handler;
    }

    @Override
    public void configureAuthenticationExecutionPlan(AuthenticationEventExecutionPlan plan) {
        plan.registerAuthenticationHandler(myAuthenticationHandler());
    }
}

还需要在src\main\resource\META-INF\spring.factories进行配置
这个文件我们可以去overlay复制过来修改

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  com.ding.CustomAuthConfig,\
  com.ding.MyBatisDataSourceConfig

6、resource建立services(注册客户端)

去将overlay的\services\HTTPSandIMAPS-10000001.json复制过来进行修改
这里由于我还没有配置https,先使用http所以serviceId配置http
我们可以通过这个进行自定义登陆页面 “theme”: “blog”,会找
resource\templates\blog\casLoginView.html

{
  "@class" : "org.apereo.cas.services.RegexRegisteredService",
  "serviceId" : "^(https||http|imaps)://.*",
  "name" : "HTTPS and IMAPS",
  "id" : 10000001,
  "description" : "自定义登陆页面  theme 获取templates/blog/casLoginView.html",
  "evaluationOrder" : 10000,
  "theme": "blog",
  "attributeReleasePolicy": {
    "@class": "org.apereo.cas.services.ReturnAllAttributeReleasePolicy"
  }
}

7、配置application.properties配置文件
这个文件我们可以去overlay复制过来修改

##
# CAS Server Context Configuration
#

server.context-path=/cas
server.port=8080
#####start#########签发证书,如果是用spring boot之类嵌入式的容器,则需要改这里的配置,如果是直接部在tomcat中,则需要把tomcat改成https的###################

#server.ssl.key-store=file:/etc/cas/thekeystore
#server.ssl.key-store-password=changeit
#server.ssl.key-password=changeit
# server.ssl.ciphers=
# server.ssl.client-auth=
# server.ssl.enabled=
# server.ssl.key-alias=
# server.ssl.key-store-provider=
# server.ssl.key-store-type=
# server.ssl.protocol=
# server.ssl.trust-store=
# server.ssl.trust-store-password=
# server.ssl.trust-store-provider=
# server.ssl.trust-store-type=

server.max-http-header-size=2097152
server.use-forward-headers=true
server.connection-timeout=20000
server.error.include-stacktrace=ALWAYS

server.compression.enabled=true
server.compression.mime-types=application/javascript,application/json,application/xml,text/html,text/xml,text/plain

server.tomcat.max-http-post-size=2097152
server.tomcat.basedir=build/tomcat
server.tomcat.accesslog.enabled=true
server.tomcat.accesslog.pattern=%t %a "%r" %s (%D ms)
server.tomcat.accesslog.suffix=.log
server.tomcat.max-threads=10
server.tomcat.port-header=X-Forwarded-Port
server.tomcat.protocol-header=X-Forwarded-Proto
server.tomcat.protocol-header-https-value=https
server.tomcat.remote-ip-header=X-FORWARDED-FOR
server.tomcat.uri-encoding=UTF-8
#####end#########签发证书,如果是用spring boot之类嵌入式的容器,则需要改这里的配置,如果是直接部在tomcat中,则需要把tomcat改成https的###################

spring.http.encoding.charset=UTF-8
spring.http.encoding.enabled=true
spring.http.encoding.force=true

##
# CAS Cloud Bus Configuration
#
spring.cloud.bus.enabled=false
# spring.cloud.bus.refresh.enabled=true
# spring.cloud.bus.env.enabled=true
# spring.cloud.bus.destination=CasCloudBus
# spring.cloud.bus.ack.enabled=true

endpoints.enabled=false
endpoints.sensitive=true

endpoints.restart.enabled=false
endpoints.shutdown.enabled=false

management.security.enabled=true
management.security.roles=ACTUATOR,ADMIN
management.security.sessions=if_required
management.context-path=/status
management.add-application-context-header=false

security.basic.authorize-mode=role
security.basic.enabled=false
security.basic.path=/cas/status/**

##
# CAS Web Application Session Configuration
#
server.session.timeout=300
server.session.cookie.http-only=true
server.session.tracking-modes=COOKIE

##
# CAS Thymeleaf View Configuration
#
spring.thymeleaf.encoding=UTF-8
spring.thymeleaf.cache=true
spring.thymeleaf.mode=HTML
##
# CAS Log4j Configuration
#
# logging.config=file:/etc/cas/log4j2.xml
server.context-parameters.isLog4jAutoInitializationDisabled=true

##
# CAS AspectJ Configuration
#
spring.aop.auto=true
spring.aop.proxy-target-class=true

##
# CAS Authentication Credentials
#
#cas.authn.accept.users=casuser::Mellon



#开启识别json文件,默认false

#自动扫描服务配置,默认开启
#cas.serviceRegistry.watcherEnabled=true
#120秒扫描一遍
#cas.serviceRegistry.repeatInterval=120000
#延迟15秒开启
#cas.serviceRegistry.startDelay=15000
#资源加载路径
cas.serviceRegistry.config.location=classpath:/services
cas.tgc.secure=false
cas.serviceRegistry.initFromJson=true

# 默认主题
#cas.theme.defaultThemeName=blog

8、自定义登陆页面
创建templates\blog目录及casLoginView.html文件
src\main\resource\templates\blog\casLoginView.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8"/>
    <meta name="viewport" content="width=device-width, initial-scale=1"/>
    <meta http-equiv="X-UA-Compatible" content="IE=edge"/>
    <link rel="stylesheet" th:href="@{${#themes.code('blog.css.file')}}"/>
    <script th:src="@{${#themes.code('blog.js.file')}}"></script>
    <title th:text="${#themes.code('demo.pageTitle')}"></title>
</head>
<!--里面以${#themes.code('blog.js.file')}形式获取的参数是从主题同名文件blog.properties中获取的:properties中获取的-->

<body>
<h1 th:text="${#themes.code('demo.pageTitle')}"></h1>
<div>
    <form method="POST" th:object="${credential}">
        <div th:if="${#fields.hasErrors('*')}">
            <span th:each="err : ${#fields.errors('*')}" th:utext="${err}"/>
        </div>
        <h2 th:utext="#{screen.welcome.instructions}"></h2>

        <section class="row">
            <label for="username" th:utext="#{screen.welcome.label.netid}"/>
            <div th:unless="${openIdLocalId}">
                <input class="required"
                       id="username"
                       size="25"
                       tabindex="1"
                       type="text"
                       th:disabled="${guaEnabled}"
                       th:field="*{username}"
                       th:accesskey="#{screen.welcome.label.netid.accesskey}"
                       autocomplete="off"/>
            </div>
        </section>

        <section class="row">
            <label for="password" th:utext="#{screen.welcome.label.password}"/>
            <div>
                <input class="required"
                       type="password"
                       id="password"
                       size="25"
                       tabindex="2"
                       th:accesskey="#{screen.welcome.label.password.accesskey}"
                       th:field="*{password}"
                       autocomplete="off"/>
            </div>
        </section>

        <section>
            <input type="hidden" name="execution" th:value="${flowExecutionKey}"/>
            <input type="hidden" name="_eventId" value="submit"/>
            <input type="hidden" name="geolocation"/>
            <input class="btn btn-submit btn-block"
                   name="submit"
                   accesskey="l"
                   th:value="#{screen.welcome.button.login}"
                   tabindex="6"
                   type="submit"/>
            <a th:href="@{/reg}">点我注册</a>
        </section>
    </form>
</div>
</body>
</html>

CAS Client搭建

1、新建一个spring boot项目
2、引入CAS客户端的依赖

	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>

		<dependency>
			<groupId>org.mybatis.spring.boot</groupId>
			<artifactId>mybatis-spring-boot-starter</artifactId>
			<version>2.1.2</version>
		</dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<scope>runtime</scope>
		</dependency>

        <!-- 要有一个验证登陆成功跳转页面,使用使用thymeleaf-->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-thymeleaf</artifactId>
		</dependency>

		<!--CAS客户端依赖-->
		<dependency>
			<groupId>org.jasig.cas.client</groupId>
			<artifactId>cas-client-core</artifactId>
			<version>3.5.0</version>
		</dependency>
	</dependencies>

3、新增CAS客户端拦截器bean的配置

package com.ding.cas;

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.beans.factory.annotation.Autowired;
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 java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 查看下面这个链接和开放平台
 * https://www.cnblogs.com/whm-blog/p/11248304.html
 */
@Configuration
public class CasConfigure {

    @Autowired
    private CasClientProperties casClientProperties;

    /**
     * 用于实现单点登出功能
     */
    @Bean
    public ServletListenerRegistrationBean<SingleSignOutHttpSessionListener> singleSignOutHttpSessionListener() {
        ServletListenerRegistrationBean<SingleSignOutHttpSessionListener> listener = new ServletListenerRegistrationBean<>();
        listener.setEnabled(true);
        listener.setListener(new SingleSignOutHttpSessionListener());
        listener.setOrder(1);
        return listener;
    }

    /**
     * 该过滤器用于实现单点登出功能,单点退出配置,一定要放在其他filter之前
     */
    @Bean
    public FilterRegistrationBean singleSignOutFilter() {
        FilterRegistrationBean filterRegistration = new FilterRegistrationBean();
        filterRegistration.setFilter(new SingleSignOutFilter());
        filterRegistration.setEnabled(true);
        filterRegistration.addUrlPatterns(casClientProperties.getFilterUrl ());
        filterRegistration.addInitParameter("casServerUrlPrefix", casClientProperties.getCasServerLoginUrl());
        filterRegistration.addInitParameter("serverName", casClientProperties.getServerName());
        filterRegistration.setOrder(3);
        return filterRegistration;
    }

    /**
     * 该过滤器负责用户的认证工作
     * @return
     */
    @Bean
    public FilterRegistrationBean authenticationFilterRegistrationBean() {
        FilterRegistrationBean authenticationFilter = new FilterRegistrationBean();
        authenticationFilter.setFilter(new AuthenticationFilter());
        Map<String, String> initParameters = new HashMap<>();
        initParameters.put("casServerLoginUrl", casClientProperties.getCasServerLoginUrl());
        initParameters.put("ignorePattern", "/openApi/|/recall/|/test/|/health|/open/|/error*|/webjars/|/swagger*|/Mei*|/assets*|/v2/api*");
        initParameters.put("serverName", casClientProperties.getServerName());
        authenticationFilter.setInitParameters(initParameters);
        authenticationFilter.setOrder(4);
        List<String> urlPatterns = new ArrayList<>();
        urlPatterns.add(casClientProperties.getFilterUrl());
        authenticationFilter.setUrlPatterns(urlPatterns);
        return authenticationFilter;
    }

    /**
     * 该过滤器负责对Ticket的校验工作
     * @return
     */
    @Bean
    public FilterRegistrationBean cas20ProxyReceivingTicketValidationFilter() {
        FilterRegistrationBean registrationBean = new FilterRegistrationBean();
        registrationBean.setFilter(new Cas30ProxyReceivingTicketValidationFilter());
        registrationBean.addUrlPatterns("/*");
        registrationBean.addInitParameter("casServerUrlPrefix", casClientProperties.getCasServerUrlPrefix());
        registrationBean.addInitParameter("serverName", casClientProperties.getServerName());
        registrationBean.addInitParameter("useSession", String.valueOf(true));
        registrationBean.addInitParameter("exceptionOnValidationFailure", String.valueOf(false));
        registrationBean.addInitParameter("redirectAfterValidation", String.valueOf(true));
        registrationBean.setEnabled(casClientProperties.isEnable());
        registrationBean.setOrder(4);
        return registrationBean;
    }

    /**
     * 该过滤器对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(casClientProperties.getFilterUrl());
        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<String>();
        urlPatterns.add(casClientProperties.getFilterUrl());
        authenticationFilter.setUrlPatterns(urlPatterns);
        return authenticationFilter;
    }
}

@Configuration
public class CasClientProperties {
    /**
     * 是否开启单点登录
     */
    private boolean enable = true;
    /**
     * 单点登录需要访问的CAS SERVER URL入口
     */
    private String casServerLoginUrl;
    /**
     * 托管此应用的服务器名称,例如本机:http://localhost:8080
     */
    private String serverName;

    /**
     * cas服务器的开头  例如 http://localhost:8443/cas
     */
    private String casServerUrlPrefix;

    /**
     * 验证白名单,当请求路径匹配此表达式时,自动通过验证
     */
    private String ignorePattern;

    /**
     * 白名单表达式的类型
     * REGEX 正则表达式 默认的
     * CONTAINS  包含匹配
     * EXACT 精确匹配
     */
    private String ignoreUrlPatternType;

    private String filterUrl;

	setget方法
}

4、新增登陆成功页面

@Controller
public class IndexController {

    @RequestMapping("/index")
    public String sayHello(){
        //方案一:获取其他属性和名字
        Map<String,Object> map = AssertionHolder.getAssertion().getPrincipal().getAttributes();
        AssertionHolder.getAssertion().getPrincipal().getName();
        //方案二:获取其他属性和名字
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        Principal principal = request.getUserPrincipal();
        Map<String, Object> attributes = ((AttributePrincipal) principal).getAttributes();
        return "index";
    }

    /**
     * 验证会不会被拦截
     * @return
     */
    @RequestMapping("/openApi/openApi")
    public String openApi(){
        return "openApi";
    }
}

新增index.html页面和openApi.html

<!DOCTYPE html>
<html lang="en" xmlns="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
index
</body>
</html>

CAS 安全性

TGC的安全性

对于一个CAS用户来说,最重要是要保护它的 TGC ,如果 TGC 不慎被 CAS Server 以外的实体获得, Hacker 能够找到该 TGC ,然后冒充 CAS 用户访问所有授权资源。

从基础模式可以看出, TGC 是 CAS Server 通过 SSL 方式发送给终端用户,因此,要截取 TGC 难度非常大,从而确保 CAS 的安全性。所以CAS的安全性是依赖于SSL的。

TGC 面临的风险主要并非传输窃取。比如你登陆了之后,没有 Logout ,离开了电脑,别人就可以打开你的浏览器,直接访问你授权访问的应用 ,设置一个 TGC 的有效期,可以减少被别人盗用

Service Ticket安全性

首要明白, Service Ticket 是通过 Http 传送的,所有网络中的其他人可以 Sniffer 到其他人的 Ticket 。

CAS 协议从几个方面让 Service Ticket 变得更加安全。

1、Service Ticket 只能使用一次。
CAS 协议规定,无论 Service Ticket 验证是否成功, CAS Server 都会将服务端的缓存中清除该 Ticket ,从而可以确保一个 Service Ticket 不能被使用两次。

2、Service Ticket 在一段时间内失效。
假设用户拿到 Service Ticket 之后,他请求 helloservice 的过程又被中断了, Service Ticket 就被空置了,事实上,此时, Service Ticket 仍然有效。 CAS 规定 Service Ticket 只能存活一定的时间,然后 CAS Server 会让它失效。

url带jsessionid处理

如下面链接,当我们从cas登录成功后跳回页面,url后面带上jsessionid,这样看起来很别扭
http://127.0.0.1:10086/index;jsessionid=5D9A11B35C145518155D141B771368F0

去掉jsessionid方法
我们只需要在cas登录页面form表单提交的时候设置method=“POST”,post要大写,小写不行

<form method="POST" th:object="${credential}"></form>

CAS返回多属性

默认情况下CAS只会返回username给客户端,但是在实际情况下,一个username是不能满足我们的要求的,我们可能需要邮件,电话号码,权限等数据,所以我们需要对CAS进行改造
1、修改Cas server的services文件

修改services可以设置不同的客户端返回不同的属性
使用ReturnAllAttributeReleasePolicy表示返回所有的属性,当然也可以限制返回部分属性,限制哪些属性不能返回。
{
  "@class" : "org.apereo.cas.services.RegexRegisteredService",
  "serviceId" : "^(https||http|imaps)://.*",
  "name" : "HTTPS and IMAPS",
  "id" : 10000001,
  "description" : "自定义登陆页面  theme 获取templates/blog/casLoginView.html",
  "evaluationOrder" : 10000,
  "theme": "blog",
  "attributeReleasePolicy": {
    "@class": "org.apereo.cas.services.ReturnAllAttributeReleasePolicy"
  }
}

2、在CAS server修改登陆处理逻辑

。。。。
。。。。
 //返回多属性
        Map<String, Object> map=new HashMap<>();
        map.put("email", "34865666@qq.com");
        map.put("phone", "18850588888");
        if(username.equals("admin") && pd.equals("123456")){
            return createHandlerResult(usernamePasswordCredential, principalFactory.createPrincipal(username, map), null);
        }
        throw new FailedLoginException("Sorry, login attemp failed.");

3、修改客户端
原本我以为只要执行上面2个步骤就行了。但是实际上,我在客户端还是获取不到。
这里我的CAS是5.2.26版本,原本是使用CAS2.0协议的,需要改成CAS3.0协议,

我们需要在检查Ticket的时候使用Cas30ProxyReceivingTicketValidationFilter,原本是使用Cas20ProxyReceivingTicketValidationFilter不行

    /**
     * 该过滤器负责对Ticket的校验工作
     * @return
     */
    @Bean
    public FilterRegistrationBean cas20ProxyReceivingTicketValidationFilter() {
        FilterRegistrationBean registrationBean = new FilterRegistrationBean();

        registrationBean.setFilter(new Cas30ProxyReceivingTicketValidationFilter());
        registrationBean.addUrlPatterns("/*");
        registrationBean.addInitParameter("casServerUrlPrefix", casClientProperties.getCasServerUrlPrefix());
        registrationBean.addInitParameter("serverName", casClientProperties.getServerName());
        registrationBean.addInitParameter("useSession", String.valueOf(true));
        registrationBean.addInitParameter("exceptionOnValidationFailure", String.valueOf(false));
        registrationBean.addInitParameter("redirectAfterValidation", String.valueOf(true));
        registrationBean.setEnabled(casClientProperties.isEnable());
        registrationBean.setOrder(4);
        return registrationBean;

4.客戶端获取数据

 @RequestMapping("/index")
    public String sayHello(){
        //方案一:获取其他属性和名字
        Map<String,Object> map = AssertionHolder.getAssertion().getPrincipal().getAttributes();
        AssertionHolder.getAssertion().getPrincipal().getName();
        //方案二:获取其他属性和名字
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        Principal principal = request.getUserPrincipal();
        Map<String, Object> attributes = ((AttributePrincipal) principal).getAttributes();
        return "index";
    }

参考

  • https://blog.csdn.net/u010588262/article/details/80014083
  • https://blog.csdn.net/weixin_37548740/article/details/104053834

原文地址(有图有真相)
https://xujd.top/article/view.do?TUMnF0tjG1NJE14mBmbCvUdZ
原文地址(有图有真相)
https://xujd.top/article/view.do?TUMnF0tjG1NJE14mBmbCvUdZ

  • 2
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值