CAS單點登錄(七)——自定義驗證碼以及自定義錯誤信息
在前面我們講解了CAS單點登錄(六)——自定義登錄界面和表單信息,知道了如何去實現頁面和表單信息的自定義信息提交,就像我們提交表單的信息可能包括手機、郵箱等等,這些都能以我們前面的知識點去解決。但平時登錄我們會發現除了必填的信息外,還需要填寫一下驗證碼。這是為了流控、暴力破解、降低數據庫壓力等等原因,今天我們就講解一下如何在CAS中添加驗證碼。
注意:這一節的內容需要上一節的知識點,其中不會再次介紹自定義驗證策略,定義Webflow校驗流程等知識了,如果不知道,請先查看上一節的內容。
一、自定義驗證碼
其實網上有很多關於CAS驗證碼如何實現的,只是CAS版本都是以前比較低的,今天講解一下CAS 5.3.x版本中如何自定義驗證碼。知識點和上一節關於自定義表單基本類似,只是這里補充了一些細節。
1、生成驗證碼類
這里提供兩種方法,一種是自定義生成驗證碼的工具類,領一種是使用谷歌提供的kaptcha類。
A、自定義工具類
package net.anumbrella.sso.utils;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.*;
import java.util.Random;
/**
* @author anumbrella
*/
public class CaptchaCodeUtils {
//寬度
private static final int CAPTCHA_WIDTH = 100;
//高度
private static final int CAPTCHA_HEIGHT = 35;
//數字的長度
private static final int NUMBER_CNT = 6;
//圖片類型
private static final String IMAGE_TYPE = "JPEG";
private Random r = new Random();
// 字體
// private String[] fontNames = { "宋體", "華文楷體", "黑體", "華文新魏", "華文隸書", "微軟雅黑", "楷體_GB2312" };
private String[] fontNames = {"宋體", "黑體", "微軟雅黑"};
// 可選字符
private String codes = "23456789abcdefghjkmnopqrstuvwxyzABCDEFGHJKMNPQRSTUVWXYZ";
// 背景色,白色
private Color bgColor = new Color(255, 255, 255);
// 驗證碼上的文本
private String text;
private static CaptchaCodeUtils utils = null;
/**
* 實例化對象
*
* @return
*/
public static CaptchaCodeUtils getInstance() {
if (utils == null) {
synchronized (CaptchaCodeUtils.class) {
if (utils == null) {
utils = new CaptchaCodeUtils();
}
}
}
return utils;
}
/**
* 創建驗證碼
*
* @param path 路徑地址
* @return
* @throws Exception
*/
public String getCode(String path) throws Exception {
BufferedImage bi = utils.getImage();
output(bi, new FileOutputStream(path));
return this.text;
}
/**
* 生成圖片對象,並返回
*
* @return
* @throws Exception
*/
public CaptchaCode getCode() throws Exception {
BufferedImage img = utils.getImage();
//返回驗證碼對象
CaptchaCode code = new CaptchaCode();
code.setText(this.text);
code.setData(this.copyImage2Byte(img));
return code;
}
/**
* 將圖片轉化為 二進制數據
*
* @param img
* @return
* @throws Exception
*/
public byte[] copyImage2Byte(BufferedImage img) throws Exception {
//字節碼輸出流
ByteArrayOutputStream bout = new ByteArrayOutputStream();
//寫數據到輸出流中
ImageIO.write(img, IMAGE_TYPE, bout);
//返回數據
return bout.toByteArray();
}
/**
* 將二進制數據轉化為文件
*
* @param data
* @param file
* @throws Exception
*/
public boolean copyByte2File(byte[] data, String file) throws Exception {
ByteArrayInputStream in = new ByteArrayInputStream(data);
FileOutputStream out = new FileOutputStream(file);
try {
byte[] buff = new byte[1024];
int len = 0;
while ((len = in.read(buff)) > -1) {
out.write(buff, 0, len);
}
out.flush();
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
} finally {
out.close();
in.close();
}
}
/**
* 生成隨機的顏色
*
* @return
*/
private Color randomColor() {
int red = r.nextInt(150);
int green = r.nextInt(150);
int blue = r.nextInt(150);
return new Color(red, green, blue);
}
/**
* 生成隨機的字體
*
* @return
*/
private Font randomFont() {
int index = r.nextInt(fontNames.length);
String fontName = fontNames[index];// 生成隨機的字體名稱
int style = r.nextInt(4);// 生成隨機的樣式, 0(無樣式), 1(粗體), 2(斜體), 3(粗體+斜體)
int size = r.nextInt(5) + 24; // 生成隨機字號, 24 ~ 28
return new Font(fontName, style, size);
}
/**
* 畫干擾線
*
* @param image
*/
private void drawLine(BufferedImage image) {
int num = 5;// 一共畫5條
Graphics2D g2 = (Graphics2D) image.getGraphics();
for (int i = 0; i < num; i++) {// 生成兩個點的坐標,即4個值
int x1 = r.nextInt(CAPTCHA_WIDTH);
int y1 = r.nextInt(CAPTCHA_HEIGHT);
int x2 = r.nextInt(CAPTCHA_WIDTH);
int y2 = r.nextInt(CAPTCHA_HEIGHT);
g2.setStroke(new BasicStroke(1.5F));
g2.setColor(randomColor()); // 隨機生成干擾線顏色
g2.drawLine(x1, y1, x2, y2);// 畫線
}
}
/**
* 隨機生成一個字符
*
* @return
*/
private char randomChar() {
int index = r.nextInt(codes.length());
return codes.charAt(index);
}
/**
* 創建BufferedImage
*
* @return
*/
private BufferedImage createImage() {
BufferedImage image = new BufferedImage(CAPTCHA_WIDTH, CAPTCHA_HEIGHT, BufferedImage.TYPE_INT_RGB);
Graphics2D g2 = (Graphics2D) image.getGraphics();
g2.setColor(this.bgColor);
g2.fillRect(0, 0, CAPTCHA_WIDTH, CAPTCHA_HEIGHT);
return image;
}
/**
* 獲取驗證碼
*
* @return
*/
public BufferedImage getImage() {
BufferedImage image = createImage();// 創建圖片緩沖區
Graphics2D g2 = (Graphics2D) image.getGraphics();// 得到繪制環境
StringBuilder sb = new StringBuilder();// 用來裝載生成的驗證碼文本
// 向圖片中畫4個字符
for (int i = 0; i < NUMBER_CNT; i++) {// 循環四次,每次生成一個字符
String s = randomChar() + "";// 隨機生成一個字母
sb.append(s); // 把字母添加到sb中
float x = i * 1.0F * CAPTCHA_WIDTH / NUMBER_CNT; // 設置當前字符的x軸坐標
g2.setFont(randomFont()); // 設置隨機字體
g2.setColor(randomColor()); // 設置隨機顏色
g2.drawString(s, x, CAPTCHA_HEIGHT - 5); // 畫圖
}
this.text = sb.toString(); // 把生成的字符串賦給了this.text
drawLine(image); // 添加干擾線
return image;
}
/**
* @return 返回驗證碼圖片上的文本
*/
public String getText() {
return text;
}
// 保存圖片到指定的輸出流
public static void output(BufferedImage image, OutputStream out) throws IOException {
ImageIO.write(image, IMAGE_TYPE, out);
}
/**
* 圖片驗證碼對象
*/
public static class CaptchaCode {
//驗證碼文字信息
private String text;
//驗證碼二進制數據
private byte[] data;
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
public byte[] getData() {
return data;
}
public void setData(byte[] data) {
this.data = data;
}
}
}
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
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
B、谷歌Kaptcha工具類
先添加依賴:
<!-- 驗證碼 -->
<dependency>
<groupId>com.github.penggle</groupId>
<artifactId>kaptcha</artifactId>
<version>2.3.2</version>
</dependency>
1
2
3
4
5
6
再新建KaptchaCodeUtils類,用於Kaptcha的相關配置信息。
/**
* Kaptcha 配置信息
*
* @return
*/
public static DefaultKaptcha getDefaultKaptcha() {
com.google.code.kaptcha.impl.DefaultKaptcha defaultKaptcha = new com.google.code.kaptcha.impl.DefaultKaptcha();
Properties properties = new Properties();
properties.setProperty("kaptcha.border", "yes");
properties.setProperty("kaptcha.border.color", "105,179,90");
properties.setProperty("kaptcha.textproducer.font.color", "blue");
properties.setProperty("kaptcha.image.width", "110");
properties.setProperty("kaptcha.image.height", "40");
properties.setProperty("kaptcha.textproducer.font.size", "30");
properties.setProperty("kaptcha.session.key", "code");
properties.setProperty("kaptcha.textproducer.char.length", "4");
properties.setProperty("kaptcha.textproducer.font.names", "宋體,楷體,微軟雅黑");
Config config = new Config(properties);
defaultKaptcha.setConfig(config);
return defaultKaptcha;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
以上兩種方法選擇一種即可,這個是用於驗證碼生成的作用。
2、定義訪問控制器
在前面CAS單點登錄(五)——Service配置及管理我們曾經講解過Restful請求訪問的方法,這里驗證碼也需要使用到,因為驗證碼其實就是生成的圖片,需要給前台去顯示。
我們新建一個CaptchaController類,用於驗證碼方法訪問:
package net.anumbrella.sso.controller;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.code.kaptcha.impl.DefaultKaptcha;
import net.anumbrella.sso.utils.CaptchaCodeUtils;
import net.anumbrella.sso.utils.KaptchaCodeUtils;
import org.springframework.stereotype.Controller;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import javax.imageio.ImageIO;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.util.HashMap;
import java.util.Map;
/**
* @author anumbrella
*/
@Controller
public class CaptchaController {
/**
* 工具類生成captcha驗證碼路徑
*
* @param request
* @param response
* @throws Exception
*/
@GetMapping(value = "/captcha", produces = "image/png")
public void captcha(HttpServletRequest request, HttpServletResponse response) throws Exception {
OutputStream out = null;
try {
//設置response頭信息
//禁止緩存
response.setHeader("Cache-Control", "no-cache");
response.setContentType("image/png");
//存儲驗證碼到session
CaptchaCodeUtils.CaptchaCode code = CaptchaCodeUtils.getInstance().getCode();
//獲取驗證碼code
String codeTxt = code.getText();
request.getSession().setAttribute("captcha_code", codeTxt);
//寫文件到客戶端
out = response.getOutputStream();
byte[] imgs = code.getData();
out.write(imgs, 0, imgs.length);
out.flush();
} finally {
if (out != null) {
out.close();
}
}
}
/**
* 谷歌kaptcha驗證碼路徑
*
* @param request
* @param response
* @throws Exception
*/
@GetMapping(value = "/kaptcha", produces = "image/png")
public void kaptcha(HttpServletRequest request, HttpServletResponse response) throws Exception {
byte[] captchaChallengeAsJpeg = null;
DefaultKaptcha captchaProducer = KaptchaCodeUtils.getDefaultKaptcha();
OutputStream out = null;
ByteArrayOutputStream jpegOutputStream = new ByteArrayOutputStream();
try {
response.setHeader("Cache-Control", "no-store");
response.setContentType("image/png");
//生產驗證碼字符串並保存到session中
String createText = captchaProducer.createText();
request.getSession().setAttribute("captcha_code", createText);
//使用生產的驗證碼字符串返回一個BufferedImage對象並轉為byte寫入到byte數組中
BufferedImage challenge = captchaProducer.createImage(createText);
ImageIO.write(challenge, "png", jpegOutputStream);
//使用response輸出流輸出圖片的byte數組
captchaChallengeAsJpeg = jpegOutputStream.toByteArray();
out = response.getOutputStream();
out.write(captchaChallengeAsJpeg);
out.flush();
} catch (IllegalArgumentException e) {
response.sendError(HttpServletResponse.SC_NOT_FOUND);
return;
} finally {
if (out != null) {
out.close();
}
}
}
/**
* 用於前端ajax校驗
*/
@RequestMapping(value = "/chkCode", method = RequestMethod.POST)
public void checkCode(String code, HttpServletRequest req, HttpServletResponse resp) {
//獲取session中的驗證碼
String storeCode = (String) req.getSession().getAttribute("captcha_code");
code = code.trim();
//返回值
Map<String, Object> map = new HashMap<String, Object>();
//驗證是否對,不管大小寫
if (!StringUtils.isEmpty(storeCode) && code.equalsIgnoreCase(storeCode)) {
map.put("error", false);
map.put("msg", "驗證成功");
} else if (StringUtils.isEmpty(code)) {
map.put("error", true);
map.put("msg", "驗證碼不能為空");
} else {
map.put("error", true);
map.put("msg", "驗證碼錯誤");
}
this.writeJSON(resp, map);
}
/**
* 在SpringMvc中獲取到Session
*
* @return
*/
public void writeJSON(HttpServletResponse response, Object object) {
try {
//設定編碼
response.setCharacterEncoding("UTF-8");
//表示是json類型的數據
response.setContentType("application/json");
//獲取PrintWriter 往瀏覽器端寫數據
PrintWriter writer = response.getWriter();
ObjectMapper mapper = new ObjectMapper(); //轉換器
//獲取到轉化后的JSON 數據
String json = mapper.writeValueAsString(object);
//寫數據到瀏覽器
writer.write(json);
//刷新,表示全部寫完,把緩存數據都刷出去
writer.flush();
//關閉writer
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
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
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
主要提供了三個方法,captcha、kaptcha、checkCode,分別是類庫生成的驗證碼路徑,谷歌kaptcha類庫生成的驗證碼路徑,前端ajax校驗路徑。主要的方法就是將生成的驗證碼圖片流輸出到響應流中,同時將文本保存在session中。
在前面我們講解過CAS要在Restful中進行訪問,定義好Controller類后,需要在resources下的META-INF文件下的spring.factories注入Spring Boot的配置。但如果我們定義多個controller類,這樣定義是很麻煩的。其實只要注入到配置里就可以了。
在config包下,新建CustomControllerConfigurer類,將我們需要使用的controller注入bean到其下即可。
package net.anumbrella.sso.config;
import net.anumbrella.sso.controller.CaptchaController;
import net.anumbrella.sso.controller.ServicesManagerController;
import org.apereo.cas.configuration.CasConfigurationProperties;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author anumbrella
*/
@Configuration("captchaConfiguration")
@EnableConfigurationProperties(CasConfigurationProperties.class)
public class CustomControllerConfigurer {
/**
* 驗證碼配置,注入bean到spring中
*
* @return
*/
@Bean
@ConditionalOnMissingBean(name = "captchaController")
public CaptchaController captchaController() {
return new CaptchaController();
}
/**
* 自定義SercicesManage管理配置,注入bean到spring中
*
* @return
*/
@Bean
@ConditionalOnMissingBean(name = "servicesManagerController")
public ServicesManagerController servicesManagerController() {
return new ServicesManagerController();
}
}
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
然后我們再到resources下的META-INF文件下的spring.factories文件里注入到Spring Boot的配置里。
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
net.anumbrella.sso.config.CustomAuthenticationConfiguration,\
net.anumbrella.sso.config.CustomerAuthWebflowConfiguration,\
net.anumbrella.sso.config.CustomControllerConfigurer
1
2
3
4
現在我們就可以啟動CAS服務,訪問路由/captcha 或者 /kaptcha,我們可以得到驗證碼圖片如下:
3、添加字段修改登錄頁面
同理,如果驗證碼字段是一個必填的字段,我們像上一節CAS單點登錄(六)——自定義登錄界面和表單信息添加手機號和郵箱一樣,新增驗證碼字段。
在entity包下的CustomCredential類里,新增capcha字段。
@Size(min = 6, max = 6, message = "required.capcha")
private String capcha;
public String getCapcha() {
return capcha;
}
public void setCapcha(String capcha) {
this.capcha = capcha;
}
1
2
3
4
5
6
7
8
9
10
同時在config包下的CustomWebflowConfigurer中添加信息綁定。
// 重寫綁定自定義credential
createFlowVariable(flow, CasWebflowConstants.VAR_ID_CREDENTIAL, CustomCredential.class);
// 登錄頁綁定新參數
final ViewState state = (ViewState) flow.getState(CasWebflowConstants.STATE_ID_VIEW_LOGIN_FORM);
final BinderConfiguration cfg = getViewStateBinderConfiguration(state);
// 由於用戶名以及密碼已經綁定,所以只需對新加系統參數綁定即可
// 字段名,轉換器,是否必須字段
cfg.addBinding(new BinderConfiguration.Binding("email", null, true));
cfg.addBinding(new BinderConfiguration.Binding("telephone", null, true));
cfg.addBinding(new BinderConfiguration.Binding("capcha", null, true));
1
2
3
4
5
6
7
8
9
10
11
如果忘記了可以去看看上一節的CAS內容。
接着我們在登錄頁面casLoginView.html中添加驗證碼的字段,如下:
<!-- 驗證碼信息 -->
<section>
<img id="captcha_img" th:src="@{/captcha}" onclick="changeCode()" />
<input
type="text"
id="code"
th:field="*{capcha}"
/>
<span id="code_str"></span>
</section>
1
2
3
4
5
6
7
8
9
10
因為這里添加了一些js功能,點擊圖片更換驗證碼,所以引入了code.js。
code.js 內容如下:
function changeCode(){
var node = document.getElementById("captcha_img");
//修改驗證碼
if (node){
node.src = node.src+'?id='+uuid();
}
}
function uuid(){
//獲取系統當前的時間
var d = new Date().getTime();
//替換uuid里面的x和y
var uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
//取余 16進制
var r = (d + Math.random()*16)%16 | 0;
//向下去整
d = Math.floor(d/16);
//toString 表示編程16進制的數據
return (c=='x' ? r : (r&0x3|0x8)).toString(16);
});
return uuid;
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
在casLoginView.html底部引入js,如下
<script th:src="@{${#themes.code('anumbrella.javascript.file')}}"></script>
<script th:src="@{${#themes.code('anumbrella.javascript.code.file')}}"></script>
1
2
在anumbrella.properties中填寫js路徑,如下:
anumbrella.javascript.file=/themes/anumbrella/js/cas.js
anumbrella.standard.css.file=/themes/anumbrella/css/cas.css
anumbrella.javascript.code.file=/themes/anumbrella/js/code.js
anumbrella.login.images.path=/themes/anumbrella/images
cas.standard.css.file=/css/cas.css
cas.javascript.file=/js/cas.js
cas.admin.css.file=/css/admin.css
spring.thymeleaf.cache=false
1
2
3
4
5
6
7
8
9
10
11
當然這里也可以不用配置,直接在casLoginView.html底部,配置js的路徑即可。
最后我們在authentication包下的CustomerHandlerAuthentication的doAuthentication中可以獲取到驗證碼進行匹配。如下:
....
CustomCredential customCredential = (CustomCredential) credential;
String username = customCredential.getUsername();
String password = customCredential.getPassword();
String email = customCredential.getEmail();
String telephone = customCredential.getTelephone();
String capcha = customCredential.getCapcha();
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
String right = attributes.getRequest().getSession().getAttribute("captcha_code").toString();
if(!capcha.equalsIgnoreCase(right)){
throw new AccountException("Sorry, capcha not correct !");
}
....
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
重啟CAS服務,可以發現驗證碼效果出來了,點擊驗證碼會自動更新。
當我們不填寫驗證碼提交后,會出現提示信息,如果填寫錯誤,也會出現相應的“認證信息無效”提示。
到此,我們的驗證碼基本功能完成。
二、自定義錯誤信息
我們可以看到當我們沒有輸入信息點擊登錄后,會出現required on x,x是必填項。這里是email、telephone、capcha。但是當我們沒有填寫用戶名和密碼的提示不一樣,這是因為CAS的對應中文提示是在messages_zh_CN.properties文件下的,我們可以在target文件中找到需要的messages_zh_CN.properties。
在文件里我們可以發現username.required,password.required這里就是配置用戶名和密碼提示的。
那么我們能通過email.required,telephone.required來配置相應的提示么,很可惜不行,CAS中的錯誤不能這樣配。對應為空的提示比較麻煩一些,我們后面點介紹,接下來先介紹錯誤提示自定義。比如驗證碼錯誤了,如何自定義提示?
我們新建一個exception包,同時新建CheckCodeErrorException類,繼承於AuthenticationException。
package net.anumbrella.sso.exection;
import org.apereo.cas.authentication.AuthenticationException;
/**
* @author Anumbrella
*/
public class CheckCodeErrorException extends AuthenticationException {
public CheckCodeErrorException(){
super();
}
public CheckCodeErrorException(String msg) {
super(msg);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
然后我們在application.properties文件中添加自定義錯誤,指定我們自己編寫的異常類。如下:
# 自定義異常配置
cas.authn.exceptions.exceptions=net.anumbrella.sso.exection.CheckCodeErrorException
1
2
最后我們依照messages_zh_CN.properties中,其他異常的配置,填寫如下:
authenticationFailure.CheckCodeErrorException=驗證碼不正確
1
最后再更改一下錯誤拋出的異常,如下:
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
String right = attributes.getRequest().getSession().getAttribute("captcha_code").toString();
if(!capcha.equalsIgnoreCase(right)){
throw new CheckCodeErrorException();
}
1
2
3
4
5
6
7
重啟CAS服務,輸入信息,驗證碼亂填,點擊登錄,提示我們配置的錯誤。
如何更改為空信息提交提示,因為我們是采用的自定義表單,所以思路是我們需要在提交驗證信息之前進行校驗。更改表單驗證信息,或者采用另一種方法,更改webflow的流程。先進行驗證后再經過我們的自定義校驗。
更改webflow可以采用XML配置,也可以通過代碼來更改,這里采用代碼更改。
在CustomWebflowConfigurer中更改bindCredential方法如下:
/**
* 綁定自定義的Credential信息
*
* @param flow
*/
protected void bindCredential(Flow flow) {
// 重寫綁定自定義credential
createFlowVariable(flow, CasWebflowConstants.VAR_ID_CREDENTIAL, CustomCredential.class);
// 登錄頁綁定新參數
final ViewState state = (ViewState) flow.getState(CasWebflowConstants.STATE_ID_VIEW_LOGIN_FORM);
final BinderConfiguration cfg = getViewStateBinderConfiguration(state);
// 由於用戶名以及密碼已經綁定,所以只需對新加系統參數綁定即可
// 字段名,轉換器,是否必須字段
cfg.addBinding(new BinderConfiguration.Binding("email", null, false));
cfg.addBinding(new BinderConfiguration.Binding("telephone", null, false));
cfg.addBinding(new BinderConfiguration.Binding("capcha", null, false));
final ActionState actionState = (ActionState) flow.getState(CasWebflowConstants.STATE_ID_REAL_SUBMIT);
final List<Action> currentActions = new ArrayList<>();
actionState.getActionList().forEach(currentActions::add);
currentActions.forEach(a -> actionState.getActionList().remove(a));
actionState.getActionList().add(createEvaluateAction("validateLoginAction"));
currentActions.forEach(a -> actionState.getActionList().add(a));
actionState.getTransitionSet().add(createTransition("emailError", CasWebflowConstants.STATE_ID_INIT_LOGIN_FORM));
actionState.getTransitionSet().add(createTransition("telephoneError", CasWebflowConstants.STATE_ID_INIT_LOGIN_FORM));
actionState.getTransitionSet().add(createTransition("captchaError", CasWebflowConstants.STATE_ID_INIT_LOGIN_FORM));
}
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
這里主要更改了兩個地方,將必填字段更改為false,讓我們手動去判斷,其次添加Webflow的流程,先將原來的action備份一下,然后刪除掉,再將需要的action添加進去,同時將備份的action還原。
新建一個action包,添加一個ValidateLoginAction類,主要用於我們自定義添加的表單信息的驗證,如下:
package net.anumbrella.sso.action;
import net.anumbrella.sso.entity.CustomCredential;
import org.apereo.cas.web.support.WebUtils;
import org.springframework.binding.message.MessageBuilder;
import org.springframework.binding.message.MessageContext;
import org.springframework.webflow.action.AbstractAction;
import org.springframework.webflow.execution.Event;
import org.springframework.webflow.execution.RequestContext;
/**
* @author anumbrella
*/
public class ValidateLoginAction extends AbstractAction {
private static final String CAPTCHA_CODE = "captchaError";
private static final String EMAIL_CODE = "emailError";
private static final String TELEPHONE_CODE = "telephoneError";
/**
* 是否開啟驗證碼
*
* @return
*/
private boolean isEnable() {
return true;
}
@Override
protected Event doExecute(RequestContext context) throws Exception {
CustomCredential credential = (CustomCredential) WebUtils.getCredential(context);
System.out.println("excute");
//系統信息不為空才檢測校驗碼
if (credential instanceof CustomCredential) {
String email = credential.getEmail();
String telephone = credential.getTelephone();
String capcha = credential.getCapcha();
if (capcha.equals("") || capcha == null) {
return getError(context, CAPTCHA_CODE);
}
if (email.equals("") || email == null) {
return getError(context, EMAIL_CODE);
}
if (telephone.equals("") || telephone == null) {
return getError(context, TELEPHONE_CODE);
}
}
return null;
}
/**
* 跳轉到錯誤頁
*
* @param requestContext
* @return
*/
private Event getError(final RequestContext requestContext, String CODE) {
final MessageContext messageContext = requestContext.getMessageContext();
messageContext.addMessage(new MessageBuilder().error().code(CODE).build());
return getEventFactorySupport().event(this, CODE);
}
}
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
最后我們將ValidateLoginAction的類注入到配置CustomerAuthWebflowConfiguration中去,如下:
@Bean
@RefreshScope
@ConditionalOnMissingBean(name = "validateLoginAction")
public Action validateLoginAction() {
ValidateLoginAction validateCaptchaAction = new ValidateLoginAction();
return validateCaptchaAction;
}
1
2
3
4
5
6
7
最后我們還需要在messages_zh_CN.properties中添加錯誤提示信息,如下:
emailError=郵箱不能為空
telephoneError=電話號碼不能為空
captchaError=驗證碼不能為空
1
2
3
重啟我們的CAS服務,當我們沒有輸入郵箱、電話或驗證碼時會提示相應的錯誤提示並不能登錄!
除此之外,驗證碼校驗那里的情況還可以采用ajax去實現異步,主要是使用JavaScript請求配置去實現,在controller中也提供了接口,可以自行擴展。好了,這節的內容到此為止!
代碼實例:Chapter6
參考
CAS單點登錄-登錄校驗碼(十七)
CAS之5.2x版本登錄驗證碼-yellowcong
https://apereo.github.io/cas/5.3.x/installation/Webflow-Customization.html
————————————————
版權聲明:本文為CSDN博主「Anumbrella」的原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/anumbrella/article/details/83154397
