springboot + shiro 驗證碼與記住登入

gcyml發表於2019-04-23

原始碼專案地址

驗證碼實現

關於kaptcha

kaptcha 是一個很有用的驗證碼生成工具。有了它,你能夠生成各種樣式的驗證碼,由於它是可配置的。使用kaptcha能夠方便的配置:

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

maven依賴

<dependency>
	<groupId>com.github.penggle</groupId>
	<artifactId>kaptcha</artifactId>
	<version>2.3.2</version>
</dependency>		
複製程式碼

注入驗證碼Servlet

** KaptchaConfig.java **

@Component
public class KaptchaConfig {
    @Bean
    public ServletRegistrationBean<KaptchaServlet> kaptchaServlet() {

        ServletRegistrationBean<KaptchaServlet> registrationBean = new ServletRegistrationBean<>(new KaptchaServlet(), "/captcha/kaptcha.jpg");

        registrationBean.addInitParameter(Constants.KAPTCHA_SESSION_CONFIG_KEY,
                Constants.KAPTCHA_SESSION_KEY);
        //寬度
        registrationBean.addInitParameter(Constants.KAPTCHA_IMAGE_WIDTH,"140");
        //高度
        registrationBean.addInitParameter(Constants.KAPTCHA_IMAGE_HEIGHT,"60");
        //字型大小
        registrationBean.addInitParameter(Constants.KAPTCHA_TEXTPRODUCER_FONT_SIZE,"50");
//        registrationBean.addInitParameter(Constants.KAPTCHA_BORDER_THICKNESS,"1"); //邊框
        //無邊框
        registrationBean.addInitParameter(Constants.KAPTCHA_BORDER,"no");
        //文字顏色
        registrationBean.addInitParameter(Constants.KAPTCHA_TEXTPRODUCER_FONT_COLOR, "blue");
        //長度
        registrationBean.addInitParameter(Constants.KAPTCHA_TEXTPRODUCER_CHAR_LENGTH, "4");
        //字元間距
        registrationBean.addInitParameter(Constants.KAPTCHA_TEXTPRODUCER_CHAR_SPACE, "6");

        //可以設定很多屬性,具體看com.google.code.kaptcha.Constants
//      kaptcha.border  是否有邊框  預設為true  我們可以自己設定yes,no
//      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
        return registrationBean;
    }
}
複製程式碼

在這裡我們注入了一個連結為“/captcha/kaptcha.jpg”的servlet。點選執行專案開啟連結如果看到驗證碼圖片,則說明配置成功了。

驗證碼攔截器

** CaptchaValidateFilter.java **

ublic class CaptchaValidateFilter extends AccessControlFilter {
    private String captchaParam = "captchaCode"; //前臺提交的驗證碼引數名  
    
    private String failureKeyAttribute = "shiroLoginFailure";  //驗證失敗後儲存到的屬性名  
    
    public String getCaptchaCode(ServletRequest request) {
    	return WebUtils.getCleanParam(request, getCaptchaParam());
    }
    
	@Override
	protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue)
			throws Exception {
	    // 從session獲取正確的驗證碼
	  	Session session = SecurityUtils.getSubject().getSession();
	    //頁面輸入的驗證碼
	    String captchaCode = getCaptchaCode(request);
	    String validateCode = (String)session.getAttribute(Constants.KAPTCHA_SESSION_KEY);
	    
	    HttpServletRequest httpServletRequest = WebUtils.toHttp(request);  
	    //判斷驗證碼是否表單提交(允許訪問)  
        if ( !"post".equalsIgnoreCase(httpServletRequest.getMethod())) {  
            return true;  
        } 
        
        // 若驗證碼為空或匹配失敗則返回false
	    if(captchaCode == null) {
	    	return false;
	    } else if (validateCode != null) {
	    	captchaCode = captchaCode.toLowerCase();
	    	validateCode = validateCode.toLowerCase();
	        if(!captchaCode.equals(validateCode)) {
	        	return false;
	        }
	    }
	    return true;
	}

	@Override
	protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
		 //如果驗證碼失敗了,儲存失敗key屬性  
        request.setAttribute(failureKeyAttribute, "驗證碼錯誤");  
        return true;  
	}

	public String getCaptchaParam() {
		return captchaParam;
	}

	public void setCaptchaParam(String captchaParam) {
		this.captchaParam = captchaParam;
	}
}
複製程式碼

驗證碼攔截器繼承了AccessControlFilter,該類提供了訪問控制的基礎功能,比如是否允許訪問/當訪問拒絕時如何處理等。主要有兩個方法:

  • isAccessAllowed:表示是否允許訪問;mappedValue就是[urls]配置中攔截器引數部分,如果允許訪問返回true,否則false;
  • onAccessDenied:表示當訪問拒絕時是否已經處理了;如果返回true表示需要繼續處理;如果返回false表示該攔截器例項已經處理了,將直接返回即可

修改ShiroConfig.java

