爲了防止經過程序進行暴力登陸等, 系統在登陸時都會增長驗證碼用來分區是人爲登陸仍是使用程序登陸.git
驗證碼的原理很簡單: 在用戶訪問登陸頁面時請求服務器生成驗證碼, 服務器將生成的驗證碼保存至SESSION後生成驗證碼圖片並顯示在登陸頁面, 因爲程序識別圖片內容的成功率較低, 而人能夠很快識別圖片中的內容, 以此減小非人爲的登陸等非法操做.github
技術發展到今天, 程序識別圖片內容的成功率愈來愈高, 驗證碼的交互形式也愈來愈多. 本文已最簡單的圖片驗證碼爲例, 講解Shiro中如何實現集成驗證碼bash
Kaptcha
是谷歌開源的一個驗證碼插件, 經過在Web.xml
中配置內置的Servlet
便可實現生成驗證碼.服務器
"com.github.penggle:kaptcha:2.3.2"
複製代碼
<!-- Kaptcha Servlet -->
<servlet>
<servlet-name>Kaptcha</servlet-name>
<servlet-class>com.google.code.kaptcha.servlet.KaptchaServlet</servlet-class>
<!-- 參數: 驗證碼圖片高度 -->
<init-param>
<param-name>kaptcha.image.width</param-name>
<param-value>200</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>Kaptcha</servlet-name>
<url-pattern>/kaptcha</url-pattern>
</servlet-mapping>
複製代碼
Kaptcha
內置的屬性可使用init-param
進行配置, 下面列出經常使用的配置信息, 更多請參考官方文檔:session
屬性 | 含義 |
---|---|
kaptcha.border | 是否有邊框, 默認有 |
kaptcha.border.color | 邊框顏色, 使用RGB值或white, red等顏色單次, 默認黑色 |
kaptcha.border.thickness | 邊框寬度, 默認1px |
kaptcha.image.width | 驗證碼圖片寬度 |
kaptcha.image.height | 驗證碼圖片高度 |
kaptcha.textproducer.char.string | 驗證碼字符內容, 默認abcde2345678gfynmnpwx |
kaptcha.textproducer.char.length | 驗證碼字符數量, 默認5 |
kaptcha.textproducer.font.names | 字體名稱, 默認Arial/Courier |
kaptcha.textproducer.font.size | 字體大小, 默認40px |
kaptcha.textproducer.font.color | 字體顏色, 默認黑色 |
kaptcha.session.key | 保存SESSION時的KEY, 默認KAPTCHA_SESSION_KEY |
kaptcha.background.clear.from/to | 背景漸變色起始/終止顏色, RGB色值, 默認淺灰/白 |
Controller
, 從SESSION獲取驗證碼的值. Kaptcha在SESSION中的KEY爲KAPTCHA_SESSION_KEY
/**
* 獲取驗證碼
*
* @author atd681
* @since 2018年8月13日
*/
@GetMapping("/kaptcha/get")
@ResponseBody
public String getKaptcha(HttpSession session) {
// Kaptcha生成驗證後保存SESSION中的KEY爲KAPTCHA_SESSION_KEY
return (String) session.getAttribute("KAPTCHA_SESSION_KEY");
}
複製代碼
驗證碼添加完成, 啓動項目:app
訪問http://localhost:6789/kaptcha
, 便可看到驗證碼圖片.jsp
訪問http://localhost:6789/kaptcha/get
, 兩次請求在同一個SESSION中, 驗證碼在SESSION的值和圖片內容相同ide
<form action="/login" method="post">
<input type="text" name="username" placeholder="用戶名" value="" />
<input type="password" name="password" placeholder="密碼" value="" />
<!-- 增長驗證碼輸入框 -->
<input type="text" name="captchaCode" placeholder="驗證碼" value="" />
<input type="submit" value="當即登陸" />
</form>
<!-- 驗證碼,請求地址爲在Web.xml中配置的Kaptcha內置的Servlet-->
<!-- Kaptcha Servlet生成驗證碼保存至SESSION並將圖片返回 -->
<img src="/kaptcha" />
複製代碼
訪問登陸頁, 會顯示驗證碼輸入框及驗證碼圖片函數
在[Shiro-認證]中講到, 登陸驗證是否合法是在Realm
實現的, 所以驗證碼也放到Realm
中進行驗證, Shiro會將登陸提交的用戶名和密碼封裝成UsernamePasswordToken
傳遞至Realm
中. 查看UsernamePasswordToken
發現該類中並保存無驗證碼的字段, 所以須要從新定義一個Token
能夠保存驗證碼.post
private String username;
private char[] password;
private boolean rememberMe = false;
private String host;
複製代碼
新建CaptchaToken
繼承UsernamePasswordToken
, 在CaptchaToken
增長驗證碼字段便可.
/**
* 擴展Shiro登陸表單Token,增長驗證碼字段
*/
public class CaptchaToken extends UsernamePasswordToken {
// 序列化ID
private static final long serialVersionUID = -2804050723838289739L;
// 驗證碼
private String captchaCode;
/**
* 構造函數
* 用戶名和密碼是登陸必須的,所以構造函數中包含兩個字段
*/
public CaptchaToken(String username, String password, String captchaCode) {
// 父類UsernamePasswordToken的構造函數,後兩個參數暫不須要, 不設置
super(username, password, false, "");
this.captchaCode = captchaCode;
}
/**
* 獲取驗證碼
*/
public String getCaptchaCode() {
return captchaCode;
}
}
複製代碼
Shiro建立Token時默認使用UsernamePasswordToken
, 在FormAuthenticationFilter
類的createToken
方法中建立.
新建CaptchaFormAuthenticationFilter
繼承FormAuthenticationFilter
並重寫createToken
方法, 使用CaptchaToken
並設置驗證碼.
/**
* 自定義認證過濾器
*/
public class CaptchaFormAuthenticationFilter extends FormAuthenticationFilter {
/**
* 構造Token,重寫Shiro構造Token的方法,增長驗證碼
*/
@Override
protected AuthenticationToken createToken(String username, String password, ServletRequest request, ServletResponse response) {
// 獲取登陸請求中用戶輸入的驗證碼
String captchaCode = request.getParameter("captchaCode");
// 返回帶驗證碼的Token,Token會被傳入Realm, 在Realm中能夠取得驗證碼
return new CaptchaToken(username, password, captchaCode);
}
}
複製代碼
FormAuthenticationFilter
父類中的Shiro內置的登陸方法中createToken
後, Shiro在建立Token時發現方法被重寫, 便會執行之定義的建立Token方法CaptchaToken
時必定要設置用戶名和密碼, 不然Realm
中沒法獲取用戶名密碼在[Shiro-認證]中講到, Shiro登陸失敗的錯誤是以異常的方式拋出, Shiro提供常見的錯誤異常, 但並提供沒有驗證碼錯誤異常. 咱們須要自定義兩個和驗證碼相關的異常
驗證碼爲空: CaptchaEmptyException
驗證碼錯誤: CaptchaErrorException
/**
* 自定義驗證碼爲空異常
* AuthenticationException爲Shiro認證錯誤的異常,不一樣錯誤類型繼承該異常便可
*/
public class CaptchaEmptyException extends AuthenticationException {
}
/**
* 自定義驗證碼錯誤異常
* AuthenticationException爲Shiro認證錯誤的異常,不一樣錯誤類型繼承該異常便可
*/
public class CaptchaErrorException extends AuthenticationException {
}
複製代碼
在Realm中增長驗證碼非空和正確性驗證, 當驗證失敗時拋出上述異常. 若是登陸過程當中拋出了父類爲AuthenticationException
的異常, Shiro認爲登陸失敗. 記錄異常信息並執行登陸失敗邏輯.
// 獲取用戶信息的方法
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
// 在自定義的認證過濾器中將驗證碼保存至KaptchaCodeToken中
// 此處的Token就是認證過濾器中實例化的Token,能夠直接強制轉換
CaptchaToken captchaToken = (CaptchaToken) token;
// 獲取用戶在登陸頁面輸入的驗證碼
String loginCaptcha = captchaToken.getCaptchaCode();
// 驗證碼未輸入
if (loginCaptcha == null || "".equals(loginCaptcha)) {
// 拋出自定義異常(繼承AuthenticationException), Shiro會捕獲AuthenticationException異常
// 發現該異常時認爲登陸失敗,執行登陸失敗邏輯,登陸失敗頁中能夠判斷若是是CaptchaEmptyException時爲驗證碼爲空
throw new CaptchaEmptyException();
}
// 獲取SESSION中的驗證碼
// Kaptcha在生成驗證碼時會將驗證碼放入SESSION中
// 默認KEY爲KAPTCHA_SESSION_KEY, 能夠在Web.xml中配置
String sessionCaptcha = (String) SecurityUtils.getSubject().getSession().getAttribute("KAPTCHA_SESSION_KEY");
// 比較登陸輸入的驗證碼和SESSION保存的驗證碼是否一致
if (!loginCaptcha.equals(sessionCaptcha)) {
// 拋出自定義異常(繼承AuthenticationException), Shiro會捕獲AuthenticationException異常
// 發現該異常時認爲登陸失敗,執行登陸失敗邏輯,登陸失敗頁中能夠判斷若是是CaptchaEmptyException時爲驗證碼錯誤
throw new CaptchaErrorException();
}
// -----------------------------------------------------------------
// 如下是atd681-shiro-authc中的登陸邏輯
// -----------------------------------------------------------------
}
複製代碼
// 使用自定義的表單認證過濾器
// 該過濾器中只是重寫了Shiro的建立Token方法(增長了驗證碼)
authc(CaptchaFormAuthenticationFilter)
複製代碼
// 配置URL規則
// 有請求訪問時Shiro會根據此規則找到對應的過濾器處理
filterChainDefinitionMap = [
"/kaptcha" : "anon", // 驗證碼不須要登陸便可訪問
"/kaptcha/get" : "anon", // 獲取驗證碼不須要登陸便可訪問
"/login_success.jsp" : "anon", // 登陸成功頁不須要認證
"/**": "authc" // 其他全部頁面須要認證(使用自定義的authc爲過濾器)
]
複製代碼
authc
爲過濾器名稱, 未聲明時使用Shiro自帶的FormAuthenticationFilter
, 已聲明時使用配置文件中聲明的過濾器<!-- 驗證碼異常 -->
<!-- 在登陸的Realm中驗證碼校驗錯誤時會拋出相關異常 -->
<c:if test="${shiroLoginFailure == 'com.atd681.shiro.kaptcha.CaptchaEmptyException'}">驗證碼爲空</c:if>
<c:if test="${shiroLoginFailure == 'com.atd681.shiro.kaptcha.CaptchaErrorException'}">驗證碼不正確</c:if>
複製代碼
至此, 基於Shiro的驗證碼示例配置完成.
示例代碼地址: https://github.com/atd681/alldemo
示例項目名稱: atd681-shiro-kaptcha