SpringBoot集成Spring Security(4)—— 自动登录2

一、前言

        上一篇介绍了关于自动登录无持久化方式的内容,通过源码我们知道这种方式的cookie中包含了用户名、密码,这个从安全性上讲是存在风险的,那还有另一种方式就是通过数据库持久化cookie所包含的信息,并且是跟用户名、密码不相关的内容。

二、实现

        Spring Security中已经定义好了对于数据库操作的类JdbcTokenRepositoryImpl,所以不需要我们自己定义了,但是存储数据的表默认是没有的需要创建。那在原有的基础上在SecurityConfig类中要增加一些内容:

// 导入数据源
@Autowired
DataSource dataSource;

@Override
protected void configure(HttpSecurity http) throws Exception {
    http
            .csrf().disable() // CSRF(跨站请求伪造)禁用,默认开启,会检测请求中是否包含令牌,没有则拒绝并返回403
            .authorizeRequests()
            // 对静态资源放行
            .mvcMatchers(HttpMethod.GET,
                    "/*.html",
                    "/**/*.html",
                    "/**/*.css",
                    "/**/*.js")
            .permitAll()
            .anyRequest().authenticated()// 除了上面其他都必须鉴权
            .and()
            .formLogin().loginPage("/loginPage")// 未认证时访问跳转登录页面
            .loginProcessingUrl("/doLogin")// 表单登录url设置,默认/login
            // 登录成功跳转
            .defaultSuccessUrl("/main",true)
            .permitAll()
            .and()
            .logout().permitAll()
            .and()
            .rememberMe() // 自动登录
            .tokenRepository(jdbcTokenRepository()) // 添加;
}

// 注入JdbcTokenRepositoryImpl 
@Bean
public JdbcTokenRepositoryImpl jdbcTokenRepository(){
    JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
    // 表示自动创建表,但是如果重复启动则会报异常,提示表存在了
    // jdbcTokenRepository.setCreateTableOnStartup(true);
    jdbcTokenRepository.setDataSource(dataSource); // 设置数据源
    return jdbcTokenRepository;
}

要注入数据源,还有操作数据库的bean,上面有个注释提到的在jdbcTokenRepository中是可以设置自动创建数据表,但是问题也在注释说明了,那我们其实可以把JdbcTokenRepository中创建表的语句拷贝出来自己手动建表就行了。

// 建表的方法
protected void initDao() {
    if (this.createTableOnStartup) {
        this.getJdbcTemplate().execute("create table persistent_logins (username varchar(64) not null, series varchar(64) primary key, token varchar(64) not null, last_used timestamp not null)");
    }
}

建表sql:

create table persistent_logins (username varchar(64) not null, series varchar(64) primary key, token varchar(64) not null, last_used timestamp not null)

建完表然后运行,登录成功后查看数据库:
在这里插入图片描述

三、细节

        下面来看下这种方式的cookie是一些什么内容,上面提到过这种方式的cookie内容与用户名、密码无关,具体源码位于PersistentTokenBasedRememberMeServices:

    protected void onLoginSuccess(HttpServletRequest request, HttpServletResponse response, Authentication successfulAuthentication) {
        String username = successfulAuthentication.getName();
        this.logger.debug("Creating new persistent login for user " + username);
        // 创建token
        PersistentRememberMeToken persistentToken = new PersistentRememberMeToken(username, this.generateSeriesData(), this.generateTokenData(), new Date());

        try {
        	// 添加到数据库
            this.tokenRepository.createNewToken(persistentToken);
            // 创建cookie并添加到response
            this.addCookie(persistentToken, request, response);
        } catch (Exception var7) {
            this.logger.error("Failed to save persistent token ", var7);
        }

    }
    
    protected String generateSeriesData() {
        byte[] newSeries = new byte[this.seriesLength];
        this.random.nextBytes(newSeries);
        return new String(Base64.getEncoder().encode(newSeries));
    }

    protected String generateTokenData() {
        byte[] newToken = new byte[this.tokenLength];
        this.random.nextBytes(newToken);
        return new String(Base64.getEncoder().encode(newToken));
    }
    
    private void addCookie(PersistentRememberMeToken token, HttpServletRequest request, HttpServletResponse response) {
    	this.setCookie(new String[]{token.getSeries(), token.getTokenValue()}, this.getTokenValiditySeconds(), request, response);
    }

        其中PersistentRememberMeToken数据表persistent_logins对应表示持久化的token的实体类,包含了用户名,序列号、token、创建token的时间,其中序列号和token都是随机生成的,并且在addCookie中创建cookie用到的就只是序列号和token,就没有用户名、密码。
        那这种方式自动登录时又是怎么校验的,还是在这个类中的processAutoLoginCookie方法:

    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) + "'");
        } else {
        	// 获取传过来cookie中的序列号
            String presentedSeries = cookieTokens[0];
            // 获取传过来cookie中的token
            String presentedToken = cookieTokens[1];
            // 通过序列号查询数据表,得到PersistentRememberMeToken对象
            PersistentRememberMeToken token = this.tokenRepository.getTokenForSeries(presentedSeries);
            if (token == null) {
                throw new RememberMeAuthenticationException("No persistent token found for series id: " + presentedSeries);
            } else if (!presentedToken.equals(token.getTokenValue())) {// token值判断
                this.tokenRepository.removeUserTokens(token.getUsername());
                throw new CookieTheftException(this.messages.getMessage("PersistentTokenBasedRememberMeServices.cookieStolen", "Invalid remember-me token (Series/token) mismatch. Implies previous cookie theft attack."));
            } else if (token.getDate().getTime() + (long)this.getTokenValiditySeconds() * 1000L < System.currentTimeMillis()) {// 有效时间判断 
                throw new RememberMeAuthenticationException("Remember-me login has expired");
            } else {
                if (this.logger.isDebugEnabled()) {
                    this.logger.debug("Refreshing persistent login token for user '" + token.getUsername() + "', series '" + token.getSeries() + "'");
                }
				// 满足上面判断的话,就说明满足自动登录了
				// 创建新的PersistentRememberMeToken,主要是更新时间
                PersistentRememberMeToken newToken = new PersistentRememberMeToken(token.getUsername(), token.getSeries(), this.generateTokenData(), new Date());

                try {
                	// 更新数据表
                    this.tokenRepository.updateToken(newToken.getSeries(), newToken.getTokenValue(), newToken.getDate());
                    // 创建cookie并添加到response
                    this.addCookie(newToken, request, response);
                } catch (Exception var9) {
                    this.logger.error("Failed to update token: ", var9);
                    throw new RememberMeAuthenticationException("Autologin failed due to data access problem");
                }
				// 最后通过persistentRememberMeToken中的用户名获取用户数据
                return this.getUserDetailsService().loadUserByUsername(token.getUsername());
            }
        }
    }

上面的注释已经把过程写的很清楚了,主要就是通过cookie中的序列号查询数据表中已有的PersistentRememberMeToken数据进行比对,满足条件即可自动登录。

这种方式重启服务器也不会影响到此前用户的cookie的有效性了,只要是在期限内重启服务器也能正常自动登录。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值