溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶(hù)服務(wù)條款》

SpringMVC中使用Interceptor+Cookie實(shí)現(xiàn)在一定天數(shù)之內(nèi)自動(dòng)登錄

發(fā)布時(shí)間:2020-07-19 19:19:04 來(lái)源:網(wǎng)絡(luò) 閱讀:14149 作者:pangfc 欄目:數(shù)據(jù)庫(kù)

一 簡(jiǎn)介

本篇文章主要介紹:在SpringMVC中如何使用Interceptor+Cookie實(shí)現(xiàn)在一定天數(shù)之內(nèi)自動(dòng)登錄的功能。同時(shí)還介紹“如果校驗(yàn)失敗則跳轉(zhuǎn)到登錄頁(yè)面,在輸入用戶(hù)名、密碼等完成登錄之后又自動(dòng)跳轉(zhuǎn)到原頁(yè)面”的功能實(shí)現(xiàn)

本次測(cè)試環(huán)境是SSM框架,在正式介紹本篇文章之前,建議需要熟悉以下前置知識(shí)點(diǎn):

  • Mybatis中使用mybatis-generator結(jié)合Ant腳本快速自動(dòng)生成Model、Mapper等文件(PS:這是為了快速生成一些基本文件)    https://www.zifangsky.cn/431.html

  • SpringMVC通過(guò)配置mvc:view-controller直接解析到視圖頁(yè)面(PS:這是為了簡(jiǎn)化controller中的代碼)    https://www.zifangsky.cn/648.html

  • 基于SpringMVC的Cookie常用操作詳解(PS:這是介紹cookie的常用操作)    https://www.zifangsky.cn/665.html

  • SpringMVC中使用forward和redirect進(jìn)行轉(zhuǎn)發(fā)和重定向以及重定向時(shí)如何傳參詳解(PS:這是介紹重定向時(shí)如何傳參的問(wèn)題)    https://www.zifangsky.cn/661.html

  • 在SpringMVC中使用攔截器(interceptor)攔截CSRF***(PS:這是介紹攔截器的一些基礎(chǔ)用法)    https://www.zifangsky.cn/671.html

二 代碼實(shí)現(xiàn)

(1)數(shù)據(jù)庫(kù)表設(shè)計(jì):

我這里采用的是MySQL,同時(shí)設(shè)計(jì)了兩張表,分別是:userpersistent_logins

i)user表:

DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(50) DEFAULT NULL,
  `password` varchar(300) DEFAULT NULL,
  `email` varchar(64) DEFAULT NULL,
  `birthday` date DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;

-- ----------------------------
-- Records of user
-- ----------------------------
INSERT INTO `user` VALUES ('1', 'admin', 'admin', 'admin@zifangsky.cn', '2016-06-30');
INSERT INTO `user` VALUES ('2', 'test', '123456', 'test@zifangsky.cn', '2015-12-12');
INSERT INTO `user` VALUES ('3', 'zifangsky', 'zifangsky', 'zifangsky@zifangsky.cn', '2010-02-10');

這張表很簡(jiǎn)單,就是一張普通的用戶(hù)表

ii)persistent_logins表:

DROP TABLE IF EXISTS `persistent_logins`;
CREATE TABLE `persistent_logins` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `username` varchar(50) NOT NULL,
  `series` varchar(300) DEFAULT NULL,
  `token` varchar(500) DEFAULT NULL,
  `validTime` datetime DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

這張表是用戶(hù)校驗(yàn)用戶(hù)自動(dòng)登錄的表。設(shè)計(jì)這張表的原因是我看過(guò)一些網(wǎng)上的文章介紹使用cookie自動(dòng)登錄,但是他們基本上都是將用戶(hù)名、密碼、salt等字符串拼接之后md5加密然后保存在cookie中。雖然使用了md5這類(lèi)非對(duì)稱(chēng)加密方式,但是將密碼這類(lèi)關(guān)鍵信息保存在用戶(hù)端,我覺(jué)得是不太靠譜的。因此設(shè)計(jì)了這張表,將用戶(hù)名、密碼等關(guān)鍵信息加密之后的數(shù)據(jù)保存到這張表中,在用戶(hù)的cookie里只保存了沒(méi)有特殊含義的UUID值以及用戶(hù)名

這張表中的幾個(gè)字段的含義分別是:

  • id    主鍵

  • username    用戶(hù)名

  • series    用戶(hù)使用密碼登錄成功之后獲取的一個(gè)UUID值,同時(shí)用戶(hù)端保存的cookie記錄就是:EncryptionUtil.base64Encode(用戶(hù)名:此UUID值)

  • token    在攔截器中校驗(yàn)是否能夠登錄的密文,其加密方式是:EncryptionUtil.sha256Hex(用戶(hù)名 + “_” + 密碼 + “_” + 自動(dòng)登錄失效的時(shí)間點(diǎn)的字符串 + “_” +  自定義的salt)

  • validTime    自動(dòng)登錄失效的時(shí)間,即:這個(gè)時(shí)間點(diǎn)之后只能重新用用戶(hù)名、密碼登錄,如果在重新登錄時(shí)勾選了“30天內(nèi)自動(dòng)登錄”則更新該用戶(hù)在persistent_logins這個(gè)表中的自動(dòng)登錄記錄

(2)幾個(gè)基本的配置文件:

i)web.xml:

