SpringBoot下SpringSecurity实现基于数据库表的下次自动登录(记住我RememberMe)

2021-07-10 661点热度 0人点赞 1条评论

本篇介绍如下在SpringBoot下使用SpringSecurity实现记住我(就是下次自动登录功能),以MySQL为例(数据库结构都一样,其他数据库稍加修改)

首先数据库建表

CREATE TABLE `t_persistent_logins` (
  `username` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
  `series` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
  `token` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
  `last_used` datetime DEFAULT NULL,
  PRIMARY KEY (`series`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='保持登录表';

修改SpringSecurity的配置类,增加自动登录相关

WebSecurityConfig.java 主要增加记住我(自动登录)相关的配置 如下

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.rememberme.PersistentTokenBasedRememberMeServices;
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;

import javax.sql.DataSource;

@Configuration
@EnableWebSecurity()
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    public static final String SECURITY_KEY = "my-app";

    @Autowired
    private MyUserDetailService myUserDetailService;


    @Autowired
    private DataSource dataSource;

    @Bean
    public PersistentTokenRepository persistentTokenRepository() {
        MyJdbcTokenRepositoryImpl jdbcTokenRepository = new MyJdbcTokenRepositoryImpl();
        jdbcTokenRepository.setDataSource(dataSource);
        jdbcTokenRepository.setCreateTableOnStartup(false);
        return jdbcTokenRepository;
    }

    @Bean
    public PersistentTokenBasedRememberMeServices rememberMeServices() {
        return new PersistentTokenBasedRememberMeServices(SECURITY_KEY, myUserDetailService, persistentTokenRepository());
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {

        http
                .authorizeRequests()
                .antMatchers("/public/**", "/login*").permitAll()  //任何人都可以访问
                .anyRequest().authenticated() //其他所有资源都需要认证,登陆后访问

                .and()
                .formLogin().loginPage("/login")
                .authenticationDetailsSource(new MyWebAuthenticationDetailsSource())
                .permitAll()

                // 这里主要增加记住我(自动登录)相关的配置
                .and().rememberMe().key(SECURITY_KEY).tokenRepository(persistentTokenRepository()).userDetailsService(myUserDetailService)
                .and().logout().logoutUrl("/logout").permitAll()
                .and().csrf().disable()
        ;

        //UserDetails实现类Account那边要 重写equals()和hashCode()
    }

    @Override
    public void configure(WebSecurity web) {
        //解决静态资源被拦截的问题
        web.ignoring().antMatchers("/assets/**", "/assets_mnt/**", "/api/**", "/dashboard/**");
        web.ignoring().antMatchers("/**/fav.ico", "/favicon.ico", "/robots.txt");
        web.ignoring().antMatchers("/**/captcha.jpg");
        web.ignoring().antMatchers("/ping", "/test", "/404_error", "/generic_error", "/error");
    }
}

MyUserDetailService.java

import com.terrynow.test.entity.Account;
import com.terrynow.test.service.intf.ISystemService;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.LockedException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;

import javax.annotation.Resource;

/**
 * @author Terry E-mail: yaoxinghuo at 126 dot com
 * @date 2021-7-7 19:34
 * @description
 */
@Configuration("myUserDetailService")
public class MyUserDetailService implements UserDetailsService {

    private static final Log log = LogFactory.getLog(MyUserDetailService.class);

    @Resource(name = "systemService")
    private ISystemService systemService;

    // 用户一登陆就会调用此方法验证
    @Override
    public UserDetails loadUserByUsername(String no) throws UsernameNotFoundException {
        Account account = systemService.getAccountByNo(no); // 数据库中查询此用户
        if (account == null) { // 判断此用户是否存在
            throw new UsernameNotFoundException(no);
        }
        if (!account.isEnabled() || !account.isAccountNonLocked()) {
            throw new LockedException("账户已停用");
        }
        log.warn("Cookie登录,姓名:" + account.getName() + ",帐号:" + account.getNo() + ",ID:" + account.getId());
        return account;
    }
}

MyJdbcTokenRepositoryImpl.java

这是操作数据库(存入读取自动登录相关信息)的关键类

import org.springframework.dao.DataAccessException;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.dao.IncorrectResultSizeDataAccessException;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.support.JdbcDaoSupport;
import org.springframework.security.web.authentication.rememberme.PersistentRememberMeToken;
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Date;

/**
 * @author Terry E-mail: yaoxinghuo at 126 dot com
 * @date 2018-3-15 08:58
 * @description
 */
public class MyJdbcTokenRepositoryImpl extends JdbcDaoSupport implements PersistentTokenRepository {
    public static final String TABLE_NAME = "t_persistent_logins";
    public static final String CREATE_TABLE_SQL = "create table "+TABLE_NAME+" (username varchar(64) not null, series varchar(64) primary key, token varchar(64) not null, last_used timestamp not null)";
    private String tokensBySeriesSql = "select username,series,token,last_used from "+TABLE_NAME+" where series = ?";
    private String insertTokenSql = "insert into "+TABLE_NAME+" (username, series, token, last_used) values(?,?,?,?)";
    private String updateTokenSql = "update "+TABLE_NAME+" set token = ?, last_used = ? where series = ?";
    public static String removeUserTokensSql = "delete from "+TABLE_NAME+" where username = ?";
    private boolean createTableOnStartup;

    protected void initDao() {
        if (this.createTableOnStartup) {
            this.getJdbcTemplate().execute(CREATE_TABLE_SQL);
        }

    }

    public void createNewToken(PersistentRememberMeToken token) {
        this.getJdbcTemplate().update(this.insertTokenSql, new Object[]{token.getUsername(), token.getSeries(), token.getTokenValue(), token.getDate()});
    }

    public void updateToken(String series, String tokenValue, Date lastUsed) {
        this.getJdbcTemplate().update(this.updateTokenSql, new Object[]{tokenValue, new Date(), series});
    }

    public PersistentRememberMeToken getTokenForSeries(String seriesId) {
        try {
            return this.getJdbcTemplate().queryForObject(this.tokensBySeriesSql, new RowMapper<PersistentRememberMeToken>() {
                public PersistentRememberMeToken mapRow(ResultSet rs, int rowNum) throws SQLException {
                    return new PersistentRememberMeToken(rs.getString(1), rs.getString(2), rs.getString(3), rs.getTimestamp(4));
                }
            }, new Object[]{seriesId});
        } catch (EmptyResultDataAccessException var3) {
            if (this.logger.isDebugEnabled()) {
                this.logger.debug("Querying token for series '" + seriesId + "' returned no results.", var3);
            }
        } catch (IncorrectResultSizeDataAccessException var4) {
            this.logger.error("Querying token for series '" + seriesId + "' returned more than one value. Series" + " should be unique");
        } catch (DataAccessException var5) {
            this.logger.error("Failed to load token for series " + seriesId, var5);
        }

        return null;
    }

    public void removeUserTokens(String username) {
        this.getJdbcTemplate().update(this.removeUserTokensSql, new Object[]{username});
    }

    public void setCreateTableOnStartup(boolean createTableOnStartup) {
        this.createTableOnStartup = createTableOnStartup;
    }
}

登录页面

写一个简单的登录页面login.html,有关记住我,只要form里增加一个name是remember-me的checkbox就可以了

<html>
<head></head>
<body>
   <h1>SpringSecurity登录</h1>
   <form name='f' action="login" method='POST'>
      <table>
         <tr>
            <td>用户名:</td>
            <td><input type='text' name='username' value=''></td>
         </tr>
         <tr>
            <td>密码:</td>
            <td><input type='password' name='password' /></td>
         </tr>
        <tr>
            <td>下次自动登录:</td>
            <td><input type="checkbox" name="remember-me"></td>
         </tr>
         <tr>
            <td><input name="submit" type="submit" value="登录" /></td>
         </tr>
      </table>
  </form>
</body>
</html>

勾选登录成功后,查看数据库表t_persistent_logins是否增加了一条记录,如果有,就说明成功了!

admin

这个人很懒,什么都没留下

文章评论

  • Asepaloupe

    Very much the helpful information

    2022-02-19
  • 您需要 登录 之后才可以评论