SpringSecurity自动登录流程与实现详解

 更新时间:2024年01月04日 09:31:09   作者:Splaying  
这篇文章主要介绍了SpringSecurity自动登录流程与实现详解,所谓的自动登录是在访问链接时浏览器自动携带上了Cookie中的Token交给后端校验,如果删掉了Cookie或者过期了同样是需要再次验证的,需要的朋友可以参考下

1、自动登录原理

在这里插入图片描述

大概的流程是这样一个图,里面还有很多细节与类下面进行分析

1.1、首次登录

  1. 第一次登录时首先需要勾选checkbox的组件,页面中应该给出一个记住我的勾选框!
  2. 然后Security会放行到AbstractAuthenticationProcessingFilter抽象类,这个类里面doFilter放行链主要调用attemptAuthentication方法、successfulAuthentication方法。
  3. 其中attemptAuthentication方法由UsernamePasswordAuthenticationFilter类实现,这里会调用自定义的UseDetailsService接口的实现类(用户登录账号密码验证),也就是说这个方法会进行账号密码的校验!
  4. successfulAuthentication方法主要是在用户验证通过之后用于Token的生成、存储;其中会用到PersistentTokenBasedRememberMeServices类中的onLoginSuccess方法
  5. onLoginSuccess方法核心就是随机生成一个Token、将Token持久化到数据库中、并且将Token写入到Cookie中!
@Override
protected void onLoginSuccess(HttpServletRequest request, HttpServletResponse response,
		Authentication successfulAuthentication) {
	// 1. 登录的用户名账号	
	String username = successfulAuthentication.getName();
	// 2. 生成一个Token
	PersistentRememberMeToken persistentToken = new PersistentRememberMeToken(username, generateSeriesData(),
			generateTokenData(), new Date());
	try {
		// 3. 持久化到数据库中
		this.tokenRepository.createNewToken(persistentToken);
		// 4. 添加到Cookie中
		addCookie(persistentToken, request, response);
	}
	catch (Exception ex) {
		this.logger.error("Failed to save persistent token ", ex);
	}
}

这里的Token可以通过代码进行设置过期时间、像什么十天内免登录、三天内免登录…

1.2、自动登录

  1. 所谓的自动登录是在访问链接时浏览器自动携带上了Cookie中的Token交给后端校验,如果删掉了Cookie或者过期了同样是需要再次验证的!
  2. 浏览器携带Token进行请求来到RememberMeAuthenticationFilter类中的doFilter方法过滤链中;这里调用AbstractRememberMeServices抽象类中的autoLogin方法
  3. autoLogin方法中会从request中拿到cookie的值,然后调用processAutoLoginCookie方法进行数据库层面的校验!
  4. processAutoLoginCookie方法是由PersistentTokenBasedRememberMeServices类给出实现;首先通过Token查到对应的登录账户名。
  5. 如果匹配失败直接拦截掉请求,否则匹配成功那么重新刷新Token的过期时间并且重新持久化并且写到Cookie中,并且调用自定义的UseDetailsService接口的实现类(用户登录账号密码验证)。
@Override
protected UserDetails processAutoLoginCookie(String[] cookieTokens, HttpServletRequest request,
		HttpServletResponse response) {
	// 1. cookie残缺	
	if (cookieTokens.length != 2) {
		throw new InvalidCookieException("Cookie token did not contain " + 2 + " tokens, but contained '"
				+ Arrays.asList(cookieTokens) + "'");
	}
	String presentedSeries = cookieTokens[0];
	String presentedToken = cookieTokens[1];
	PersistentRememberMeToken token = this.tokenRepository.getTokenForSeries(presentedSeries);
	
	....
	// 2. 这里有一大堆的校验失败
	....
	
	// 3. 校验成功,重新生成Cookie等一系列操作
	PersistentRememberMeToken newToken = new PersistentRememberMeToken(token.getUsername(), token.getSeries(),
			generateTokenData(), new Date());
	try {
		this.tokenRepository.updateToken(newToken.getSeries(), newToken.getTokenValue(), newToken.getDate());
		addCookie(newToken, request, response);
	}
	
	// 4. 查用户(因为有可能用户删除掉)
	return getUserDetailsService().loadUserByUsername(token.getUsername());
}