<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
	  http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
         version="3.1">

	<context-param>
		<param-name>contextConfigLocation</param-name>
		<param-value>
			classpath:context/context.xml
		</param-value>
	</context-param>
	<!-- 這兩個(gè)listener不加會(huì)出現(xiàn)無(wú)法依賴(lài)注入問(wèn)題 -->
	<listener>
		<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
	</listener>
	<listener>
		<listener-class>org.springframework.web.context.request.RequestContextListener</listener-class>
	</listener>	
	<servlet>
		<servlet-name>springmvc</servlet-name>
		<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
		<init-param>
			<param-name>contextConfigLocation</param-name>
			<param-value>classpath:context/springmvc-servlet.xml</param-value>
		</init-param>
		<load-on-startup>1</load-on-startup>
	</servlet>

	<servlet-mapping>
		<servlet-name>springmvc</servlet-name>
		<url-pattern>*.html</url-pattern>
	</servlet-mapping>
	
	<filter>
		<filter-name>characterEncodingFilter</filter-name>
		<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
		<init-param>
			<param-name>encoding</param-name>
			<param-value>UTF-8</param-value>
		</init-param>
	</filter>
	<filter-mapping>
		<filter-name>characterEncodingFilter</filter-name>
		<url-pattern>/*</url-pattern>
	</filter-mapping>

</web-app>

ii)context.xml:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:cache="http://www.springframework.org/schema/cache" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:jee="http://www.springframework.org/schema/jee"
	xsi:schemaLocation="http://www.springframework.org/schema/beans 
			http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
            http://www.springframework.org/schema/jee 
            http://www.springframework.org/schema/jee/spring-jee-4.0.xsd
            http://www.springframework.org/schema/aop 
            http://www.springframework.org/schema/aop/spring-aop-4.0.xsd
            http://www.springframework.org/schema/context 
            http://www.springframework.org/schema/context/spring-context-4.0.xsd
            http://www.springframework.org/schema/cache  
       		http://www.springframework.org/schema/cache/spring-cache-4.0.xsd  
            http://www.springframework.org/schema/tx 
            http://www.springframework.org/schema/tx/spring-tx-4.0.xsd"
	xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx"
	xmlns:aop="http://www.springframework.org/schema/aop">

	<!-- 配置數(shù)據(jù)源 -->
	<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"
		destroy-method="close">

		<property name="driverClass">
			<value>com.mysql.jdbc.Driver</value>
		</property>
		<property name="jdbcUrl">
			<value>jdbc:mysql://127.0.0.1:3306/cookie_db</value>
		</property>
		<property name="user">
			<value>root</value>
		</property>
		<property name="password">
			<value>root</value>
		</property>
		<!--連接池中保留的最小連接數(shù)。 -->
		<property name="minPoolSize">
			<value>5</value>
		</property>
		<!--連接池中保留的最大連接數(shù)。Default: 15 -->
		<property name="maxPoolSize">
			<value>30</value>
		</property>
		<!--初始化時(shí)獲取的連接數(shù),取值應(yīng)在minPoolSize與maxPoolSize之間。Default: 3 -->
		<property name="initialPoolSize">
			<value>10</value>
		</property>
		<!--最大空閑時(shí)間,60秒內(nèi)未使用則連接被丟棄。若為0則永不丟棄。Default: 0 -->
		<property name="maxIdleTime">
			<value>60</value>
		</property>
		<!--當(dāng)連接池中的連接耗盡的時(shí)候c3p0一次同時(shí)獲取的連接數(shù)。Default: 3 -->
		<property name="acquireIncrement">
			<value>5</value>
		</property>
		<!--JDBC的標(biāo)準(zhǔn)參數(shù),用以控制數(shù)據(jù)源內(nèi)加載的PreparedStatements數(shù)量。但由于預(yù)緩存的statements 屬于單個(gè) connection而不是整個(gè)連接池。所以設(shè)置這個(gè)參數(shù)需要考慮到多方面的因素。 
			如果maxStatements與maxStatementsPerConnection均為0,則緩存被關(guān)閉。Default: 0 -->
		<property name="maxStatements">
			<value>0</value>
		</property>
		<!--每60秒檢查所有連接池中的空閑連接。Default: 0 -->
		<property name="idleConnectionTestPeriod">
			<value>60</value>
		</property>
		<!--定義在從數(shù)據(jù)庫(kù)獲取新連接失敗后重復(fù)嘗試的次數(shù)。Default: 30 -->
		<property name="acquireRetryAttempts">
			<value>30</value>
		</property>
		<!--獲取連接失敗將會(huì)引起所有等待連接池來(lái)獲取連接的線(xiàn)程拋出異常。但是數(shù)據(jù)源仍有效 保留,并在下次調(diào)用 getConnection()的時(shí)候繼續(xù)嘗試獲取連接。如果設(shè)為true,那么在嘗試 
			獲取連接失敗后該數(shù)據(jù)源將申明已斷開(kāi)并永久關(guān)閉。Default: false -->
		<property name="breakAfterAcquireFailure">
			<value>true</value>
		</property>
		<!--因性能消耗大請(qǐng)只在需要的時(shí)候使用它。如果設(shè)為true那么在每個(gè)connection提交的 時(shí)候都將校驗(yàn)其有效性。建議 使用idleConnectionTestPeriod或automaticTestTable 
			等方法來(lái)提升連接測(cè)試的性能。Default: false -->
		<property name="testConnectionOnCheckout">
			<value>false</value>
		</property>
	</bean>
	<!-- MyBatis相關(guān)配置 -->
	<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
		<property name="configLocation" value="classpath:context/sql-map-config.xml" />
		<property name="dataSource" ref="dataSource" />
	</bean>
	<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
		<property name="basePackage" value="cn.zifangsky.mapper" />
		<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
	</bean>

	<bean id="sqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate">
		<constructor-arg index="0" ref="sqlSessionFactory" />
	</bean>

	<!-- 事務(wù)相關(guān)配置 -->
	<bean id="transactionManager"
		class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
		<property name="dataSource" ref="dataSource" />
	</bean>

	<tx:annotation-driven transaction-manager="transactionManager" />
</beans>

iii)SpringMVC的配置文件springmvc-servlet.xml:

<?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:cache="http://www.springframework.org/schema/cache"
	xmlns:mvc="http://www.springframework.org/schema/mvc"
	xsi:schemaLocation="http://www.springframework.org/schema/beans 
	   http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
       http://www.springframework.org/schema/context 
       http://www.springframework.org/schema/context/spring-context-4.0.xsd
       http://www.springframework.org/schema/cache  
       http://www.springframework.org/schema/cache/spring-cache-4.0.xsd  
       http://www.springframework.org/schema/mvc 
       http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd"
       default-lazy-init="true">
	
	<mvc:annotation-driven />
	<!-- 組件掃描 -->
	<context:component-scan base-package="cn.zifangsky.controller" />
	<context:component-scan base-package="cn.zifangsky.manager.impl"/>		
	<!-- 配置直接轉(zhuǎn)發(fā)的頁(yè)面 -->
	<mvc:view-controller path="/login.html" view-name="login" />
	<mvc:view-controller path="/user/callback.html" view-name="user/callback" />
	
	<!-- 攔截器 -->
	<mvc:interceptors>
		<mvc:interceptor>
			<!-- 對(duì)登錄操作進(jìn)行攔截 -->
			<mvc:mapping path="/check.html"/>
			<bean class="cn.zifangsky.interceptor.LoginInterceptor" />
		</mvc:interceptor>
		<mvc:interceptor>
			<!-- 對(duì)/user/**的請(qǐng)求進(jìn)行攔截 -->
			<mvc:mapping path="/user/**"/>
			<bean class="cn.zifangsky.interceptor.UserInterceptor" />
		</mvc:interceptor>
	</mvc:interceptors>
	
	<!-- 視圖解析 -->
	<bean
		class="org.springframework.web.servlet.view.InternalResourceViewResolver">
		<property name="prefix" value="/WEB-INF/pages/" />
		<property name="suffix" value=".jsp" />
	</bean>
</beans>

這里需要注意的是第31-35行的攔截器的配置,這個(gè)攔截器就是用于使用cookie自動(dòng)登錄的攔截器,上面那個(gè)用于攔截登錄時(shí)的CSRF***的攔截器可以先不用管,直接注釋掉或者參考下我的這篇文章:https://www.zifangsky.cn/671.html

(3)Mapper層的代碼:

i)UserMapper:

package cn.zifangsky.mapper;

import cn.zifangsky.model.User;

public interface UserMapper {
    int deleteByPrimaryKey(Integer id);

    int insert(User record);

    int insertSelective(User record);

    User selectByPrimaryKey(Integer id);

    int updateByPrimaryKeySelective(User record);

    int updateByPrimaryKey(User record);
    
    /**
     * 根據(jù)用戶(hù)信息查用戶(hù)詳情(登錄)
     * */
    User selectByUser(User user);
    /**
     * 根據(jù)用戶(hù)名查用戶(hù)詳情
     * */
    User selectByName(String name);
}

