简介
关于CAS的登录流程,overlay中只是一个简单的用户名密码登录**casuser::Mellon**,这个肯定是不能满足日常生产的需求的,在日常开发中遇到最通用的情况就是从数据库中进行身份认证密码的比对等等,那么这篇文章将提供一种自定义登录认证的解决方案。
两个重要的接口抽象定义
1.AbstractPreAndPostProcessingAuthenticationHandler认证handler
该接口中定义了两个方法:
- doAuthentication:
该方法提供了一套认证的逻辑,是我们这次主要实现的方法,我们可以在其中实现各种我们想要的认证逻辑。 - supports:
这个方法其实也很重要,用于判断当前凭证是否使用与该类的认证逻辑。
2.AbstractCredential
CAS提供了多套凭证的model,都是继承的AbstractCredential,今天我们是基于数据库的用户名密码认证模式实现,那么其实CAS在提供的UsernamePasswordCredential model中已经包含了我们想要的凭证字段。
![image.png](/img/bVcL6cW)
从上图可以看到,其中已经包含了我们想要的username与password字段,其实这些字段对应的是我们的登录页面上的表单字段,如果我们的表单页面新增了认证字段,比如增加了短信验证码校验,那么我们可以自己继承UsernamePasswordCredential,并增加验证码字段。
实现步骤
1.定义CustomerCredential
public class CustomerCredential extends UsernamePasswordCredential {
private static final Logger LOGGER = LoggerFactory.getLogger(CustomerCredential.class);
private static final long serialVersionUID = -4166149641561667276L;
public CustomerCredential() {
}
public CustomerCredential(String message, String telephone) {
this.message = message;
this.telephone = telephone;
}
@Size(min = 1, message = "require message")
private String message;
@Size(min = 1, message = "require telephone")
private String telephone;
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public String getTelephone() {
return telephone;
}
public void setTelephone(String telephone) {
this.telephone = telephone;
}
}
我们需要自定义我们登录认证所需要的凭证model,比如我们在页面上增加了短信验证码message字段和telephone手机号字段
2.定义Handle
public class CustomerAuthenticationHandler extends AbstractPreAndPostProcessingAuthenticationHandler implements ApplicationContextAware {
@Autowired
private TuStaffMapper staffMapper;
public CustomerAuthenticationHandler(String name, ServicesManager servicesManager, PrincipalFactory principalFactory, Integer order) {
super(name, servicesManager, principalFactory, order);
}
@Override
protected AuthenticationHandlerExecutionResult doAuthentication(Credential credential) throws GeneralSecurityException, PreventedException {
UsernamePasswordCredential usernamePasswordCredential = (UsernamePasswordCredential) credential;
String password = usernamePasswordCredential.getPassword();
String username = usernamePasswordCredential.getUsername();
String telephone = customerCredential.getTelephone();
String message = customerCredential.getMessage();
TuStaff tuStaff = staffMapper.findByAccount(username);
String verify = this.getCode(telephone).split("^")[0];
String salt = tuStaff.getSalt();
String pswMD5 = this.getMD5(password + salt);
if (tuStaff != null){
if (pswMD5.equals(tuStaff.getPassword())){
final List list = new ArrayList<>();
Map> map = new HashedMap();
List roles = new ArrayList<>();
roles.add("admin");
map.put("roles", Collections.singletonList(roles));
return createHandlerResult(usernamePasswordCredential,
this.principalFactory.createPrincipal(username, map), list);
}else {
throw new FailedLoginException("密码不正确");
}
}else {
throw new FailedLoginException("无用户信息");
}
}
}
我们通过实现自己的handle处理器来实现自定义登录认证逻辑,此处我已经略过mybatis的配置,只展示主要实现逻辑过程。
3.配置自定义handler
@Configuration("CustomerHandlerConfig")
@EnableConfigurationProperties(CasConfigurationProperties.class)
public class CustomerHandlerConfig implements AuthenticationEventExecutionPlanConfigurer {
@Autowired
private CasConfigurationProperties casProperties;
@Autowired
@Qualifier("servicesManager")
private ServicesManager servicesManager;
@Bean
public AuthenticationHandler myAuthenticationHandler() {
// 参数: name, servicesManager, principalFactory, order
// 定义为优先使用它进行认证
return new CustomerAuthenticationHandler(CustomerAuthenticationHandler.class.getName(),
servicesManager, new DefaultPrincipalFactory(), 1);
}
@Override
public void configureAuthenticationExecutionPlan(AuthenticationEventExecutionPlan plan) {
plan.registerAuthenticationHandler(myAuthenticationHandler());
}
}
将我们自定义的处理器配置最高执行优先级,并注册到执行计划中。
4.将自定义Credential绑定表单
public class CustomWebflowConfigurer extends AbstractCasWebflowConfigurer {
public CustomWebflowConfigurer(FlowBuilderServices flowBuilderServices, FlowDefinitionRegistry loginFlowDefinitionRegistry, ApplicationContext applicationContext, CasConfigurationProperties casProperties) {
super(flowBuilderServices, loginFlowDefinitionRegistry, (ConfigurableApplicationContext) applicationContext, casProperties);
}
@Override
protected void doInitialize() {
final Flow flow = super.getLoginFlow();
bindCredential(flow);
}
protected void bindCredential(Flow flow) {
// 重写绑定自定义credential
// 重写绑定自定义credential
createFlowVariable(flow, CasWebflowConstants.VAR_ID_CREDENTIAL, CustomerCredential.class);
// 登录页绑定新参数
final ViewState state = (ViewState) flow.getState(CasWebflowConstants.STATE_ID_VIEW_LOGIN_FORM);
final BinderConfiguration cfg = getViewStateBinderConfiguration(state);
// 由于用户名以及密码已经绑定,所以只需对新加系统参数绑定即可
// 字段名,转换器,是否必须字段
//cfg.addBinding(new BinderConfiguration.Binding("message", null, true));
//cfg.addBinding(new BinderConfiguration.Binding("telephone", null, true));
}
}
5.将CustomWebflowConfigurer注册
同理上文将handle注册进执行计划并给予最高优先级,WebFlow也需要重复一样的操作。
@Configuration("customerAuthWebflowConfiguration")
@EnableConfigurationProperties(CasConfigurationProperties.class)
public class CustomerAuthWebflowConfiguration implements CasWebflowExecutionPlanConfigurer {
@Autowired
private CasConfigurationProperties casProperties;
@Autowired
@Qualifier("loginFlowRegistry")
private FlowDefinitionRegistry loginFlowDefinitionRegistry;
@Autowired
private ApplicationContext applicationContext;
@Autowired
private FlowBuilderServices flowBuilderServices;
@Bean
public CasWebflowConfigurer customWebflowConfigurer() {
// 实例化自定义的表单配置类
final CustomWebflowConfigurer c = new CustomWebflowConfigurer(flowBuilderServices, loginFlowDefinitionRegistry,
applicationContext, casProperties);
// 初始化
c.initialize();
// 返回对象
return c;
}
@Override
public void configureWebflowExecutionPlan(CasWebflowExecutionPlan plan) {
plan.registerWebflowConfigurer(customWebflowConfigurer());
}
}
结束
至此,整个自定义登录流程结束。