github:

自定义校验策略

# 继承 AbstractUsernamePasswordAuthenticationHandler

官方提供的认证策略通常是不够,这就需要我们能自定义认证校验策略

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

主要分为下面三个步骤:

  1. <mark>设计</mark>自己的认证处理数据的程序
  2. <mark>注册</mark>认证***到CAS的认证引擎中
  3. 更改认证<mark>配置</mark>到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

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

package cn.cas;

import cn.cas.utils.UserUtils;
import org.apereo.cas.authentication.AuthenticationHandlerExecutionResult;
import org.apereo.cas.authentication.MessageDescriptor;
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.util.StringUtils;

import javax.security.auth.login.AccountException;
import javax.security.auth.login.FailedLoginException;
import java.security.GeneralSecurityException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

/** * @author lawsssscat */
public class CustomerAuthenticationHandler extends AbstractUsernamePasswordAuthenticationHandler {

    public CustomerAuthenticationHandler(
            String name, ServicesManager servicesManager, PrincipalFactory principalFactory, Integer order) {
        super(name, servicesManager, principalFactory, order);
    }

    @Override
    protected AuthenticationHandlerExecutionResult authenticateUsernamePasswordInternal(
            UsernamePasswordCredential credential, String originalPassword)
            throws GeneralSecurityException, PreventedException {
        String username = credential.getUsername();
        String password = credential.getPassword();

        if (StringUtils.isEmpty(username)) {
            throw new AccountException("enter your username");
        } else if (StringUtils.isEmpty(password)) {
            throw new AccountException("enter your PIN");
        }

        System.out.println("username:" + username);
        System.out.println("password:" + password);

        User user = UserUtils.findUser(username);

        System.out.println("user:" + user);

        if (user == null) {
            throw new AccountException("Sorry, username not found !");
        }

        System.out.println("database username:" + user.getUsername());
        System.out.println("database password:" + user.getPassword());

        if (!password.equals(user.getPassword())) {
            throw new FailedLoginException("Sorry, password not correct !");
        }

        // 可自定义返回给客户端多个属性信息
        HashMap<String, Object> info = new HashMap<>();
        info.put("expired", user.getExpired());

        // 不能为null,否则提交信息无法认证成功!
        List<MessageDescriptor> warning = new ArrayList<>();

        return createHandlerResult(credential, this.principalFactory.createPrincipal(username, info), warning);
    }
}

其中涉及到的 实体类 User、连接数据库的工具类UserUtils 不是重点,就到 github 上看吧
https://github.com/LawssssCat/v-cas

由于版本原因(官方是上一个版本),这里给出的与官方实例不同在两个地方

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

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

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

package cn.cas;

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.authentication.principal.PrincipalFactory;
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;

/** * @author lawsssscat */
@Configuration("CustomAuthenticationConfiguration")  // 此需要添加,否则bean会被注册两次(虽然不影响正常运行)
@EnableConfigurationProperties(CasConfigurationProperties.class)
public class CustomAuthenticationConfiguration implements AuthenticationEventExecutionPlanConfigurer {

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

    // 验证器交给Spring管理
    @Bean
    public AuthenticationHandler customerAuthenticationHandler() {
        System.out.println("initializating AuthenticationHandler@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@");
        String name = CustomerAuthenticationHandler.class.getName();
        ServicesManager servicesManager = this.servicesManager;
        PrincipalFactory principalFactory = new DefaultPrincipalFactory();
        // 定义为优先优先使用
        Integer order = 1;
        return new CustomerAuthenticationHandler(name,servicesManager, principalFactory, order) ;
    }

    // 注册自定义验证器
    @Override
    public void configureAuthenticationExecutionPlan(AuthenticationEventExecutionPlan plan) {
        plan.registerAuthenticationHandler(customerAuthenticationHandler());
    }
}

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

