0%

二次开发CAS服务端登录流程

简介

CAS 服务端登录提供了默认的登录流程并采用WebFlow进行实现,但是再实际项目中需要对接外部的登录系统,如果外部账号之前没有在本系统登录过,那么就要在内部账号进行一次二次注册,这需要对原有的登录流程进行修改,如果外部账号验证通过则进行判断该账号时候在本系统注册过,如果没有跳转到二次注册页面进行注册,我这里使用的版本是5.3.X

强制条件

CAS 服务端采用Spring WebFlow进行流程编排,需要对WebFlow有个大概的了解才能进行开发
https://blog.csdn.net/u013040472/article/details/71375058

修改切入点

通过CAS官网得知 登录流程Webflow的默认实现类为DefaultLoginWebflowConfigurer,通过检索源码发现,该类会在工程开启时自动在Spring容器中注册,那么我们对流程的二次开发就已经明了,我们继承该类,加入自己的逻辑并在Spring容器中注册即可

1
2
3
4
5
6
7
8
9
10
@ConditionalOnMissingBean(name = "defaultWebflowConfigurer")
@Bean
@Order(0)
@RefreshScope
public CasWebflowConfigurer defaultWebflowConfigurer() {
final DefaultLoginWebflowConfigurer c = new DefaultLoginWebflowConfigurer(builder(), loginFlowRegistry(), applicationContext, casProperties);
c.setLogoutFlowDefinitionRegistry(logoutFlowRegistry());
c.setOrder(Ordered.HIGHEST_PRECEDENCE);
return c;
}

WebFlow 定义入口,在resources/webflow/login 创建了一个名为loginwebflow,以下的代码都是对这个webflow做扩展

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
<?xml version="1.0" encoding="UTF-8"?>
<flow xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.springframework.org/schema/webflow"
xsi:schemaLocation="http://www.springframework.org/schema/webflow
http://www.springframework.org/schema/webflow/spring-webflow.xsd">

<action-state id="initializeLoginForm">
<evaluate expression="initializeLoginAction" />
<transition on="success" to="viewLoginForm"/>
</action-state>

<view-state id="viewLoginForm" view="casLoginView" model="credential">
<binder>
<binding property="username" required="true"/>
<binding property="password" required="true"/>
<binding property="code"/>
<binding property="type" required="true"/>
</binder>
<transition on="submit" bind="true" validate="true" to="realSubmit" history="invalidate"/>
</view-state>

<action-state id="realSubmit">
<evaluate expression="authenticationViaFormAction"/>
<transition on="warn" to="warn"/>
<transition on="success" to="createTicketGrantingTicket"/>
<transition on="successWithWarnings" to="showAuthenticationWarningMessages"/>
<transition on="authenticationFailure" to="handleAuthenticationFailure"/>
<transition on="error" to="initializeLoginForm"/>
</action-state>
</flow>

CAS默认登录流程

通过查看DefaultLoginWebflowConfigurer,绘制了CAS流程登录时候的默认流程图,流程图中省略了一些非主要的逻辑

流程图

经过二次开发后的流程

我在生成st 的流程下(generateServiceTicket)添加了一个自定义流程checkUserExister,来判断用户是否进行过二次注册,如果注册过就就跳转,如果没有注册过就跳转到二次注册页面,同样我在checkUserExister下也添加了checkUserExister来对用户是否进行过二次注册进行判断

流程图

