天天看點

SpringBoot+Shiro學習之“記住我”和“GIF驗證碼”功能的實作

學習目标

如标題有如下兩個功能實作:

  • 記住我的功能:通過設定key為“rememberMe”的cookie儲存在用戶端來完成記住我的功能,下次使用者通路指定頁面時就不會重新登入,一直到cookie過期後才會重新登入。
  • GIF格式驗證碼:
    SpringBoot+Shiro學習之“記住我”和“GIF驗證碼”功能的實作
    ,這個要感謝sojson的部落客對這個GIF驗證碼插件的實作。

個人部落格:http://z77z.oschina.io/

此項目下載下傳位址:https://git.oschina.net/z77z/springboot_mybatisplus

記住我

  • ShiroConfig的配置:

在ShiroConfig.java中添加如下方法:

/**
 * cookie對象;
 * @return
 */
public SimpleCookie rememberMeCookie(){
   //這個參數是cookie的名稱,對應前端的checkbox的name = rememberMe
   SimpleCookie simpleCookie = new SimpleCookie("rememberMe");
   //<!-- 記住我cookie生效時間30天 ,機關秒;-->
   simpleCookie.setMaxAge();
   return simpleCookie;
}

/**
 * cookie管理對象;記住我功能
 * @return
 */
public CookieRememberMeManager rememberMeManager(){
   CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager();
   cookieRememberMeManager.setCookie(rememberMeCookie());
   //rememberMe cookie加密的密鑰 建議每個項目都不一樣 預設AES算法 密鑰長度(128 256 512 位)
   cookieRememberMeManager.setCipherKey(Base64.decode("3AvVhmFLUs0KTA3Kprsdag=="));
   return cookieRememberMeManager;
}
           

rememberMeCookie()方法是設定Cookie的生成模版,比如cookie的name,cookie的有效時間等等。

rememberMeManager()方法是生成rememberMe管理器,而且要将這個rememberMe管理器設定到securityManager中,如下:

@Bean
public SecurityManager securityManager() {
    DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
    // 設定realm.
    securityManager.setRealm(myShiroRealm());
    // 自定義緩存實作 使用redis
    securityManager.setCacheManager(cacheManager());
    // 自定義session管理 使用redis
    securityManager.setSessionManager(SessionManager());
    //注入記住我管理器;
    securityManager.setRememberMeManager(rememberMeManager());
    return securityManager;
}
           

其實上面的步驟,也就是rememberMe管理器可以不用配置,shiro會使用預設的配置,之是以要配置的目的是為了能夠在實際業務環境中自定義其中的參數。

  • 登入controller的改造
@RequestMapping(value="ajaxLogin",method=RequestMethod.POST)
@ResponseBody
public Map<String,Object> submitLogin(String username, String password,Boolean rememberMe,Model model) {
    Map<String, Object> resultMap = new LinkedHashMap<String, Object>();
    try {
        UsernamePasswordToken token = new UsernamePasswordToken(username, password,rememberMe);
        SecurityUtils.getSubject().login(token);
        resultMap.put("status", );
        resultMap.put("message", "登入成功");

    } catch (Exception e) {
        resultMap.put("status", );
        resultMap.put("message", e.getMessage());
    }
    return resultMap;
}
           

之前我是将shiro已經實作的UsernamePasswordToken類再封裝了一層,最後發現沒有必要,直接使用shiro提供的UsernamePasswordToken的類,其中有一個構造函數需要傳rememberMe這個參數,也就是shiro為我們已經實作好了,推薦大家去看下UsernamePasswordToken這個類的源碼。

  • jsp頁面的改造
<!DOCTYPE html>
<%@ page language="java" contentType="text/html; charset=utf-8"
    pageEncoding="utf-8"%>
<%
    String path = request.getContextPath();
    String basePath = request.getScheme() + "://"
            + request.getServerName() + ":" + request.getServerPort()
            + path;
%>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<script type="text/javascript"
    src="<%=basePath%>/static/js/jquery-1.11.3.js"></script>
<title>登入</title>
</head>
<body>
    錯誤資訊:
    <h4 id="erro"></h4>
    <form>
        <p>
            賬号:<input type="text" name="username" id="username" value="admin" />
        </p>
        <p>
            密碼:<input type="text" name="password" id="password" value="123" />
        </p>        
        <P><input type="checkbox" name="rememberMe"  id="rememberMe" />記住我</P>
        <p>
            <input type="button" id="ajaxLogin" value="登入" />
        </p>

    </form>
</body>
<script>
$(function(){
    $("#ajaxLogin").click(function() {
        var username = $("#username").val();
        var password = $("#password").val();
        var rememberMe =$('#rememberMe').is(':checked');
        $.post("/ajaxLogin", {
            "username" : username,
            "password" : password,
            "rememberMe" : rememberMe
        }, function(result) {
            if (result.status == ) {
                location.href = "/index";
            } else {
                $("#erro").html(result.message);
            }
        });
    });
});
</script>
</html>
           

添加一個記住我的單選框,來控制是否需要記住我。

  • 修改權限配置,修改sys_permission_init表
SpringBoot+Shiro學習之“記住我”和“GIF驗證碼”功能的實作

因為getGifCode是擷取驗證碼的連結,是以要配置為anon,不需要權限驗證。user權限是配置記住我或認證通過可以通路,是以将/**連結設定為user權限,就可以實作記住我的功能。

注意權限添加的排序。

GIF驗證碼

  • 編寫一個擷取GIF驗證碼圖檔的controller:
/**
 * 擷取驗證碼(Gif版本)
 * @param response
 */
