SpringBoot 集成cas5.3 实现自定义认证策略

本文涉及的产品
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS MySQL,高可用系列 2核4GB
简介: 如果CAS框架提供的方案还是不能满足我们的需要,比如我们不仅需要用户名和密码,还要验证其他信息,比如邮箱,手机号,但是邮箱,手机信息在另一个数据库,还有在一段时间内同一IP输入错误次数限制等。这里就需要我们自定义认证策略,自定义CAS的web认证流程。

如果CAS框架提供的方案还是不能满足我们的需要,比如我们不仅需要用户名和密码,还要验证其他信息,比如邮箱,手机号,但是邮箱,手机信息在另一个数据库,还有在一段时间内同一IP输入错误次数限制等。这里就需要我们自定义认证策略,自定义CAS的web认证流程。

自定义认证校验策略

我们知道CAS为我们提供了多种认证数据源,我们可以选择JDBC、File、JSON等多种方式,但是如果我想在自己的认证方式中可以根据提交的信息实现不同数据源选择,这种方式就需要我们去实现自定义认证。


自定义策略主要通过现实更改CAS配置,通过AuthenticationHandler在CAS中设计和注册自定义身份验证策略,拦截数据源达到目的。


主要分为下面三个步骤:

  • 设计自己的认证处理数据的程序
  • 注册认证拦截器到CAS的认证引擎中
  • 更改认证配置到CAS中


首先我们还是添加需要的依赖库:

<!-- Custom Authentication -->
<dependency>
    <groupId>org.apereo.cas</groupId>
    <artifactId>cas-server-core-authentication-api</artifactId>
    <version>${cas.version}</version>
</dependency>
<!-- Custom Configuration -->
<dependency>
    <groupId>org.apereo.cas</groupId>
    <artifactId>cas-server-core-configuration-api</artifactId>
    <version>${cas.version}</version>
</dependency>

如果我们认证的方式仅仅是传统的用户名和密码,实现AbstractUsernamePasswordAuthenticationHandler这个抽象类就可以了,官方给的实例也是这个。


可以查看官方参考文档:Configuring-Custom-Authentication。官方的实例有一个坑,给出的是5.2.x版本以前的例子,5.3.x版本后的jar包更改了,而且有个地方有坑,在5.2.x版本前的可以,新的5.3.x是不行的。


接着我们自定义我们自己的实现类CustomUsernamePasswordAuthentication,如下:

/**
 * @author tongyao
 */
public class CustomUsernamePasswordAuthentication extends AbstractUsernamePasswordAuthenticationHandler {
    public CustomUsernamePasswordAuthentication(String name, ServicesManager servicesManager, PrincipalFactory principalFactory, Integer order) {
        super(name, servicesManager, principalFactory, order);
    }
    @Override
    protected AuthenticationHandlerExecutionResult authenticateUsernamePasswordInternal(UsernamePasswordCredential usernamePasswordCredential, String s) throws GeneralSecurityException, PreventedException {
        String username = usernamePasswordCredential.getUsername();
        String password = usernamePasswordCredential.getPassword();
        System.out.println("username : " + username);
        System.out.println("password : " + password);
        // JDBC模板依赖于连接池来获得数据的连接,所以必须先要构造连接池
        DriverManagerDataSource dataSource = new DriverManagerDataSource();
        dataSource.setDriverClassName("com.mysql.jdbc.Driver");
        dataSource.setUrl("jdbc:mysql://localhost:3306/cas");
        dataSource.setUsername("root");
        dataSource.setPassword("123");
        // 创建JDBC模板
        JdbcTemplate jdbcTemplate = new JdbcTemplate();
        jdbcTemplate.setDataSource(dataSource);
        String sql = "SELECT * FROM user WHERE username = ?";
        User info = (User) jdbcTemplate.queryForObject(sql, new Object[]{username}, new BeanPropertyRowMapper(User.class));
        System.out.println("database username : "+ info.getUsername());
        System.out.println("database password : "+ info.getPassword());
        if (info == null) {
            throw new AccountException("Sorry, username not found!");
        }
        if (!info.getPassword().equals(password)) {
            throw new FailedLoginException("Sorry, password not correct!");
        } else {
            //可自定义返回给客户端的多个属性信息
            HashMap<String, Object> returnInfo = new HashMap<>();
            returnInfo.put("expired", info.getDisabled());
            final List<MessageDescriptor> list = new ArrayList<>();
            return createHandlerResult(usernamePasswordCredential,
                    this.principalFactory.createPrincipal(username, returnInfo), list);
        }
    }
}

