SpringSecurity自動登錄流程與實現(xiàn)詳解
1、自動登錄原理

大概的流程是這樣一個圖,里面還有很多細(xì)節(jié)與類下面進(jìn)行分析
1.1、首次登錄
- 第一次登錄時首先需要勾選checkbox的組件,頁面中應(yīng)該給出一個記住我的勾選框!
- 然后Security會放行到AbstractAuthenticationProcessingFilter抽象類,這個類里面doFilter放行鏈主要調(diào)用attemptAuthentication方法、successfulAuthentication方法。
- 其中attemptAuthentication方法由UsernamePasswordAuthenticationFilter類實現(xiàn),這里會調(diào)用自定義的UseDetailsService接口的實現(xiàn)類(用戶登錄賬號密碼驗證),也就是說這個方法會進(jìn)行賬號密碼的校驗!
- successfulAuthentication方法主要是在用戶驗證通過之后用于Token的生成、存儲;其中會用到PersistentTokenBasedRememberMeServices類中的onLoginSuccess方法
- onLoginSuccess方法核心就是隨機(jī)生成一個Token、將Token持久化到數(shù)據(jù)庫中、并且將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. 持久化到數(shù)據(jù)庫中
this.tokenRepository.createNewToken(persistentToken);
// 4. 添加到Cookie中
addCookie(persistentToken, request, response);
}
catch (Exception ex) {
this.logger.error("Failed to save persistent token ", ex);
}
}這里的Token可以通過代碼進(jìn)行設(shè)置過期時間、像什么十天內(nèi)免登錄、三天內(nèi)免登錄…
1.2、自動登錄
- 所謂的自動登錄是在訪問鏈接時瀏覽器自動攜帶上了Cookie中的Token交給后端校驗,如果刪掉了Cookie或者過期了同樣是需要再次驗證的!
- 瀏覽器攜帶Token進(jìn)行請求來到RememberMeAuthenticationFilter類中的doFilter方法過濾鏈中;這里調(diào)用AbstractRememberMeServices抽象類中的autoLogin方法。
- autoLogin方法中會從request中拿到cookie的值,然后調(diào)用processAutoLoginCookie方法進(jìn)行數(shù)據(jù)庫層面的校驗!
- processAutoLoginCookie方法是由PersistentTokenBasedRememberMeServices類給出實現(xiàn);首先通過Token查到對應(yīng)的登錄賬戶名。
- 如果匹配失敗直接攔截掉請求,否則匹配成功那么重新刷新Token的過期時間并且重新持久化并且寫到Cookie中,并且調(diào)用自定義的UseDetailsService接口的實現(xiàn)類(用戶登錄賬號密碼驗證)。
@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中有殘余數(shù)據(jù)!
2、具體實現(xiàn)
前言:首先需要看一下JdbcTokenRepositoryImpl類的源碼,這個類的源碼里給出了存放token、用戶名,時間戳等一系列參數(shù)的建表語句;以及操作數(shù)據(jù)庫的語句。

