Spring Boot2(十五):Shiro記住我rememberMe、驗證碼Kaptcha

接着上次學習的《SpringBoot2(十二):手摸手教你搭建Shiro安全框架》,實現了Shiro的認證和受權。今天繼續在這個基礎上學習Shiro實現功能記住我rememberMe,以及登陸時驗證碼Kaptcha。javascript

Remember Me記住我:用戶的登陸狀態會不會由於瀏覽器的關閉而失效,直到Cookie失效。關閉瀏覽器後,再次訪問登陸後的頁面能夠不用登陸。由於用Cookie實現,故只在同一瀏覽器中有效。css

Kaptcha驗證碼:是谷歌開源的驗證碼插件,實現登陸的驗證碼驗證攔截。html

1、記住我rememberMe

用戶的登陸狀態會不會由於瀏覽器的關閉而失效,直到Cookie失效。關閉瀏覽器後,再次訪問登陸後的頁面能夠不用登陸。由於用Cookie實現,故只在同一瀏覽器中有效。前端

修改ShiroConfig

/** * 路徑過濾規則 * @return */
@Bean
public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {
	ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
	shiroFilterFactoryBean.setSecurityManager(securityManager);
	// 若是不設置默認會自動尋找Web工程根目錄下的"/login.jsp"頁面
	shiroFilterFactoryBean.setLoginUrl("/login");
	shiroFilterFactoryBean.setSuccessUrl("/index");
	// 攔截器
	LinkedHashMap<String, String> map = new LinkedHashMap<>();
	// 配置不會被攔截的連接 順序判斷
	// 對靜態資源設置匿名訪問
	map.put("/static/**", "anon");
	map.put("/css/**", "anon");
	map.put("/js/**", "anon");

	// 過濾鏈定義,從上向下順序執行,通常將/**放在最爲下邊
	// 進行身份認證後才能訪問
	// authc:全部url都必須認證經過才能夠訪問; anon:全部url都均可以匿名訪問
	// user指的是用戶認證經過或者配置了Remember Me記住用戶登陸狀態後可訪問
	map.put("/**", "user");
	shiroFilterFactoryBean.setFilterChainDefinitionMap(map);
	return shiroFilterFactoryBean;
}
複製代碼

由於對登陸頁面作了一些樣式,新增了靜態資源文件static,這時候遇到了坑,頁面引用的jscss都無效了,而後發現時由於被攔截了,咱們須要在Shiro的攔截器中容許對靜態資源的匿名anon訪問。java

注意到將ShiroFilterFactoryBeanmap.put("/**", "authc");更改成map.put("/**", "user");user是指用戶認證經過或配置了RememberMe記住用戶登陸狀態後可訪問。jquery

解決過程查閱了一些資料,不光光只對cssjs的放開,還須要對static也放開git

對靜態資源的攔截相關問題能夠參照這裏瞭解學習一下:Spring Boot Shiro沒法訪問JS/CSS/IMG+自定義Filter沒法訪問完美方案github

回來繼續,調用SimpleCookie,配置Cookie的基本屬性:名稱和過時時間。ajax

/**
 * cookie對象
 * @return
 */
public SimpleCookie rememberMeCookie() {
	// 設置cookie名稱,對應login.html頁面的<input type="checkbox" name="rememberMe"/>
	SimpleCookie cookie = new SimpleCookie("rememberMe");
	// 設置cookie的過時時間,單位爲秒,這裏爲一天
	cookie.setMaxAge(86400);
	return cookie;
}
複製代碼

SimleCookie參數中的名稱爲頁面的name標籤屬性名稱。算法

實現了Cookie對象屬性配置,還須要經過CookieRememberMeManager進行管理起來。

/** * cookie管理對象 * rememberMeManager()方法是生成rememberMe管理器,並且要將這個rememberMe管理器設置到securityManager中 * @return */
public CookieRememberMeManager rememberMeManager() {
	CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager();
	cookieRememberMeManager.setCookie(rememberMeCookie());
	// rememberMe cookie加密的密鑰 建議每一個項目都不同 默認AES算法 密鑰長度(128 256 512 位)
	cookieRememberMeManager.setCipherKey(Base64.decode("3AvVhmFLUs0KTA3Kprsdag=="));
	return cookieRememberMeManager;
}
複製代碼

接下來將cookie管理對象設置到SecurityManager中:

@Bean
public SecurityManager securityManager() {
	DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
	// 設置realm
	securityManager.setRealm(authRealm());
	// 用戶受權/認證信息Cache, 採用EhC//注入記住我管理器
	securityManager.setRememberMeManager(rememberMeManager());
	return securityManager;
}
複製代碼

加密處理

《Spring Boot2(十二):手摸手教你搭建Shiro安全框架》這個項目中用的明文,這裏咱們升個級,使用MD5加密

新建MD5加密工具類。

public class MD5Utils {

    private static final String SALT = "niaobulashi";

    private static final String ALGORITH_NAME = "md5";

    private static final int HASH_ITERATIONS = 2;

    public static String encrypt(String pwd) {
        String newPassword = new SimpleHash(ALGORITH_NAME, pwd, ByteSource.Util.bytes(SALT), HASH_ITERATIONS).toHex();
        return newPassword;
    }

    public static String encrypt(String username, String pwd) {
        String newPassword = new SimpleHash(ALGORITH_NAME, pwd, ByteSource.Util.bytes(username + SALT),
                HASH_ITERATIONS).toHex();
        return newPassword;
    }
    
    public static void main(String[] args) {
        System.out.println("MD5加密後的密文爲:" + MD5Utils.encrypt("root", "root"));
    }
}
複製代碼

其中SALT是加密的鹽,可自行定義。

main方法中,根據登陸名和密碼明文,輸出最終加密的密文,將輸出內容粘貼到咱們的數據庫中,待後續登陸時使用。

新增登陸頁面和主頁面

登陸頁login.html

添加Remember Me checkbox

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>登陸</title>
    <link rel="stylesheet" th:href="@{/static/css/login.css}" type="text/css">
    <script th:src="@{/static/js/jquery-1.11.1.min.js}"></script>
</head>
<body>
<div class="login-page">
    <div class="form">
        <input type="text" placeholder="用戶名" name="account" required="required"/>
        <input type="password" placeholder="密碼" name="password" required="required"/>
        <p><input type="checkbox" name="rememberMe"/>記住我</p>
        <button onclick="login()">登陸</button>
    </div>
</div>
</body>
<script th:inline="javascript">var ctx = [[@{/}]];</script>
<script th:inline="javascript"> function login() { var account = $("input[name='account']").val(); var password = $("input[name='password']").val(); var rememberMe = $("input[name='rememberMe']").is(':checked'); $.ajax({ type: "post", url: ctx + "login", data: { "account": account, "password": password, "rememberMe": rememberMe }, success: function(r) { if (r.code == 100) { location.href = ctx + 'index'; } else { alert(r.message); } } }); } </script>
</html>
複製代碼

靜態資源js和css能夠在源碼中查看

登陸頁面

首頁index.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>首頁</title>
</head>
<body>
<p>你好![[${user.getUsername()}]]</p>
<a th:href="@{/logout}">註銷</a>
</body>
</html>
複製代碼

Controller層

在原來的基礎上,新增參數rememberMe,同時對用戶名和明文密碼進行MD5加密處理得到密文。

登陸接口

/** * 登陸操做 * @param account * @param password * @param rememberMe * @return */
@PostMapping("/login")
@ResponseBody
public ResponseCode login(String account, String password, Boolean rememberMe) {
	logger.info("登陸請求-start");
	password = MD5Utils.encrypt(account, password);
	Subject userSubject = SecurityUtils.getSubject();
	UsernamePasswordToken token = new UsernamePasswordToken(account, password, rememberMe);
	try {
		// 登陸驗證
		userSubject.login(token);
		return ResponseCode.success();
	} catch (UnknownAccountException e) {
		return ResponseCode.error(StatusEnums.ACCOUNT_UNKNOWN);
	} catch (DisabledAccountException e) {
		return ResponseCode.error(StatusEnums.ACCOUNT_IS_DISABLED);
	} catch (IncorrectCredentialsException e) {
		return ResponseCode.error(StatusEnums.INCORRECT_CREDENTIALS);
	} catch (AuthenticationException e) {
		return ResponseCode.error(StatusEnums.AUTH_ERROR);
	} catch (Throwable e) {
		e.printStackTrace();
		return ResponseCode.error(StatusEnums.SYSTEM_ERROR);
	}
}
複製代碼

註銷接口

/**
 * 登出
 * @return
 */
@GetMapping("/logout")
public String logout() {
	getSubject().logout();
	return "login";
}
複製代碼

啓動項目,進行測試能夠看到效果以下:

登陸操做

2、驗證碼Kaptcha