這里除了使用插件自動(dòng)生成的幾個(gè)方法之外,還添加了兩個(gè)其他的方法,它們對(duì)應(yīng)的SQL語(yǔ)句是:

  <select id="selectByName" resultMap="BaseResultMap" parameterType="java.lang.String" >
    select 
    <include refid="Base_Column_List" />
    from user
    where name = #{name,jdbcType=VARCHAR}
  </select>
    
  <select id="selectByUser" resultMap="BaseResultMap" parameterType="cn.zifangsky.model.User" >
    select 
    <include refid="Base_Column_List" />
    from user
    where name = #{name,jdbcType=VARCHAR} and password = #{password,jdbcType=VARCHAR}
      <if test="email != null" >
        and email = #{email,jdbcType=VARCHAR}
      </if>
      <if test="birthday != null" >
        and birthday = #{birthday,jdbcType=DATE}
      </if>
  </select>

ii)PersistentLoginsMapper:

package cn.zifangsky.mapper;

import org.apache.ibatis.annotations.Param;

import cn.zifangsky.model.PersistentLogins;

public interface PersistentLoginsMapper {
	int deleteByPrimaryKey(Integer id);

	int insert(PersistentLogins record);

	int insertSelective(PersistentLogins record);

	PersistentLogins selectByPrimaryKey(Integer id);

	int updateByPrimaryKeySelective(PersistentLogins record);

	int updateByPrimaryKey(PersistentLogins record);

	/**
	 * 通過(guò)用戶(hù)名和UUID值查詢(xún)自動(dòng)登錄記錄
	 * 
	 * @param username
	 *            用戶(hù)名
	 * @param series
	 *            UUID值
	 */
	PersistentLogins selectByUsernameAndSeries(@Param("username") String username, @Param("series") String series);

	/**
	 * 通過(guò)用戶(hù)名查詢(xún)自動(dòng)登錄記錄
	 * 
	 * @param username
	 *            用戶(hù)名
	 */
	PersistentLogins selectByUsername(@Param("username") String username);
}

同樣,這里也添加了兩個(gè)其他的方法,它們對(duì)應(yīng)的SQL語(yǔ)句是:

  <select id="selectByUsername" resultMap="BaseResultMap" parameterType="java.lang.String" >
    select 
    <include refid="Base_Column_List" />
    from persistent_logins where username = #{username,jdbcType=VARCHAR}
  </select>
    
  <select id="selectByUsernameAndSeries" resultMap="BaseResultMap" parameterType="java.util.Map" >
    select 
    <include refid="Base_Column_List" />
    from persistent_logins
    where username = #{username,jdbcType=VARCHAR} and series = #{series,jdbcType=VARCHAR}
  </select>

(4)Manager層(即:業(yè)務(wù)邏輯層):

i)UserManager接口:

package cn.zifangsky.manager;

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

import cn.zifangsky.model.User;

public interface UserManager {
	int deleteByPrimaryKey(Integer id);

	int insert(User user);

	int insertSelective(User user);

	User selectByPrimaryKey(Integer id);

	int updateByPrimaryKeySelective(User user);

	int updateByPrimaryKey(User user);
	
	/**
     * 根據(jù)用戶(hù)名查用戶(hù)詳情
     * */
    User selectByName(String name);
    
	/**
	 * 登錄
	 * 
	 * @param user
	 *            登錄的用戶(hù)信息
	 * @param rememberme
	 *            是否記住登錄
	 * @param response
	 *            HttpServletResponse
	 * @return 根據(jù)傳遞的用戶(hù)信息在數(shù)據(jù)庫(kù)中查詢(xún)到的用戶(hù)詳情
	 */
	User login(User user, boolean rememberme, HttpServletResponse response);
	
	/**
	 * 退出登錄
	 * */
	void logout(HttpServletRequest request,HttpServletResponse response);
}

ii)PersistentLoginsManager接口:

package cn.zifangsky.manager;

import cn.zifangsky.model.PersistentLogins;

public interface PersistentLoginsManager {
    int deleteByPrimaryKey(Integer id);

    int insert(PersistentLogins pLogins);

