SSO统一身份认证——在原有页面中增加验证码(十九)
背景
单点登录(SingleSignOn,SSO),就是通过用户的一次性鉴别登录。当用户在身份认证服务器上登录一次以后,即可获得访问单点登录系统中其他关联系统和应用软件的权限,同时这种实现是不需要管理员对用户的登录状态或其他信息进行修改的,这意味着在多个应用系统中,用户只需一次登录就可以访问所有相互信任的应用系统。这种方式减少了由登录产生的时间消耗,辅助了用户管理,是目前比较流行的。
单点登录的使用场景有很多,C/S、B/S架构的系统均可使用,通常是支持快速配置使用。
业内目前实现SSO的方式有很多种,在ToC场景下互联网公司通常使用的是OAuth2协议,而ToB场景下大家通常是囊括百家,既支持OAuth2又支持CAS,还滴支持LDAP。其造成的原因主要是因为在ToB场景下需要对接SSO的系统通常仅支持某个协议,而这类系统又不是同一个协议导致。
而我当前境况下就是既有ToC场景又有ToB场景,在该种情况下,我开始对其业内的各种协议进行整合集成,这一系列文章将对其业内各个协议从基础到深入、从搭建到二次开发进行记录,同时将其整理出来分享给大家。
本节简介
本节我们将对于上一文中修改后的页面进行增加验证码框,同时本节分为两部分,第一部分我们实现验证码框并在原有基础认证中增加非空效验,第二部分我们将在页面中增加图形验证码,实现完整的自定义验证逻辑。
环境
主要使用的环境如下 服务器系统:windows 10 环境:OpenJDK 11 web中间件:tomcat9 CAS Server:6.3.x 数据库:MariaDB 或 PostgreSQL 快速软件包openjdk11 tomcat9 CASServer.tar
正文
下面我们开始本章节的第一部分,增加验证码框,并对其进行非空效验。
1、在build.gradle中增加我们需要的一些基础jar包。
代码语言:javascript复制 // 动态更改webflow使用的模块包
implementation "org.apereo.cas:cas-server-core-webflow"
implementation "org.apereo.cas:cas-server-core-webflow-api"
2、创建一个定制的Credential,在该定制版中将增加验证码的验证字段
代码语言:javascript复制package com.sso.credential;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apereo.cas.authentication.credential.UsernamePasswordCredential;
import javax.validation.constraints.Size;
/**
* 类 {@code CustomCredential} 在该定制版中将增加验证码的验证字段 <br> 定制版的credential.
*
* <p>详细描述
* <p>
* @author <a href="mailto:lz2392504@gmail.com">CN華少</a>
* @see UsernamePasswordCredential
* @since v1.0.0
*/
@Slf4j
@Data
public class CustomCredential extends UsernamePasswordCredential {
@Size(min = 4,message = "require captcha")
private String captcha;
}
3、增加一个 webflow配置加载类,并将刚定制的Credential加载到CAS Server的前端视图生成逻辑和flow流程中。
代码语言:javascript复制package com.sso.webflow;
import com.sso.credential.CustomCredential;
import org.apereo.cas.configuration.CasConfigurationProperties;
import org.apereo.cas.web.flow.CasWebflowConstants;
import org.apereo.cas.web.flow.configurer.AbstractCasWebflowConfigurer;
import org.springframework.context.ConfigurableApplicationContext;
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;
/**
* 类 {@code SomethingWebflowConfigurer} <br> 一些自定义的webflow.
*
* <p>详细描述
* <p>
*
* @author <a href="mailto:lz2392504@gmail.com">CN華少</a>
* @see AbstractCasWebflowConfigurer
* @since v1.0.0
*/
public class SomethingWebflowConfigurer extends AbstractCasWebflowConfigurer {
public SomethingWebflowConfigurer(FlowBuilderServices flowBuilderServices,
FlowDefinitionRegistry mainFlowDefinitionRegistry,
ConfigurableApplicationContext applicationContext,
CasConfigurationProperties casProperties) {
super(flowBuilderServices, mainFlowDefinitionRegistry, applicationContext, casProperties);
}
/**
* 重新实现初始化逻辑
*/
@Override
protected void doInitialize() {
System.out.println("经过了重构的初始流程");
// 获取到登录flow,并对其进行添加进入我们需要的逻辑。
final Flow flow = super.getLoginFlow();
bindCredential(flow);
}
/**
* 对于原有登录flow流程配置中增加定制的Credential
* 对于原有登录的前端视图增加captcha字段
* @param flow
*/
protected void bindCredential(Flow flow){
// 1、这里我们重新绑定了Credential环节,将其原有的UsernamePasswordCredential替换为了我们做过定制的CustomCredential
// 创建流量变量。 参数:flow—流id—id type—类型
createFlowVariable(flow, CasWebflowConstants.VAR_ID_CREDENTIAL, CustomCredential.class);
// 2、对于登录页面让其进行输入该验证码参数。
// 从flow中获取到前端视图的ViewState
final ViewState state = (ViewState)flow.getState(CasWebflowConstants.STATE_ID_VIEW_LOGIN_FORM);
// 从视图中获取到绑定的配置对象
final BinderConfiguration binderConfiguration = getViewStateBinderConfiguration(state);
// 将验证码添加进入配置对象中
binderConfiguration.addBinding(new BinderConfiguration.Binding("captcha",null,true));
}
}
4、增加一个配置注入类,并在其中增加我们上一步创建的webflow配置类。
代码语言:javascript复制package org.apereo.cas.config;
import com.sso.webflow.SomethingWebflowConfigurer;
import org.apereo.cas.configuration.CasConfigurationProperties;
import org.apereo.cas.web.flow.CasWebflowConfigurer;
import org.apereo.cas.web.flow.CasWebflowExecutionPlan;
import org.apereo.cas.web.flow.CasWebflowExecutionPlanConfigurer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
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;
/**
* 类 {@code SomethingConfiguration} <br> 一些我们自定义的配置.
*
* <p>详细描述
* <p>
* @author <a href="mailto:lz2392504@gmail.com">CN華少</a>
* @see CasWebflowExecutionPlanConfigurer
* @since v1.0.0
*/
@Configuration("somethingConfiguration")
@EnableConfigurationProperties(CasConfigurationProperties.class)
public class SomethingConfiguration implements CasWebflowExecutionPlanConfigurer {
/**
* CAS 配置
*/
@Autowired
private CasConfigurationProperties casProperties;
/**
* 登录流程定义注册表
*/
@Autowired
@Qualifier("loginFlowRegistry")
private FlowDefinitionRegistry loginFlowDefinitionRegistry;
/**
* 应用关系
*/
@Autowired
private ConfigurableApplicationContext configurableApplicationContext;
/**
* 流建设者服务
*/
@Autowired
private FlowBuilderServices flowBuilderServices;
@Bean
public CasWebflowConfigurer somethingWebflowConfigurer() {
System.out.println("初始化配置");
final SomethingWebflowConfigurer somethingWebflowConfigurer = new SomethingWebflowConfigurer(flowBuilderServices,
loginFlowDefinitionRegistry, configurableApplicationContext, casProperties);
somethingWebflowConfigurer.initialize();
return somethingWebflowConfigurer;
}
@Override
public void configureWebflowExecutionPlan(CasWebflowExecutionPlan plan) {
plan.registerWebflowConfigurer(somethingWebflowConfigurer());
}
}
5、增加一个自定义验证逻辑。
代码语言:javascript复制package com.sso.auth;
import com.sso.credential.CustomCredential;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.apereo.cas.authentication.AuthenticationHandlerExecutionResult;
import org.apereo.cas.authentication.Credential;
import org.apereo.cas.authentication.MessageDescriptor;
import org.apereo.cas.authentication.PreventedException;
import org.apereo.cas.authentication.credential.UsernamePasswordCredential;
import org.apereo.cas.authentication.handler.support.AbstractPreAndPostProcessingAuthenticationHandler;
import org.apereo.cas.authentication.handler.support.AbstractUsernamePasswordAuthenticationHandler;
import org.apereo.cas.authentication.principal.PrincipalFactory;
import org.apereo.cas.services.ServicesManager;
import java.security.GeneralSecurityException;
import java.util.ArrayList;
import java.util.List;
/**
* 类 {@code MyAuthenticationHandler} 自定义账号密码验证逻辑 <br> 自定义账号密码验证逻辑.
*
* <p>详细描述
* <p>
*
* @author <a href="mailto:lz2392504@gmail.com">CN華少</a>
* @see AbstractUsernamePasswordAuthenticationHandler
* @see UsernamePasswordCredential
* @since v1.0.0
*/
@Slf4j
@Setter
@Getter
public class MyAuthenticationHandler extends AbstractPreAndPostProcessingAuthenticationHandler {
public MyAuthenticationHandler(String name, ServicesManager servicesManager, PrincipalFactory principalFactory, Integer order) {
super(name, servicesManager, principalFactory, order);
}
@Override
protected AuthenticationHandlerExecutionResult doAuthentication(Credential credential) throws GeneralSecurityException, PreventedException {
CustomCredential customCredential = (CustomCredential) credential;
// 继承过来的账号密码
String username = customCredential.getUsername();
String password = customCredential.getPassword();
// 定制中的验证码
String captcha = customCredential.getCaptcha();
System.out.println("当前验证信息为:" username " " password " " captcha);
//TODO 省略验证步骤,后续单独讲解,第二部分补充内容
final List<MessageDescriptor> list = new ArrayList<>();
return createHandlerResult(credential,this.principalFactory.createPrincipal(username),list);
}
@Override
public boolean preAuthenticate(Credential credential) {
return super.preAuthenticate(credential);
}
@Override
public AuthenticationHandlerExecutionResult postAuthenticate(Credential credential, AuthenticationHandlerExecutionResult result) {
return super.postAuthenticate(credential, result);
}
}
6、增加一个配置注入类,并在其中加载起来上一步的自定义验证类。
代码语言:javascript复制package org.apereo.cas.config;
import com.sso.auth.MyAuthenticationHandler;
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;
/**
* 类 {@code CustomLoginCredentialConfiguration} <br> 定制登录配置.
*
* <p>详细描述
* <p>
*
* @author <a href="mailto:lz2392504@gmail.com">CN華少</a>
* @TODO 代办事项的标记
* @see AuthenticationEventExecutionPlanConfigurer
* @since v1.0.0
*/
@Configuration("customLoginCredentialConfiguration")
@EnableConfigurationProperties(CasConfigurationProperties.class)
public class CustomLoginCredentialConfiguration implements AuthenticationEventExecutionPlanConfigurer {
@Autowired
private CasConfigurationProperties casConfigurationProperties;
@Autowired
@Qualifier("servicesManager")
private ServicesManager servicesManager;
@Bean
public AuthenticationHandler myAuthenticationHandler(){
return new MyAuthenticationHandler(MyAuthenticationHandler.class.getName(),
servicesManager, new DefaultPrincipalFactory(), 1
);
}
/**
* 使用自定义的handler进行验证工作
* @param plan
*/
@Override
public void configureAuthenticationExecutionPlan(AuthenticationEventExecutionPlan plan) {
plan.registerAuthenticationHandler(myAuthenticationHandler());
}
@Override
public String getName() {
return AuthenticationEventExecutionPlanConfigurer.super.getName();
}
@Override
public int getOrder() {
return AuthenticationEventExecutionPlanConfigurer.super.getOrder();
}
}
7、修改templatesfragmentsloginform.html登录页面,在其113行后面增加我们的验证码部分
代码语言:javascript复制 <section class="cas-field my-3" id="userCaptcha">
<div class="d-flex">
<label for="captcha" class="mdc-text-field mdc-text-field--outlined">
<input class="mdc-text-field__input"
id="captcha"
size="6"
type="text"
th:field="*{captcha}"
autocomplete="off"/>
<span class="mdc-notched-outline">
<span class="mdc-notched-outline__leading"></span>
<span class="mdc-notched-outline__notch">
<span class="mdc-floating-label" th:utext="#{screen.welcome.label.captcha}">验证码</span>
</span>
<span class="mdc-notched-outline__trailing"></span>
</span>
</label>
</div>
</section>
8、修改国际化配置文件,在其中增加我们刚才使用到的验证码配置部分。
代码语言:javascript复制screen.welcome.label.captcha=验证码:
9、在其spring.factories配置文件中,增加上我们前面创建好的配置注入类。
代码语言:javascript复制org.springframework.boot.autoconfigure.EnableAutoConfiguration=
org.apereo.cas.config.SomethingConfiguration,
org.apereo.cas.config.CustomLoginCredentialConfiguration
10、启动并测试进行登录,不填写验证码测试。
image.png
到这里,我们的第一部分已经完成,下一节我们对于自定义验证逻辑进行完善,并在页面中增加上对比使用的验证码。
本文声明:
本作品由 cn華少 采用 知识共享署名-非商业性使用 4.0 国际许可协议 进行许可。