CAS单点登录-自定义认证之重写Credential(十五)

CAS单点登录-自定义认证之重写Credential (十五)

ps:这一章拖了非常久,最近事情也比较多,希望能谅解~

再次目前使用cas版本为:5.1.5


我们使用sso的时候往往登录不只是需要用户名密码,更多的是有时候选择部门,系统等等的扩展校验信息,当然有时候还有校验码策略,例如同一个IP10分钟内输入密码错误3次输入验证码等等的业务场景~

那么这一章讲一下如何扩展校验信息以及自定义校验器。

需求

  • 登录新增校验信息
  • 自定义校验模式

业务场景:
1. 登录页新增系统下拉框选择,当是SSO时,匹配自定义校验器
2. 自定义校验器,当系统未sso并且用户名为admin时,允许认证成功

本章目的,自定义Credential以及自定义AuthenticationHandler

铺垫

所需知识:
1. 对cas有所了解
2. 对自定义AuthenticationHandler有所了解
3. 对cas的credential有所了解
4. 对spring webflow有基础了解
5. spring boot的使用经验

若不具备以上知识或许看以下的内容有所吃力。

实战

介绍

流程:

  • 重新指定CasWebflowConstants.VAR_ID_CREDENTIAL
  • 对登录页的flowCasWebflowConstants.STATE_ID_VIEW_LOGIN_FORM重写绑定参数
  • 登录页锁定参数
  • 自定义AuthenticationHandler
  • 采用spring.factories注册

程序

UsernamePasswordSysCredential

该代码定义前端所需定义的绑定参数,后续会交给AuthenticationHandler进行认证

为了测试简单,也不影响其他鉴权模式,所以直接继承RememberMeUsernamePasswordCredential,再日常的开发慎重考虑这个bean的设计

/*
 * 版权所有.(c)2008-2017. 卡尔科技工作室
 */

package com.carl.sso.support.auth;

import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.apereo.cas.authentication.RememberMeUsernamePasswordCredential;

import javax.validation.constraints.Size;

/**
 * 用户名,密码,系统
 *
 * @author Carl
 * @date 2017/10/23
 * @since
 */
public class UsernamePasswordSysCredential extends RememberMeUsernamePasswordCredential {
    @Size(min = 2, message = "require system")
    private String system;

    public String getSystem() {
        return system;
    }

    public UsernamePasswordSysCredential setSystem(String system) {
        this.system = system;
        return this;
    }

    @Override
    public int hashCode() {
        return new HashCodeBuilder()
                .appendSuper(super.hashCode())
                .append(this.system)
                .toHashCode();
    }
}

绑定参数

前端重写绑定参数,并且重写指定原有的“

/*
 * 版权所有.(c)2008-2017. 卡尔科技工作室
 */

package com.carl.sso.support.auth;

import org.apereo.cas.web.flow.AbstractCasWebflowConfigurer;
import org.apereo.cas.web.flow.CasWebflowConstants;
import org.springframework.webflow.definition.registry.FlowDefinitionRegistry;
import org.springframework.webflow.engine.Flow;
import org.springframework.webflow.engine.ViewState;
import org.springframework.webflow.engine.builder.BinderConfiguration;
import org.springframework.webflow.engine.builder.support.FlowBuilderServices;

/**
 * 重新定义默认的web流程
 *
 * @author Carl
 * @date 2017/10/23
 * @since 1.6.0
 */
public class CustomWebflowConfigurer extends AbstractCasWebflowConfigurer {

    public CustomWebflowConfigurer(FlowBuilderServices flowBuilderServices, FlowDefinitionRegistry flowDefinitionRegistry) {
        super(flowBuilderServices, flowDefinitionRegistry);
    }

    @Override
    protected void doInitialize() throws Exception {
        final Flow flow = getLoginFlow();
        bindCredential(flow);
    }