部分代码

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
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
public class MyLoginWebflowConfiger extends DefaultLoginWebflowConfigurer {


public MyLoginWebflowConfiger(FlowBuilderServices flowBuilderServices, FlowDefinitionRegistry flowDefinitionRegistry, ApplicationContext applicationContext, CasConfigurationProperties casProperties) {
super(flowBuilderServices, flowDefinitionRegistry, applicationContext, casProperties);
}

//登录页面属性和后端绑定
@Override
protected void createRememberMeAuthnWebflowConfig(Flow flow){
if (this.casProperties.getTicket().getTgt().getRememberMe().isEnabled()) {
this.createFlowVariable(flow, "credential", RememberMeUsernamePasswordCredential.class);
ViewState state = (ViewState)this.getState(flow, "viewLoginForm", ViewState.class);
BinderConfiguration cfg = this.getViewStateBinderConfiguration(state);
cfg.addBinding(new BinderConfiguration.Binding("rememberMe", (String)null, false));
} else {
this.createFlowVariable(flow, "credential", LoginUsernamePasswordCredential.class);
}

}


//generateServiceTicket后加入验证用户是否进行过二次注册checkUserExist
@Override
protected void createGenerateServiceTicketAction(Flow flow) {
ActionState handler = this.createActionState(flow, "generateServiceTicket", this.createEvaluateAction("generateServiceTicketAction"));
this.createTransitionForState(handler, "success", "checkUserExist");
this.createTransitionForState(handler, "warn", "warn");
this.createTransitionForState(handler, "authenticationFailure", "handleAuthenticationFailure");
this.createTransitionForState(handler, "error", "initializeLoginForm");
this.createTransitionForState(handler, "gateway", "gatewayServicesManagementCheck");
createCheckUserExist(flow);

}




//hasServiceCheck后加入验证用户是否进行过二次注册checkUserExist
@Override
protected void createHasServiceCheckDecisionState(Flow flow) {
this.createDecisionState(flow, "hasServiceCheck", "flowScope.service != null", "renewRequestCheck", "checkUserExist");
}

//serviceCheck后加入验证用户是否进行过二次注册checkUserExist
@Override
protected void createServiceCheckDecisionState(Flow flow) {
this.createDecisionState(flow, "serviceCheck", "flowScope.service != null", "generateServiceTicket", "checkUserExist");

}

//创建serviceCheck action
private void createCheckUserExist(Flow flow){

ActionState handler = this.createActionState(flow, "checkUserExist", this.createEvaluateAction("checkUserExistAction"));
//没有注册添加到注册页面
this.createTransitionForState(handler, "noRegister", "addUser");
//注册过跳转到判断是否有service回调地址
this.createTransitionForState(handler, "register", "checkServiceDecisionState");

createAddUserView(flow);
createRegisteredUserView(flow);
createCheckServiceDecisionState(flow);



}

//如果有回调地址service则重定向,如果么有则跳转到默认页面
private void createCheckServiceDecisionState(Flow flow){
this.createDecisionState(flow, "checkServiceDecisionState", "flowScope.service != null", "redirect", "registeredUser");
}





private void createAddUserView(Flow flow){
EndState state = this.createEndState(flow, "addUser", "addUser");

}

private void createRegisteredUserView(Flow flow){
EndState state = this.createEndState(flow, "registeredUser", "registeredUser");

}





}

CheckUserExistAction 代码
requestContext.getFlowScope().put(“redirect_service”,service);方法可以在flow域中传入参数,这样在页面中就可通过themeleaf模板方法
获取之前传入的值

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
public class CheckUserExistAction extends AbstractAction {


private CentralAuthenticationService centralAuthenticationService;

private ServiceConfig serviceConfig;

public CheckUserExistAction(CentralAuthenticationService centralAuthenticationService,ServiceConfig serviceConfig) {
this.centralAuthenticationService = centralAuthenticationService;
this.serviceConfig=serviceConfig;
}

@Override
protected Event doExecute(RequestContext requestContext) throws IOException, URISyntaxException {
String tgt = WebUtils.getTicketGrantingTicketId(requestContext);
TicketGrantingTicket ticketGrantingTicket = this.centralAuthenticationService.getTicket(tgt, TicketGrantingTicket.class);
Principal principal =ticketGrantingTicket.getAuthentication().getPrincipal();
String userJson=principal.getId();
User user=JSON.toJavaObject(JSON.parseObject(userJson), User.class);
String idCard=user.getIdCard();
Map<String,String> params=new HashMap<>();
params.put("userIdcard",idCard);
String response=HttpClientUtil.get(serviceConfig.find_useridcard_url,params);
validateResponse(response);
requestContext.getFlowScope().put("user_info",JSON.toJavaObject(JSON.parseObject(principal.getId()),User.class));
WebApplicationService webApplicationService=WebUtils.getService(requestContext);
String service=webApplicationService==null?null:webApplicationService.getOriginalUrl();
requestContext.getFlowScope().put("redirect_service",service);
if(noRegisterAndCompany(response)){
return this.getEventFactorySupport().event(this,"noRegister");
}else {
return this.getEventFactorySupport().event(this,"register");
}
}


private void validateResponse(String response){

Object code=JSON.parseObject(response).get("code");
if(code==null || !"1".equals(code.toString())){
throw new RuntimeException("根据信用代码查询用户信息错误");
}
}

private boolean noRegisterAndCompany(String response) {
Object data=JSON.parseObject(response).get("data");
return data==null?true:
!"2d0f4e4b-4a7f-463c-abb1-20d33ea14f1a".equals(JSON.parseObject(data.toString()).get("type"))
;
}


}