SpringSecurity之记住我功能的实现

SpringSecurity之记住我功能的实现

Spring security记住我基本原理:

登录的时候,请求发送给过滤器UsernamePasswordAuthenticationFilter,当该过滤器认证成功后,会调用RememberMeService,会生成一个token,将token写入到浏览器cookie,同时RememberMeService里边还有个TokenRepository,将token和用户信息写入到数据库中。这样当用户再次访问系统,访问某一个接口时,会经过一个RememberMeAuthenticationFilter的过滤器,他会读取cookie中的token,交给RememberService,RememberService会用TokenRepository根据token从数据库中查是否有记录,如果有记录会把用户名取出来,再调用UserDetailService根据用户名获取用户信息,然后放在SecurityContext里。

SpringSecurity之记住我功能的实现_第1张图片
image.png

RememberMeAuthenticationFilter在Spring Security中认证过滤器链的倒数第二个过滤器位置,当其他认证过滤器都没法认证成功的时候,就会调用RememberMeAuthenticationFilter尝试认证。

SpringSecurity之记住我功能的实现_第2张图片
image.png

实现:

1,登录表单加上,SpringSecurity在SpringSessionRememberMeServices类里定义了一个常量,默认值就是remember-me

2,根据上边的原理图可知,要配置TokenRepository,把生成的token存进数据库,这是一个配置bean的配置,放在了BrowserSecurityConfig里

3,在configure里配置

4,在BrowserProperties里加上自动登录时间,把记住我时间做成可配置的

//记住我秒数配置
private int rememberMeSeconds = 10;

以下是相关的配置

pom.xml:



    4.0.0
    urity
    demo
    0.0.1-SNAPSHOT
    jar
    demo
    Demo project for Spring Boot
    
        org.springframework.boot
        spring-boot-starter-parent
        2.0.3.RELEASE
         
    
    
        UTF-8
        UTF-8
        1.8
    
    
        
            org.springframework.boot
            spring-boot-starter-web
        

        
            org.springframework.boot
            spring-boot-starter-test
            test
        
        
        
        
            org.apache.tomcat.embed
            tomcat-embed-jasper
            8.5.12
        
        
        
            javax.servlet
            javax.servlet-api
            3.1.0
        
        
        
            javax.servlet
            jstl
            1.2
        

        
        
            org.springframework.boot
            spring-boot-starter-web
        
        
            org.springframework.boot
            spring-boot-starter-test
            test
        
        
            org.springframework.boot
            spring-boot-starter-thymeleaf
        

        
            
            org.springframework.boot
            spring-boot-starter-security
        
        
            org.slf4j
            jcl-over-slf4j
            1.7.25
        
        
            org.projectlombok
            lombok
            1.16.22
        

        
        
            org.mybatis.spring.boot
            mybatis-spring-boot-starter
            1.2.0
        
        
            mysql
            mysql-connector-java
        
        
        
            com.alibaba
            druid
            1.0.25
        

        
        
            org.springframework.social
            spring-social-config
        
        
            org.springframework.social
            spring-social-security
        
        
            org.springframework.social
            spring-social-web
        
        
        
            org.springframework.security
            spring-security-config
            5.0.6.RELEASE
        
    
    
        
            
                org.springframework.boot
                spring-boot-maven-plugin
            
        
    

SecurityConfiguration:

package urity.demo.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
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.builders.WebSecurity;
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.rememberme.JdbcTokenRepositoryImpl;
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;

import javax.annotation.Resource;
import javax.sql.DataSource;

@Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {


    @Resource
    private DataSource dataSource;

    @Resource
    private UserDetailsService myUserDetailsService;

    /**
     * 配置TokenRepository
     *
     * @return
     */
    @Bean
    public PersistentTokenRepository persistentTokenRepository() {
        JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
        // 配置数据源
        jdbcTokenRepository.setDataSource(dataSource);
        // 第一次启动的时候自动建表(可以不用这句话,自己手动建表,源码中有语句的)
//         jdbcTokenRepository.setCreateTableOnStartup(true);
        return jdbcTokenRepository;
    }


    // 处理密码加密解密逻辑
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }


    //验证相关
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        super.configure(auth);
    }

    //浏览器相关
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .authorizeRequests()
                .antMatchers("/hello", "/login.html").permitAll()
                .anyRequest().authenticated()
                .and()
                .formLogin()
                //指定登录页的路径
                .loginPage("/hello")
                //指定自定义form表单请求的路径
                .loginProcessingUrl("/authentication/form")
                .failureUrl("/login?error")
                .defaultSuccessUrl("/success")
                //必须允许所有用户访问我们的登录页(例如未验证的用户,否则验证流程就会进入死循环)
                //这个formLogin().permitAll()方法允许所有用户基于表单登录访问/login这个page。
                .permitAll()
                .and()
                .rememberMe()                                   // 记住我相关配置
                .tokenRepository(persistentTokenRepository())
                .tokenValiditySeconds(1209600)
        ;


        //默认都会产生一个hiden标签 里面有安全相关的验证 这边我们不需要 可禁用掉
        http.csrf().disable();

    }
    //web安全相关
    @Override
    public void configure(WebSecurity web) throws Exception {
        super.configure(web);
    }
}