分析:最后为什么要重新查询一次用户?因为Token查询的是表中一个Token + Username的表,并不是用户登录账号表;有可能Token没过期但是删除掉了这个用户,Token中有残余数据!

2、具体实现

前言:首先需要看一下JdbcTokenRepositoryImpl类的源码,这个类的源码里给出了存放token、用户名,时间戳等一系列参数的建表语句;以及操作数据库的语句。

在这里插入图片描述

2.1、创建数据表

  • 创建数据表可以自己创建,也可以在服务启动时让其自动创建
  • 这里选择自动创建表,直接把它的源码复制过来;表名、列名一些参数最好不要动。
// 操作token的数据表
create table persistent_logins (
    username varchar(64) not null, 
    series varchar(64) primary key, 
    token varchar(64) not null, 
    last_used timestamp not null
)engine=innodb default charset=utf8

// 存放用户信息表
create table `account` (
  `id` int(11) not null auto_increment comment '编号',
  `username` varchar(30) not null comment '姓名',
  `password` varchar(30) not null comment '密码',
  `role` varchar(100) not null comment '权限',
  primary key (`id`)
) engine=innodb default charset=utf8
insert into account(`id`, `username`, `password`, `role`) values (1, 'admin', '123456', 'root')


2.2、UserDetailsService实现

编写Pojo类对应数据库中的表

// pojo
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Account {

    private Integer id;
    private String username;
    private String password;
    private String role;
}

编写mapper用户操作数据库的接口

// mapper
@Mapper
@Repository
public interface AccountMapper {

    Account getLoginAccount(String username);
}

编写mapper用户操作数据库的接口

// accountmapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.splay.mapper.AccountMapper">
    
    <select id="getLoginAccount" parameterType="string" resultType="Account">
        select *from account where username = #{username}
    </select>
</mapper>

编写UserDetailsService接口的实现类,注入Mapper

@Service
public class UserDetailsServiceImpl implements UserDetailsService {

    // 注入dao层
    @Autowired
    AccountMapper mapper;

    @Autowired
    BCryptPasswordEncoder passwordEncoder;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException{
        Account account = mapper.getLoginAccount(username);
        System.out.println(account.toString());
        List<GrantedAuthority> list = new ArrayList<>();
        // SpringSecurity权限控制角色需要使用"ROLE_"开头, 并且密码在构造时需要进行加密。
        list.add(new SimpleGrantedAuthority("ROLE_" + account.getRole()));

        return new User(passwordEncoder.encode(account.getUsername()), passwordEncoder.encode(account.getPassword()), list);
    }
}

2.3、Security配置

Security中首先需要注入BCryptPasswordEncoder加密解密类、数据源DataSource、JdbcTokenRepositoryImpl操作Token的驱动类、以及UserDetailsService类(也可以在其他Configuration中注入)

@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
	
	// 数据源
	@Autowired
    DataSource dataSource;

	// UserDetailsService实现类
	@Autowired
    UserDetailsService userDetailsService;

    // 注入密码加密解密类
    @Bean
    public BCryptPasswordEncoder bCryptPasswordEncoder(){
        return new BCryptPasswordEncoder();
    }

    // 注入jdbc Token操作类
    @Bean
    PersistentTokenRepository persistentTokenRepository(){
        JdbcTokenRepositoryImpl repository = new JdbcTokenRepositoryImpl();
        repository.setCreateTableOnStartup(false);				//关闭自动创建表
        repository.setDataSource(dataSource);					//注入数据源
        return repository;
    }
		
}

配置用户登录密码校验,这里就是查询数据库校验账号密码的有效性

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {

	// 密码需要进行加密解密进行验证匹配!
	auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder);
}

配置登录、登出、记住我、Cookie有效时间