kaptcha 是一個很是實用的驗證碼生成工具。有了它,你能夠生成各類樣式的驗證碼,由於它是可配置的。kaptcha工做的原理是調用 com.google.code.kaptcha.servlet.KaptchaServlet,生成一個圖片。同時將生成的驗證碼字符串放到 HttpSession中。

Kaptcha官網:code.google.com/archive/p/k…

使用kaptcha能夠方便的配置:

  • 驗證碼的字體
  • 驗證碼字體的大小
  • 驗證碼字體的字體顏色
  • 驗證碼內容的範圍(數字,字母,中文漢字!)
  • 驗證碼圖片的大小,邊框,邊框粗細,邊框顏色
  • 驗證碼的干擾線(能夠本身繼承com.google.code.kaptcha.NoiseProducer寫一個自定義的干擾線)
  • 驗證碼的樣式(魚眼樣式、3D、普通模糊……固然也能夠繼承com.google.code.kaptcha.GimpyEngine自定義樣式)

kaptcha配置詳解

kaptcha對象屬性 做用 默認值
kaptcha.border 是否有邊框 默認爲true
kaptcha.border.color 邊框顏色 默認爲Color.BLACK
kaptcha.border.thickness 邊框粗細度 默認爲1
kaptcha.producer.impl 驗證碼生成器 默認爲DefaultKaptcha
kaptcha.textproducer.impl 驗證碼文本生成器 默認爲DefaultTextCreator
kaptcha.textproducer.char.string 驗證碼文本字符內容範圍 默認爲abcde2345678gfynmnpwx
kaptcha.textproducer.char.length 驗證碼文本字符長度 默認爲5
kaptcha.textproducer.font.names 驗證碼文本字體樣式 宋體,楷體,微軟雅黑,默認爲new Font("Arial", 1, fontSize), new Font("Courier", 1, fontSize)
kaptcha.textproducer.font.size 驗證碼文本字符大小 默認爲40
kaptcha.textproducer.font.color 驗證碼文本字符顏色 默認爲Color.BLACK
kaptcha.textproducer.char.space 驗證碼文本字符間距 默認爲2
kaptcha.noise.impl 驗證碼噪點生成對象 默認爲DefaultNoise
kaptcha.noise.color 驗證碼噪點顏色 默認爲Color.BLACK
kaptcha.obscurificator.impl 驗證碼樣式引擎 默認爲WaterRipple
kaptcha.word.impl 驗證碼文本字符渲染 默認爲DefaultWordRenderer
kaptcha.background.impl 驗證碼背景生成器 默認爲DefaultBackground
kaptcha.background.clear.from 驗證碼背景顏色漸進 默認爲Color.LIGHT_GRAY
kaptcha.background.clear.to 驗證碼背景顏色漸進 默認爲Color.WHITE
kaptcha.image.width 驗證碼圖片寬度 默認爲200
kaptcha.image.height 驗證碼圖片高度 默認爲50

添加maven依賴

<!--驗證碼-->
<dependency>
	<groupId>com.github.penggle</groupId>
	<artifactId>kaptcha</artifactId>
	<version>2.3.2</version>
</dependency>
複製代碼

新增驗證碼圖片樣式配置器

具體配置能夠參考上面的kaptche配置詳情,針對不一樣的常見配置。

@Configuration
public class KaptchaConfig {

    @Bean(name="captchaProducer")
    public DefaultKaptcha getKaptchaBean(){
        DefaultKaptcha defaultKaptcha=new DefaultKaptcha();
        Properties properties=new Properties();
        //驗證碼字符範圍
        properties.setProperty("kaptcha.textproducer.char.string", "23456789");
        //圖片邊框顏色
        properties.setProperty("kaptcha.border.color", "245,248,249");
        //字體顏色
        properties.setProperty("kaptcha.textproducer.font.color", "black");
        //文字間隔
        properties.setProperty("kaptcha.textproducer.char.space", "1");
        //圖片寬度
        properties.setProperty("kaptcha.image.width", "100");
        //圖片高度
        properties.setProperty("kaptcha.image.height", "35");
        //字體大小
        properties.setProperty("kaptcha.textproducer.font.size", "30");
        //session的key
        //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;
    }
}
複製代碼

新增圖片驗證碼Controller層

是一個建立文件圖片流的過程,使用ServletOutPutStream輸出最後的圖片。

開頭聲明的@Resource(name = "captchaProducer"),是驗證碼圖片樣式配置器啓動時配置的Bean:captchaProducer

@Controller
@RequestMapping("/captcha")
public class KaptchaController {