MyUserDetailService:

package urity.demo.support;

import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;
import urity.demo.entity.User;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Logger;

//自定义用户处理的逻辑
//用户的信息的service
@Component
public class MyUserDetailService implements UserDetailsService {
    /**
     * 日志处理类
     */
    private org.slf4j.Logger logger =  LoggerFactory.getLogger(this.getClass());

    @Autowired
    private PasswordEncoder passwordEncoder;


    /**
     * 根据用户名加载用户信息
     *
     * @param username 用户名
     * @return UserDetails
     * @throws UsernameNotFoundException
     */

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        logger.info("表单登录用户名:" + username);
        System.out.println("表单登录用户名:" + username);
        List grantedAuthorityList = new ArrayList<>();
        grantedAuthorityList.add(new GrantedAuthority() {
            @Override
            public String getAuthority() {
                return "admin";
            }
        });

       User user = new User();
       user.setUsername("test");
       user.setPassword("123");
        String pWord =passwordEncoder.encode(user.getPassword());
        System.out.println("表单登录加密后密码:" + pWord);
        System.out.println("库中的username:"+user.getUsername());
        if(username.equals(user.getUsername())) {
            MyUser myUser = new MyUser(user.getUsername(), pWord, true, true, true, true, AuthorityUtils.commaSeparatedStringToAuthorityList("admin,ROLE_USER"));
            return myUser;
        }else {
            throw  new UsernameNotFoundException("用户["+username+"]不存在");
        }

    }
}

MyUser:

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.security.core.CredentialsContainer;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.SpringSecurityCoreVersion;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.util.Assert;

import java.io.Serializable;
import java.util.*;
import java.util.function.Function;

public class MyUser implements UserDetails, CredentialsContainer {

    private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;

    // ~ Instance fields
    // ================================================================================================
    private String password;
    private final String username;
    private final Set authorities;
    private final boolean accountNonExpired;
    private final boolean accountNonLocked;
    private final boolean credentialsNonExpired;
    private final boolean enabled;

    // ~ Constructors
    // ===================================================================================================

    /**
     * Calls the more complex constructor with all boolean arguments set to {@code true}.
     */
    public MyUser(String username, String password,
                             Collection authorities) {
        this(username, password, true, true, true, true, authorities);
    }

    /**
     * Construct the User with the details required by
     * {@link org.springframework.security.authentication.dao.DaoAuthenticationProvider}.
     *
     * @param username the username presented to the
     * DaoAuthenticationProvider
     * @param password the password that should be presented to the
     * DaoAuthenticationProvider
     * @param enabled set to true if the user is enabled
     * @param accountNonExpired set to true if the account has not expired
     * @param credentialsNonExpired set to true if the credentials have not
     * expired
     * @param accountNonLocked set to true if the account is not locked
     * @param authorities the authorities that should be granted to the caller if they
     * presented the correct username and password and the user is enabled. Not null.
     *
     * @throws IllegalArgumentException if a null value was passed either as
     * a parameter or as an element in the GrantedAuthority collection
     */
    public MyUser(String username, String password, boolean enabled,
                             boolean accountNonExpired, boolean credentialsNonExpired,
                             boolean accountNonLocked, Collection authorities) {

        if (((username == null) || "".equals(username)) || (password == null)) {
            throw new IllegalArgumentException(
                    "Cannot pass null or empty values to constructor");
        }

        this.username = username;
        this.password = password;
        this.enabled = enabled;
        this.accountNonExpired = accountNonExpired;
        this.credentialsNonExpired = credentialsNonExpired;
        this.accountNonLocked = accountNonLocked;
        this.authorities = Collections.unmodifiableSet(sortAuthorities(authorities));
    }

    // ~ Methods
    // ========================================================================================================

    public Collection getAuthorities() {
        return authorities;
    }

    public String getPassword() {
        return password;
    }

    public String getUsername() {
        return username;
    }

    public boolean isEnabled() {
        return enabled;
    }

    public boolean isAccountNonExpired() {
        return accountNonExpired;
    }

    public boolean isAccountNonLocked() {
        return accountNonLocked;
    }

    public boolean isCredentialsNonExpired() {
        return credentialsNonExpired;
    }

    public void eraseCredentials() {
        password = null;
    }

    private static SortedSet sortAuthorities(
            Collection authorities) {
        Assert.notNull(authorities, "Cannot pass a null GrantedAuthority collection");
        // Ensure array iteration order is predictable (as per
        // UserDetails.getAuthorities() contract and SEC-717)
        SortedSet sortedAuthorities = new TreeSet(
                new MyUser.AuthorityComparator());

        for (GrantedAuthority grantedAuthority : authorities) {
            Assert.notNull(grantedAuthority,
                    "GrantedAuthority list cannot contain any null elements");
            sortedAuthorities.add(grantedAuthority);
        }

        return sortedAuthorities;
    }

    private static class AuthorityComparator implements Comparator,
            Serializable {
        private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;

        public int compare(GrantedAuthority g1, GrantedAuthority g2) {
            // Neither should ever be null as each entry is checked before adding it to
            // the set.
            // If the authority is null, it is a custom authority and should precede
            // others.
            if (g2.getAuthority() == null) {
                return -1;
            }

            if (g1.getAuthority() == null) {
                return 1;
            }

            return g1.getAuthority().compareTo(g2.getAuthority());
        }
    }

    /**
     * Returns {@code true} if the supplied object is a {@code User} instance with the
     * same {@code username} value.
     * 

* In other words, the objects are equal if they have the same username, representing * the same principal. */ @Override public boolean equals(Object rhs) { if (rhs instanceof MyUser) { return username.equals(((MyUser) rhs).username); } return false; } /** * Returns the hashcode of the {@code username}. */ @Override public int hashCode() { return username.hashCode(); } @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append(super.toString()).append(": "); sb.append("Username: ").append(this.username).append("; "); sb.append("Password: [PROTECTED]; "); sb.append("Enabled: ").append(this.enabled).append("; "); sb.append("AccountNonExpired: ").append(this.accountNonExpired).append("; "); sb.append("credentialsNonExpired: ").append(this.credentialsNonExpired) .append("; "); sb.append("AccountNonLocked: ").append(this.accountNonLocked).append("; "); if (!authorities.isEmpty()) { sb.append("Granted Authorities: "); boolean first = true; for (GrantedAuthority auth : authorities) { if (!first) { sb.append(","); } first = false; sb.append(auth); } } else { sb.append("Not granted any authorities"); } return sb.toString(); } public static MyUser.UserBuilder withUsername(String username) { return new MyUser.UserBuilder().username(username); } /** * Builds the user to be added. At minimum the username, password, and authorities * should provided. The remaining attributes have reasonable defaults. */ public static class UserBuilder { private String username; private String password; private List authorities; private boolean accountExpired; private boolean accountLocked; private boolean credentialsExpired; private boolean disabled; /** * Creates a new instance */ private UserBuilder() { } /** * Populates the username. This attribute is required. * * @param username the username. Cannot be null. * @return the {@link User.UserBuilder} for method chaining (i.e. to populate * additional attributes for this user) */ private MyUser.UserBuilder username(String username) { Assert.notNull(username, "username cannot be null"); this.username = username; return this; } /** * Populates the password. This attribute is required. * * @param password the password. Cannot be null. * @return the {@link User.UserBuilder} for method chaining (i.e. to populate * additional attributes for this user) */ public MyUser.UserBuilder password(String password) { Assert.notNull(password, "password cannot be null"); this.password = password; return this; } /** * Populates the roles. This method is a shortcut for calling * {@link #authorities(String...)}, but automatically prefixes each entry with * "ROLE_". This means the following: * * * builder.roles("USER","ADMIN"); * * * is equivalent to * * * builder.authorities("ROLE_USER","ROLE_ADMIN"); * * *

* This attribute is required, but can also be populated with * {@link #authorities(String...)}. *

* * @param roles the roles for this user (i.e. USER, ADMIN, etc). Cannot be null, * contain null values or start with "ROLE_" * @return the {@link User.UserBuilder} for method chaining (i.e. to populate * additional attributes for this user) */ public MyUser.UserBuilder roles(String... roles) { List authorities = new ArrayList( roles.length); for (String role : roles) { Assert.isTrue(!role.startsWith("ROLE_"), role + " cannot start with ROLE_ (it is automatically added)"); authorities.add(new SimpleGrantedAuthority("ROLE_" + role)); } return authorities(authorities); } /** * Populates the authorities. This attribute is required. * * @param authorities the authorities for this user. Cannot be null, or contain * null values * @return the {@link User.UserBuilder} for method chaining (i.e. to populate * additional attributes for this user) * @see #roles(String...) */ public MyUser.UserBuilder authorities(GrantedAuthority... authorities) { return authorities(Arrays.asList(authorities)); } /** * Populates the authorities. This attribute is required. * * @param authorities the authorities for this user. Cannot be null, or contain * null values * @return the {@link User.UserBuilder} for method chaining (i.e. to populate * additional attributes for this user) * @see #roles(String...) */ public MyUser.UserBuilder authorities(List authorities) { this.authorities = new ArrayList(authorities); return this; } /** * Populates the authorities. This attribute is required. * * @param authorities the authorities for this user (i.e. ROLE_USER, ROLE_ADMIN, * etc). Cannot be null, or contain null values * @return the {@link User.UserBuilder} for method chaining (i.e. to populate * additional attributes for this user) * @see #roles(String...) */ public MyUser.UserBuilder authorities(String... authorities) { return authorities(AuthorityUtils.createAuthorityList(authorities)); } /** * Defines if the account is expired or not. Default is false. * * @param accountExpired true if the account is expired, false otherwise * @return the {@link User.UserBuilder} for method chaining (i.e. to populate * additional attributes for this user) */ public MyUser.UserBuilder accountExpired(boolean accountExpired) { this.accountExpired = accountExpired; return this; } /** * Defines if the account is locked or not. Default is false. * * @param accountLocked true if the account is locked, false otherwise * @return the {@link User.UserBuilder} for method chaining (i.e. to populate * additional attributes for this user) */ public MyUser.UserBuilder accountLocked(boolean accountLocked) { this.accountLocked = accountLocked; return this; } /** * Defines if the credentials are expired or not. Default is false. * * @param credentialsExpired true if the credentials are expired, false otherwise * @return the {@link User.UserBuilder} for method chaining (i.e. to populate * additional attributes for this user) */ public MyUser.UserBuilder credentialsExpired(boolean credentialsExpired) { this.credentialsExpired = credentialsExpired; return this; } /** * Defines if the account is disabled or not. Default is false. * * @param disabled true if the account is disabled, false otherwise * @return the {@link User.UserBuilder} for method chaining (i.e. to populate * additional attributes for this user) */ public MyUser.UserBuilder disabled(boolean disabled) { this.disabled = disabled; return this; } public UserDetails build() { return new User(username, password, !disabled, !accountExpired, !credentialsExpired, !accountLocked, authorities); } } }

login.html:




    
    第一个HTML页面



    
    Title



自定义表单验证:



用户名:
密码:
记住我

LoginController:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import urity.demo.entity.User;
@Controller
public class LoginController {

    @RequestMapping("/hello")
    public String hello() {
        System.out.println("kkkk==");
        return "login";
    }

    @RequestMapping("/success")
    public String success(){

        return "success";
    }

    @RequestMapping("/forkl")
    public String check(User user){
        System.out.println(user);
        return "success";
    }

    @RequestMapping("/user")
    public String fuinduser(){
       return "user";
    }
}

user.html:




    
    Title


由于用了记住我 所以现在可以直接访问了哦!


到此我们来启动项目,首次访问http://localhost:8787/user会需要我们登录,这里我们进行登录先不勾选记住我:

SpringSecurity之记住我功能的实现_第3张图片
image.png

登录成功后可以正常访问user,然后我们关闭浏览器重新打开 访问http://localhost:8787/user会被返回到登录的页面,这个就是没有任何效果的演示.

然后我们再次登录,并勾选记住我:

SpringSecurity之记住我功能的实现_第4张图片
image.png

这里我们登录成功后关闭浏览器再打开 仍然可以访问http://localhost:8787/user,而且不需要登录:

SpringSecurity之记住我功能的实现_第5张图片
image.png

这里浏览器做了如下的事情:

  • 在我们数据库建立表并插入数据
img_64d9b0742d07fff8ce81f92d33f02e71.png
image.png
  • 然后我们关闭浏览器在访问,它会去库里面查找响应的token,如果有就不用登录直接访问:
SpringSecurity之记住我功能的实现_第6张图片
image.png

到此,rememberme的功能就完成了

你可能感兴趣的:(SpringSecurity之记住我功能的实现)