广告
返回顶部
首页 > 资讯 > 数据库 >SpringMVC中使用Interceptor+Cookie实现在一定天数之内自动登录
  • 313
分享到

SpringMVC中使用Interceptor+Cookie实现在一定天数之内自动登录

2024-04-02 19:04:59 313人浏览 安东尼
摘要

一 简介本篇文章主要介绍:在springMVC中如何使用Interceptor+Cookie实现在一定天数之内自动登录的功能。同时还介绍“如果校验失败则跳转到登录页面,在输入用户名、密码等完成登录之后又自动

一 简介

本篇文章主要介绍:在springMVC中如何使用Interceptor+Cookie实现在一定天数之内自动登录的功能。同时还介绍“如果校验失败则跳转到登录页面,在输入用户名、密码等完成登录之后又自动跳转到原页面”的功能实现

本次测试环境是SSM框架,在正式介绍本篇文章之前,建议需要熟悉以下前置知识点:

  • mybatis中使用mybatis-generator结合Ant脚本快速自动生成Model、Mapper等文件(PS:这是为了快速生成一些基本文件)    https://www.zifangsky.cn/431.html

  • springmvc通过配置mvc:view-controller直接解析到视图页面(PS:这是为了简化controller中的代码)    Https://www.zifangsky.cn/648.html

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

  • SpringMVC中使用forward和redirect进行转发和重定向以及重定向时如何传参详解(PS:这是介绍重定向时如何传参的问题)    https://www.zifangsky.cn/661.html

  • 在SpringMVC中使用拦截器(interceptor)拦截CSRF***(PS:这是介绍拦截器的一些基础用法)    https://www.zifangsky.cn/671.html

二 代码实现

(1)数据库表设计:

我这里采用的是Mysql,同时设计了两张表,分别是:user和persistent_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');

这张表很简单,就是一张普通的用户表

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;

这张表是用户校验用户自动登录的表。设计这张表的原因是我看过一些网上的文章介绍使用cookie自动登录,但是他们基本上都是将用户名、密码、salt等字符串拼接之后md5加密然后保存在cookie中。虽然使用了md5这类非对称加密方式,但是将密码这类关键信息保存在用户端,我觉得是不太靠谱的。因此设计了这张表,将用户名、密码等关键信息加密之后的数据保存到这张表中,在用户的cookie里只保存了没有特殊含义的UUID值以及用户名

这张表中的几个字段的含义分别是:

  • id    主键

  • username    用户名

  • series    用户使用密码登录成功之后获取的一个UUID值,同时用户端保存的cookie记录就是:EncryptionUtil.base64Encode(用户名:此UUID值)

  • token    在拦截器中校验是否能够登录的密文,其加密方式是:EncryptionUtil.sha256Hex(用户名 + “_” + 密码 + “_” + 自动登录失效的时间点的字符串 + “_” +  自定义的salt)

  • validTime    自动登录失效的时间,即:这个时间点之后只能重新用用户名、密码登录,如果在重新登录时勾选了“30天内自动登录”则更新该用户在persistent_logins这个表中的自动登录记录

(2)几个基本的配置文件:

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>
	<!-- 这两个listener不加会出现无法依赖注入问题 -->
	<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>
    User selectByUser(User user);
    
    User selectByName(String name);
}

这里除了使用插件自动生成的几个方法之外,还添加了两个其他的方法,它们对应的sql语句是:

  <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);

	
	PersistentLogins selectByUsernameAndSeries(@Param("username") String username, @Param("series") String series);

	
	PersistentLogins selectByUsername(@Param("username") String username);
}

同样,这里也添加了两个其他的方法,它们对应的SQL语句是:

  <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层(即:业务逻辑层):

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);
	
	
    User selectByName(String name);
    
	
	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);
    
	PersistentLogins selectByUsernameAndSeries(String username,String series);
	
	PersistentLogins selectByUsername(String username);	
}

iii)PersistentLoginsManagerImpl实现类:

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实现类:

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();
		// 如果用户名和密码不为空,执行登录
		if (StringUtils.isNotBlank(user.getName()) && StringUtils.isNotBlank(user.getPassword())) {
			result = userMapper.selectByUser(user);
			// 如果rememberme为true,则保存cookie值,下次自动登录
			if (result != null && rememberme == true) {
				// 有效期
				Calendar calendar = Calendar.getInstance();
				calendar.add(Calendar.MONTH, 1); // 一个月
				Date validTime = calendar.getTime();
				// 精确到分的时间字符串
				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加密用户信息
				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);

				// 在数据库中保存自动登录记录(如果已有该用户的记录则更新记录)
				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中获取用户详情
		User user = (User) request.getSession().getAttribute("user");
		//删除数据库中的自动登录记录
		PersistentLogins pLogins = persistentLoginsManagerImpl.selectByUsername(user.getName());
		if(pLogins != null)
			persistentLoginsManagerImpl.deleteByPrimaryKey(pLogins.getId());
		//清除session和用于自动登录的cookie
		request.getSession().removeAttribute("user");
		CookieUtils.delCookie(request, response, CookieConstantTable.RememberMe);
	}
}