    private static final Logger logger = LoggerFactory.getLogger(KaptchaController.class);

    @Resource(name = "captchaProducer")
    private Producer captchaProducer;

    @GetMapping("/captchaImage")
    public ModelAndView getKaptchaImage(HttpServletRequest request, HttpServletResponse response) throws Exception {
        ServletOutputStream out = response.getOutputStream();
        try {
            HttpSession session = request.getSession();
            response.setDateHeader("Expires", 0);
            // Set standard HTTP/1.1 no-cache headers.
            response.setHeader("Cache-Control", "no-store, no-cache, must-revalidate");
            // Set IE extended HTTP/1.1 no-cache headers (use addHeader).
            response.addHeader("Cache-Control", "post-check=0, pre-check=0");
            // Set standard HTTP/1.0 no-cache header.
            response.setHeader("Pragma", "no-cache");
            // return a jpeg
            response.setContentType("image/jpeg");
            // create the text for the image
            String capText = captchaProducer.createText();
            //將驗證碼存到session
            session.setAttribute(Constants.KAPTCHA_SESSION_KEY, capText);
            logger.info(capText);
            // 建立一張文本圖片
            BufferedImage bi = captchaProducer.createImage(capText);
            // 響應
            out = response.getOutputStream();
            // 寫入數據
            ImageIO.write(bi, "jpg", out);

            out.flush();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (out != null) {
                    out.close();
                }
            }
            catch (IOException e) {
                e.printStackTrace();
            }
        }
        return null;
    }
}
複製代碼

注意最後都須要將流關閉out.close()

放開圖片驗證碼的攔截

重啓會發現,圖片驗證碼的接口請求沒法訪問,仍是跳轉到了localhost:8081/login登陸頁面

由於Shiro配置的攔截器沒有放開,須要再ShiroConfig中容許匿名訪問改請求資源

map.put("/captcha/captchaImage**", "anon");
複製代碼

登陸頁面添加圖片驗證碼

<div class="login-page">
    <div class="form">
        <input type="text" placeholder="用戶名" name="account" required="required"/>
        <input type="password" placeholder="密碼" name="password" required="required"/>
        <p>
            <label>驗證碼<br/>
                <input type="text" name="validateCode" id="validateCode" class="validateCode" required="required"/>
                <a href="javascript:void(0);">
                    <img src="/captcha/captchaImage" onclick="this.src='/captcha/captchaImage?'+Math.random()"/>
                </a>
            </label>
        </p>
        <br>
        <p><input type="checkbox" name="rememberMe"/>記住我</p>
        <button onclick="login()">登陸</button>
    </div>
</div>
複製代碼

上面div爲body的所有部分

我在請求/captcha/captchaImage後面添加隨機值Math.random()。是由於客戶瀏覽器會緩存URL相同的資源,故使用隨機數來從新請求。這和前端上線時,請求後綴都會變動一個版本號同樣,不須要讓客戶手動刷新瀏覽器就能夠獲取最新資源同樣。

驗證碼請求

修改登陸請求接口

主要是驗證後臺生成的驗證碼,與前臺輸入的驗證碼進行比較,驗證是否相同

這裏只粘貼出驗證碼驗證的邏輯,源碼在文章最後。

能夠看出validateCode是前端請求過來的參數,先校驗是否爲空。

而後從session中獲取後臺生成的驗證碼。

最後經過比較前端輸入的驗證碼和後臺生成的是否一致。

//一、檢驗驗證碼
if(validateCode == null || validateCode == ""){
	return ResponseCode.error(StatusEnums.PARAM_NULL);
}
Session session = SecurityUtils.getSubject().getSession();
//轉化成小寫字母
validateCode = validateCode.toLowerCase();
String v = (String) session.getAttribute(Constants.KAPTCHA_SESSION_KEY);
//還能夠讀取一次後把驗證碼清空,這樣每次登陸都必須獲取驗證碼
//session.removeAttribute("_come");
if(!validateCode.equals(v)){
	return ResponseCode.error(StatusEnums.VALIDATECODE_ERROR);
}
複製代碼

下圖是登陸校驗驗證碼的debug過程。

kaptcha驗證碼校驗

3、源碼

源碼地址:spring-boot-23-shiro-remember 歡迎star、fork,給做者一些鼓勵


菜鳥也要成爲架構師,一塊兒努力

歡迎關注我微信公衆號【鳥不拉屎】

謝謝,一塊兒學習,共同進步,成爲優秀的人

img
相關文章
相關標籤/搜索