这里给出的与官方实例不同在两个地方, 其一,返回的为AuthenticationHandlerExecutionResult而不是HandlerResult,其实源码是一样的,在新版本重新命名了而已。第二点,createHandlerResult传入的warings不能为null,不然程序运行后提交信息始终无法认证成功!!!


代码主要通过拦截传入的Credential,获取用户名和密码,然后再自定义返回给客户端的用户信息。这里便可以通过代码方式自定义返回给客户端多个不同属性信息。


接着我们注入配置信息,继承AuthenticationEventExecutionPlanConfigurer。

/**
 * @author tongyao
 */
@Configuration("CustomAuthenticationConfiguration")
@EnableConfigurationProperties(CasConfigurationProperties.class)
public class CustomAuthenticationConfiguration implements AuthenticationEventExecutionPlanConfigurer {
    @Autowired
    private CasConfigurationProperties casProperties;
    @Autowired
    @Qualifier("servicesManager")
    private ServicesManager servicesManager;
    @Bean
    public AuthenticationHandler myAuthenticationHandler() {
        // 参数: name, servicesManager, principalFactory, order
        // 定义为优先使用它进行认证
        return new CustomUsernamePasswordAuthentication(CustomUsernamePasswordAuthentication.class.getName(),
                servicesManager, new DefaultPrincipalFactory(), 1);
    }
    @Override
    public void configureAuthenticationExecutionPlan(final AuthenticationEventExecutionPlan plan) {
        plan.registerAuthenticationHandler(myAuthenticationHandler());
    }
}

最后我们我们在src/main/resources目录下新建META-INF目录,同时在下面新建spring.factories文件,将配置指定为我们自己新建的信息。

image.png

org.springframework.boot.autoconfigure.EnableAutoConfiguration=net.anumbrella.sso.config.CustomAuthenticationConfiguration


数据库还是原来的设计,不明白的可以看看我往前的文章如下:

image.png

启动应用,输入用户名和密码,查看控制台我们打印的信息,可以发现我们从登陆页面提交的数据以及从数据库中查询到的数据,匹配信息,登录认证成功!!


从而现实了我们自定义用户名和密码的校验,同时我们还可以选择不同的数据源方式。


可能还有读者提出疑问,我提交的信息不止用户名和密码,那该如何自定义认证?


这里就要我们继承AbstractPreAndPostProcessingAuthenticationHandler这个借口,其实上面的AbstractUsernamePasswordAuthenticationHandler就是继承实现的这个类,它只是用于简单的用户名和密码的校验。我们可以查看源码,如下:

image.png

所以我们要自定义实现AbstractPreAndPostProcessingAuthenticationHandler接可以了。


比如这里我新建CustomerHandlerAuthentication类,如下:

/**
 * @author tongyao
 */
