zoukankan      html  css  js  c++  java
  • CAS单点登录(四)——自定义认证登录策略

    CAS单点登录(四)——自定义认证登录策略

    https://blog.csdn.net/Anumbrella/article/details/81590595

    在上一节中我们使用了CAS的多种认证方式完成了多种方式的登录认证,也就是我们主要是使用了CAS为我们封装的多种不同的认证方案,基本上能够满足我们多种需求的认证,如果还是对CAS多种认证方式不是很了解,可以先去复习一下原文——CAS单点登录(三)——多种认证方式。

    但是如果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>
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    如果我们认证的方式仅仅是传统的用户名和密码,实现AbstractUsernamePasswordAuthenticationHandler这个抽象类就可以了,官方给的实例也是这个。

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

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

    /**
    * @author anumbrella
    */
    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);
    }
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    这里给出的与官方实例不同在两个地方, 其一,返回的为AuthenticationHandlerExecutionResult而不是HandlerResult,其实源码是一样的,在新版本重新命名了而已。第二点,createHandlerResult传入的warings不能为null,不然程序运行后提交信息始终无法认证成功!!!

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

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

    /**
    * @author anumbrella
    */
    @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());
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    最后我们我们在src/main/resources目录下新建META-INF目录,同时在下面新建spring.factories文件,将配置指定为我们自己新建的信息。

    org.springframework.boot.autoconfigure.EnableAutoConfiguration=net.anumbrella.sso.config.CustomAuthenticationConfiguration
    1
    数据库还是原来的设计,如下:

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

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

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

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


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

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

    /**
    * @author anumbrella
    */
    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);
    }
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    这里我只是简单实现了用户名和密码的信息获取,当有更多信息提交时,在转换Credential时便可以拿到提交的信息。后面我会讲解,这里不明白没关系。

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

    @Bean
    public AuthenticationHandler myAuthenticationHandler() {
    // 参数: name, servicesManager, principalFactory, order
    // 定义为优先使用它进行认证
    return new CustomerHandlerAuthentication(CustomerHandlerAuthentication.class.getName(),
    servicesManager, new DefaultPrincipalFactory(), 1);
    }
    1
    2
    3
    4
    5
    6
    7
    启动应用,可以发现跟先前能达到相同效果。

    关于扩展用户提交的自定义表单信息的知识将在下一节进行讲解。

    代码实例:Chapter3

    参考
    https://apereo.github.io/cas/5.3.x/installation/Configuring-Authentication-Components.html
    CAS之5.2x版本自定义登录,多数据源登录-yellowcong
    ————————————————
    版权声明:本文为CSDN博主「Anumbrella」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
    原文链接:https://blog.csdn.net/Anumbrella/article/details/81590595

  • 相关阅读:
    FORM触发器执行顺序
    Invoke和BeginInvoke理解
    理解AppDomain
    BackgroundWorker学习笔记
    NLog类库使用探索——编程配置
    NLog类库使用探索——详解配置
    NLog类库的使用探索——认识配置+实习小感悟
    深入探讨WPF的ListView控件
    深入理解IOC模式及Unity框架
    网络通信之 字节序转换原理与网络字节序、大端和小端模式
  • 原文地址:https://www.cnblogs.com/handsome1013/p/12394536.html
Copyright © 2011-2022 走看看