    int insertSelective(PersistentLogins pLogins);

    PersistentLogins selectByPrimaryKey(Integer id);

    int updateByPrimaryKeySelective(PersistentLogins pLogins);

    int updateByPrimaryKey(PersistentLogins pLogins);
    /**
	 * 通過(guò)用戶(hù)名和UUID值查詢(xún)自動(dòng)登錄記錄
	 * 
	 * @param username
	 *            用戶(hù)名
	 * @param series
	 *            UUID值
	 */
	PersistentLogins selectByUsernameAndSeries(String username,String series);
	/**
	 * 通過(guò)用戶(hù)名查詢(xún)自動(dòng)登錄記錄
	 * 
	 * @param username
	 *            用戶(hù)名
	 */
	PersistentLogins selectByUsername(String username);	
}

iii)PersistentLoginsManagerImpl實(shí)現(xiàn)類(lèi):

package cn.zifangsky.manager.impl;

import javax.annotation.Resource;

import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;

import cn.zifangsky.manager.PersistentLoginsManager;
import cn.zifangsky.mapper.PersistentLoginsMapper;
import cn.zifangsky.model.PersistentLogins;

@Service("persistentLoginsManagerImpl")
public class PersistentLoginsManagerImpl implements PersistentLoginsManager {
	@Resource(name="persistentLoginsMapper")
	private PersistentLoginsMapper persistentLoginsMapper;

	public int deleteByPrimaryKey(Integer id) {
		return persistentLoginsMapper.deleteByPrimaryKey(id);
	}

	@Override
	public int insert(PersistentLogins pLogins) {
		return persistentLoginsMapper.insert(pLogins);
	}

	@Override
	public int insertSelective(PersistentLogins pLogins) {
		return persistentLoginsMapper.insertSelective(pLogins);
	}

	@Override
	public PersistentLogins selectByPrimaryKey(Integer id) {
		return persistentLoginsMapper.selectByPrimaryKey(id);
	}

	@Override
	public int updateByPrimaryKeySelective(PersistentLogins pLogins) {
		return persistentLoginsMapper.updateByPrimaryKeySelective(pLogins);
	}

	@Override
	public int updateByPrimaryKey(PersistentLogins pLogins) {
		return persistentLoginsMapper.updateByPrimaryKey(pLogins);
	}

	public PersistentLogins selectByUsernameAndSeries(String username, String series) {
		if(StringUtils.isNotBlank(username) && StringUtils.isNotBlank(series))
			return persistentLoginsMapper.selectByUsernameAndSeries(username, series);
		else
			return null;
	}

	@Override
	public PersistentLogins selectByUsername(String username) {
		return persistentLoginsMapper.selectByUsername(username);
	}
}

iv)UserManagerImpl實(shí)現(xiàn)類(lèi):

package cn.zifangsky.manager.impl;

import java.util.Calendar;
import java.util.Date;
import java.util.UUID;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;

import cn.zifangsky.manager.UserManager;
import cn.zifangsky.mapper.UserMapper;
import cn.zifangsky.model.PersistentLogins;
import cn.zifangsky.model.User;
import cn.zifangsky.utils.CookieConstantTable;
import cn.zifangsky.utils.CookieUtils;
import cn.zifangsky.utils.EncryptionUtil;

@Service("userManagerImpl")
public class UserManagerImpl implements UserManager {
	

	@Resource(name = "userMapper")
	private UserMapper userMapper;
	@Resource(name = "persistentLoginsManagerImpl")
	private PersistentLoginsManagerImpl persistentLoginsManagerImpl;

	public int deleteByPrimaryKey(Integer id) {
		return userMapper.deleteByPrimaryKey(id);
	}

	@Override
	public int insert(User user) {
		return userMapper.insert(user);
	}

	@Override
	public int insertSelective(User user) {
		return userMapper.insertSelective(user);
	}

	@Override
	public User selectByPrimaryKey(Integer id) {
		return userMapper.selectByPrimaryKey(id);
	}

	@Override
	public int updateByPrimaryKeySelective(User user) {
		return userMapper.updateByPrimaryKeySelective(user);
	}

	@Override
	public int updateByPrimaryKey(User user) {
		return userMapper.updateByPrimaryKey(user);
	}

	@Override
	public User selectByName(String name) {
		return userMapper.selectByName(name);
	}

	@Override
	public User login(User user, boolean rememberme, HttpServletResponse response) {
		User result = new User();
		// 如果用戶(hù)名和密碼不為空,執(zhí)行登錄
		if (StringUtils.isNotBlank(user.getName()) && StringUtils.isNotBlank(user.getPassword())) {
			result = userMapper.selectByUser(user);
			// 如果rememberme為true,則保存cookie值,下次自動(dòng)登錄
			if (result != null && rememberme == true) {
				// 有效期
				Calendar calendar = Calendar.getInstance();
				calendar.add(Calendar.MONTH, 1); // 一個(gè)月
				Date validTime = calendar.getTime();
				// 精確到分的時(shí)間字符串
				String timeString = calendar.get(Calendar.YEAR) + "-" + calendar.get(Calendar.MONTH) + "-"
						+ calendar.get(Calendar.DAY_OF_MONTH) + "-" + calendar.get(Calendar.HOUR_OF_DAY) + "-"
						+ calendar.get(Calendar.MINUTE);

				// sha256加密用戶(hù)信息
				String userInfoBySha256 = EncryptionUtil
						.sha256Hex(result.getName() + "_" + result.getPassword() + "_" + timeString + "_" + CookieConstantTable.salt);
				// UUID值
				String uuidString = UUID.randomUUID().toString();
				// Cookie值
				String cookieValue = EncryptionUtil.base64Encode(result.getName() + ":" + uuidString);

				// 在數(shù)據(jù)庫(kù)中保存自動(dòng)登錄記錄(如果已有該用戶(hù)的記錄則更新記錄)
				PersistentLogins pLogin = persistentLoginsManagerImpl.selectByUsername(result.getName());
				if (pLogin == null) {
					pLogin = new PersistentLogins();
					pLogin.setUsername(result.getName());
					pLogin.setSeries(uuidString);
					pLogin.setToken(userInfoBySha256);
					pLogin.setValidtime(validTime);
					persistentLoginsManagerImpl.insertSelective(pLogin);
				}else{
					pLogin.setSeries(uuidString);
					pLogin.setToken(userInfoBySha256);
					pLogin.setValidtime(validTime);
					persistentLoginsManagerImpl.updateByPrimaryKeySelective(pLogin);
				}

				// 保存cookie
				CookieUtils.addCookie(response, CookieConstantTable.RememberMe, cookieValue, null);
			}

		}
		return result;
	}

