SSM整合系列之 基於Shiro框架實現自動登入(RememberMe)
一、前言:Shiro框架提供了記住我(RememerMe)的功能,比如我們訪問一些網站,關閉了瀏覽器,下次再開啟還是能記住你是誰,下次訪問的時候無需登入即可訪問,本文將實現記住我的功能。
專案git地址:https://github.com/gitcaiqing/SSM_DEMO.git
二、大概流程:
1.首先在登入頁面中選中RememerMe,然後登入成功,如果是瀏覽器登入,會把RememberMe的Cookie寫到客戶端儲存下來;
2.關閉瀏覽器重新開啟時;會發現瀏覽器仍然能記住;
3.訪問一般的網頁服務,服務端知道你是誰,且能夠正常訪問;
4.但如果我們訪問一些特殊的敏感資訊,如檢視訂單或者訂單支付之類的操作,還是需要再次進行身份認證的,以保證當前使用者還是你。
三、瞭解下登入認證和記住我的區別
在上一篇文章中,講解了登入認證的簡單實現,部落格地址:https://blog.csdn.net/caiqing116/article/details/84637699 我們使用瞭如 subject.isAuthenticated()來驗證使用者是否進行了身份驗證,然後使用Subject.login進行登入,而subject.isRemembered()表示使用者是通過記住我登入的,此時可能並不真正的你(可能是別人使用了你的電腦,或者你的cookie被竊取等等)在訪問。需要注意的是登入認證和記住我只能二選一,即subject.isAuthenticated() 和 subject.isRemembered()互斥。
四、具體實現
整個專案是基於之前的文章,請參考其他文章搭建整體專案,這裡不重新搭建了
(1)登入頁面和主頁簡單修改
修改內容:
①登入頁如果是登入認證或記住我為真,直接跳轉到主頁,
②登入頁新增記住我checkbox,如果選中傳值true
③主頁新增登入認證進入還是記住我進入的展示
login.jsp
<%@page import="com.ssm.entity.BasicUser"%> <%@page import="org.apache.shiro.SecurityUtils"%> <%@page import="org.apache.shiro.subject.Subject"%> <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <%@ include file="/WEB-INF/common/taglib.jsp"%> <% //如果登入認證或記住我,則直接跳轉到主頁 Subject subject = SecurityUtils.getSubject(); if(subject.isAuthenticated() || subject.isRemembered()){ response.sendRedirect(request.getContextPath()+"/ssm/home"); } %> <html> <head> <title>登陸</title> <meta name="viewport" content="width=device-width, initial-scale=1"> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <meta name="keywords" content="" /> <script type="application/x-javascript"> addEventListener("load", function() { setTimeout(hideURLbar, 0); }, false); function hideURLbar(){ window.scrollTo(0,1); } </script> <!-- Bootstrap Core CSS --> <link href="${base }/static/css/bootstrap.min.css" rel='stylesheet' type='text/css' /> <!-- Custom CSS --> <link href="${base }/static/css/style.css" rel='stylesheet' type='text/css' /> <link rel="stylesheet" href="${base }/static/css/morris.css" type="text/css"/> <!-- Graph CSS --> <link href="${base }/static/css/font-awesome.css" rel="stylesheet"> <link rel="stylesheet" href="${base }/static/css/jquery-ui.css"> <!-- jQuery --> <script src="${base }/static/js/jquery-2.1.4.min.js"></script> <!-- //jQuery --> <link href='http://fonts.googleapis.com/css?family=Roboto:700,500,300,100italic,100,400' rel='stylesheet' type='text/css'/> <link href='http://fonts.googleapis.com/css?family=Montserrat:400,700' rel='stylesheet' type='text/css'> <!-- lined-icons --> <link rel="stylesheet" href="${base }/static/css/icon-font.min.css" type='text/css' /> <!-- //lined-icons --> </head> <body> <div class="main-wthree"> <div class="container"> <div class="sin-w3-agile"> <h2>Sign In</h2> <form id="form" action="#" method="post"> <div class="username"> <span class="username">賬號:</span> <input type="text" name="username" class="name" placeholder="" required=""> <div class="clearfix"></div> </div> <div class="password-agileits"> <span class="username">密碼:</span> <input type="password" name="password" class="password" placeholder="" required=""> <div class="clearfix"></div> </div> <div class="rem-for-agile"> <input type="checkbox" name="rememberMe" class="remember" value="true">記住我<br> <!-- <a href="#">忘記密碼</a><br> --> </div> <div class="login-w3"> <input type="button" class="login" value="登陸" onclick="login()"> </div> <div class="clearfix"></div> </form> <div class="back"> <a href="https://blog.csdn.net/caiqing116" target="_blank">去主人部落格</a> </div> <div class="footer"> <p>© 2018 Design by <a href="https://blog.csdn.net/caiqing116" target="_blank">https://blog.csdn.net/caiqing116</a></p> </div> </div> </div> </div> </body> <script type="text/javascript"> function login(){ $.post("${base}/ssm/shirologin",$("#form").serialize(),function(data){ if(data.resultCode == 0){ window.location.href = "${base}/ssm/home"; }else{ alert(data.msg); } }) } </script> </html>
views/home.jsp
<%@page import="org.apache.shiro.SecurityUtils"%>
<%@page import="org.apache.shiro.subject.Subject"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<%@ include file="/WEB-INF/common/taglib.jsp"%>
<%
//如果登陸成功,則直接跳轉到主頁
Subject subject = SecurityUtils.getSubject();
%>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<h1>登陸成功</h1>
<h2>
<input type="button" value="退出" onclick='window.location.href="${base}/ssm/shirologout"'>
</h2>
<h2>是否【登陸認證】訪問:<%=subject.isAuthenticated() %></h2>
<h2>是否【記住我】訪問:<%=subject.isRemembered() %></h2>
</body>
</html>
(2)登入認證實現
我們在登入的方法中接收rememberMe引數,如果為true,則說明選中了記住我,我們就可以設定usernamePasswordToken.setRememberMe(rememberMe) 這裡我們對src/main/java/com/ssm/security/LoginHandler.java的方法shirologin進行修改
@RequestMapping("/shirologin")
@ResponseBody
public ResultModel shirologin(String username, String password, boolean rememberMe) {
try {
Subject currentUser = SecurityUtils.getSubject();
//未認證登入
if(!currentUser.isAuthenticated()) {
//密碼進行MD5加密
UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(username, EncryptKit.MD5(password));
//是否記住我
usernamePasswordToken.setRememberMe(rememberMe);
//認證登陸
currentUser.login(usernamePasswordToken);
}
} catch (AuthenticationException e) {
if(e instanceof AccountException) {
return new ResultModel(1, "賬號或密碼錯誤");
}
}
return new ResultModel(0, "登陸成功");
}
(3)修改maxAge記住我的有效時間,單位為秒
在上面我們只是設定了記住我的功能,我們猜測記住我的時長肯定有個預設值,但是我們想要修改這個時間,該如何操作,首先我們來看下設定有效時長的原始碼
public class CookieRememberMeManager extends AbstractRememberMeManager {
//TODO - complete JavaDoc
private static transient final Logger log = LoggerFactory.getLogger(CookieRememberMeManager.class);
/**
* The default name of the underlying rememberMe cookie which is {@code rememberMe}.
*/
public static final String DEFAULT_REMEMBER_ME_COOKIE_NAME = "rememberMe";
private Cookie cookie;
/**
* Constructs a new {@code CookieRememberMeManager} with a default {@code rememberMe} cookie template.
*/
public CookieRememberMeManager() {
Cookie cookie = new SimpleCookie(DEFAULT_REMEMBER_ME_COOKIE_NAME);
cookie.setHttpOnly(true);
//One year should be long enough - most sites won't object to requiring a user to log in if they haven't visited
//in a year:
cookie.setMaxAge(Cookie.ONE_YEAR);
this.cookie = cookie;
}
....
}
從中我們可以知道預設時長為一年:
public static final int ONE_YEAR = 60 * 60 * 24 * 365;
cookie.setMaxAge(Cookie.ONE_YEAR);
我們來手動配置修改這個值為30s,如下
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.1.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.1.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.1.xsd"
default-lazy-init="true">
<description>Spring Shiro整合配置檔案</description>
<!--1. 配置securityManager安全管理器 -->
<!--
SecurityManager:安全管理器;即所有與安全有關的操作都會與SecurityManager互動;
且它管理著所有Subject;可以看出它是Shiro 的核心,它負責與後邊介紹的其他元件進行互動
-->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="realm" ref="shiroDbRealm" />
<property name="rememberMeManager.cookie.maxAge" value="30"/>
</bean>
<!--2. 配置 CacheManager. 2.1需要加入 ehcache 的 jar 包及配置檔案. -->
<bean id="cacheManager" class="org.apache.shiro.cache.MemoryConstrainedCacheManager"> </bean>
<!--3.配置realm 自定義的Realm-->
<!--
Shiro 從Realm獲取安全資料(如使用者、角色、許可權),就是說SecurityManager要驗證使用者身份,
那麼它需要從Realm獲取相應的使用者進行比較以確定使用者身份是否合法;
也需要從Realm得到使用者相應的角色/許可權進行驗證使用者是否能進行操作;
可以把Realm看成DataSource , 即安全資料來源
-->
<bean id="shiroDbRealm" class="com.ssm.security.ShiroRealm"></bean>
<!--4.配置lifecycleBeanPostProcessor,可以自動呼叫spring ioc 容器中的shiro bean 的生命週期方法 -->
<!-- 開啟Shiro註解的Spring配置方式的beans。在lifecycleBeanPostProcessor之後執行 -->
<bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor" />
<!--5. 啟用 IOC 容器中使用 shiro 的註解. 但必須在配置了 LifecycleBeanPostProcessor 之後才可以使用. -->
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"
depends-on="lifecycleBeanPostProcessor">
<property name="proxyTargetClass" value="true" />
</bean>
<!-- Shiro Filter id值和web.xml檔案配置的過濾器名稱相同 -->
<bean id="shiroFilter"
class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<property name="securityManager" ref="securityManager" />
<!-- 登入頁面 -->
<property name="loginUrl" value="/login.jsp" />
<!-- 登入成功頁面 -->
<property name="successUrl" value="/WEB-INF/views/home.jsp"/>
<!-- 沒有許可權的頁面 -->
<!-- <property name="unauthorizedUrl" value="/unauthorized.jsp"/> -->
<property name="filters">
<map>
<entry key="user" value-ref="sysUserFilter"/>
</map>
</property>
<!--
配置哪些頁面需要受保護.
以及訪問這些頁面需要的許可權.
1). anon 可以被匿名訪問
2). authc 必須認證(即登入)後才可能訪問的頁面.
3). logout 登出.
4). roles 角色過濾器
-->
<property name="filterChainDefinitions">
<value>
<!-- 登入可匿名訪問 -->
/static/**= anon
/ssm/shirologin/** = anon
/ssm/logout = logout
/ssm/home = user
<!-- 其他的需要授權訪問authc -->
/* = authc
</value>
</property>
</bean>
<!-- 開啟Shiro註解的Spring配置方式的beans。在lifecycleBeanPostProcessor之後執行 -->
<bean
class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
<property name="securityManager" ref="securityManager" />
</bean>
<!-- shiro為整合spring -->
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<property name="exceptionMappings">
<props>
<!-- 無許可權跳轉到登陸頁,可自行定義 -->
<prop key="org.apache.shiro.authz.UnauthorizedException">/ssm/home</prop>
</props>
</property>
</bean>
</beans>
需要注意一個地方就是,我們讓[記住我]也有許可權訪問ssm/home,配置了過濾器鏈:/ssm/home = user
我們新增一個UserFilter的子類com/ssm/security/SysUserFilter.java
package com.ssm.security;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import org.apache.shiro.web.filter.authc.UserFilter;
import org.springframework.stereotype.Service;
@Service
public class SysUserFilter extends UserFilter{
@Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
// TODO Auto-generated method stub
return super.isAccessAllowed(request, response, mappedValue);
}
}
(4)登入測試
後臺debug我們可以看到時長設定生效,如下圖:
通過瀏覽器訪問我們的登入頁登入
30秒內再次訪問登入頁或主頁
上面兩張截圖充分說明了首次是登入認證,第二次進入是通過RememberMe,30秒殺後再次訪問主頁將跳轉到登入頁。
五、簡單介紹退出
實現subject.logout()即可,很簡單就不詳細介紹了,請客觀自己實現吧。
Subject subject = SecurityUtils.getSubject();
subject.logout();