SpringSecurity自動(dòng)登錄流程與實(shí)現(xiàn)詳解
1、自動(dòng)登錄原理
大概的流程是這樣一個(gè)圖,里面還有很多細(xì)節(jié)與類下面進(jìn)行分析
1.1、首次登錄
- 第一次登錄時(shí)首先需要勾選checkbox的組件,頁(yè)面中應(yīng)該給出一個(gè)記住我的勾選框!
- 然后Security會(huì)放行到AbstractAuthenticationProcessingFilter抽象類,這個(gè)類里面doFilter放行鏈主要調(diào)用attemptAuthentication方法、successfulAuthentication方法。
- 其中attemptAuthentication方法由UsernamePasswordAuthenticationFilter類實(shí)現(xiàn),這里會(huì)調(diào)用自定義的UseDetailsService接口的實(shí)現(xiàn)類(用戶登錄賬號(hào)密碼驗(yàn)證),也就是說(shuō)這個(gè)方法會(huì)進(jìn)行賬號(hào)密碼的校驗(yàn)!
- successfulAuthentication方法主要是在用戶驗(yàn)證通過(guò)之后用于Token的生成、存儲(chǔ);其中會(huì)用到PersistentTokenBasedRememberMeServices類中的onLoginSuccess方法
- onLoginSuccess方法核心就是隨機(jī)生成一個(gè)Token、將Token持久化到數(shù)據(jù)庫(kù)中、并且將Token寫入到Cookie中!
@Override protected void onLoginSuccess(HttpServletRequest request, HttpServletResponse response, Authentication successfulAuthentication) { // 1. 登錄的用戶名賬號(hào) String username = successfulAuthentication.getName(); // 2. 生成一個(gè)Token PersistentRememberMeToken persistentToken = new PersistentRememberMeToken(username, generateSeriesData(), generateTokenData(), new Date()); try { // 3. 持久化到數(shù)據(jù)庫(kù)中 this.tokenRepository.createNewToken(persistentToken); // 4. 添加到Cookie中 addCookie(persistentToken, request, response); } catch (Exception ex) { this.logger.error("Failed to save persistent token ", ex); } }
這里的Token可以通過(guò)代碼進(jìn)行設(shè)置過(guò)期時(shí)間、像什么十天內(nèi)免登錄、三天內(nèi)免登錄…
1.2、自動(dòng)登錄
- 所謂的自動(dòng)登錄是在訪問(wèn)鏈接時(shí)瀏覽器自動(dòng)攜帶上了Cookie中的Token交給后端校驗(yàn),如果刪掉了Cookie或者過(guò)期了同樣是需要再次驗(yàn)證的!
- 瀏覽器攜帶Token進(jìn)行請(qǐng)求來(lái)到RememberMeAuthenticationFilter類中的doFilter方法過(guò)濾鏈中;這里調(diào)用AbstractRememberMeServices抽象類中的autoLogin方法。
- autoLogin方法中會(huì)從request中拿到cookie的值,然后調(diào)用processAutoLoginCookie方法進(jìn)行數(shù)據(jù)庫(kù)層面的校驗(yàn)!
- processAutoLoginCookie方法是由PersistentTokenBasedRememberMeServices類給出實(shí)現(xiàn);首先通過(guò)Token查到對(duì)應(yīng)的登錄賬戶名。
- 如果匹配失敗直接攔截掉請(qǐng)求,否則匹配成功那么重新刷新Token的過(guò)期時(shí)間并且重新持久化并且寫到Cookie中,并且調(diào)用自定義的UseDetailsService接口的實(shí)現(xiàn)類(用戶登錄賬號(hào)密碼驗(yà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. 這里有一大堆的校驗(yàn)失敗 .... // 3. 校驗(yàn)成功,重新生成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. 查用戶(因?yàn)橛锌赡苡脩魟h除掉) return getUserDetailsService().loadUserByUsername(token.getUsername()); }
分析:最后為什么要重新查詢一次用戶?因?yàn)門oken查詢的是表中一個(gè)Token + Username的表,并不是用戶登錄賬號(hào)表;有可能Token沒過(guò)期但是刪除掉了這個(gè)用戶,Token中有殘余數(shù)據(jù)!
2、具體實(shí)現(xiàn)
前言:首先需要看一下JdbcTokenRepositoryImpl類的源碼,這個(gè)類的源碼里給出了存放token、用戶名,時(shí)間戳等一系列參數(shù)的建表語(yǔ)句;以及操作數(shù)據(jù)庫(kù)的語(yǔ)句。
2.1、創(chuàng)建數(shù)據(jù)表
- 創(chuàng)建數(shù)據(jù)表可以自己創(chuàng)建,也可以在服務(wù)啟動(dòng)時(shí)讓其自動(dòng)創(chuàng)建
- 這里選擇自動(dòng)創(chuàng)建表,直接把它的源碼復(fù)制過(guò)來(lái);表名、列名一些參數(shù)最好不要?jiǎng)印?/li>
// 操作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 '編號(hào)', `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實(shí)現(xiàn)
編寫Pojo類對(duì)應(yīng)數(shù)據(jù)庫(kù)中的表
// pojo @Data @AllArgsConstructor @NoArgsConstructor public class Account { private Integer id; private String username; private String password; private String role; }
編寫mapper用戶操作數(shù)據(jù)庫(kù)的接口
// mapper @Mapper @Repository public interface AccountMapper { Account getLoginAccount(String username); }
編寫mapper用戶操作數(shù)據(jù)庫(kù)的接口
// 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接口的實(shí)現(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)造時(shí)需要進(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ū)動(dòng)類、以及UserDetailsService類(也可以在其他Configuration中注入)
@EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { // 數(shù)據(jù)源 @Autowired DataSource dataSource; // UserDetailsService實(shí)現(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)閉自動(dòng)創(chuàng)建表 repository.setDataSource(dataSource); //注入數(shù)據(jù)源 return repository; } }
配置用戶登錄密碼校驗(yàn),這里就是查詢數(shù)據(jù)庫(kù)校驗(yàn)賬號(hào)密碼的有效性
@Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { // 密碼需要進(jìn)行加密解密進(jìn)行驗(yàn)證匹配! auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder); }
配置登錄、登出、記住我、Cookie有效時(shí)間
@Override protected void configure(HttpSecurity http) throws Exception { http .formLogin() .loginPage("/login") // 登錄頁(yè)面 .loginProcessingUrl("/user/login") // 提交表單處理的請(qǐng)求,由Security實(shí)現(xiàn) .defaultSuccessUrl("/index",true).permitAll() //成功訪問(wèn)哪里 .and() .logout() .logoutUrl("/logout") .logoutSuccessUrl("/index").deleteCookies().permitAll() //退出成功頁(yè)面 // 2. 無(wú)需保護(hù)的頁(yè)面 .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有效時(shí)間 單位: 秒 .userDetailsService(userDetailsService); //注入用戶驗(yàn)證UserDetailsService http.exceptionHandling().accessDeniedPage("/nodeny"); http.csrf().disable(); }
2.4、編寫前端登錄頁(yè)面
這里一定要開啟checkbox復(fù)選框,并且這個(gè)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自動(dòng)登錄流程與實(shí)現(xiàn)詳解的文章就介紹到這了,更多相關(guān)SpringSecurity自動(dòng)登錄流程內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- SpringSecurity多表多端賬戶登錄的實(shí)現(xiàn)
- SpringSecurity集成第三方登錄過(guò)程詳解(最新推薦)
- springsecurity實(shí)現(xiàn)用戶登錄認(rèn)證快速使用示例代碼(前后端分離項(xiàng)目)
- SpringSecurity6自定義JSON登錄的實(shí)現(xiàn)
- SpringSecurity6.x多種登錄方式配置小結(jié)
- 如何使用JWT的SpringSecurity實(shí)現(xiàn)前后端分離
- SpringSecurity+Redis+Jwt實(shí)現(xiàn)用戶認(rèn)證授權(quán)
- SpringSecurity角色權(quán)限控制(SpringBoot+SpringSecurity+JWT)
- SpringBoot3.0+SpringSecurity6.0+JWT的實(shí)現(xiàn)
- springSecurity之如何添加自定義過(guò)濾器
- springSecurity自定義登錄接口和JWT認(rèn)證過(guò)濾器的流程
相關(guān)文章
Java8 Collectors求和功能的自定義擴(kuò)展操作
這篇文章主要介紹了Java8 Collectors求和功能的自定義擴(kuò)展操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2021-02-02Springboot+Stomp協(xié)議實(shí)現(xiàn)聊天功能
本示例實(shí)現(xiàn)一個(gè)功能,前端通過(guò)websocket發(fā)送消息給后端服務(wù),后端服務(wù)接收到該消息時(shí),原樣將消息返回給前端,前端技術(shù)棧html+stomp.js,后端SpringBoot,需要的朋友可以參考下2024-02-02使用Spring CROS解決項(xiàng)目中的跨域問(wèn)題詳解
這篇文章主要介紹了使用Spring CROS解決項(xiàng)目中的跨域問(wèn)題詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-01-01Maven 項(xiàng)目生成jar運(yùn)行時(shí)提示“沒有主清單屬性”
這篇文章主要介紹了Maven 項(xiàng)目生成jar運(yùn)行時(shí)提示“沒有主清單屬性”,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-03-03利用openoffice+jodconverter-code-3.0-bate4實(shí)現(xiàn)ppt轉(zhuǎn)圖片
這篇文章主要為大家詳細(xì)介紹了利用openoffice+jodconverter-code-3.0-bate4實(shí)現(xiàn)ppt轉(zhuǎn)圖片,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-07-07