	@Override
	public void logout(HttpServletRequest request, HttpServletResponse response) {
		//從session中獲取用戶(hù)詳情
		User user = (User) request.getSession().getAttribute("user");
		//刪除數(shù)據(jù)庫(kù)中的自動(dòng)登錄記錄
		PersistentLogins pLogins = persistentLoginsManagerImpl.selectByUsername(user.getName());
		if(pLogins != null)
			persistentLoginsManagerImpl.deleteByPrimaryKey(pLogins.getId());
		//清除session和用于自動(dòng)登錄的cookie
		request.getSession().removeAttribute("user");
		CookieUtils.delCookie(request, response, CookieConstantTable.RememberMe);
	}
}

注:CookieConstantTable類(lèi):

package cn.zifangsky.utils;

public class CookieConstantTable {
	// cookie的有效期默認(rèn)為30天
	public final static int COOKIE_MAX_AGE = 60 * 60 * 24 * 30; 
	//cookie加密時(shí)的額外的salt
	public final static String salt = "www.zifangsky.cn";
	//自動(dòng)登錄的Cookie名
	public final static String RememberMe = "remember-me";
}

CookieUtils類(lèi):

package cn.zifangsky.utils;

import javax.servlet.http.HttpServletResponse;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;

public class CookieUtils {

	

	/**
	 * 添加一個(gè)新Cookie
	 * 
	 * @author zifangsky
	 * @param response
	 *            HttpServletResponse
	 * @param cookie
	 *            新cookie
	 * 
	 * @return null
	 */
	public static void addCookie(HttpServletResponse response, Cookie cookie) {
		if (cookie != null)
			response.addCookie(cookie);
	}

	/**
	 * 添加一個(gè)新Cookie
	 * 
	 * @author zifangsky
	 * @param response
	 *            HttpServletResponse
	 * @param cookieName
	 *            cookie名稱(chēng)
	 * @param cookieValue
	 *            cookie值
	 * @param domain
	 *            cookie所屬的子域
	 * @param httpOnly
	 *            是否將cookie設(shè)置成HttpOnly
	 * @param maxAge
	 *            設(shè)置cookie的最大生存期
	 * @param path
	 *            設(shè)置cookie路徑
	 * @param secure
	 *            是否只允許HTTPS訪(fǎng)問(wèn)
	 * 
	 * @return null
	 */
	public static void addCookie(HttpServletResponse response, String cookieName, String cookieValue, String domain,
			boolean httpOnly, int maxAge, String path, boolean secure) {
		if (cookieName != null && !cookieName.equals("")) {
			if (cookieValue == null)
				cookieValue = "";

			Cookie newCookie = new Cookie(cookieName, cookieValue);
			if (domain != null)
				newCookie.setDomain(domain);

			newCookie.setHttpOnly(httpOnly);

			if (maxAge > 0)
				newCookie.setMaxAge(maxAge);

			if (path == null)
				newCookie.setPath("/");
			else
				newCookie.setPath(path);

			newCookie.setSecure(secure);

			addCookie(response, newCookie);
		}
	}

	/**
	 * 添加一個(gè)新Cookie
	 * 
	 * @author zifangsky
	 * @param response
	 *            HttpServletResponse
	 * @param cookieName
	 *            cookie名稱(chēng)
	 * @param cookieValue
	 *            cookie值
	 * @param domain
	 *            cookie所屬的子域
	 * 
	 * @return null
	 */
	public static void addCookie(HttpServletResponse response, String cookieName, String cookieValue, String domain) {
		addCookie(response, cookieName, cookieValue, domain, true, CookieConstantTable.COOKIE_MAX_AGE, "/", false);
	}

	/**
	 * 根據(jù)Cookie名獲取對(duì)應(yīng)的Cookie
	 * 
	 * @author zifangsky
	 * @param request
	 *            HttpServletRequest
	 * @param cookieName
	 *            cookie名稱(chēng)
	 * 
	 * @return 對(duì)應(yīng)cookie,如果不存在則返回null
	 */
	public static Cookie getCookie(HttpServletRequest request, String cookieName) {
		Cookie[] cookies = request.getCookies();

		if (cookies == null || cookieName == null || cookieName.equals(""))
			return null;

		for (Cookie c : cookies) {
			if (c.getName().equals(cookieName))
				return (Cookie) c;
		}
		return null;
	}

	/**
	 * 根據(jù)Cookie名獲取對(duì)應(yīng)的Cookie值
	 * 
	 * @author zifangsky
	 * @param request
	 *            HttpServletRequest
	 * @param cookieName
	 *            cookie名稱(chēng)
	 * 
	 * @return 對(duì)應(yīng)cookie值,如果不存在則返回null
	 */
	public static String getCookieValue(HttpServletRequest request, String cookieName) {
		Cookie cookie = getCookie(request, cookieName);
		if (cookie == null)
			return null;
		else
			return cookie.getValue();
	}

	/**
	 * 刪除指定Cookie
	 * 
	 * @author zifangsky
	 * @param response
	 *            HttpServletResponse
	 * @param cookie
	 *            待刪除cookie
	 */
	public static void delCookie(HttpServletResponse response, Cookie cookie) {
		if (cookie != null) {
			cookie.setPath("/"); 
			cookie.setMaxAge(0);
			cookie.setValue(null);

			response.addCookie(cookie);
		}
	}

	/**
	 * 根據(jù)cookie名刪除指定的cookie
	 * 
	 * @author zifangsky
	 * @param request
	 *            HttpServletRequest
	 * @param response
	 *            HttpServletResponse
	 * @param cookieName
	 *            待刪除cookie名
	 */
	public static void delCookie(HttpServletRequest request, HttpServletResponse response, String cookieName) {
		Cookie c = getCookie(request, cookieName);
		if (c != null && c.getName().equals(cookieName)) {
			delCookie(response, c);
		}
	}

