頁面防重提交技術之令牌機制


  今天給你們帶來的是頁面防重提交之令牌機制技術,若有不足之處,敬請指正。那麼首先咱們必須知道頁面防重是什麼?html

  例如:咱們在瀏覽器輸入表單信息提交後,會將數據插入到數據庫中,此時瀏覽器會保存上一次請求的數據,若是咱們不作頁面防重,當咱們點擊頁面刷新按鈕時,就會再次將已經填好的表單提交,再次插入到數據庫中。java

瀏覽器中的防重提交示例
頁面防重提交

  要解決這個問題,首先要理解token機制(令牌機制)web

  如何理解token機制:通俗的講就比如古代將軍帶兵出征打仗,可是將軍若是在千里以外須要執行皇帝的命令,只能皇帝派親信帶着皇帝的信物虎符(虎符一分爲二)來傳達命令。若親信帶着的虎符與將軍的虎符匹配,則證實爲皇帝真實命令。咱們此次的令牌機制與此情景十分類似。spring


token示意圖
token示意圖

  Token機制的實現:數據庫

  1. 咱們在進入提交數據的表單頁面以前,先建立一個Token給頁面
  2. 在頁面表單設置Token,做爲表單的參數
  3. 提交數據到服務器後,首先判斷是不是一個防重提的表單,若是是,就判斷是不是 須要建立Token仍是校驗刪除Token,仍是處理重提。
  4. 若是是處理重提交表單,須要頁面指定一個跳轉路徑

1、建立一個註解判斷請求方法是否支持防重提交

做用:用於標識方法是否須要防重提交瀏覽器

package com.xkt.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 自定義一個Token註解,用於標識防重提交的方法
 * @author lzx
 *
 */
@Target(value=ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TokenForm {
	
	//用於標記須要防重提交的方法,建立Token的屬性
	boolean create() default false;
	//用於標記防重提交方法的,刪除Token的屬性
	boolean remove() default false;
}

2、編寫攔截器

package com.xkt.interceptor;

import java.util.UUID;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import com.xkt.annotation.TokenForm;

/**
 * @author lzx
 *
 */
public class TokenInterceptor implements HandlerInterceptor {

	@Override
	public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
			throws Exception {
		// 第一步:得到調用處理方法的註解
		// 1.得到方法
		HandlerMethod hm = (HandlerMethod) handler;
		// 2.得到方法調用的Token的註解
		TokenForm tokenForm = hm.getMethodAnnotation(TokenForm.class);

		// 第二步:判斷是否有Token註解
		if (tokenForm != null) {
			HttpSession session = request.getSession();
			if (tokenForm.create() == true) {
				// 將Token惟一標識建立在服務器中,並在頁面中做用域中取得,以便於在提交表單時進行驗證
				session.setAttribute("token", UUID.randomUUID().toString());
			}
			if (tokenForm.remove() == true) {
				// 判斷表單的Token是否與服務端的Token相同
				// 1.獲取表單的Token
				String formToken = request.getParameter("token");
				// 2.獲取服務端的Token
				Object sessionToken = session.getAttribute("token");
				// 表單傳遞過來的Token與服務端相同,容許操做,而且刪除session的Token
				if (formToken.equals(sessionToken)) {
					// 若是相同,則移除服務端的Token
					session.removeAttribute("token");
				} else {
					// 若不一樣,跳轉到指定的路徑(由頁面傳遞token.invoke來指定)
					String invoke = request.getParameter("token.invoke");
					response.sendRedirect(request.getContextPath() + invoke);
					return false;
				}

			}
		}
		return true;
	}

	@Override
	public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
			ModelAndView modelAndView) throws Exception {
	}

	@Override
	public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
			throws Exception {
	}

}

3、配置攔截器(代碼中的令牌攔截器部分)

package com.xkt.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.DefaultServletHandlerConfigurer;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.InterceptorRegistration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import org.springframework.web.servlet.view.InternalResourceViewResolver;
import org.springframework.web.servlet.view.JstlView;

import com.xkt.interceptor.LoginStatusInterceptor;
import com.xkt.interceptor.PermissionInterceptor;
import com.xkt.interceptor.TokenInterceptor;

/**
 * @author lzx
 *
 */
@Configuration
@EnableWebMvc // <mvc:annotation-driver/>
public class MvcConfig extends WebMvcConfigurerAdapter {

	//<mvc:default-servlet-handler/>
	@Override
	public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
		configurer.enable();
	}

	// 配置視圖解釋器
	@Bean
	public InternalResourceViewResolver getInternalResourceViewResolver() {

		InternalResourceViewResolver resolver = new InternalResourceViewResolver();
		// 支持JSTL的視圖
		resolver.setViewClass(JstlView.class);
		// 配置視圖前綴
		resolver.setPrefix("/WEB-INF/views/");
		// 配置視圖後綴
		resolver.setSuffix(".jsp");
		return resolver;
	}

	@Bean
	public PermissionInterceptor getPermissionInterceptor() {
		PermissionInterceptor permission = new PermissionInterceptor();
		return permission;
	}

	@Override
	public void addInterceptors(InterceptorRegistry registry) {
		// 1.登陸狀態過濾
		LoginStatusInterceptor loginStatus = new LoginStatusInterceptor();
		InterceptorRegistration loginStatusRegistration = registry.addInterceptor(loginStatus);
		// 攔截全部請求
		loginStatusRegistration.addPathPatterns("/**");
		// 排除登錄請求不攔截
		loginStatusRegistration.excludePathPatterns("/admin/loginAdmin");

		// 2.權限控制
		PermissionInterceptor permission = this.getPermissionInterceptor();
		InterceptorRegistration permissionRegistration = registry.addInterceptor(permission);
		// 攔截全部請求
		permissionRegistration.addPathPatterns("/**");
		// 排除登錄請求
		permissionRegistration.excludePathPatterns("/admin/loginAdmin");
		// 排除註銷請求
		permissionRegistration.excludePathPatterns("/admin/undoAdmin");

		// 3.令牌攔截器
		TokenInterceptor token= new TokenInterceptor();
		InterceptorRegistration tokenRegistration = registry.addInterceptor(token);
		tokenRegistration.addPathPatterns("/**");

	}

}

4、指定頁面的兩個參數支持防重提交

(兩個type="hidden"的input標籤)服務器

<form action="${pageContext.request.contextPath}/admin/addAdmin" method="post" class="form-horizontal" role="form">
	<!-- 頁面令牌 -->
	<!-- token的值從做用域中取出,在頁面提交時用來比對 -->
	<!-- 當頁面token與服務端token不匹配時,token.invole指定跳轉路徑 -->
	<input type="hidden" name="token" value="${sessionScope.token}"/>
	<input type="hidden" name="token.invoke" value="/admin/toAdminAdd"/>
	<div class="form-group">
		<label class="col-sm-3 control-label no-padding-right" for="form-field-1"> 用戶名 </label>

		<div class="col-sm-9">
			<input name="admin_name" type="text" id="form-field-1" placeholder="用戶名" class="col-xs-10 col-sm-5" />
		</div>
	</div>
	<div class="form-group">
		<label class="col-sm-3 control-label no-padding-right" for="form-field-1"> 帳號名 </label>

		<div class="col-sm-9">
			<input name="admin_account" type="text" id="form-field-1" placeholder="帳號名" class="col-xs-10 col-sm-5" />
		</div>
	</div>

	<div class="form-group">
		<label class="col-sm-3 control-label no-padding-right" for="form-field-1"> 密碼 </label>

		<div class="col-sm-9">
			<input name="admin_pwd" type="password" id="form-field-1" placeholder="密碼" class="col-xs-10 col-sm-5" />
		</div>
	</div>
	<div class="form-group">
		<label class="col-sm-3 control-label no-padding-right" for="form-field-1"> 用戶狀態 </label>

		<div class="col-sm-9">
			<select name="admin_status">
			   <c:forEach var="dictionary" items="${applicationScope.global_dictionarys}">
			   		<c:if test="${dictionary.dictionary_type_code==10002}">
			   			<option value="${dictionary.dictionary_value}">${dictionary.dictionary_name}</option>
			   		</c:if>
			   </c:forEach>
			</select>
		</div>
	</div>
	<div class="form-group">
		<label class="col-sm-3 control-label no-padding-right" for="form-field-1"> 角色 </label>

		<div class="col-sm-9">
			<select name="role_id">
				<c:forEach var="role" items="${applicationScope.global_roles}">
					<option value="${role.role_id}">${role.role_name}</option>
				</c:forEach>
			</select>
		</div>
	</div>
	<div class="form-group">
		
		<div class="col-sm-7 text-right">
			<button type="submit" class="btn btn-primary">增長後臺用戶</button>
		</div>
	</div>
</form>

附:後臺添加token處的兩個方法

/**
	 * 增長管理員 請求路徑:${pageContext.request.contextPath }/admin/addAdmin
	 * 
	 * @param admin
	 * @param request
	 * @return
	 */
	@RequestMapping("/addAdmin")
	@TokenForm(remove=true)
	public String addAdmin(@RequestParam Map<String, Object> admin, HttpServletRequest request) {

		try {
			//將密碼md5加密後再插入
			admin.put("admin_pwd", Md5Utils.md5((String)admin.get("admin_pwd")));
			LOGGER.debug("------增長管理員------" + admin);
			Map<String, Object> result = adminService.insert(admin);
			if (result != null) {
				request.setAttribute("admin_add_msg", "增長管理員成功");
			} else {
				request.setAttribute("admin_add_msg", "增長管理員失敗");
			}
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			request.setAttribute("admin_add_msg", "增長管理員失敗");
		}

		return "/manager/adminAdd";
	}


	/**
	 * 跳轉到增長頁面 請求路徑:${pageContext.request.contextPath}/admin/toAdminAdd
	 * 
	 * @return
	 */
	@RequestMapping("/toAdminAdd")
	@TokenForm(create=true)
	public String toAdminAdd() {
		
		return "/manager/adminAdd";
	}

版權說明:歡迎以任何方式進行轉載,但請在轉載後註明出處!session

相關文章
相關標籤/搜索