2.1、創(chuàng)建數(shù)據(jù)表
- 創(chuàng)建數(shù)據(jù)表可以自己創(chuàng)建,也可以在服務(wù)啟動時讓其自動創(chuàng)建
- 這里選擇自動創(chuàng)建表,直接把它的源碼復(fù)制過來;表名、列名一些參數(shù)最好不要動。
// 操作token的數(shù)據(jù)表
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 '權(quán)限',
primary key (`id`)
) engine=innodb default charset=utf8
insert into account(`id`, `username`, `password`, `role`) values (1, 'admin', '123456', 'root')
2.2、UserDetailsService實現(xiàn)
編寫Pojo類對應(yīng)數(shù)據(jù)庫中的表
// pojo
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Account {
private Integer id;
private String username;
private String password;
private String role;
}
編寫mapper用戶操作數(shù)據(jù)庫的接口
// mapper
@Mapper
@Repository
public interface AccountMapper {
Account getLoginAccount(String username);
}
編寫mapper用戶操作數(shù)據(jù)庫的接口
// 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接口的實現(xiàn)類,注入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權(quán)限控制角色需要使用"ROLE_"開頭, 并且密碼在構(gòu)造時需要進(jìn)行加密。
list.add(new SimpleGrantedAuthority("ROLE_" + account.getRole()));
return new User(passwordEncoder.encode(account.getUsername()), passwordEncoder.encode(account.getPassword()), list);
}
}
2.3、Security配置
Security中首先需要注入BCryptPasswordEncoder加密解密類、數(shù)據(jù)源DataSource、JdbcTokenRepositoryImpl操作Token的驅(qū)動類、以及UserDetailsService類(也可以在其他Configuration中注入)
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
// 數(shù)據(jù)源
@Autowired
DataSource dataSource;
// UserDetailsService實現(xiàn)類
@Autowired
UserDetailsService userDetailsService;
// 注入密碼加密解密類
@Bean
public BCryptPasswordEncoder bCryptPasswordEncoder(){
return new BCryptPasswordEncoder();
}
// 注入jdbc Token操作類
@Bean
PersistentTokenRepository persistentTokenRepository(){
JdbcTokenRepositoryImpl repository = new JdbcTokenRepositoryImpl();
repository.setCreateTableOnStartup(false); //關(guān)閉自動創(chuàng)建表
repository.setDataSource(dataSource); //注入數(shù)據(jù)源
return repository;
}
}
配置用戶登錄密碼校驗,這里就是查詢數(shù)據(jù)庫校驗賬號密碼的有效性
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// 密碼需要進(jìn)行加密解密進(jìn)行驗證匹配!
auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder);
}
配置登錄、登出、記住我、Cookie有效時間
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.formLogin()
.loginPage("/login") // 登錄頁面
.loginProcessingUrl("/user/login") // 提交表單處理的請求,由Security實現(xiàn)
.defaultSuccessUrl("/index",true).permitAll() //成功訪問哪里
.and()
.logout()
.logoutUrl("/logout")
.logoutSuccessUrl("/index").deleteCookies().permitAll() //退出成功頁面
// 2. 無需保護(hù)的頁面
.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復(fù)選框,并且這個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>
到此這篇關(guān)于SpringSecurity自動登錄流程與實現(xiàn)詳解的文章就介紹到這了,更多相關(guān)SpringSecurity自動登錄流程內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- SpringSecurity多表多端賬戶登錄的實現(xiàn)
- SpringSecurity集成第三方登錄過程詳解(最新推薦)
- springsecurity實現(xiàn)用戶登錄認(rèn)證快速使用示例代碼(前后端分離項目)
- SpringSecurity6自定義JSON登錄的實現(xiàn)
- SpringSecurity6.x多種登錄方式配置小結(jié)
- 如何使用JWT的SpringSecurity實現(xiàn)前后端分離
- SpringSecurity+Redis+Jwt實現(xiàn)用戶認(rèn)證授權(quán)
- SpringSecurity角色權(quán)限控制(SpringBoot+SpringSecurity+JWT)
- SpringBoot3.0+SpringSecurity6.0+JWT的實現(xiàn)
- springSecurity之如何添加自定義過濾器
- springSecurity自定義登錄接口和JWT認(rèn)證過濾器的流程
相關(guān)文章
SpringBoot四種讀取properties文件的方式(小結(jié))
這篇文章主要介紹了SpringBoot四種讀取properties文件的方式,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-05-05
SpringBoot實現(xiàn)日志文件分隔(根據(jù)日期和文件大小)
文章簡要介紹了如何在Spring Boot項目中配置日志文件路徑,包括在resource目錄下創(chuàng)建logback-spring.xml文件,并在yml配置文件中設(shè)置logging.file.path屬性來指定日志文件的輸出路徑2026-01-01
MyBatis-Plus集成Druid環(huán)境搭建的詳細(xì)教程
這篇文章主要介紹了MyBatis-Plus集成Druid環(huán)境搭建的詳細(xì)教程,需要的朋友可以參考下2020-08-08
Java?中的整型數(shù)據(jù)類型((byte,?short,?int,?long?)的取值范圍及使用不同的整型的場景
Java中的整型數(shù)據(jù)類型包括byte、short、int和long,每種類型都有不同的內(nèi)存占用和取值范圍,選擇合適的整型類型取決于具體需求,如內(nèi)存敏感場景、性能考量和避免整數(shù)溢出,示例代碼展示了如何聲明和初始化不同整型變量,并打印它們的最大值,感興趣的朋友一起看看吧2025-03-03
SpringBoot+Mybatis+Vue 實現(xiàn)商品模塊的crud操作
這篇文章主要介紹了SpringBoot+Mybatis+Vue 實現(xiàn)商品模塊的crud操作,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-10-10