注:CookieConstantTable类:

package cn.zifangsky.utils;

public class CookieConstantTable {
	// cookie的有效期默认为30天
	public final static int COOKIE_MAX_AGE = 60 * 60 * 24 * 30; 
	//cookie加密时的额外的salt
	public final static String salt = "www.zifangsky.cn";
	//自动登录的Cookie名
	public final static String RememberMe = "remember-me";
}

CookieUtils类:

package cn.zifangsky.utils;

import javax.servlet.http.HttpServletResponse;

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

public class CookieUtils {

	

	
	public static void addCookie(HttpServletResponse response, Cookie cookie) {
		if (cookie != null)
			response.addCookie(cookie);
	}

	
	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);
		}
	}

	
	public static void addCookie(HttpServletResponse response, String cookieName, String cookieValue, String domain) {
		addCookie(response, cookieName, cookieValue, domain, true, CookieConstantTable.COOKIE_MAX_AGE, "/", false);
	}

	
	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;
	}

	
	public static String getCookieValue(HttpServletRequest request, String cookieName) {
		Cookie cookie = getCookie(request, cookieName);
		if (cookie == null)
			return null;
		else
			return cookie.getValue();
	}

	
	public static void delCookie(HttpServletResponse response, Cookie cookie) {
		if (cookie != null) {
			cookie.setPath("/"); 
			cookie.setMaxAge(0);
			cookie.setValue(null);

			response.addCookie(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);
		}
	}

	
	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类:

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 {
    
    public static String base64Encode(String data){
        return Base64.encodeBase64String(data.getBytes());
    }
     
    
    public static String base64Decode(String data) throws UnsupportedEncodingException{
        return new String(Base64.decodeBase64(data.getBytes()),"utf-8");
    }
     
    
    public static String md5Hex(String data){
        return DigestUtils.md5Hex(data);
    }
     
    
    public static String sha1Hex(String data){
        return DigestUtils.sha1Hex(data);
    }
     
    
    public static String sha256Hex(String data){
        return DigestUtils.sha256Hex(data);
    }
     
}

这个方法类本质上调用的是 commons-codec-1.10.jar 这个jar包中的方法

在这个类中,关于退出登录就不用多做解释了,有详细注释自己参考下就行

关于这个登录方法,实际上我这里的执行流程是这样的:

  1. 根据用户名、密码执行登录验证

  2. 如果前台登录的fORM表单中勾选了“30天内自动登录”的选项,那么就执行下面的保存登录记录到persistent_logins这个表以及cookie中;如果没勾选,那么就直接将验证结果返回到controller中

  3. 执行保存记录的这个操作,实际上分为以下两步操作:a:向表persistent_logins保存记录,username是当前用户;series是获取的当前的UUID值;token是用户名、密码、cookie到期时间、以及自定义的salt经过sha256非对称加密之后的字符串;validTime是到期时间。b:向“remember-me”这个cookie保存的记录值是用户名和UUID值经过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;

	
	@RequestMapping("/user/index.html")
	public ModelAndView userIndex() {
		return new ModelAndView("user/index");
	}

	
	@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;
	}
}

在这里,对“callback”的操作主要是在拦截器中判断是否能够自动登录时,如果能够登录那么不用多说直接转到目标页面;如果不能通过验证,那么需要跳转到登录页面进行用户名、密码登录,这里的callback参数的目的就是在拦截器中验证失败跳转到登录页面之前,将本来想要访问的页面路径存储在session中,然后在controller中登录成功之后从session中取出,最后再重定向到那个目标页面

如果对这里的重定向等代码不太理解的话,建议可以参考下我在本篇文章开始时列举的那几篇文章

(6)几个测试使用的前台页面:

首先给出这几个页面之间的层次关系:

SpringMVC中使用Interceptor+Cookie实现在一定天数之内自动登录

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>用户名:</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天内自动登录</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>
		测试 callback 页面跳转
	</div>
</body>
</html>

这个页面主要是为了测试登录之后是否能够跳转到原来想要访问的页面