public class CustomerHandlerAuthentication extends AbstractPreAndPostProcessingAuthenticationHandler {
    public CustomerHandlerAuthentication(String name, ServicesManager servicesManager, PrincipalFactory principalFactory, Integer order) {
        super(name, servicesManager, principalFactory, order);
    }
    @Override
    public boolean supports(Credential credential) {
        //判断传递过来的Credential 是否是自己能处理的类型
        return credential instanceof UsernamePasswordCredential;
    }
    @Override
    protected AuthenticationHandlerExecutionResult doAuthentication(Credential credential) throws GeneralSecurityException, PreventedException {
        UsernamePasswordCredential usernamePasswordCredentia = (UsernamePasswordCredential) credential;
        String username = usernamePasswordCredentia.getUsername();
        String password = usernamePasswordCredentia.getPassword();
        System.out.println("username : " + username);
        System.out.println("password : " + password);
        // JDBC模板依赖于连接池来获得数据的连接,所以必须先要构造连接池
        DriverManagerDataSource dataSource = new DriverManagerDataSource();
        dataSource.setDriverClassName("com.mysql.jdbc.Driver");
        dataSource.setUrl("jdbc:mysql://localhost:3306/cas");
        dataSource.setUsername("root");
        dataSource.setPassword("123");
        // 创建JDBC模板
        JdbcTemplate jdbcTemplate = new JdbcTemplate();
        jdbcTemplate.setDataSource(dataSource);
        String sql = "SELECT * FROM user WHERE username = ?";
        User info = (User) jdbcTemplate.queryForObject(sql, new Object[]{username}, new BeanPropertyRowMapper(User.class));
        System.out.println("database username : "+ info.getUsername());
        System.out.println("database password : "+ info.getPassword());
        if (info == null) {
            throw new AccountException("Sorry, username not found!");
        }
        if (!info.getPassword().equals(password)) {
            throw new FailedLoginException("Sorry, password not correct!");
        } else {
            final List<MessageDescriptor> list = new ArrayList<>();
            return createHandlerResult(usernamePasswordCredentia,
                    this.principalFactory.createPrincipal(username, Collections.emptyMap()), list);
        }
    }
}

这里我只是简单实现了用户名和密码的信息获取,当有更多信息提交时,在转换Credential时便可以拿到提交的信息。后面我会讲解,这里不明白没关系。


接着我们在CustomAuthenticationConfiguration中将AuthenticationHandler更改为CustomerHandlerAuthentication。

    @Bean
    public AuthenticationHandler myAuthenticationHandler() {
        // 参数: name, servicesManager, principalFactory, order
        // 定义为优先使用它进行认证
        return new CustomerHandlerAuthentication(CustomerHandlerAuthentication.class.getName(),
                servicesManager, new DefaultPrincipalFactory(), 1);
    }

启动应用,可以发现跟先前能达到相同效果。

关于扩展用户提交的自定义表单信息的可以查看我更多的文章。


参考