    /**
     * 绑定输入信息
     *
     * @param flow
     */
    protected void bindCredential(Flow flow) {
        //重写绑定自定义credential
        createFlowVariable(flow, CasWebflowConstants.VAR_ID_CREDENTIAL, UsernamePasswordSysCredential.class);
        //登录页绑定新参数
        final ViewState state = (ViewState) flow.getState(CasWebflowConstants.STATE_ID_VIEW_LOGIN_FORM);
        final BinderConfiguration cfg = getViewStateBinderConfiguration(state);
        //由于用户名以及密码已经绑定,所以只需对新加系统参数绑定即可
        cfg.addBinding(new BinderConfiguration.Binding("system", null, false));
    }
}

UsernamePasswordSystemAuthenticationHandler

当用户名为admin,并且system为sso即允许通过为了测试简单才定义简单的逻辑,开发过程中慎重考虑

/*
 * 版权所有.(c)2008-2017. 卡尔科技工作室
 */

package com.carl.sso.support.auth.handler;

import com.carl.sso.support.auth.UsernamePasswordSysCredential;
import org.apereo.cas.authentication.Credential;
import org.apereo.cas.authentication.HandlerResult;
import org.apereo.cas.authentication.PreventedException;
import org.apereo.cas.authentication.handler.support.AbstractPreAndPostProcessingAuthenticationHandler;
import org.apereo.cas.authentication.principal.PrincipalFactory;
import org.apereo.cas.services.ServicesManager;

import javax.security.auth.login.AccountNotFoundException;
import java.security.GeneralSecurityException;
import java.util.Collections;

/**
 * 用户名系统认证,只要是admin用户加上sso系统就允许通过
 *
 * @author Carl
 * @date 2017/10/23
 * @since 1.6.0
 */
public class UsernamePasswordSystemAuthenticationHandler extends AbstractPreAndPostProcessingAuthenticationHandler {
    public UsernamePasswordSystemAuthenticationHandler(String name, ServicesManager servicesManager, PrincipalFactory principalFactory, Integer order) {
        super(name, servicesManager, principalFactory, order);
    }

    @Override
    protected HandlerResult doAuthentication(Credential credential) throws GeneralSecurityException, PreventedException {
        //当用户名为admin,并且system为sso即允许通过
        UsernamePasswordSysCredential sysCredential = (UsernamePasswordSysCredential) credential;
        if ("admin".equals(sysCredential.getUsername()) && "sso".equals(sysCredential.getSystem())) {
            //这里可以自定义属性数据
            return createHandlerResult(credential, this.principalFactory.createPrincipal(((UsernamePasswordSysCredential) credential).getUsername(), Collections.emptyMap()), null);
        } else {
            throw new AccountNotFoundException("必须是admin用户才允许通过");
        }
    }


    @Override
    public boolean supports(Credential credential) {
        return credential instanceof UsernamePasswordSysCredential;
    }
}

注册CasWebflowConfigurer

这里是spring boot的知识,需要对配置进行识别

由于需要对Credential进行重写定义,必须在该配置之前注册,否则自定义的无法重写

@AutoConfigureBefore(value = CasWebflowContextConfiguration.class)

/*
 * 版权所有.(c)2008-2017. 卡尔科技工作室
 */


package com.carl.sso.support.auth.config;

import com.carl.sso.support.auth.CustomWebflowConfigurer;
import org.apereo.cas.config.CasWebflowContextConfiguration;
import org.apereo.cas.configuration.CasConfigurationProperties;
import org.apereo.cas.web.flow.CasWebflowConfigurer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.webflow.definition.registry.FlowDefinitionRegistry;
import org.springframework.webflow.engine.builder.support.FlowBuilderServices;

/**
 * @author Carl
 * @date 2017/10/23
 * @since 1.6.0
 */
@Configuration("customerAuthWebflowConfiguration")
@EnableConfigurationProperties(CasConfigurationProperties.class)
@AutoConfigureBefore(value = CasWebflowContextConfiguration.class)
public class CustomerAuthWebflowConfiguration {
    @Autowired
    @Qualifier("logoutFlowRegistry")
    private FlowDefinitionRegistry logoutFlowRegistry;
    @Autowired
    @Qualifier("loginFlowRegistry")
    private FlowDefinitionRegistry loginFlowRegistry;

    @Autowired
    @Qualifier("builder")
    private FlowBuilderServices builder;

