Spring Security 中实现 Remember Me 记住密码功能

来源:互联网 发布:动态恐怖漫画软件 编辑:程序博客网 时间:2024/05/22 09:43

在 Spring Boot 应用中使用 Spring Security 并实现 Remember Me 记住密码功能,实现自动登录


前置条件:在 Spring Boot 应用中已正确配置 Spring Security

在页面添加记住密码的复选框

 <input type="checkbox" name="remember-me"/> Remember me

在 Security Config 配置文件中启用记住密码功能(验证信息存放在内存中)

  • SecurityConfig
    import cn.com.hellowood.springsecurity.security.CustomAuthenticationProvider;    import cn.com.hellowood.springsecurity.security.CustomUserDetailsService;    import org.springframework.beans.factory.annotation.Autowired;    import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;    import org.springframework.security.config.annotation.web.builders.HttpSecurity;    import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;    import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdater;    @EnableWebSecurity    public class SecurityConfig extends WebSecurityConfigurerAdapter {        @Autowired        private CustomAuthenticationProvider customAuthenticationProvider;        @Autowired        private CustomUserDetailsService userDetailsService;        @Override        protected void configure(HttpSecurity http) throws Exception {            // 任何用户都可以访问以下URI            http.authorizeRequests()                    .antMatchers("/", "/login", "/login-error", "/css/**", "/index")                    .permitAll();            // 其他URI均需要权限校验            http.authorizeRequests()                    .anyRequest()                    .authenticated();            // 只需要以下配置即可启用记住密码            http.authorizeRequests()                    .and()                    .rememberMe();            http.formLogin()                    .loginPage("/login")                    .usernameParameter("username")                    .passwordParameter("password")                    .successForwardUrl("/user/index")                    .failureUrl("/login-error");        }        @Autowired        public void configureGlobal(AuthenticationManagerBuilder auth) {            // 为了使用用户名密码校验实现了AuthenticationProvider和UserDetailsService类            auth.authenticationProvider(customAuthenticationProvider);            try {                auth.userDetailsService(userDetailsService);            } catch (Exception e) {                e.printStackTrace();            }        }    }

这样就可以使用记住密码了,选择记住密码登录后会在本地保存 Cookie,下次登录的时候通过 Cookie 校验用户信息;用户登录的信息保存在内存中,当内存断电或被清除之后该 Cookie 即使在有效期内也无法登录。


通过数据库存放校验信息实现记住密码登录

创建保存校验信息的表(表名和字段值必须为以下内容)

    CREATE TABLE persistent_logins (      username  VARCHAR(64) NOT NULL,      series    VARCHAR(64) NOT NULL PRIMARY KEY,      token     VARCHAR(64) NOT NULL,      last_used TIMESTAMP   NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP    );

配置 Security Config

    import cn.com.hellowood.springsecurity.security.*;    import org.slf4j.Logger;    import org.slf4j.LoggerFactory;    import org.springframework.beans.factory.annotation.Autowired;    import org.springframework.beans.factory.annotation.Qualifier;    import org.springframework.context.annotation.Bean;    import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;    import org.springframework.security.config.annotation.web.builders.HttpSecurity;    import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;    import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;    import org.springframework.security.core.session.SessionRegistry;    import org.springframework.security.core.session.SessionRegistryImpl;    import org.springframework.security.web.authentication.RememberMeServices;    import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl;    import org.springframework.security.web.authentication.rememberme.PersistentTokenBasedRememberMeServices;    import javax.sql.DataSource;    @EnableWebSecurity    public class SecurityConfig extends WebSecurityConfigurerAdapter {        private final Logger logger = LoggerFactory.getLogger(getClass());        @Autowired        private CustomAuthenticationProvider customAuthenticationProvider;        @Autowired        private CustomUserDetailsService userDetailsService;        // 数据源是为了JdbcRememberMeImpl实例而注入的,如果不设置数据源会在登陆的时候抛空指针异常        @Autowired        @Qualifier("dataSource")        DataSource dataSource;        @Override        protected void configure(HttpSecurity http) throws Exception {            // 任何用户都可以访问以下URI            http.authorizeRequests()                    .antMatchers("/", "/login", "/login-error", "/css/**", "/index")                    .permitAll();            // 其他URI需要权限验证            http.authorizeRequests()                    .anyRequest()                    .authenticated();            // 当通过JDBC方式记住密码时必须设置 key,key 可以为任意非空(null 或 "")字符串,但必须和 RememberMeService 构造参数的            // key 一致,否则会导致通过记住密码登录失败            http.authorizeRequests()                    .and()                    .rememberMe()                    .rememberMeServices(rememberMeServices())                    .key("INTERNAL_SECRET_KEY");            // 当登录成功后会被重定向到 /user/index, 所以 loginPage 和 loginProcessingUrl 相同            http.formLogin()                    .loginPage("/login")                    .loginProcessingUrl("/login")                    .usernameParameter("username")                    .passwordParameter("password")                    .successForwardUrl("/user/index")                    .failureUrl("/login-error");        }        /**         * 返回 RememberMeServices 实例         *         * @return the remember me services         */        @Bean        public RememberMeServices rememberMeServices() {            JdbcTokenRepositoryImpl rememberMeTokenRepository = new JdbcTokenRepositoryImpl();            // 此处需要设置数据源,否则无法从数据库查询验证信息            rememberMeTokenRepository.setDataSource(dataSource);            // 此处的 key 可以为任意非空值(null 或 ""),单必须和起前面            // rememberMeServices(RememberMeServices rememberMeServices).key(key)的值相同            PersistentTokenBasedRememberMeServices rememberMeServices =                    new PersistentTokenBasedRememberMeServices("INTERNAL_SECRET_KEY", userDetailsService, rememberMeTokenRepository);            // 该参数不是必须的,默认值为 "remember-me", 但如果设置必须和页面复选框的 name 一致            rememberMeServices.setParameter("remember-me");            return rememberMeServices;        }        /**         * Configure global.         *         * @param auth the auth         * @throws Exception the exception         */        @Autowired        public void configureGlobal(AuthenticationManagerBuilder auth) {            // 为了使用用户名密码校验实现了AuthenticationProvider和UserDetailsService类            auth.authenticationProvider(customAuthenticationProvider);            try {                auth.userDetailsService(userDetailsService);            } catch (Exception e) {                logger.error("Set userDetailService failed, {}", e.getMessage());                e.printStackTrace();            }        }    }

这样就可以实现将校验信息保存在数据库中,下次通过记住密码登录后再次访问页面会根据 Cookie 查找该用户的登录信息,如果登录信息有效则登录成功, 否则重定向到登录页面


  • 自定义实现 AuthenticationProvider 接口
    import org.slf4j.Logger;    import org.slf4j.LoggerFactory;    import org.springframework.beans.factory.annotation.Autowired;    import org.springframework.security.authentication.AuthenticationProvider;    import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;    import org.springframework.security.core.Authentication;    import org.springframework.security.core.AuthenticationException;    import org.springframework.security.core.GrantedAuthority;    import org.springframework.stereotype.Component;    import java.util.ArrayList;    import java.util.List;    /**     * The type Custom authentication provider.     *     * @author HelloWood     */    @Component    public class CustomAuthenticationProvider implements AuthenticationProvider {        private final Logger logger = LoggerFactory.getLogger(getClass());        @Autowired        private CustomUserDetailsService userDetailsService;        /**         * Validate user info is correct form database         *         * @param authentication         * @return         * @throws AuthenticationException         */        @Override        public Authentication authenticate(Authentication authentication) throws AuthenticationException {            String username = authentication.getName();            String password = authentication.getCredentials().toString();            List<GrantedAuthority> grantedAuthorities = new ArrayList<>();            logger.info("start validate user {} login", username);            // 通过用户名和密码校验,如果校验不通过会抛出 AuthenticationException            userDetailsService.loadUserByUsernameAndPassword(username, password);            Authentication auth = new UsernamePasswordAuthenticationToken(username, password, grantedAuthorities);            return auth;        }        @Override        public boolean supports(Class<?> authentication) {            return authentication.equals(UsernamePasswordAuthenticationToken.class);        }    }
  • 自定义实现 UserDetailsService 接口
    import cn.com.hellowood.springsecurity.mapper.UserMapper;    import cn.com.hellowood.springsecurity.model.UserModel;    import org.slf4j.Logger;    import org.slf4j.LoggerFactory;    import org.springframework.beans.factory.annotation.Autowired;    import org.springframework.security.authentication.AccountExpiredException;    import org.springframework.security.authentication.BadCredentialsException;    import org.springframework.security.core.AuthenticationException;    import org.springframework.security.core.GrantedAuthority;    import org.springframework.security.core.userdetails.User;    import org.springframework.security.core.userdetails.UserDetails;    import org.springframework.security.core.userdetails.UserDetailsService;    import org.springframework.security.core.userdetails.UsernameNotFoundException;    import org.springframework.stereotype.Service;    import javax.servlet.http.HttpSession;    import java.util.ArrayList;    /**     * The type Custom user details service.     *     * @author HelloWood     */    @Service("userDetailsService")    public class CustomUserDetailsService implements UserDetailsService {        private Logger logger = LoggerFactory.getLogger(getClass());        @Autowired        private UserMapper userMapper;        @Autowired        private HttpSession session;        /**         * 通过用户名和密码加载用户信息并校验         *         * @param username the username         * @param password the password         * @return the user model         * @throws AuthenticationException the authentication exception         */        public UserModel loadUserByUsernameAndPassword(String username, String password) throws AuthenticationException {            logger.info("user {} is login by username and password", username);            UserModel user = userMapper.getUserByUsernameAndPassword(username, password);            validateUser(username, user);            return user;        }        /**        * 通过用户名加载用户信息,重写该方法用于记住密码后通过 Cookie 登录        *         * @param username        * @param user        */        @Override        public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {            logger.info("user {} is login by remember me cookie", username);            UserModel user = userMapper.getUserByUsername(username);            validateUser(username, user);            return new User(user.getUsername(), user.getPassword(), new ArrayList<GrantedAuthority>());        }        /**         * 校验用户信息并将用户信息放在 Session 中         *         * @param username         * @param user         */        private void validateUser(String username, UserModel user) {            if (user == null) {                logger.error("user {} login failed, username or password is wrong", username);                throw new BadCredentialsException("Username or password is not correct");            } else if (!user.getEnabled()) {                logger.error("user {} login failed, this account had expired", username);                throw new AccountExpiredException("Account had expired");            }            // TODO There should add more logic to determine locked, expired and others status            logger.info("user {} login success", username);            // 当用户信息有效时放入 Session 中            session.setAttribute("user", user);        }    }
阅读全文
0 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 淘宝凑单地址不一样怎么办 任性付还不了款怎么办 京东购物卷删了怎么办 荣耀手环3进水了怎么办 手机疏油层没了怎么办 快递到了处理中心不走了怎么办 京东取消了退款怎么办 我的订单删除了怎么办 近邻宝箱子误关怎么办 把收件人电话写错了怎么办 吧收件人电话写错了怎么办 速递易收件人电话写错怎么办 书包上的皮掉了怎么办 美亚很多商品不直邮中国怎么办 我想买刘涛用的化妆品贵妇膏怎么办 自提柜号码忘记了怎么办 京东商城误点确认收货怎么办 京东购物到货后怎么办 京东地址错了怎么办 没买运费险退货怎么办 没有买运费险退货怎么办 买的运费险换货怎么办 顾客买衣服说贵怎么办 卖衣服顾客说贵怎么办 汽车前保险杠刮蹭怎么办 洗手盆下水管堵了怎么办 装修好的卫生间漏水怎么办 马桶水箱盖碎了怎么办 马桶陶瓷盖碎了怎么办 电脑左右分屏了怎么办 在东东助手里安装软件怎么办 王者荣耀进入安全系统怎么办 京东限时达超时怎么办 京东京准达晚点怎么办 京东京准达超时怎么办 南京犬类免疫证怎么办 合肥犬类免疫证怎么办 快递号码写错了怎么办 网购下单后商家说没有货该怎么办 京东账号换手机怎么办 手机不发验证码怎么办