相关实践学习
如何快速连接云数据库RDS MySQL
本场景介绍如何通过阿里云数据管理服务DMS快速连接云数据库RDS MySQL,然后进行数据表的CRUD操作。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
目录
相关文章
|
5天前
|
监控 Java Nacos
使用Spring Boot集成Nacos
通过上述步骤,Spring Boot应用可以成功集成Nacos,利用Nacos的服务发现和配置管理功能来提升微服务架构的灵活性和可维护性。通过这种集成,开发者可以更高效地管理和部署微服务。
68 17
|
5天前
|
XML JavaScript Java
SpringBoot集成Shiro权限+Jwt认证
本文主要描述如何快速基于SpringBoot 2.5.X版本集成Shiro+JWT框架,让大家快速实现无状态登陆和接口权限认证主体框架,具体业务细节未实现,大家按照实际项目补充。
38 11
|
7天前
|
缓存 安全 Java
Spring Boot 3 集成 Spring Security + JWT
本文详细介绍了如何使用Spring Boot 3和Spring Security集成JWT,实现前后端分离的安全认证概述了从入门到引入数据库,再到使用JWT的完整流程。列举了项目中用到的关键依赖,如MyBatis-Plus、Hutool等。简要提及了系统配置表、部门表、字典表等表结构。使用Hutool-jwt工具类进行JWT校验。配置忽略路径、禁用CSRF、添加JWT校验过滤器等。实现登录接口,返回token等信息。
133 12
|
6天前
|
负载均衡 IDE Java
SpringBoot整合XXL-JOB【04】- 以GLUE模式运行与执行器负载均衡策略
在本节中,我们将介绍XXL-JOB的GLUE模式和集群模式下的路由策略。GLUE模式允许直接在线上改造方法为定时任务,无需重新部署。通过一个测试方法,展示了如何在调度中心配置并使用GLUE模式执行定时任务。接着,我们探讨了多实例环境下的负载均衡策略,确保任务不会重复执行,并可通过修改路由策略(如轮训)实现任务在多个实例间的均衡分配。最后,总结了GLUE模式和负载均衡策略的应用,帮助读者更深入理解XXL-JOB的使用。
24 9
|
13天前
|
存储 安全 Java
Spring Boot 3 集成Spring AOP实现系统日志记录
本文介绍了如何在Spring Boot 3中集成Spring AOP实现系统日志记录功能。通过定义`SysLog`注解和配置相应的AOP切面,可以在方法执行前后自动记录日志信息,包括操作的开始时间、结束时间、请求参数、返回结果、异常信息等,并将这些信息保存到数据库中。此外,还使用了`ThreadLocal`变量来存储每个线程独立的日志数据,确保线程安全。文中还展示了项目实战中的部分代码片段,以及基于Spring Boot 3 + Vue 3构建的快速开发框架的简介与内置功能列表。此框架结合了当前主流技术栈,提供了用户管理、权限控制、接口文档自动生成等多项实用特性。
56 8
|
Java 应用服务中间件
SpringBoot集成使用jsp(超详细)
SpringBoot集成使用jsp(超详细)
SpringBoot集成使用jsp(超详细)
|
3月前
|
JavaScript 安全 Java
如何使用 Spring Boot 和 Ant Design Pro Vue 实现动态路由和菜单功能,快速搭建前后端分离的应用框架
本文介绍了如何使用 Spring Boot 和 Ant Design Pro Vue 实现动态路由和菜单功能,快速搭建前后端分离的应用框架。首先,确保开发环境已安装必要的工具,然后创建并配置 Spring Boot 项目,包括添加依赖和配置 Spring Security。接着,创建后端 API 和前端项目,配置动态路由和菜单。最后,运行项目并分享实践心得,包括版本兼容性、安全性、性能调优等方面。
216 1
|
2月前
|
JavaScript 安全 Java
如何使用 Spring Boot 和 Ant Design Pro Vue 构建一个具有动态路由和菜单功能的前后端分离应用。
本文介绍了如何使用 Spring Boot 和 Ant Design Pro Vue 构建一个具有动态路由和菜单功能的前后端分离应用。首先,创建并配置 Spring Boot 项目,实现后端 API;然后,使用 Ant Design Pro Vue 创建前端项目,配置动态路由和菜单。通过具体案例,展示了如何快速搭建高效、易维护的项目框架。
145 62
|
1月前
|
存储 JavaScript 前端开发
基于 SpringBoot 和 Vue 开发校园点餐订餐外卖跑腿Java源码
一个非常实用的校园外卖系统,基于 SpringBoot 和 Vue 的开发。这一系统源于黑马的外卖案例项目 经过站长的进一步改进和优化,提供了更丰富的功能和更高的可用性。 这个项目的架构设计非常有趣。虽然它采用了SpringBoot和Vue的组合,但并不是一个完全分离的项目。 前端视图通过JS的方式引入了Vue和Element UI,既能利用Vue的快速开发优势,
129 13
|
1月前
|
JavaScript 安全 Java
java版药品不良反应智能监测系统源码,采用SpringBoot、Vue、MySQL技术开发
基于B/S架构,采用Java、SpringBoot、Vue、MySQL等技术自主研发的ADR智能监测系统,适用于三甲医院,支持二次开发。该系统能自动监测全院患者药物不良反应,通过移动端和PC端实时反馈,提升用药安全。系统涵盖规则管理、监测报告、系统管理三大模块,确保精准、高效地处理ADR事件。

热门文章

最新文章