1. 程式人生 > >Spring Boot2(十五):Shiro記住我rememberMe、驗證碼Kaptcha

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,這時候遇到了坑,頁面引用的jscss都無效了,然後發現時因為被攔截了,我們需要在Shiro的攔截器中允許對靜態資源的匿名anon訪問。

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

解決過程查閱了一些資料,不光光只對cssjs的放開,還需要對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