JavaWeb-SpringSecurity记住我功能

 

 

  系列博文

  项目已上传至guthub  传送门

  JavaWeb-SpringSecurity初认识  传送门

  JavaWeb-SpringSecurity在数据库中查询登陆用户  传送门

  JavaWeb-SpringSecurity自定义登陆页面  传送门

  JavaWeb-SpringSecurity实现需求-判断请求是否以html结尾  传送门

  JavaWeb-SpringSecurity自定义登陆配置  传送门

  JavaWeb-SpringSecurity图片验证ImageCode  传送门

  JavaWeb-SpringSecurity记住我功能  传送门

  JavaWeb-SpringSecurity使用短信验证码登陆  传送门

 

  在login.html中添加一个复选框,表示"记住我"功能【注意:<input>标签的name一定是remember-me】

    <form action="/loginPage" method="post">
    
        用户名:
        <input type="text" name="username">
        <br>
        
        密码:
        <input type="password" name="password">
        <br>
        
        图片验证码:
        <input type="text" name="imageCode">
        <img src="/code/image">
        <br>
        
        <input name="remember-me" type="checkbox" value="true">
        记住我
        
        <input type="submit">
    
    </form>

 

  

 

  在config层SecurityConfig.java中添加persistentTokenRepository()方法,用来在server层操作数据库

    @Autowired    
    private DataSource dataSource;
    
    //负责操作数据库
    public PersistentTokenRepository  persistentTokenRepository()
    {
        JdbcTokenRepositoryImpl tokenRepository = new JdbcTokenRepositoryImpl();
        tokenRepository.setDataSource(dataSource);
        return tokenRepository;
    }

 

  JdbcTokenRepositoryImpl要操作数据库,得在数据库中存在操作存储用户信息token数据库表,使用JdbcTokenRepositoryImpl接口中提供创建数据库语句

/** Default SQL for creating the database table to store the tokens */
    public static final String CREATE_TABLE_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)";
    /** The default SQL used by the <tt>getTokenBySeries</tt> query */
    public static final String DEF_TOKEN_BY_SERIES_SQL = "select username,series,token,last_used from persistent_logins where series = ?";
    /** The default SQL used by <tt>createNewToken</tt> */
    public static final String DEF_INSERT_TOKEN_SQL = "insert into persistent_logins (username, series, token, last_used) values(?,?,?,?)";
    /** The default SQL used by <tt>updateToken</tt> */
    public static final String DEF_UPDATE_TOKEN_SQL = "update persistent_logins set token = ?, last_used = ? where series = ?";
    /** The default SQL used by <tt>removeUserTokens</tt> */
    public static final String DEF_REMOVE_USER_TOKENS_SQL = "delete from persistent_logins where username = ?";

 

  

 

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

 

  在SecurityConfig.java实现"记住我"功能

@Autowired    
    private DataSource dataSource;
    
    //负责操作数据库
    public PersistentTokenRepository  persistentTokenRepository()
    {
        JdbcTokenRepositoryImpl tokenRepository = new JdbcTokenRepositoryImpl();
        tokenRepository.setDataSource(dataSource);
        return tokenRepository;
    }
    
    @Autowired    
    public UserDetailsService userDetailService;
    
    
    protected void configure(HttpSecurity http) throws Exception{
        
        //声明我们自己写的过滤器
        ValidateCodeFilter validateCodeFilter = new ValidateCodeFilter();
        //给过滤器赋值
        validateCodeFilter.setAuthenticationFailureHandler(loginFailureHandler);
        validateCodeFilter.setGarySecurityProperties(garySecurityProperties);
        validateCodeFilter.afterPropertiesSet();
        
        //表单验证(身份认证)
        http.addFilterBefore(validateCodeFilter, UsernamePasswordAuthenticationFilter.class)
            .formLogin()
            //自定义登陆页面
            .loginPage("/require")
            //如果URL为loginPage,则用SpringSecurity中自带的过滤器去处理该请求
            .loginProcessingUrl("/loginPage")
            //配置登陆成功调用loginSuccessHandler
            .successHandler(loginSuccessHandler)
            //配置登陆失败调用loginFailureHandler
            .failureHandler(loginFailureHandler)
            //记住我功能
            .and()
            .rememberMe()
            //配置persistentTokenRepository
            .tokenRepository(persistentTokenRepository())
            //配置userDetailsService
            .userDetailsService(userDetailService)
            .and()
            //请求授权
            .authorizeRequests()
            //在访问我们的URL时,我们是不需要省份认证,可以立即访问
            .antMatchers("/login.html","/require","/code/image").permitAll()
            //所有请求都被拦截,跳转到(/login请求中)
            .anyRequest()
            //都需要我们身份认证
            .authenticated()
            //SpringSecurity保护机制
            .and().csrf().disable();
    }

 

