Spring Boot2(十五):Shiro記住我rememberMe、驗證碼Kaptcha
接著上次學習的《Spring Boot2(十二):手摸手教你搭建Shiro安全框架》,實現了Shiro的認證和授權。今天繼續在這個基礎上學習Shiro實現功能記住我rememberMe,以及登入時驗證碼Kaptcha。
Remember Me記住我:使用者的登入狀態會不會因為瀏覽器的關閉而失效,直到Cookie失效。關閉瀏覽器後,再次訪問登入後的頁面可以不用登入。因為用Cookie實現,故只在同一瀏覽器中有效。
Kaptcha驗證碼:是谷歌開源的驗證碼外掛,實現登入的驗證碼驗證攔截。
一、記住我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,這時候遇到了坑,頁面引用的js
和css
都無效了,然後發現時因為被攔截了,我們需要在Shiro的攔截器中允許對靜態資源的匿名anon
訪問。
注意到將ShiroFilterFactoryBean
的map.put("/**", "authc");
更改為map.put("/**", "user");
user是指使用者認證通過或配置了RememberMe記住使用者登入狀態後可訪問。
解決過程查閱了一些資料,不光光只對css
和js
的放開,還需要對static
也放開
對靜態資源的攔截相關問題可以參照這裡瞭解學習一下:Spring Boot Shiro無法訪問JS/CSS/IMG+自定義Filter無法訪問完美方案
回來繼續,呼叫SimpleCookie,配置Cookie的基本屬性:名稱和過期時間。
/**
* 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";
}
啟動專案,進行測試可以看到效果如下:
二、驗證碼Kaptcha
kaptcha 是一個非常實用的驗證碼生成工具。有了它,你可以生成各種樣式的驗證碼,因為它是可配置的。kaptcha工作的原理是呼叫 com.google.code.kaptcha.servlet.KaptchaServlet,生成一個圖片。同時將生成的驗證碼字串放到 HttpSession中。
Kaptcha官網:https://code.google.com/archive/p/kaptcha/
使用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中獲取後臺生成的驗證碼。
最後通過比較前端輸入的驗證碼和後臺生成的是否一致。
//1、檢驗驗證碼
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過程。
三、原始碼
原始碼地址:spring-boot-23-shiro-remember
歡迎star、fork,給作者一些鼓勵
菜鳥也要成為架構師,一起努力
歡迎關注我微信公眾號【鳥不拉屎】
謝謝,一起學習,共同進步,成為優秀的人。
相關推薦
Spring Boot2(十五):Shiro記住我rememberMe、驗證碼Kaptcha
接著上次學習的《Spring Boot2(十二):手摸手教你搭建Shiro安全框架》,實現了Shiro的認證和授權。今天繼續在這個基礎上學習Shiro實現功能記住我rememberMe,以及登入時驗證碼Kaptcha。 Remember Me記住我:使用者的登入狀態會不會因為瀏覽器的關閉而失效,直到Cooki
Spring Boot2(十二):手摸手教你搭建Shiro安全框架
一、前言 SpringBoot+Shiro+Mybatis完成的。 之前看了一位小夥伴的Shiro教程,跟著做了,遇到蠻多坑的(´இ皿இ`) 修改整理了一下,成功跑起來了。可以通過postman進行測試 不多比比∠( ᐛ 」∠)_,直接上原始碼:https://github.com/niaobulashi/s
Spring Boot(十五):spring boot+jpa+thymeleaf增刪改查示例
app 配置文件 quest 重啟 fin nbu 生產 prot html Spring Boot(十五):spring boot+jpa+thymeleaf增刪改查示例 一、快速上手 1,配置文件 (1)pom包配置 pom包裏面添加jpa和thymeleaf的相關包引
Spring Boot2(十一):Mybatis使用總結(自增長、多條件、批量操作、多表查詢等等)
一、前言 上次用Mybatis還是2017年做專案的時候,已經很久過去了。中途再沒有用過Mybatis。導致現在學習SpringBoot過程中遇到一些Mybatis的問題,以此做出總結(XML極簡模式)。當然只是實用方面的總結,具體就不深究♂了。這裡只總結怎麼用!!! (這次直接跳到十一,是因為中間是Rabb
Spring入門(十五):使用Spring JDBC操作資料庫
在本系列的之前部落格中,我們從沒有講解過操作資料庫的方法,但是在實際的工作中,幾乎所有的系統都離不開資料的持久化,所以掌握操作資料庫的使用方法就非常重要。 在Spring中,操作資料庫有很多種方法,我們可以使用JDBC、Hibernate、MyBatis或者其他的資料持久化框架,本篇部落格的重點是講解下在Sp
Spring Security 實戰乾貨:從零手寫一個驗證碼登入
![](https://img2020.cnblogs.com/other/1739473/202007/1739473-20200727090335562-1694643164.png) ## 1. 前言 前面關於**Spring Security**寫了兩篇文章,一篇是介紹[UsernamePassw
十五:Spring Cloud 之Eureka服務註冊中心(HA版)
1. Eureka簡介 2. 程式碼實現 2.1涉及的模組 eureka-server-ha:通過profiles指定不同的埠來模擬多服務例項。 eureka-service:服務提供者 2.2
Spring Boot教程十五:多執行緒
實際的開發應該開發過程中,經常需要使用到多執行緒,而且大多時候需要獲取到每個執行緒執行的結果,然後再執行剩下的業務邏輯。具體實現如下; pom檔案引用: <dependency> <groupId>com.go
springcloud(十五):Spring Cloud 終於按捺不住推出了自己的服務閘道器 Gateway
Spring 官方最終還是按捺不住推出了自己的閘道器元件:Spring Cloud Gateway ,相比之前我們使用的 Zuul(1.x) 它有哪些優勢呢?Zuul(1.x) 基於 Servlet,使用阻塞 API,它不支援任何長連線,如 WebSockets,Spring Cloud Gateway 使用
Spring Security原始碼分析十五:Spring Security 頁面許可權控制
Spring Security是一個能夠為基於Spring的企業應用系統提供宣告式的安全訪問控制解決方案的安全框架。它提供了一組可以在Spring應用上下文中配置的Bean,充分利用了Spring IoC,DI(控制反轉Inversion of Contr
Spring Boot 2.X(十五):整合 Swagger2 開發 API 文件(線上+離線)
前言 相信很多後端開發在專案中都會碰到要寫 api 文件,不管是給前端、移動端等提供更好的對接,還是以後為了以後交接方便,都會要求寫 api 文件。 而手寫 api 文件的話有諸多痛點: 文件更新的時候,需要再次傳送給對接人 介面太對,手寫文件很難管理 介面返回的結果不明確 不能直接線上測試介面,通常需要使
UFLDL講義十五:從自我學習到深層網絡
學習課程 機器學習 .cn font style pan 學習 技術 nbsp 註:本講義來源為NG教授的機器學習課程講義,詳見 http://deeplearning.stanford.edu/wiki/index.php UFLDL講義十五:從自我學習到深層
[Python爬蟲] 之十五:Selenium +phantomjs根據微信公眾號抓取微信文章
頭部 drive lac 過程 標題 操作 函數 軟件測試 init 借助搜索微信搜索引擎進行抓取 抓取過程 1、首先在搜狗的微信搜索頁面測試一下,這樣能夠讓我們的思路更加清晰 在搜索引擎上使用微信公眾號英文名進行“搜公眾號&r
springboot(十五):springboot+jpa+thymeleaf增刪改查示例
pen 其中 底層原理 protect roo back styles ttr 喜歡 這篇文章介紹如何使用jpa和thymeleaf做一個增刪改查的示例。 先和大家聊聊我為什麽喜歡寫這種腳手架的項目,在我學習一門新技術的時候,總是想快速的搭建起一個demo來試試它的效果,越
十五:階乘計算
windows.h clas -- sca 算法 turn str scanf 可能 題目:階乘計算 輸入一個正整數n,輸出n!的值。 其中n!=1*2*3*…*n。算法描述 n!可能很大,而計算機能表示的整數範圍有限,需要使用高精度計算的方法。使用一個數組A來表示一個
二十五: 藍橋杯-蘭頓螞蟻
pre 任務 開始 style post -s wid 輸出數據 數字 問題:蘭頓螞蟻 問題描述 蘭頓螞蟻,是於1986年,由克裏斯·蘭頓提出來的,屬於細胞自動機的一種。 平面上的正方形格子被填上黑色或白色。在其中一格正方形內有一只“螞蟻”。 螞蟻的頭部朝向為:上下
Hulu機器學習問題與解答系列 | 十五:多層感知機與布爾函數
功能 目標 機器學習 分享圖片 研究 vue gic per 發展 今天沒有別的話,好好學習,多多轉發! 本期內容是 【多層感知機與布爾函數】 場景描述 神經網絡概念的誕生很大程度上受到了神經科學的啟發。生物學研究表明,大腦皮層的感知與計算功能是通過分多層實現的
C++筆記十五:C++對C的擴展——三目運算符功能增強
str std CP c++編譯 CI res 數字 return namespace 三目運算符在C編譯器中的表現: int main() { int a=10; int b=20; //三目運算符是一個表達式,表達式不能做左值 (a<b?a:b)=30; pr
python接口自動化測試二十五:執行所有用例,並生成HTML測試報告
odin 所有 郵件發送 QQ 二進制 multipart 分享圖片 html sse import requestsimport unittestclass TestQQ(unittest.TestCase): ‘‘‘測試QQ號接口‘‘‘
spring boot(十五)spring boot+thymeleaf+jpa增刪改查示例
ali 遍歷 config link examples 技術分享 返回 stat 業務 快速上手 配置文件 pom包配置 pom包裏面添加jpa和thymeleaf的相關包引用 <dependency> <groupId>org.sprin