SpringSecurity学习笔记(九)RememberMe进阶

参考视频,编程不良人

前面我们介绍了rememberMe的实现原理,从中我们可以思考这样一个问题,如果我们的cookie被非法用户获取,然后携带这个cookie进行访问我们的项目中的内容,就会导致非法用户登录。这个问题怎么解决呢?

RememberMe进阶

在之前我们提到了cookie的生成以及验证都是在TokenBasedRememberMeServices这个类里面完成的,RememberMeServices这个接口还有一个实现类PersistentTokenBasedRememberMeServices
在这里插入图片描述
PersistentTokenBasedRememberMeServices就是一个进阶版本的rememberMe的实现,这个生成的cookie是SeriesTokenValue组成的

protected void onLoginSuccess(HttpServletRequest request,
			HttpServletResponse response, Authentication successfulAuthentication) {
		String username = successfulAuthentication.getName();

		logger.debug("Creating new persistent login for user " + username);

		PersistentRememberMeToken persistentToken = new PersistentRememberMeToken(
				username, generateSeriesData(), generateTokenData(), new Date());
		try {
			tokenRepository.createNewToken(persistentToken);
			//这个生成的cookie
			addCookie(persistentToken, request, response);
		}
		catch (Exception e) {
			logger.error("Failed to save persistent token ", e);
		}
	}
private void addCookie(PersistentRememberMeToken token, HttpServletRequest request,
			HttpServletResponse response) {
		setCookie(new String[] { token.getSeries(), token.getTokenValue() },
				getTokenValiditySeconds(), request, response);
	}

如果下次会话过期了之后就会走下面这块的认证逻辑,这里面的PersistentTokenRepository 的实现默认是基于内存的实现

private PersistentTokenRepository tokenRepository = new InMemoryTokenRepositoryImpl();
protected UserDetails processAutoLoginCookie(String[] cookieTokens,
			HttpServletRequest request, HttpServletResponse response) {

		if (cookieTokens.length != 2) {
			throw new InvalidCookieException("Cookie token did not contain " + 2
					+ " tokens, but contained '" + Arrays.asList(cookieTokens) + "'");
		}

		final String presentedSeries = cookieTokens[0];
		final String presentedToken = cookieTokens[1];

		PersistentRememberMeToken token = tokenRepository
				.getTokenForSeries(presentedSeries);

		if (token == null) {
			// No series match, so we can't authenticate using this cookie
			throw new RememberMeAuthenticationException(
					"No persistent token found for series id: " + presentedSeries);
		}

		// We have a match for this user/series combination
		//这里对比内存中token对应的value和cookie里面的进行对比,如果相同则认证通过
		if (!presentedToken.equals(token.getTokenValue())) {
			// Token doesn't match series value. Delete all logins for this user and throw
			// an exception to warn them.
			tokenRepository.removeUserTokens(token.getUsername());

			throw new CookieTheftException(
					messages.getMessage(
							"PersistentTokenBasedRememberMeServices.cookieStolen",
							"Invalid remember-me token (Series/token) mismatch. Implies previous cookie theft attack."));
		}

		if (token.getDate().getTime() + getTokenValiditySeconds() * 1000L < System
				.currentTimeMillis()) {
			throw new RememberMeAuthenticationException("Remember-me login has expired");
		}

		// Token also matches, so login is valid. Update the token value, keeping the
		// *same* series number.
		if (logger.isDebugEnabled()) {
			logger.debug("Refreshing persistent login token for user '"
					+ token.getUsername() + "', series '" + token.getSeries() + "'");
		}

//下面这一块的逻辑就是把cookie进行一个更新,也就是说一旦会话失效,如果使用了之前的cookie就会生成新
//的cookie,原阿里的cookie九无法使用了。这在一定程度上增加了安全性
		PersistentRememberMeToken newToken = new PersistentRememberMeToken(
				token.getUsername(), token.getSeries(), generateTokenData(), new Date());

		try {
		//这里更新的时候series是不变的,变的是series对应的value值
			tokenRepository.updateToken(newToken.getSeries(), newToken.getTokenValue(),
					newToken.getDate());
			addCookie(newToken, request, response);
		}
		catch (Exception e) {
			logger.error("Failed to update token: ", e);
			throw new RememberMeAuthenticationException(
					"Autologin failed due to data access problem");
		}

		return getUserDetailsService().loadUserByUsername(token.getUsername());
	}

RememberMe的持久化令牌

对于前面的基于内存的记住我的功能,一旦项目重启了之后就需要重新登录,这有的时候是不符合要求的,我们需要把这种基于内存实现的方式,放到数据库里面进行实现。

在这里插入图片描述
默认的是基于内存的,我们可以自定义基于数据库的实现。

在配置类中添加

@Bean
    protected PersistentTokenBasedRememberMeServices persistentTokenBasedRememberMeServices(){
        PersistentTokenBasedRememberMeServices persistentTokenBasedRememberMeServices =
//                new PersistentTokenBasedRememberMeServices(UUID.randomUUID().toString(), myUserDetailService, new InMemoryTokenRepositoryImpl());
                new PersistentTokenBasedRememberMeServices(UUID.randomUUID().toString(), myUserDetailService, jdbcTokenRepository());
        return persistentTokenBasedRememberMeServices;
    }

    @Bean
    protected JdbcTokenRepositoryImpl jdbcTokenRepository(){
        JdbcTokenRepositoryImpl tokenRepository = new JdbcTokenRepositoryImpl();
        tokenRepository.setDataSource(dataSource);
        tokenRepository.setCreateTableOnStartup(true);
        return tokenRepository;
    }

在这里插入图片描述
这个时候启动项目就会生成表结构
在这里插入图片描述
此时我登录用户就会生成对应的cookie持久化到数据库
在这里插入图片描述
这个时候我重启项目然后刷新页面一样是不需要重新登录的。重启的时候要把这句话注释掉

//        tokenRepository.setCreateTableOnStartup(true);

刷新之后
在这里插入图片描述
可以发现token的值变了,series值没变,这和我们上面的分析是一致的。

对于前后端分离项目,如果要设置rememberMe,就需要修改下面的部分
在这里插入图片描述

protected boolean rememberMeRequested(HttpServletRequest request, String parameter) {
		if (alwaysRemember) {
			return true;
		}

		String paramValue = request.getParameter(parameter);

		if (paramValue != null) {
			if (paramValue.equalsIgnoreCase("true") || paramValue.equalsIgnoreCase("on")
					|| paramValue.equalsIgnoreCase("yes") || paramValue.equals("1")) {
				return true;
			}
		}

		if (logger.isDebugEnabled()) {
			logger.debug("Did not send remember-me cookie (principal did not set parameter '"
					+ parameter + "')");
		}

		return false;
	}

我们需要写个子类覆盖父类的rememberMeRequested方法。具体的做法可以参考不良人的视频

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

北海冥鱼未眠

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值