package com.Gary.GaryRESTful.config;

import javax.sql.DataSource;

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.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl;
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;

import com.Gary.GaryRESTful.filter.ValidateCodeFilter;
import com.Gary.GaryRESTful.handler.LoginFailureHandler;
import com.Gary.GaryRESTful.handler.LoginSuccessHandler;
import com.Gary.GaryRESTful.properties.GarySecurityProperties;


//Web应用安全适配器
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter{

    //告诉SpringSecurity密码用什么加密的
    @Bean
    public PasswordEncoder passwordEncoder()
    {
        return new BCryptPasswordEncoder();
    }
    
    @Autowired
    private LoginSuccessHandler loginSuccessHandler;

    @Autowired
    private LoginFailureHandler loginFailureHandler;
     
    @Autowired
    private GarySecurityProperties garySecurityProperties;
    
    @Autowired    
    private DataSource dataSource;
    
    //负责操作数据库
    public PersistentTokenRepository  persistentTokenRepository()
    {
        JdbcTokenRepositoryImpl tokenRepository = new JdbcTokenRepositoryImpl();
        tokenRepository.setDataSource(dataSource);
        return tokenRepository;
    }
    
    @Autowired    
    public UserDetailsService userDetailService;
    
    
    protected void configure(HttpSecurity http) throws Exception{
        
        //声明我们自己写的过滤器
        ValidateCodeFilter validateCodeFilter = new ValidateCodeFilter();
        //给过滤器赋值
        validateCodeFilter.setAuthenticationFailureHandler(loginFailureHandler);
        validateCodeFilter.setGarySecurityProperties(garySecurityProperties);
        validateCodeFilter.afterPropertiesSet();
        
        //表单验证(身份认证)
        http.addFilterBefore(validateCodeFilter, UsernamePasswordAuthenticationFilter.class)
            .formLogin()
            //自定义登陆页面
            .loginPage("/require")
            //如果URL为loginPage,则用SpringSecurity中自带的过滤器去处理该请求
            .loginProcessingUrl("/loginPage")
            //配置登陆成功调用loginSuccessHandler
            .successHandler(loginSuccessHandler)
            //配置登陆失败调用loginFailureHandler
            .failureHandler(loginFailureHandler)
            //记住我功能
            .and()
            .rememberMe()
            //配置persistentTokenRepository
            .tokenRepository(persistentTokenRepository())
            //配置userDetailsService
            .userDetailsService(userDetailService)
            .and()
            //请求授权
            .authorizeRequests()
            //在访问我们的URL时,我们是不需要省份认证,可以立即访问
            .antMatchers("/login.html","/require","/code/image").permitAll()
            //所有请求都被拦截,跳转到(/login请求中)
            .anyRequest()
            //都需要我们身份认证
            .authenticated()
            //SpringSecurity保护机制
            .and().csrf().disable();
    }
    
}
SecurityConfig.java

 

  为防止一直记住用户,在GaryRESTful.properties中的GarySecurityProperties()方法下,配置token过期时间

    //LoginType登陆的方式,默认为JSON(restful设计风格)
    private LoginType loginType = LoginType.JSON;

    private ValidateCodeProperties code = new ValidateCodeProperties();
    
    private int rememberMeSeconds = 60*60;

    //getter()、setter()

 

  在application.properties中配置Token过期时间

#Token过期时间
gary.security.rememberMeSeconds = 3600

 

  在SecurityConfig.java下的configure()方法中配置过期秒数

    protected void configure(HttpSecurity http) throws Exception{
        
        //声明我们自己写的过滤器
        ValidateCodeFilter validateCodeFilter = new ValidateCodeFilter();
        //给过滤器赋值
        validateCodeFilter.setAuthenticationFailureHandler(loginFailureHandler);
        validateCodeFilter.setGarySecurityProperties(garySecurityProperties);
        validateCodeFilter.afterPropertiesSet();
        
        //表单验证(身份认证)
        http.addFilterBefore(validateCodeFilter, UsernamePasswordAuthenticationFilter.class)
            .formLogin()
            //自定义登陆页面
            .loginPage("/require")
            //如果URL为loginPage,则用SpringSecurity中自带的过滤器去处理该请求
            .loginProcessingUrl("/loginPage")
            //配置登陆成功调用loginSuccessHandler
            .successHandler(loginSuccessHandler)
            //配置登陆失败调用loginFailureHandler
            .failureHandler(loginFailureHandler)
            //记住我功能
            .and()
            .rememberMe()
            //配置persistentTokenRepository
            .tokenRepository(persistentTokenRepository())
            //配置过期秒数
            .tokenValiditySeconds(garySecurityProperties.getRememberMeSeconds())
            //配置userDetailsService
            .userDetailsService(userDetailService)
            .and()
            //请求授权
            .authorizeRequests()
            //在访问我们的URL时,我们是不需要省份认证,可以立即访问
            .antMatchers("/login.html","/require","/code/image").permitAll()
            //所有请求都被拦截,跳转到(/login请求中)
            .anyRequest()
            //都需要我们身份认证
            .authenticated()
            //SpringSecurity保护机制
            .and().csrf().disable();
    }

 

