Shiro-集成驗證碼

0) 前言

爲了防止經過程序進行暴力登陸等, 系統在登陸時都會增長驗證碼用來分區是人爲登陸仍是使用程序登陸.git

驗證碼的原理很簡單: 在用戶訪問登陸頁面時請求服務器生成驗證碼, 服務器將生成的驗證碼保存至SESSION後生成驗證碼圖片並顯示在登陸頁面, 因爲程序識別圖片內容的成功率較低, 而人能夠很快識別圖片中的內容, 以此減小非人爲的登陸等非法操做.github

技術發展到今天, 程序識別圖片內容的成功率愈來愈高, 驗證碼的交互形式也愈來愈多. 本文已最簡單的圖片驗證碼爲例, 講解Shiro中如何實現集成驗證碼bash


1) 集成Kaptcha

Kaptcha是谷歌開源的一個驗證碼插件, 經過在Web.xml中配置內置的Servlet便可實現生成驗證碼.服務器

  • 引入Kaptcha類庫
"com.github.penggle:kaptcha:2.3.2"
複製代碼
  • Web.xml增長Kaptcha內置Servlet配置
<!-- 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


2) 登陸頁顯示驗證碼

<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" />
複製代碼

訪問登陸頁, 會顯示驗證碼輸入框及驗證碼圖片函數


3) 擴展Token

[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;
    }

}
複製代碼

4) Shiro使用CaptchaToken

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);
    }

}
複製代碼
  • 建立Token是在FormAuthenticationFilter父類中的Shiro內置的登陸方法中
  • 登陸頁面中已經傳入驗證碼, 經過Request直接獲取便可.
  • 重寫createToken後, Shiro在建立Token時發現方法被重寫, 便會執行之定義的建立Token方法
  • 使用CaptchaToken時必定要設置用戶名和密碼, 不然Realm中沒法獲取用戶名密碼

5) 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中的登陸邏輯
    // -----------------------------------------------------------------


}
複製代碼

6) Shiro配置自定義過濾器

  • 聲明自定義過濾器
// 使用自定義的表單認證過濾器
// 該過濾器中只是重寫了Shiro的建立Token方法(增長了驗證碼)
authc(CaptchaFormAuthenticationFilter)
複製代碼
  • 配置全部請求使用自定義過濾器
// 配置URL規則
// 有請求訪問時Shiro會根據此規則找到對應的過濾器處理
filterChainDefinitionMap = [
    "/kaptcha" : "anon", // 驗證碼不須要登陸便可訪問
    "/kaptcha/get" : "anon", // 獲取驗證碼不須要登陸便可訪問
    "/login_success.jsp" : "anon", // 登陸成功頁不須要認證
    "/**": "authc" // 其他全部頁面須要認證(使用自定義的authc爲過濾器)
]
複製代碼
  • authc爲過濾器名稱, 未聲明時使用Shiro自帶的FormAuthenticationFilter, 已聲明時使用配置文件中聲明的過濾器
  • 獲取驗證碼URL不須要認證

7) 登陸失敗頁增長提示信息

<!-- 驗證碼異常 -->
<!-- 在登陸的Realm中驗證碼校驗錯誤時會拋出相關異常 -->
<c:if test="${shiroLoginFailure == 'com.atd681.shiro.kaptcha.CaptchaEmptyException'}">驗證碼爲空</c:if>
<c:if test="${shiroLoginFailure == 'com.atd681.shiro.kaptcha.CaptchaErrorException'}">驗證碼不正確</c:if>
複製代碼

8) 示例代碼

至此, 基於Shiro的驗證碼示例配置完成.

  • 示例代碼地址: https://github.com/atd681/alldemo

  • 示例項目名稱: atd681-shiro-kaptcha

相關文章
相關標籤/搜索