	/**
	 * 根據(jù)cookie名修改指定的cookie
	 * 
	 * @author zifangsky
	 * @param request
	 *            HttpServletRequest
	 * @param response
	 *            HttpServletResponse
	 * @param cookieName
	 *            cookie名
	 * @param cookieValue
	 *            修改之后的cookie值
	 * @param domain
	 *            修改之后的domain值
	 */
	public static void editCookie(HttpServletRequest request, HttpServletResponse response, String cookieName,
			String cookieValue,String domain) {
		Cookie c = getCookie(request, cookieName);
		if (c != null && cookieName != null && !cookieName.equals("") && c.getName().equals(cookieName)) {
			addCookie(response, cookieName, cookieValue, domain);
		}
	}
}

EncryptionUtil類(lèi):

package cn.zifangsky.utils;

import java.io.UnsupportedEncodingException;

import org.apache.commons.codec.binary.Base64;
import org.apache.commons.codec.digest.DigestUtils;

public class EncryptionUtil {
    /**
     * Base64 encode
     * */
    public static String base64Encode(String data){
        return Base64.encodeBase64String(data.getBytes());
    }
     
    /**
     * Base64 decode
     * @throws UnsupportedEncodingException 
     * */
    public static String base64Decode(String data) throws UnsupportedEncodingException{
        return new String(Base64.decodeBase64(data.getBytes()),"utf-8");
    }
     
    /**
     * md5
     * */
    public static String md5Hex(String data){
        return DigestUtils.md5Hex(data);
    }
     
    /**
     * sha1
     * */
    public static String sha1Hex(String data){
        return DigestUtils.sha1Hex(data);
    }
     
    /**
     * sha256
     * */
    public static String sha256Hex(String data){
        return DigestUtils.sha256Hex(data);
    }
     
}

這個(gè)方法類(lèi)本質(zhì)上調(diào)用的是 commons-codec-1.10.jar 這個(gè)jar包中的方法

在這個(gè)類(lèi)中,關(guān)于退出登錄就不用多做解釋了,有詳細(xì)注釋自己參考下就行

關(guān)于這個(gè)登錄方法,實(shí)際上我這里的執(zhí)行流程是這樣的:

  1. 根據(jù)用戶(hù)名、密碼執(zhí)行登錄驗(yàn)證

  2. 如果前臺(tái)登錄的form表單中勾選了“30天內(nèi)自動(dòng)登錄”的選項(xiàng),那么就執(zhí)行下面的保存登錄記錄到persistent_logins這個(gè)表以及cookie中;如果沒(méi)勾選,那么就直接將驗(yàn)證結(jié)果返回到controller中

  3. 執(zhí)行保存記錄的這個(gè)操作,實(shí)際上分為以下兩步操作:a:向表persistent_logins保存記錄,username是當(dāng)前用戶(hù);series是獲取的當(dāng)前的UUID值;token是用戶(hù)名、密碼、cookie到期時(shí)間、以及自定義的salt經(jīng)過(guò)sha256非對(duì)稱(chēng)加密之后的字符串;validTime是到期時(shí)間。b:向“remember-me”這個(gè)cookie保存的記錄值是用戶(hù)名和UUID值經(jīng)過(guò)base64編碼之后的字符串

  4. 保存記錄,并返回到controller中操作

(5)Controller層:

package cn.zifangsky.controller;

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

import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;

import cn.zifangsky.manager.UserManager;
import cn.zifangsky.model.User;

@Controller
public class UserController {
	@Resource(name = "userManagerImpl")
	private UserManager userManager;

	/**
	 * 用戶(hù)主頁(yè)
	 */
	@RequestMapping("/user/index.html")
	public ModelAndView userIndex() {
		return new ModelAndView("user/index");
	}

	/**
	 * 登錄校驗(yàn)
	 */
	@RequestMapping("/check.html")
	public ModelAndView login(@RequestParam("username") String username, @RequestParam("password") String password,
			@RequestParam(name = "remember-me", required = false) boolean rememberme, HttpServletRequest request,
			HttpServletResponse response, RedirectAttributes redirectAttributes) {
		HttpSession session = request.getSession();
		User user = new User();
		user.setName(username);
		user.setPassword(password);

		User result = userManager.login(user, rememberme, response);
		if (result != null) {
			ModelAndView mAndView = null;
			//登錄之前地址
			String callback = (String) session.getAttribute("callback");
			session.removeAttribute("callback"); // 獲取之后移除
			// 基本路徑
			String basePath = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort()
					+ request.getContextPath();
			if (StringUtils.isNotBlank(callback)) {
				String[] urls = callback.split(basePath);
				if (urls.length == 2 && StringUtils.isNotBlank(urls[1])) {
					mAndView = new ModelAndView("redirect:" + urls[1]);
				}else{
					mAndView = new ModelAndView("redirect:/user/index.html");
				}
			}else{
				mAndView = new ModelAndView("redirect:/user/index.html");
			}
			
			session.setAttribute("user", result); // 登錄成功之后加入session中
			redirectAttributes.addFlashAttribute("user", result); 

			return mAndView;
		} else {
			return new ModelAndView("redirect:/login.html");
		}
	}

	/**
	 * 退出登錄
	 */
	@RequestMapping("/logout.html")
	public ModelAndView logout(HttpServletRequest request, HttpServletResponse response) {
		ModelAndView mAndView = new ModelAndView("redirect:/login.html");

		userManager.logout(request, response);

		return mAndView;
	}
}

在這里,對(duì)“callback”的操作主要是在攔截器中判斷是否能夠自動(dòng)登錄時(shí),如果能夠登錄那么不用多說(shuō)直接轉(zhuǎn)到目標(biāo)頁(yè)面;如果不能通過(guò)驗(yàn)證,那么需要跳轉(zhuǎn)到登錄頁(yè)面進(jìn)行用戶(hù)名、密碼登錄,這里的callback參數(shù)的目的就是在攔截器中驗(yàn)證失敗跳轉(zhuǎn)到登錄頁(yè)面之前,將本來(lái)想要訪(fǎng)問(wèn)的頁(yè)面路徑存儲(chǔ)在session中,然后在controller中登錄成功之后從session中取出,最后再重定向到那個(gè)目標(biāo)頁(yè)面