package com.Gary.GaryRESTful.config;

import javax.sql.DataSource;

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.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl;
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;

import com.Gary.GaryRESTful.filter.ValidateCodeFilter;
import com.Gary.GaryRESTful.handler.LoginFailureHandler;
import com.Gary.GaryRESTful.handler.LoginSuccessHandler;
import com.Gary.GaryRESTful.properties.GarySecurityProperties;


//Web应用安全适配器
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter{

    //告诉SpringSecurity密码用什么加密的
    @Bean
    public PasswordEncoder passwordEncoder()
    {
        return new BCryptPasswordEncoder();
    }
    
    @Autowired
    private LoginSuccessHandler loginSuccessHandler;

    @Autowired
    private LoginFailureHandler loginFailureHandler;
     
    @Autowired
    private GarySecurityProperties garySecurityProperties;
    
    @Autowired    
    private DataSource dataSource;
    
    //负责操作数据库
    public PersistentTokenRepository  persistentTokenRepository()
    {
        JdbcTokenRepositoryImpl tokenRepository = new JdbcTokenRepositoryImpl();
        tokenRepository.setDataSource(dataSource);
        return tokenRepository;
    }
    
    @Autowired    
    public UserDetailsService userDetailService;
    
    
    protected void configure(HttpSecurity http) throws Exception{
        
        //声明我们自己写的过滤器
        ValidateCodeFilter validateCodeFilter = new ValidateCodeFilter();
        //给过滤器赋值
        validateCodeFilter.setAuthenticationFailureHandler(loginFailureHandler);
        validateCodeFilter.setGarySecurityProperties(garySecurityProperties);
        validateCodeFilter.afterPropertiesSet();
        
        //表单验证(身份认证)
        http.addFilterBefore(validateCodeFilter, UsernamePasswordAuthenticationFilter.class)
            .formLogin()
            //自定义登陆页面
            .loginPage("/require")
            //如果URL为loginPage,则用SpringSecurity中自带的过滤器去处理该请求
            .loginProcessingUrl("/loginPage")
            //配置登陆成功调用loginSuccessHandler
            .successHandler(loginSuccessHandler)
            //配置登陆失败调用loginFailureHandler
            .failureHandler(loginFailureHandler)
            //记住我功能
            .and()
            .rememberMe()
            //配置persistentTokenRepository
            .tokenRepository(persistentTokenRepository())
            //配置过期秒数
            .tokenValiditySeconds(garySecurityProperties.getRememberMeSeconds())
            //配置userDetailsService
            .userDetailsService(userDetailService)
            .and()
            //请求授权
            .authorizeRequests()
            //在访问我们的URL时,我们是不需要省份认证,可以立即访问
            .antMatchers("/login.html","/require","/code/image").permitAll()
            //所有请求都被拦截,跳转到(/login请求中)
            .anyRequest()
            //都需要我们身份认证
            .authenticated()
            //SpringSecurity保护机制
            .and().csrf().disable();
    }
    
}
SecurityConfig.java

 

  测试:每次用户勾选了了记住我,在persistent_logins表中就会多处一条token记录【如果用户不勾选记住我,persistent_logins表中不会多处token记录】

 

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>

<h1>Gary登陆页面</h1>
    <form action="/loginPage" method="post">
    
        用户名:
        <input type="text" name="username">
        <br>
        
        密码:
        <input type="password" name="password">
        <br>
        
        图片验证码:
        <input type="text" name="imageCode">
        <img src="/code/image">
        <br>
        
        <input name="remember-me" type="checkbox" value="true">
        记住我
        
        <input type="submit">
    
    </form>

</body>
</html>
login.html

 

#datasource
spring.datasource.url=jdbc:mysql:///springsecurity?serverTimezone=UTC&characterEncoding=utf-8
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.dricer-class-name=com.mysql.jdbc.Driver