(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;

	
	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]; // 获取用户名
					String uuidByCookie = cValues[1]; // 获取UUID值
					// 到数据库中查询自动登录记录
					PersistentLogins pLogins = persistentLoginsManagerImpl.selectByUsernameAndSeries(usernameByCookie,
							uuidByCookie);
					if (pLogins != null) {
						String savedToken = pLogins.getToken(); // 数据库中保存的密文

						// 获取有效时间
						Date savedValidtime = pLogins.getValidtime();
						Date currentTime = new Date();

						// 如果还在cookie有效期之内,继续判断是否可以自动登录
						if (currentTime.before(savedValidtime)) {
							User u = userManager.selectByName(usernameByCookie);
							if (u != null) {
								Calendar calendar = Calendar.getInstance();
								calendar.setTime(pLogins.getValidtime());
								// 精确到分的时间字符串
								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);
								// 为了校验而生成的密文
								String newToken = EncryptionUtil.sha256Hex(u.getName() + "_" + u.getPassword() + "_"
										+ timeString + "_" + CookieConstantTable.salt);

								// 校验sha256加密的值,如果不一样则表示用户部分信息已被修改,需要重新登录
								if (savedToken.equals(newToken)) {
									
									// 更新cookie值
									String uuidNewString = UUID.randomUUID().toString();
									String newCookieValue = EncryptionUtil
											.base64Encode(u.getName() + ":" + uuidNewString);
									CookieUtils.editCookie(request, response, CookieConstantTable.RememberMe,
											newCookieValue, null);

									// 更新数据库
									pLogins.setSeries(uuidNewString);
									persistentLoginsManagerImpl.updateByPrimaryKeySelective(pLogins);

									
									session.setAttribute("user", u);

									return true;  //校验成功,此次拦截操作完成
								} else { // 用户部分信息被修改,删除cookie并清空数据库中的记录
									CookieUtils.delCookie(response, rememberme);
									persistentLoginsManagerImpl.deleteByPrimaryKey(pLogins.getId());
								}
							}
						} else { // 超过保存的有效期,删除cookie并清空数据库中的记录
							CookieUtils.delCookie(response, rememberme);
							persistentLoginsManagerImpl.deleteByPrimaryKey(pLogins.getId());
						}
					}
				}
			}
			//将来源地址存放在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);
	}

}

在这里,验证流程如下:

  1. 如果在session中存在“user”对象,那么验证通过,允许访问

  2. 如果session中没有,则需要取出cookie中名字为“remember-me”对应的值,用于下一步验证

  3. 根据base64解码之后的用户名和UUID值从表“persistent_logins”查询记录

  4. 取出数据库中保存的token值和到期时间,根据同样的加密方法加密待校验的密文,然后和数据库中的token相比较

  5. 如果一样,则表示可以自动登录。同时,为了提高安全性,在校验成功之后更新用户端的用于自动登录的cookie记录

  6. 将“user”对象添加到session中,本次拦截器校验通过

当然,我这里只是简单叙述了下流程,更具体的流程可以自行参考代码中的注释

三 测试

(1)测试使用cookie实现自动登录:

启动项目后,访问:http://localhost:9180/CookieDemo/login.html

输入用户名、密码并勾上“30天内自动登录”:

SpringMVC中使用Interceptor+Cookie实现在一定天数之内自动登录

点击登录之后,可以发现页面跳转到了:http://localhost:9180/CookieDemo/user/index.html

同时生成了一条名为“remember-me”的cookie记录值,其值是:YWRtaW46YzhjYTU3NjktNDhjZi00NWQ4LTk4YzQtM2QzMDMwNWVlMWY5

SpringMVC中使用Interceptor+Cookie实现在一定天数之内自动登录如果使用在线base64解码工具解码之后可以发现,这个cookie值的原文是:

SpringMVC中使用Interceptor+Cookie实现在一定天数之内自动登录

恰好与数据库中persistent_logins表中的记录相对应:

SpringMVC中使用Interceptor+Cookie实现在一定天数之内自动登录接着,退出浏览器之后再次打开该浏览器访问:http://localhost:9180/CookieDemo/user/index.html

可以发现:可以直接访问该页面,同时已经是登录状态了。到此,我们的目的已经达成了

(2)测试登录之后跳回到原来想要访问的页面:

删除浏览器中的“remember-me”这个cookie,或者删掉数据库中persistent_logins表中的记录,然后在退出登录之后访问:http://localhost:9180/CookieDemo/user/callback.html

可以发现,页面已经被自动重定向到登录页面了

SpringMVC中使用Interceptor+Cookie实现在一定天数之内自动登录

接着,输入用户名、密码登录,可以发现:在登录成功之后能够正常跳转到我们原来请求的页面:

SpringMVC中使用Interceptor+Cookie实现在一定天数之内自动登录附:本次测试项目的完整源代码:

  • 链接:http://pan.baidu.com/s/1nvo72a9 密码:dkxa

PS:上面图片中的水印是我个人博客的域名,因此还请管理员手下留情不要给我标为“转载文章”,谢谢!!!

您可能感兴趣的文档:

--结束END--

本文标题: SpringMVC中使用Interceptor+Cookie实现在一定天数之内自动登录

本文链接: https://www.lsjlt.com/news/42518.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作