@Override
protected void configure(HttpSecurity http) throws Exception {
	http
        .formLogin()
        .loginPage("/login")                    // 登录页面
        .loginProcessingUrl("/user/login")      // 提交表单处理的请求,由Security实现
        .defaultSuccessUrl("/index",true).permitAll()      //成功访问哪里
    .and()
        .logout()
        .logoutUrl("/logout")
        .logoutSuccessUrl("/index").deleteCookies().permitAll()    //退出成功页面

    // 2. 无需保护的页面
    .and()
        .authorizeRequests()
            .antMatchers("/level1/**").permitAll()
            .antMatchers("/level2/**").hasAnyRole("customer", "admin")
            .antMatchers("/level3/**").hasRole("admin")
    .anyRequest().authenticated().and()
            .rememberMe()                                       //记住我
            .tokenRepository(persistentTokenRepository)         //注入操作token的jdbc
            .tokenValiditySeconds(60).rememberMeCookieName("remember-me")                   //Cookie有效时间 单位: 秒
            .userDetailsService(userDetailsService);             //注入用户验证UserDetailsService
    http.exceptionHandling().accessDeniedPage("/nodeny");
    http.csrf().disable();
}

2.4、编写前端登录页面

这里一定要开启checkbox复选框,并且这个name = “remember-me”。

<form action="/user/login" method="post">
    用户名: <input type="text" name="username"><br/>
    密码: <input type="text" name="password"><br/>
    <input type="checkbox" name="remember-me">记住我<br/>
    <input type="submit" value="登录"/>
</form>

到此这篇关于SpringSecurity自动登录流程与实现详解的文章就介绍到这了,更多相关SpringSecurity自动登录流程内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Spring Boot项目中遇到`if-else`语句七种具体使用方法解析

    Spring Boot项目中遇到`if-else`语句七种具体使用方法解析

    当在Spring Boot项目中遇到大量if-else语句时,优化这些代码变得尤为重要,因为它们不仅增加了维护难度,还可能影响应用程序的可读性和性能,以下是七种具体的方法,用于在Spring Boot项目中优化和重构if-else语句,感兴趣的朋友一起看看吧
    2024-07-07
  • java实现租车系统

    java实现租车系统

    这篇文章主要为大家详细介绍了java实现租车系统,以及遇到的两个问题解决方法,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2019-01-01
  • springboot使用yml文件配置多环境方式(dev、test、prod)

    springboot使用yml文件配置多环境方式(dev、test、prod)

    这篇文章主要介绍了springboot使用yml文件配置多环境方式(dev、test、prod),具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2023-09-09
  • Java并发统计变量值偏差原因及解决方案

    Java并发统计变量值偏差原因及解决方案

    这篇文章主要介绍了Java并发统计变量值偏差原因及解决方案,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-06-06
  • java操作ftp下载文件示例

    java操作ftp下载文件示例

    这篇文章主要介绍了java操作ftp下载文件的示例,需要的朋友可以参考下
    2014-02-02
  • Springboot整合策略模式详解

    Springboot整合策略模式详解

    这篇文章主要介绍了Springboot整合策略模式详解的相关资料,需要的朋友可以参考下
    2023-01-01
  • 基于javamelody监控springboot项目过程详解

    基于javamelody监控springboot项目过程详解

    这篇文章主要介绍了基于javamelody监控springboot项目过程详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2019-11-11
  • java底层JDK Logging日志模块处理细节深入分析

    java底层JDK Logging日志模块处理细节深入分析

    这篇文章主要为大家介绍了java底层JDK Logging日志模块处理细节深入分析,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-03-03
  • maven利用tomcat插件部署远程Linux服务器的步骤详解

    maven利用tomcat插件部署远程Linux服务器的步骤详解

    Maven已经是Java的项目管理常用方式,下面这篇文章主要给大家介绍了关于maven利用tomcat插件部署远程Linux服务器的相关资料,文中通过示例代码介绍的非常详细,需要的朋友可以参考借鉴,下面随着小编来一起学习学习吧。
    2017-11-11
  • 在Java的MyBatis框架中建立接口进行CRUD操作的方法

    在Java的MyBatis框架中建立接口进行CRUD操作的方法

    这篇文章主要介绍了在Java的MyBatis框架中建立接口进行CRUD操作的方法,CRUD是指在做计算处理时的增加(Create)、重新取得数据(Retrieve)、更新(Update)和删除(Delete)几个单词的首字母简写,需要的朋友可以参考下
    2016-04-04

最新评论