#jpa
#打印出数据库语句
spring.jpa.show-sql=true
#更新数据库表
spring.jpa.hibernate.ddl-auto=update

#配置登陆方式
gary.security.loginType = JSON

server.port=8081

#验证码长度
gary.security.code.image.length = 6
#验证码图片的长
gary.security.code.image.width = 100

#配置哪些需要我们验证码的Filter
gary.security.code.image.url = /user,/user/*

#Token过期时间
gary.security.rememberMeSeconds = 3600
application.properties

 

package com.Gary.GaryRESTful.properties;

import org.springframework.boot.context.properties.ConfigurationProperties;

@ConfigurationProperties(prefix = "gary.security")
public class GarySecurityProperties {
    
    //LoginType登陆的方式,默认为JSON(restful设计风格)
    private LoginType loginType = LoginType.JSON;

    private ValidateCodeProperties code = new ValidateCodeProperties();
    
    private int rememberMeSeconds = 60*60;
    
    public int getRememberMeSeconds() {
        return rememberMeSeconds;
    }

    public void setRememberMeSeconds(int rememberMeSeconds) {
        this.rememberMeSeconds = rememberMeSeconds;
    }

    public ValidateCodeProperties getCode() {
        return code;
    }

    public void setCode(ValidateCodeProperties code) {
        this.code = code;
    }

    public LoginType getLoginType() {
        return loginType;
    }

    public void setLoginType(LoginType loginType) {
        this.loginType = loginType;
    }
    
    
    
}
GarySecurityProperties.java

 

package com.Gary.GaryRESTful.config;

import javax.sql.DataSource;

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.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl;
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;

import com.Gary.GaryRESTful.filter.ValidateCodeFilter;
import com.Gary.GaryRESTful.handler.LoginFailureHandler;
import com.Gary.GaryRESTful.handler.LoginSuccessHandler;
import com.Gary.GaryRESTful.properties.GarySecurityProperties;


//Web应用安全适配器
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter{

    //告诉SpringSecurity密码用什么加密的
    @Bean
    public PasswordEncoder passwordEncoder()
    {
        return new BCryptPasswordEncoder();
    }
    
    @Autowired
    private LoginSuccessHandler loginSuccessHandler;

    @Autowired
    private LoginFailureHandler loginFailureHandler;
     
    @Autowired
    private GarySecurityProperties garySecurityProperties;
    
    @Autowired    
    private DataSource dataSource;
    
    //负责操作数据库
    public PersistentTokenRepository  persistentTokenRepository()
    {
        JdbcTokenRepositoryImpl tokenRepository = new JdbcTokenRepositoryImpl();
        tokenRepository.setDataSource(dataSource);
        return tokenRepository;
    }
    
    @Autowired    
    public UserDetailsService userDetailService;
    
    
    protected void configure(HttpSecurity http) throws Exception{
        
        //声明我们自己写的过滤器
        ValidateCodeFilter validateCodeFilter = new ValidateCodeFilter();
        //给过滤器赋值
        validateCodeFilter.setAuthenticationFailureHandler(loginFailureHandler);
        validateCodeFilter.setGarySecurityProperties(garySecurityProperties);
        validateCodeFilter.afterPropertiesSet();
        
        //表单验证(身份认证)
        http.addFilterBefore(validateCodeFilter, UsernamePasswordAuthenticationFilter.class)
            .formLogin()
            //自定义登陆页面
            .loginPage("/require")
            //如果URL为loginPage,则用SpringSecurity中自带的过滤器去处理该请求
            .loginProcessingUrl("/loginPage")
            //配置登陆成功调用loginSuccessHandler
            .successHandler(loginSuccessHandler)
            //配置登陆失败调用loginFailureHandler
            .failureHandler(loginFailureHandler)
            //记住我功能
            .and()
            .rememberMe()
            //配置persistentTokenRepository
            .tokenRepository(persistentTokenRepository())
            //配置过期秒数
            .tokenValiditySeconds(garySecurityProperties.getRememberMeSeconds())
            //配置userDetailsService
            .userDetailsService(userDetailService)
            .and()
            //请求授权
            .authorizeRequests()
            //在访问我们的URL时,我们是不需要省份认证,可以立即访问
            .antMatchers("/login.html","/require","/code/image").permitAll()
            //所有请求都被拦截,跳转到(/login请求中)
            .anyRequest()
            //都需要我们身份认证
            .authenticated()
            //SpringSecurity保护机制
            .and().csrf().disable();
    }
    
}
SecurityConfig.java

 

 

posted @ 2019-11-08 14:10  Cynical丶Gary  阅读(305)  评论(0编辑  收藏  举报