    @Bean
    public CasWebflowConfigurer customWebflowConfigurer() {
        final CustomWebflowConfigurer c = new CustomWebflowConfigurer(builder, loginFlowRegistry);
        c.setLogoutFlowDefinitionRegistry(logoutFlowRegistry);
        return c;
    }
}

注册AuthenticationHandler

/*
 * 版权所有.(c)2008-2017. 卡尔科技工作室
 */


package com.carl.sso.support.auth.config;

import com.carl.sso.support.auth.handler.UsernamePasswordSystemAuthenticationHandler;
import org.apereo.cas.authentication.AuthenticationEventExecutionPlan;
import org.apereo.cas.authentication.AuthenticationEventExecutionPlanConfigurer;
import org.apereo.cas.authentication.AuthenticationHandler;
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 Carl
 * @date 2017/10/23
 * @since 1.6.0
 */
@Configuration("customAuthenticationEventExecutionPlanConfiguration")
@EnableConfigurationProperties(CasConfigurationProperties.class)
public class CustomAuthenticationEventExecutionPlanConfiguration implements AuthenticationEventExecutionPlanConfigurer {
    @Autowired
    @Qualifier("servicesManager")
    private ServicesManager servicesManager;

    @Autowired
    @Qualifier("jdbcPrincipalFactory")
    public PrincipalFactory jdbcPrincipalFactory;


    /**
     * 注册验证器
     *
     * @return
     */
    @Bean
    public AuthenticationHandler customAuthenticationHandler() {
        //优先验证
        return new UsernamePasswordSystemAuthenticationHandler("customAuthenticationHandler",
                servicesManager, jdbcPrincipalFactory, 1);
    }

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

spring.factories

org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.carl.sso.support.auth.config.CustomerAuthWebflowConfiguration,com.carl.sso.support.auth.config.CustomAuthenticationEventExecutionPlanConfiguration

casLoginView.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"/>
    <title th:text="${#themes.code('demo.pageTitle')}">title>
    <link rel="stylesheet" th:href="@{${#themes.code('demo.css.file')}}"/>
head>

<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 class="row">
            <label for="system" >系统label>
            <div>
                <select class="required"
                        id="system"
                        tabindex="2"
                        th:accesskey="#{screen.welcome.label.password.accesskey}"
                        th:field="*{system}"
                        autocomplete="off">
                    <option value="sso">SSOoption>
                    <option value="oa">OAoption>
                    <option value="crm">CRMoption>
                select>
            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"/>
        section>
    form>
div>
body>
html>

重点为以下代码

<section class="row">
            <label for="system" >系统label>
            <div>
                <select class="required"
                        id="system"
                        tabindex="2"
                        th:accesskey="#{screen.welcome.label.password.accesskey}"
                        th:field="*{system}"
                        autocomplete="off">
                    <option value="sso">SSOoption>
                    <option value="oa">OAoption>
                    <option value="crm">CRMoption>
                select>
            div>
        section>

测试

  1. 由于配置了主题,demo主题在触发到demo主题下才会进入以上节目
  2. 由于继承了RememberMeUsernamePasswordCredential所以其他配置该credential依然生效
  3. 由于自定义UsernamePasswordSystemAuthenticationHandler的认证循序放在第一,所以优先验证

CAS单点登录-自定义认证之重写Credential(十五)_第1张图片

CAS单点登录-自定义认证之重写Credential(十五)_第2张图片

CAS单点登录-自定义认证之重写Credential(十五)_第3张图片

注意事项

  1. 优先绑定Credential @AutoConfigureBefore(value = CasWebflowContextConfiguration.class)
  2. 页面绑定新参数th:field="*{system}"
  3. 采用spring.factories注册
  4. flow绑定新参数CasWebflowConstants.STATE_ID_VIEW_LOGIN_FORM

下载代码尝试: 其他版本可以到GitHub或者码云查看

发现一些意外的事情可以考虑翻翻前面的博客进行学习哦

作者联系方式

如果技术的交流或者疑问可以联系或者提出issue。

邮箱:[email protected]

QQ: 756884434 (请注明:SSO-CSDN)

你可能感兴趣的:(cas,spring,sso,单点登录,自定义认证,spring-boot,cas)