如果對(duì)這里的重定向等代碼不太理解的話(huà),建議可以參考下我在本篇文章開(kāi)始時(shí)列舉的那幾篇文章

(6)幾個(gè)測(cè)試使用的前臺(tái)頁(yè)面:

首先給出這幾個(gè)頁(yè)面之間的層次關(guān)系:

SpringMVC中使用Interceptor+Cookie實(shí)現(xiàn)在一定天數(shù)之內(nèi)自動(dòng)登錄

i)login.jsp:

<%@page import="java.security.SecureRandom"%>
<%@ 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">
<base href="<%=basePath%>">
<title>SpringMVC Cookie Demo</title>
<%
	SecureRandom random = new SecureRandom();
	random.setSeed(8738);
	double _csrf = random.nextDouble();
	session.setAttribute("_csrf", _csrf);
%>
</head>
<body>
	<div align="center">
		<h3>SpringMVC Cookie Demo</h3>
		<form action="check.html" method="post">
			<table>
				<tr>
					<td>用戶(hù)名:</td>
					<td><input type="text" name="username" /></td>
				</tr>
				<tr>
					<td>密碼:</td>
					<td><input type="password" name="password" /></td>
				</tr>
				<tr>
					<td><input name="remember-me" type="checkbox">30天內(nèi)自動(dòng)登錄</input></td>
				</tr>
				<tr>
					<td colspan="2" align="center"><input type="submit" value="登錄" />
						<input type="reset" value="重置" /></td>
				</tr>			
			</table>
			<input type="hidden" name="_csrf" value="<%=_csrf %>" />
		</form>
		
	</div>
</body>
</html>

登錄用的form表單

ii)user目錄下的index.jsp:

<%@ 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">
<base href="<%=basePath%>">
<title>SpringMVC Cookie Demo</title>
</head>
<body>
	<div align="center">
		<h3>SpringMVC Cookie Demo</h3>
		<div align="right">
			<a href="logout.html">退出登錄</a>
		</div>
		Hello <b>${user.name}</b>,welcome to user home page!
	</div>
</body>
</html>

iii)callback.jsp:

<%@ 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+"/";
%>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
	<base href="<%=basePath%>">
	<title>SpringMVC Cookie Demo</title>
</head>
<body>
	<div align="center">
		<h3>SpringMVC Cookie Demo</h3>
		測(cè)試 callback 頁(yè)面跳轉(zhuǎn)
	</div>
</body>
</html>

這個(gè)頁(yè)面主要是為了測(cè)試登錄之后是否能夠跳轉(zhuǎn)到原來(lái)想要訪(fǎng)問(wèn)的頁(yè)面

(7)攔截器UserInterceptor:

package cn.zifangsky.interceptor;

import java.util.Calendar;
import java.util.Date;
import java.util.UUID;

import javax.annotation.Resource;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

import cn.zifangsky.manager.UserManager;
import cn.zifangsky.manager.impl.PersistentLoginsManagerImpl;
import cn.zifangsky.model.PersistentLogins;
import cn.zifangsky.model.User;
import cn.zifangsky.utils.CookieConstantTable;
import cn.zifangsky.utils.CookieUtils;
import cn.zifangsky.utils.EncryptionUtil;

public class UserInterceptor extends HandlerInterceptorAdapter {
	@Resource(name = "persistentLoginsManagerImpl")
	private PersistentLoginsManagerImpl persistentLoginsManagerImpl;
	@Resource(name = "userManagerImpl")
	private UserManager userManager;

	/**
	 * 用于處理自動(dòng)登錄
	 */
	public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
			throws Exception {
		HttpSession session = request.getSession();
		User user = (User) session.getAttribute("user");

		// 已登錄
		if (user != null) {
			return true;
		} else {
			// 從cookie中取值
			Cookie rememberme = CookieUtils.getCookie(request, CookieConstantTable.RememberMe);
			if (rememberme != null) {
				String cookieValue = EncryptionUtil.base64Decode(rememberme.getValue());

				String[] cValues = cookieValue.split(":");
				if (cValues.length == 2) {
					String usernameByCookie = cValues[0]; // 獲取用戶(hù)名
					String uuidByCookie = cValues[1]; // 獲取UUID值
					// 到數(shù)據(jù)庫(kù)中查詢(xún)自動(dòng)登錄記錄
					PersistentLogins pLogins = persistentLoginsManagerImpl.selectByUsernameAndSeries(usernameByCookie,
							uuidByCookie);
					if (pLogins != null) {
						String savedToken = pLogins.getToken(); // 數(shù)據(jù)庫(kù)中保存的密文

						// 獲取有效時(shí)間
						Date savedValidtime = pLogins.getValidtime();
						Date currentTime = new Date();

						// 如果還在cookie有效期之內(nèi),繼續(xù)判斷是否可以自動(dòng)登錄
						if (currentTime.before(savedValidtime)) {
							User u = userManager.selectByName(usernameByCookie);
							if (u != null) {
								Calendar calendar = Calendar.getInstance();
								calendar.setTime(pLogins.getValidtime());
								// 精確到分的時(shí)間字符串
								String timeString = calendar.get(Calendar.YEAR) + "-" + calendar.get(Calendar.MONTH)
										+ "-" + calendar.get(Calendar.DAY_OF_MONTH) + "-"
										+ calendar.get(Calendar.HOUR_OF_DAY) + "-" + calendar.get(Calendar.MINUTE);
								// 為了校驗(yàn)而生成的密文
								String newToken = EncryptionUtil.sha256Hex(u.getName() + "_" + u.getPassword() + "_"
										+ timeString + "_" + CookieConstantTable.salt);

								// 校驗(yàn)sha256加密的值,如果不一樣則表示用戶(hù)部分信息已被修改,需要重新登錄
								if (savedToken.equals(newToken)) {
									/**
									 * 為了提高安全性,每次登錄之后都更新自動(dòng)登錄的cookie值
									 */
									// 更新cookie值
									String uuidNewString = UUID.randomUUID().toString();
									String newCookieValue = EncryptionUtil
											.base64Encode(u.getName() + ":" + uuidNewString);
									CookieUtils.editCookie(request, response, CookieConstantTable.RememberMe,
											newCookieValue, null);

									// 更新數(shù)據(jù)庫(kù)
									pLogins.setSeries(uuidNewString);
									persistentLoginsManagerImpl.updateByPrimaryKeySelective(pLogins);

									/**
									 * 將用戶(hù)加到session中,不退出瀏覽器時(shí)就只需判斷session即可
									 */
									session.setAttribute("user", u);

									return true;  //校驗(yàn)成功,此次攔截操作完成
								} else { // 用戶(hù)部分信息被修改,刪除cookie并清空數(shù)據(jù)庫(kù)中的記錄
									CookieUtils.delCookie(response, rememberme);
									persistentLoginsManagerImpl.deleteByPrimaryKey(pLogins.getId());
								}
							}
						} else { // 超過(guò)保存的有效期,刪除cookie并清空數(shù)據(jù)庫(kù)中的記錄
							CookieUtils.delCookie(response, rememberme);
							persistentLoginsManagerImpl.deleteByPrimaryKey(pLogins.getId());
						}
					}
				}
			}
			//將來(lái)源地址存放在session中,登錄成功之后跳回原地址
			String callback = request.getRequestURL().toString();
			session.setAttribute("callback", callback);
			response.sendRedirect(
					request.getContextPath() + "/login.html?callback=" + callback);
			return false;
		}
	}

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