@RequestMapping(value="getGifCode",method=RequestMethod.GET)
public void getGifCode(HttpServletResponse response,HttpServletRequest request){
    try {
        response.setHeader("Pragma", "No-cache");  
        response.setHeader("Cache-Control", "no-cache");  
        response.setDateHeader("Expires", );  
        response.setContentType("image/gif");  
        /**
         * gif格式動畫驗證碼
         * 寬,高,位數。
         */
        Captcha captcha = new GifCaptcha(,,);
        //輸出
        captcha.out(response.getOutputStream());
        HttpSession session = request.getSession(true);  
        //存入Session
        session.setAttribute("_code",captcha.text().toLowerCase());  
    } catch (Exception e) {
        System.err.println("擷取驗證碼異常:"+e.getMessage());
    }
}
           

生成驗證碼後,将圖檔傳回到頁面,将字元串儲存在目前會話的session域中。

這個GIF驗證碼的生成插件源碼在我的項目io.z77z.vcode這個包下面,大家需要的話可以在我的碼雲上去下載下傳。

  • 改造登入controller:
/**
 * ajax登入請求
 * @param username
 * @param password
 * @return
 */
@RequestMapping(value="ajaxLogin",method=RequestMethod.POST)
@ResponseBody
public Map<String,Object> submitLogin(String username, String password,String vcode,Boolean rememberMe,Model model) {
    Map<String, Object> resultMap = new LinkedHashMap<String, Object>();

    if(vcode==null||vcode==""){
        resultMap.put("status", );
        resultMap.put("message", "驗證碼不能為空!");
        return resultMap;
    }

    Session session = SecurityUtils.getSubject().getSession();
    //轉化成小寫字母
    vcode = vcode.toLowerCase();
    String v = (String) session.getAttribute("_code");
    //還可以讀取一次後把驗證碼清空,這樣每次登入都必須擷取驗證碼
    //session.removeAttribute("_come");
    if(!vcode.equals(v)){
        resultMap.put("status", );
        resultMap.put("message", "驗證碼錯誤!");
        return resultMap;
    }

    try {
        UsernamePasswordToken token = new UsernamePasswordToken(username, password,rememberMe);
        SecurityUtils.getSubject().login(token);
        resultMap.put("status", );
        resultMap.put("message", "登入成功");

    } catch (Exception e) {
        resultMap.put("status", );
        resultMap.put("message", e.getMessage());
    }
    return resultMap;
}
           

登入的時候判斷前台傳入的驗證碼和session中的是否一緻。

  • 前端jsp頁面改造
<!DOCTYPE html>
<%@ page language="java" contentType="text/html; charset=utf-8"
    pageEncoding="utf-8"%>
<%
    String path = request.getContextPath();
    String basePath = request.getScheme() + "://"
            + request.getServerName() + ":" + request.getServerPort()
            + path;
%>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<script type="text/javascript"
    src="<%=basePath%>/static/js/jquery-1.11.3.js"></script>
<title>登入</title>
</head>
<body>
    錯誤資訊:
    <h4 id="erro"></h4>
    <form>
        <p>
            賬号:<input type="text" name="username" id="username" value="admin" />
        </p>
        <p>
            密碼:<input type="text" name="password" id="password" value="123" />
        </p>
        <p>
            驗證碼:<input type="text" name="vcode" id="vcode"/>
        </p>
        <p>
            <img alt="驗證碼" src="/getGifCode">
        </p>

        <P><input type="checkbox" name="rememberMe"  id="rememberMe" />記住我</P>
        <p>
            <input type="button" id="ajaxLogin" value="登入" />
        </p>

    </form>
</body>
<script>
$(function(){
    $("#ajaxLogin").click(function() {
        var username = $("#username").val();
        var password = $("#password").val();
        var vcode = $("#vcode").val();
        var rememberMe =$('#rememberMe').is(':checked');
        $.post("/ajaxLogin", {
            "username" : username,
            "password" : password,
            "vcode" : vcode,
            "rememberMe" : rememberMe
        }, function(result) {
            if (result.status == ) {
                location.href = "/index";
            } else {
                $("#erro").html(result.message);
            }
        });
    });
});
</script>
</html>
           

總結:

到此,我們內建shiro和redis,學習了一下功能的實作:

  1. 使用者必須要登陸之後才能通路定義連結,否則跳轉到登入頁面,被禁使用者不能登入。并且對一些敏感操作連結設定權限,隻有滿足權限的才可以通路。
  2. 每個連結的權限資訊儲存在資料庫,可以動态進行設定,并且熱加載權限。
  3. 使用redis對shiro的使用者資訊進行緩存,不用每次都去執行MyShiroRealm.doGetAuthorizationInfo()權限認證方法。
  4. 之前有很多同學下載下傳我的項目時,運作會報錯,那是因為最近都在不斷修改送出,有可能會出現版本問題,現在我在我的碼雲上面建立了stable_version分支,都是可以跑起來的。sqltable放在resource目錄下面。
  5. “記住我”的功能,利用cookie。
  6. GIF驗證碼的生成,在登陸時進行驗證碼的校驗。利用session。
  7. 下一博,我應該會寫對線上使用者的管理,踢出登入的功能學習記錄。

香蕉硬币點贊走一波啦。。。。。。

繼續閱讀