public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager);

        //攔截器.
        Map<String,String> filterChainDefinitionMap = new LinkedHashMap<>();
        // 配置不會被攔截的連結 順序判斷

        filterChainDefinitionMap.put("/css/**", "anon");
        filterChainDefinitionMap.put("/js/**", "anon");
        filterChainDefinitionMap.put("/img/**", "anon");
        filterChainDefinitionMap.put("/layui/**", "anon");
        filterChainDefinitionMap.put("/captcha/**", "anon");
        filterChainDefinitionMap.put("/favicon.ico", "anon");

        //配置退出 過濾器,其中的具體的退出程式碼Shiro已經替我們實現了
        filterChainDefinitionMap.put("/logout", "logout");
        //<!-- 過濾鏈定義,從上向下順序執行,一般將/**放在最為下邊 -->:這是一個坑呢,一不小心程式碼就不好使了;
        //<!-- authc:所有url都必須認證通過才可以訪問; anon:所有url都都可以匿名訪問; user”表示訪問該地址的使用者是身份驗證通過或RememberMe登入的都可以-->
        filterChainDefinitionMap.put("/add", "perms[add]");
        filterChainDefinitionMap.put("/login", "captchaVaildate,authc");

        filterChainDefinitionMap.put("/**", "user");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
        
        // 如果不設定預設會自動尋找Web工程根目錄下的"/login.jsp"頁面
        shiroFilterFactoryBean.setLoginUrl("/login");
        // 登入成功後要跳轉的連結
        shiroFilterFactoryBean.setSuccessUrl("/index");
        //未授權介面;
        shiroFilterFactoryBean.setUnauthorizedUrl("/403");

        //自定義攔截器
        Map<String, Filter> filters = shiroFilterFactoryBean.getFilters();
        filters.put("captchaVaildate", new CaptchaValidateFilter());
        filters.put("authc", new MyFormAuthenticationFilter());
        return shiroFilterFactoryBean;
    }
複製程式碼

在表單驗證攔截器前加入驗證碼攔截器

記住登入實現

ShiroConfig的配置

在ShiroConfig.java中新增如下方法:

    public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) {
        ......
        shiroFilterFactoryBean.setSecurityManager(securityManager);
shiroFilterFactoryBean.setSecurityManager(securityManager);
        .....
}
  /**
     * 安全管理器
     * @return securityManager
     */
    @Bean
    public SecurityManager securityManager(){
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(myShiroRealm());
        
        //注入記住我管理器
        securityManager.setRememberMeManager(rememberMeManager());
        return securityManager;
    }
    
    /**
     * cookie物件;
     * rememberMeCookie()方法是設定Cookie的生成模版,比如cookie的name,cookie的有效時間等等。
     * @return rememberMeCookie
     */
    @Bean
    public SimpleCookie rememberMeCookie(){
        //這個引數是cookie的名稱,對應前端的checkbox的name = rememberMe
        SimpleCookie simpleCookie = new SimpleCookie("rememberMe");
        //<!-- 記住我cookie生效時間30天 ,單位秒;-->
        simpleCookie.setMaxAge(30*24*60*60);
        simpleCookie.setHttpOnly(true);
        return simpleCookie;
    }
    
    /**
     * cookie管理物件;
     * rememberMeManager()方法是生成rememberMe管理器,而且要將這個rememberMe管理器設定到securityManager中
     * @return rememberMeManager
     */
    @Bean
    public CookieRememberMeManager rememberMeManager(){
        CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager();
        cookieRememberMeManager.setCookie(rememberMeCookie());
        //rememberMe cookie加密的金鑰 建議每個專案都不一樣 預設AES演算法 金鑰長度(128 256 512 位)
        cookieRememberMeManager.setCipherKey(Base64.decode("3AvVhmFLUs0KTA3Kprsdag=="));
        return cookieRememberMeManager;
    }

......
複製程式碼

login頁面

<!DOCTYPE html>
<html  lang="en" class="no-js" xmlns:th="http://www.thymeleaf.org">
<head>
	<meta charset="utf-8"/>
	<title>登入--layui後臺管理模板</title>
	<link rel="stylesheet" href="../../layui/css/layui.css" media="all" />

	<link rel="stylesheet" href="../css/login.css" media="all" />
</head>
<body>
<div class="login">
	<h1>layuiCMS-管理登入</h1>
	<form class="layui-form" method="post">
		<div class="layui-form-item">
			<input class="layui-input" name="username" placeholder="使用者名稱" type="text" autocomplete="off"/>
		</div>
		<div class="layui-form-item">
			<input class="layui-input" name="password" placeholder="密碼"  type="password" autocomplete="off"/>
		</div>
		<div class="layui-form-item form_code">
			<input class="layui-input"  name="captchaCode" placeholder="驗證碼" lay-verify="required" type="text" autocomplete="off"/>
			<div>
				<img type="image" src="../captcha/kaptcha.jpg" id="codeImage" onclick="chageCode()" title="圖片看不清?點選重新得到驗證碼" style="cursor:pointer;" width="116" height="36"/>
			</div>
		</div>
		<div class="layui-form-item">
			<input type="checkbox" name="rememberMe" title="記住我" lay-skin="primary"/>
		</div>

		<button class="layui-btn login_btn" lay-submit="" lay-filter="login">登入</button>
	</form>
</div>
<script type="text/javascript" src="../layui/layui.js"></script>

<script th:inline="javascript">

layui.use(['layer'], function(){
    var layer = layui.layer;
    var message = [[${shiroLoginFailure}]]?[[${shiroLoginFailure}]]:getUrlPara("shiroLoginFailure");
    if(message) {
    	layer.msg(message);
    }        	
});

function getUrlPara(name)
{
    var url = document.location.toString();
    var arrUrl = url.split("?"+name +"=");
    var para = arrUrl[1];
    console.log(para);
    if(para)
    	return decodeURI(para);
}
function chageCode(){
    document.getElementById("codeImage").src="../captcha/kaptcha.jpg?"+Math.random();
}

</script>
</body>
</html>
複製程式碼

相關文章