		super.afterCompletion(request, response, handler, ex);
	}

}

在這里,驗(yàn)證流程如下:

  1. 如果在session中存在“user”對(duì)象,那么驗(yàn)證通過(guò),允許訪(fǎng)問(wèn)

  2. 如果session中沒(méi)有,則需要取出cookie中名字為“remember-me”對(duì)應(yīng)的值,用于下一步驗(yàn)證

  3. 根據(jù)base64解碼之后的用戶(hù)名和UUID值從表“persistent_logins”查詢(xún)記錄

  4. 取出數(shù)據(jù)庫(kù)中保存的token值和到期時(shí)間,根據(jù)同樣的加密方法加密待校驗(yàn)的密文,然后和數(shù)據(jù)庫(kù)中的token相比較

  5. 如果一樣,則表示可以自動(dòng)登錄。同時(shí),為了提高安全性,在校驗(yàn)成功之后更新用戶(hù)端的用于自動(dòng)登錄的cookie記錄

  6. 將“user”對(duì)象添加到session中,本次攔截器校驗(yàn)通過(guò)

當(dāng)然,我這里只是簡(jiǎn)單敘述了下流程,更具體的流程可以自行參考代碼中的注釋

三 測(cè)試

(1)測(cè)試使用cookie實(shí)現(xiàn)自動(dòng)登錄:

啟動(dòng)項(xiàng)目后,訪(fǎng)問(wèn):http://localhost:9180/CookieDemo/login.html

輸入用戶(hù)名、密碼并勾上“30天內(nèi)自動(dòng)登錄”:

SpringMVC中使用Interceptor+Cookie實(shí)現(xiàn)在一定天數(shù)之內(nèi)自動(dòng)登錄

點(diǎn)擊登錄之后,可以發(fā)現(xiàn)頁(yè)面跳轉(zhuǎn)到了:http://localhost:9180/CookieDemo/user/index.html

同時(shí)生成了一條名為“remember-me”的cookie記錄值,其值是:YWRtaW46YzhjYTU3NjktNDhjZi00NWQ4LTk4YzQtM2QzMDMwNWVlMWY5

SpringMVC中使用Interceptor+Cookie實(shí)現(xiàn)在一定天數(shù)之內(nèi)自動(dòng)登錄如果使用在線(xiàn)base64解碼工具解碼之后可以發(fā)現(xiàn),這個(gè)cookie值的原文是:

SpringMVC中使用Interceptor+Cookie實(shí)現(xiàn)在一定天數(shù)之內(nèi)自動(dòng)登錄

恰好與數(shù)據(jù)庫(kù)中persistent_logins表中的記錄相對(duì)應(yīng):

SpringMVC中使用Interceptor+Cookie實(shí)現(xiàn)在一定天數(shù)之內(nèi)自動(dòng)登錄接著,退出瀏覽器之后再次打開(kāi)該瀏覽器訪(fǎng)問(wèn):http://localhost:9180/CookieDemo/user/index.html

可以發(fā)現(xiàn):可以直接訪(fǎng)問(wèn)該頁(yè)面,同時(shí)已經(jīng)是登錄狀態(tài)了。到此,我們的目的已經(jīng)達(dá)成了

(2)測(cè)試登錄之后跳回到原來(lái)想要訪(fǎng)問(wèn)的頁(yè)面:

刪除瀏覽器中的“remember-me”這個(gè)cookie,或者刪掉數(shù)據(jù)庫(kù)中persistent_logins表中的記錄,然后在退出登錄之后訪(fǎng)問(wèn):http://localhost:9180/CookieDemo/user/callback.html

可以發(fā)現(xiàn),頁(yè)面已經(jīng)被自動(dòng)重定向到登錄頁(yè)面了

SpringMVC中使用Interceptor+Cookie實(shí)現(xiàn)在一定天數(shù)之內(nèi)自動(dòng)登錄

接著,輸入用戶(hù)名、密碼登錄,可以發(fā)現(xiàn):在登錄成功之后能夠正常跳轉(zhuǎn)到我們?cè)瓉?lái)請(qǐng)求的頁(yè)面:

SpringMVC中使用Interceptor+Cookie實(shí)現(xiàn)在一定天數(shù)之內(nèi)自動(dòng)登錄附:本次測(cè)試項(xiàng)目的完整源代碼:

  • 鏈接:http://pan.baidu.com/s/1nvo72a9 密碼:dkxa

PS:上面圖片中的水印是我個(gè)人博客的域名,因此還請(qǐng)管理員手下留情不要給我標(biāo)為“轉(zhuǎn)載文章”,謝謝?。?!

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀(guān)點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI