全部基于SpringBoot,以及SpringWebflow开发,请在有此基础再进行学习!
添加Maven依赖
<dependency>
<groupId>org.apereo.casgroupId>
<artifactId>cas-server-core-authentication-apiartifactId>
dependency>
<dependency>
<groupId>org.apereo.casgroupId>
<artifactId>cas-server-core-webflowartifactId>
<version>${cas.version}version>
dependency>
<dependency>
<groupId>org.apereo.casgroupId>
<artifactId>cas-server-core-webflow-apiartifactId>
<version>${cas.version}version>
dependency>
<dependency>
<groupId>org.apereo.casgroupId>
<artifactId>cas-server-core-web-apiartifactId>
<version>${cas.version}version>
dependency>
<dependency>
<groupId>javax.servletgroupId>
<artifactId>javax.servlet-apiartifactId>
<version>3.1.0version>
<scope>providedscope>
dependency>
扩展caswebflow默认的登录方式
import net.pubone.cas.support.mobile.credential.MobileIdCredential;
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.ApplicationContext;
import org.springframework.webflow.definition.registry.FlowDefinitionRegistry;
import org.springframework.webflow.engine.ActionState;
import org.springframework.webflow.engine.Flow;
import org.springframework.webflow.engine.ViewState;
import org.springframework.webflow.engine.builder.support.FlowBuilderServices;
public class MobileidWebflowConfigurer extends AbstractCasWebflowConfigurer {
public MobileidWebflowConfigurer(FlowBuilderServices flowBuilderServices, FlowDefinitionRegistry loginFlowDefinitionRegistry, ApplicationContext applicationContext, CasConfigurationProperties casProperties) {
super(flowBuilderServices, loginFlowDefinitionRegistry, applicationContext, casProperties);
}
private static final String EVENT_ID_START_AUTHENTICATE_MOBILE_ID = "startAuthenticateMobileId";
@Override
protected void doInitialize() {
final Flow flow = getLoginFlow();
if (flow != null) {
//创建Action
final ActionState actionState = createActionState(flow, EVENT_ID_START_AUTHENTICATE_MOBILE_ID,
//自定义Action
createEvaluateAction("mobileidCredentialsAction"));
//添加成功后的Transition
actionState.getTransitionSet().add(createTransition(CasWebflowConstants.TRANSITION_ID_SUCCESS,
CasWebflowConstants.STATE_ID_CREATE_TICKET_GRANTING_TICKET));
//添加警告的Transition
actionState.getTransitionSet()
.add(createTransition(CasWebflowConstants.TRANSITION_ID_WARN, CasWebflowConstants.TRANSITION_ID_WARN));
//添加错误的Transition跳转到login
actionState.getTransitionSet()
.add(createTransition(CasWebflowConstants.TRANSITION_ID_ERROR, CasWebflowConstants.STATE_ID_VIEW_LOGIN_FORM));
//添加认证错误的的Transition跳转到login并显示错误信息
actionState.getTransitionSet().add(createTransition(CasWebflowConstants.TRANSITION_ID_AUTHENTICATION_FAILURE,
CasWebflowConstants.STATE_ID_HANDLE_AUTHN_FAILURE));
actionState.getExitActionList().add(createEvaluateAction("clearWebflowCredentialsAction"));
registerMultifactorProvidersStateTransitionsIntoWebflow(actionState);
ViewState viewLoginState = (ViewState) flow.getState(CasWebflowConstants.STATE_ID_VIEW_LOGIN_FORM);
createTransitionForState(viewLoginState, "submitMobileId", EVENT_ID_START_AUTHENTICATE_MOBILE_ID, true);
}
}
}
自定义的手机短信登录的Action
这里使用了lombok 所以idea要装一个lombok插件(自行百度)
package net.pubone.cas.support.mobile.action;
import lombok.extern.slf4j.Slf4j;
import net.pubone.cas.support.mobile.credential.MobileIdCredential;
import org.apereo.cas.authentication.Credential;
import org.apereo.cas.authentication.adaptive.AdaptiveAuthenticationPolicy;
import org.apereo.cas.web.flow.actions.AbstractNonInteractiveCredentialsAction;
import org.apereo.cas.web.flow.resolver.CasDelegatingWebflowEventResolver;
import org.apereo.cas.web.flow.resolver.CasWebflowEventResolver;
import org.apereo.cas.web.support.WebUtils;
import org.springframework.webflow.execution.RequestContext;
import static org.apereo.cas.CipherExecutor.LOGGER;
import javax.servlet.http.HttpServletRequest;
@Slf4j
public class MobileIdCredentialsAction extends AbstractNonInteractiveCredentialsAction {
public MobileIdCredentialsAction(CasDelegatingWebflowEventResolver initialAuthenticationAttemptWebflowEventResolver,
CasWebflowEventResolver serviceTicketRequestWebflowEventResolver,
AdaptiveAuthenticationPolicy adaptiveAuthenticationPolicy) {
super(initialAuthenticationAttemptWebflowEventResolver, serviceTicketRequestWebflowEventResolver,
adaptiveAuthenticationPolicy);
}
@Override
protected Credential constructCredentialsFromRequest(RequestContext requestContext) {
try {
final HttpServletRequest request;
request = WebUtils.getHttpServletRequestFromExternalWebflowContext(requestContext);
MobileIdCredential credentials = new MobileIdCredential(request.getParameter("phoneNumber"), "");
if (credentials != null) {
LOGGER.debug("Received mobile authentication request from credentials [{}]", credentials);
return credentials;
}
} catch (final Exception e) {
LOGGER.warn(e.getMessage(), e);
e.printStackTrace();
}
return null;
}
}
自定义credential
package net.pubone.cas.support.mobile.credential;
import lombok.*;
import org.apereo.cas.authentication.AbstractCredential;
import org.apereo.cas.authentication.OneTimeTokenCredential;
@ToString
@Setter
@Getter
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode(callSuper = true)
public class MobileIdCredential extends AbstractCredential {
private static final long serialVersionUID = 1L;
private String phoneNumber;
private String validataCode;
@Override
public String getId() {
return this.phoneNumber;
}
}
自定义Handler
package net.pubone.cas.support.mobile.handler;
import lombok.extern.slf4j.Slf4j;
import net.pubone.cas.entity.Puser;
import net.pubone.cas.execption.NoFindUserExecption;
import net.pubone.cas.repository.UserRepository;
import net.pubone.cas.support.mobile.credential.MobileIdCredential;
import org.apereo.cas.authentication.AuthenticationHandlerExecutionResult;
import org.apereo.cas.authentication.Credential;
import org.apereo.cas.authentication.PreventedException;
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.beans.factory.annotation.Autowired;
import javax.security.auth.login.FailedLoginException;
import java.security.GeneralSecurityException;
import java.util.Objects;
import static org.apereo.cas.CipherExecutor.LOGGER;
@Slf4j
public class MobileHandler extends AbstractPreAndPostProcessingAuthenticationHandler {
public MobileHandler(String name, ServicesManager servicesManager, PrincipalFactory principalFactory, Integer order ) {
super(name, servicesManager, principalFactory, order);
}
//@Autowired
//UserRepository jpaMobileUserRepository;
@Override
public boolean supports(Credential credential) {
//判断传递过来的Credential 是否是自己能处理的类型
return credential instanceof MobileIdCredential;
}
@Override
protected AuthenticationHandlerExecutionResult doAuthentication(Credential credential) throws GeneralSecurityException, PreventedException {
MobileIdCredential mobileIdCredential = (MobileIdCredential) credential;
String phoneNumber = mobileIdCredential.getPhoneNumber();
String validataCode = mobileIdCredential.getValidataCode();
System.out.println(phoneNumber);
System.out.println(validataCode);
Puser puser = null;
if(phoneNumber==null){
log.debug("手机号码为null");
throw new FailedLoginException("手机号码必须填写");
}
//TODO 这里做手机验证码校验操作
throw new FailedLoginException("手机号码必须填写");
Principal principal = principalFactory.createPrincipal(credential.getId());
return createHandlerResult(credential,
principal);
}
}
配置上面我们自定义的Action,webflow,credential,handler
package net.pubone.cas.support.mobile.config;
import net.pubone.cas.support.mobile.action.MobileIdCredentialsAction;
import net.pubone.cas.support.mobile.handler.MobileHandler;
import net.pubone.cas.support.mobile.webflow.MobileidWebflowConfigurer;
import org.apereo.cas.authentication.AuthenticationEventExecutionPlan;
import org.apereo.cas.authentication.AuthenticationEventExecutionPlanConfigurer;
import org.apereo.cas.authentication.AuthenticationHandler;
import org.apereo.cas.authentication.adaptive.AdaptiveAuthenticationPolicy;
import org.apereo.cas.authentication.principal.DefaultPrincipalFactory;
import org.apereo.cas.configuration.CasConfigurationProperties;
import org.apereo.cas.services.ServicesManager;
import org.apereo.cas.ticket.registry.TicketRegistry;
import org.apereo.cas.web.flow.CasWebflowConfigurer;
import org.apereo.cas.web.flow.CasWebflowExecutionPlan;
import org.apereo.cas.web.flow.CasWebflowExecutionPlanConfigurer;
import org.apereo.cas.web.flow.config.CasWebflowContextConfiguration;
import org.apereo.cas.web.flow.resolver.CasDelegatingWebflowEventResolver;
import org.apereo.cas.web.flow.resolver.CasWebflowEventResolver;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import org.springframework.webflow.definition.registry.FlowDefinitionRegistry;
import org.springframework.webflow.engine.builder.support.FlowBuilderServices;
import org.springframework.webflow.execution.Action;
@Configuration("mobileLoginConfiguration")
@AutoConfigureBefore(value = CasWebflowContextConfiguration.class)
@EnableConfigurationProperties(CasConfigurationProperties.class)
public class MobileidConfiguration implements CasWebflowExecutionPlanConfigurer {
@Autowired
private CasConfigurationProperties casProperties;
@Autowired
@Qualifier("logoutFlowRegistry")
private FlowDefinitionRegistry logoutFlowRegitry;
@Autowired
@Qualifier("loginFlowRegistry")
private FlowDefinitionRegistry loginFlowRegistry;
@Autowired
private ApplicationContext applicationContext;
@Autowired
@Qualifier("builder")
private FlowBuilderServices builder;
@Autowired
@Qualifier("adaptiveAuthenticationPolicy")
private AdaptiveAuthenticationPolicy adaptiveAuthenticationPolicy;
@Autowired
@Qualifier("serviceTicketRequestWebflowEventResolver")
private CasWebflowEventResolver serviceTicketRequestWebflowEventResolver;
@Autowired
@Qualifier("initialAuthenticationAttemptWebflowEventResolver")
private CasDelegatingWebflowEventResolver initialAuthenticationAttemptWebflowEventResolver;
@ConditionalOnMissingBean(name = "mobileidWebflowConfigurer")
@Bean
public CasWebflowConfigurer mobileidWebflowConfigurer() {
MobileidWebflowConfigurer configurer = new MobileidWebflowConfigurer(builder,
loginFlowRegistry, applicationContext, casProperties);
// configurer.setLogoutFlowDefinitionRegistry(logoutFlowRegitry);
return configurer;
}
@Bean
public Action mobileidCredentialsAction() {
return new MobileIdCredentialsAction(initialAuthenticationAttemptWebflowEventResolver,
serviceTicketRequestWebflowEventResolver, adaptiveAuthenticationPolicy);
}
@Configuration("mobileidAuthenticationEventExecutionPlanConfiguration")
public class MobileidAuthenticationEventExecutionPlanConfiguration
implements AuthenticationEventExecutionPlanConfigurer {
@Autowired
@Qualifier("servicesManager")
private ServicesManager servicesManager;
//注册验证器
@DependsOn("mobileUserRepository")
@Bean
public AuthenticationHandler mobileAuthenticationHandler() {
//优先验证
return new MobileHandler("mobileHandler",
servicesManager, new DefaultPrincipalFactory(), 1);
}
@Override
public void configureAuthenticationExecutionPlan(final AuthenticationEventExecutionPlan plan) {
plan.registerAuthenticationHandler(mobileAuthenticationHandler());
}
}
@Override
public void configureWebflowExecutionPlan(final CasWebflowExecutionPlan plan) {
plan.registerWebflowConfigurer(mobileidWebflowConfigurer());
}
}
最后
在 resources/META-INF/spring.factories中配置让SpringBoot扫描到我们的配置类
org.springframework.boot.autoconfigure.EnableAutoConfiguration=net.pubone.cas.support.mobile.config.MobileidConfiguration
在 resources/templates/casLoginView.html中加入一段html
<form>
<input type="hidden" name="execution" th:value="${flowExecutionKey}" />
<input type="hidden" name="_eventId" value="submitMobileId" />
<input placeholder="手机号" name="phoneNumber" autocomplete="off"/>
<input placeholder="验证码" name="validataCode" autocomplete="off"/>
<input class="button" id="login-submit" style="color:#fff;"
th:value="#{screen.welcome.button.login}"
tabindex="6"
type="submit"
/>
form>
效果如下
进行手机验证码登录时cas会调用我们自己的Action 里的 constructCredentialsFromRequest方法
创建好Credential后会进入我们自定义的Handler
如果在doAuthentication方法中抛出异常则认证失败
登录成功