org.springframework.boot.autoconfigure.EnableAutoConfiguration=cn.cas.CustomAuthenticationConfiguration
-- auto-generated definition
create table sp_manager
(
    mg_id       int auto_increment comment '主键id'
        primary key,
    mg_name     varchar(32)           not null comment '名称',
    mg_pwd      char(32)              not null comment '密码',
    mg_salt     char(36)              not null comment 'salt',
    mg_time     int unsigned          not null comment '注册时间',
    role_id     tinyint(11) default 0 not null comment '角色id',
    mg_mobile   varchar(32)           null,
    mg_email    varchar(64)           null,
    mg_expired  tinyint(2)  default 0 null comment '0:表示启用 1:表示过期',
    mg_disabled tinyint(2)  default 0 null comment '0:表示启用 1:表示禁用'
)
    comment '管理员表';


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

# 继承 AbstractPreAndPostProcessingAuthenticationHandler

上面的继承其实有问题,我提交的信息不止用户名和密码,那该如何自定义认证?

这里就要我们继承 AbstractPreAndPostProcessingAuthenticationHandler 这个接口
(其实上面的AbstractUsernamePasswordAuthenticationHandler就是继承实现的这个类,它只是用于简单的用户名和密码的校验。)

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

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

package cn.cas.authentication.handler;

import cn.cas.model.User;
import cn.cas.utils.UserUtils;
import lombok.NonNull;
import lombok.extern.log4j.Log4j2;
import org.apereo.cas.authentication.AuthenticationHandlerExecutionResult;
import org.apereo.cas.authentication.Credential;
import org.apereo.cas.authentication.PreventedException;
import org.apereo.cas.authentication.UsernamePasswordCredential;
import org.apereo.cas.authentication.handler.support.AbstractPreAndPostProcessingAuthenticationHandler;
import org.apereo.cas.authentication.principal.Principal;
import org.apereo.cas.authentication.principal.PrincipalFactory;
import org.apereo.cas.services.ServicesManager;
import org.springframework.util.StringUtils;

import javax.security.auth.login.AccountException;
import javax.security.auth.login.FailedLoginException;
import java.security.GeneralSecurityException;

/** * @author lawsssscat */
@Log4j2
public class CustomerAuthenticationHandler2 extends AbstractPreAndPostProcessingAuthenticationHandler {

    public CustomerAuthenticationHandler2(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 usernamePasswordCredential = (UsernamePasswordCredential) credential;

        String username = usernamePasswordCredential.getUsername();
        String password = usernamePasswordCredential.getPassword();

        if (StringUtils.isEmpty(username)) {
            throw new AccountException("enter your username");
        } else if (StringUtils.isEmpty(password)) {
            throw new AccountException("enter your PIN");
        }

        log.info("log4j2 is running @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@");
        System.out.println("username:" + username);
        System.out.println("password:" + password);

        User user = UserUtils.findUser(username);

        System.out.println("user:" + user);

        if (user == null) {
            throw new AccountException("Sorry, username not found !");
        }

        if (!password.equals(user.getPassword())) {
            throw new FailedLoginException("Sorry, password not correct!");
        }

        @NonNull Principal principal = this.principalFactory.createPrincipal(username);

        return createHandlerResult(usernamePasswordCredential, principal);
    }
}

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

接着我们在CustomAuthenticationConfiguration中将CustomerAuthenticationHandler 更改为CustomerAuthenticationHandler2 。

    // 验证器交给Spring管理
    @Bean
    public AuthenticationHandler customerAuthenticationHandler() {
        System.out.println("initializating AuthenticationHandler@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@");
        String name = CustomerAuthenticationHandler2.class.getName();
        ServicesManager servicesManager = this.servicesManager;
        PrincipalFactory principalFactory = new DefaultPrincipalFactory();
        // 定义为优先优先使用
        Integer order = 1;
        return new CustomerAuthenticationHandler2(name,servicesManager, principalFactory, order) ;
    }

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

github: