javascript
一篇文章轻松搞定SpringSecurity权限框架!
目錄
前言
一、引入依賴
二、提供正常的業(yè)務(wù)接口
?三、自定義用戶認(rèn)證
3.1 編寫配置類
3.2 編寫UserDetailsService實(shí)現(xiàn)類
3.3 啟動(dòng)項(xiàng)目,完成認(rèn)證功能的驗(yàn)證
?3.4 小說明
?3.5 自定義用戶登錄頁面及訪問權(quán)限基本設(shè)置
四、授權(quán)
4.1 增加的授權(quán)代碼
?4.2?驗(yàn)證
?4.3 常見的授權(quán)方法
?4.4 自定義403頁面
?五、授權(quán)(注解方式)
5.1 主啟動(dòng)類上添加注解
5.2 控制器方法添加相關(guān)授權(quán)注解
六、關(guān)于密碼加密補(bǔ)充說明
七、用戶注銷
7.1 配置類中增加注銷相關(guān)配置
?7.2 編寫退出超鏈接
7.3 測試?
八、記住我功能的實(shí)現(xiàn)
8.1 數(shù)據(jù)庫建表
8.2?編寫配置類
?8.3 完善登錄頁面
8.4 測試?
九、CSRF功能
十、踢下線功能
10.1 核心代碼
10.2 測試
前言
Spring Security是非常流行的安全(權(quán)限)框架,Web應(yīng)用框架。
主要有兩大作用:一個(gè)是認(rèn)證,一個(gè)是授權(quán)。
本質(zhì)上,它就是Filter過濾器。而且是過濾器鏈。
SpringSecurity與Shiro的區(qū)別
Spring Security的特點(diǎn):
Spring家族的,能很好的整合Spring。
專門為Web應(yīng)用開發(fā)設(shè)計(jì)的。
提供專業(yè)全面的權(quán)限。
重量級的。依賴于很多其他組件。在SSM中整合比Shiro麻煩。但在springboot中提供了自動(dòng)配置方案。
Shiro的特點(diǎn):
它是Apache下的輕量級的權(quán)限框架。
輕量級的,依賴少,本身的大小也相對小。
不局限于Web環(huán)境,JavaSE下也可以運(yùn)行。
缺點(diǎn)是針對Web環(huán)境下特定需求需要手動(dòng)編寫代碼定制。功能沒有Spring Security強(qiáng)大。
一般來說,常見的安全管理技術(shù)棧:
SSM+shiro
Spring Boot + Spring Security
一、引入依賴
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId> </dependency>當(dāng)然了,完整的springboot工程還會引入其他所需要的相關(guān)依賴,具體根據(jù)項(xiàng)目而定:
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</artifactId> </dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope> </dependency> <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.5.1</version> </dependency> <dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-generator</artifactId><version>3.5.1</version> </dependency> <dependency><groupId>org.apache.velocity</groupId><artifactId>velocity-engine-core</artifactId><version>2.3</version> </dependency> <dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId> </dependency> <dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId> </dependency> <dependency><groupId>io.springfox</groupId><artifactId>springfox-swagger-ui</artifactId><version>2.7.0</version> </dependency> <dependency><groupId>io.springfox</groupId><artifactId>springfox-swagger2</artifactId><version>2.7.0</version> </dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId> </dependency>二、提供正常的業(yè)務(wù)接口
例如我這里提供的測試業(yè)務(wù)接口,具體的業(yè)務(wù)邏輯就不展示了:
??
?
?三、自定義用戶認(rèn)證
實(shí)現(xiàn)通過查找數(shù)據(jù)庫來獲取用戶名密碼,完成登錄功能。具體的密碼校驗(yàn)由spring security內(nèi)部完成。
3.1 編寫配置類
設(shè)置使用哪個(gè)UserDetailsService 實(shí)現(xiàn)類
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.authentication.builders.AuthenticationManagerBuilder; 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;@Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter {@Autowiredprivate UserDetailsService userDetailsService;@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {auth.userDetailsService(userDetailsService).passwordEncoder(password());}@Beanpublic PasswordEncoder password(){return new BCryptPasswordEncoder();} }3.2 編寫UserDetailsService實(shí)現(xiàn)類
這個(gè)UserDetailsService接口是springsecurity內(nèi)部提供的,我們只需要編寫對應(yīng)的實(shí)現(xiàn)類即可完成用戶認(rèn)證授權(quán)
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.hssy.authoritydemo.entity.User; import com.hssy.authoritydemo.mapper.UserMapper; 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.bcrypt.BCryptPasswordEncoder; import org.springframework.stereotype.Service; import java.util.List;@Service public class MyUserDetailsService implements UserDetailsService {@Autowiredprivate UserMapper userMapper;@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {List<GrantedAuthority> authorities =AuthorityUtils.commaSeparatedStringToAuthorityList("manager");LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();queryWrapper.eq(User::getUsername,username);User user = userMapper.selectOne(queryWrapper);if (user == null){throw new UsernameNotFoundException("用戶不存在");}return new org.springframework.security.core.userdetails.User(user.getUsername(),new BCryptPasswordEncoder().encode(user.getPassword()),authorities);} }3.3 啟動(dòng)項(xiàng)目,完成認(rèn)證功能的驗(yàn)證
在驗(yàn)證之前,我們先為數(shù)據(jù)庫中創(chuàng)建一個(gè)測試用戶。
?然后啟動(dòng)項(xiàng)目,我們訪問任意的接口,即便是沒有編寫的接口,它默認(rèn)都跳轉(zhuǎn)到spring security自帶的登錄頁面了。
?此時(shí),我們即可使用數(shù)據(jù)庫準(zhǔn)備好的測試用戶【username:security】 【password:123456】進(jìn)行驗(yàn)證。
假如使用錯(cuò)誤的用戶名密碼,是無法登錄的。
?
?登錄成功,完成跳轉(zhuǎn),由于我們沒有編寫對應(yīng)的接口,所以如下是404白標(biāo)簽頁面。
如果我們之前訪問login接口,則登錄成功會跳轉(zhuǎn)到根路徑,也就是localhost:8080
?此時(shí)訪問我們之前提供的接口,就都能正常訪問了,如
?3.4 小說明
通過以上案例,我們知道:
1. 系統(tǒng)默認(rèn)會為我們提供一個(gè)登錄頁面和登錄接口,我們不編寫相應(yīng)頁面和接口代碼也能實(shí)現(xiàn)。
2.?密碼校驗(yàn)是由SpringSecurity內(nèi)部完成。不需要我們來處理。我們只需要將數(shù)據(jù)庫查出來的用戶名和密碼交給spring security提供的User類即可。
3. 如果想自定義登錄頁面或者登錄處理接口,那么還需要增加一項(xiàng)配置。下面一起來看看吧。
?3.5 自定義用戶登錄頁面及訪問權(quán)限基本設(shè)置
3.5.1 代碼
主要是通過配置類中,重寫configure(HttpSecurity http)的這個(gè)方法
@Overrideprotected void configure(HttpSecurity http) throws Exception {http.formLogin()//表示進(jìn)行表單登錄.loginPage("/login.html")//自定義的登錄頁面.loginProcessingUrl("/login")//傳一個(gè)登錄處理的接口,不管你傳的接口地址是什么,都由Security內(nèi)部完成。當(dāng)然也可以自己寫這個(gè)接口,這樣就不會用系統(tǒng)來完成登錄處理校驗(yàn)用戶名密碼了。還有就是如果自定義了登錄頁面,那么登錄處理的接口loginProcessingUrl項(xiàng)一定要寫,不管是寫系統(tǒng)自帶的,還是你自己寫的處理接口都行,否則報(bào)錯(cuò)。.usernameParameter("username") //定義登錄時(shí)的用戶名的key,即表單中name的值,默認(rèn)為username.passwordParameter("password") //定義登錄時(shí)的密碼key,即表單中name的值,默認(rèn)是password//設(shè)置的這兩個(gè)用戶名、密碼的key,如果不自己寫登錄頁面的話,可以不用寫,因?yàn)橄到y(tǒng)默認(rèn)提供的頁面就是這個(gè)默認(rèn)值。寫了的話,一定要與表單頁面中定義的name值一致才行。.defaultSuccessUrl("/pages/main")//登錄成功跳轉(zhuǎn)到的頁面或者路徑。當(dāng)然,如果你不是從登錄頁面登錄的,那么攔截之后會進(jìn)入到你的請求路徑(或頁面)中.failureUrl("/login.html")//登錄失敗跳轉(zhuǎn)到的頁面.permitAll() //指和登錄表單相關(guān)的接口 都通過,不攔截.and().authorizeRequests()//開啟授權(quán)請求.antMatchers("/","/pages/main","/login").permitAll()//設(shè)置哪些路徑放行,不需要認(rèn)證 不需要登錄可以訪問的.anyRequest().authenticated()//除開上面的,其他所有請求全部都需要權(quán)限驗(yàn)證。因?yàn)檫€沒有用戶授權(quán),所以目前所有的接口登錄后都能訪問。.and().csrf().disable();//關(guān)閉csrf防護(hù)}3.5.2?驗(yàn)證
此時(shí)重啟項(xiàng)目,它就會自動(dòng)跳轉(zhuǎn)到我們配置的login.html頁面,由于沒有編寫登錄頁的代碼,它就會報(bào)錯(cuò)404。
?我們編寫一份前端代碼吧
此時(shí)再次重啟驗(yàn)證?
當(dāng)然了,我這里一開始地址欄輸入的是訪問/data-city/findAll這個(gè)接口,否則直接訪問登錄頁的話,它就會跳轉(zhuǎn)到我們配置的登錄成功后的路徑去。
四、授權(quán)
我們再開始之前再多寫幾個(gè)測試接口
前面我們在重寫configure(HttpSecurity http)方法中,有開啟基本的授權(quán)配置。
但是之前因?yàn)檫€沒有用戶授權(quán),也沒有配置哪些路徑需要什么樣的權(quán)限才能訪問,所以所有的接口在登錄后都能訪問。
4.1 增加的授權(quán)代碼
所以,我們只需要再增加哪些路徑需要什么權(quán)限才能訪問即可完成授權(quán)。其他不用變
.antMatchers("/security/test1").hasAuthority("admin")//表示當(dāng)前登錄用戶,只有具有權(quán)限名稱為admin時(shí),才能訪問此地址 @Overrideprotected void configure(HttpSecurity http) throws Exception {http.formLogin()//表示進(jìn)行表單登錄.loginPage("/login.html")//自定義的登錄頁面.loginProcessingUrl("/login")//傳一個(gè)登錄處理的接口,不管你傳的接口地址是什么,都由Security內(nèi)部完成。當(dāng)然也可以自己寫這個(gè)接口,這樣就不會用系統(tǒng)來完成登錄處理校驗(yàn)用戶名密碼了。還有就是如果自定義了登錄頁面,那么登錄處理的接口loginProcessingUrl項(xiàng)一定要寫,不管是寫系統(tǒng)自帶的,還是你自己寫的處理接口都行,否則報(bào)錯(cuò)。.usernameParameter("username") //定義登錄時(shí)的用戶名的key,即表單中name的值,默認(rèn)為username.passwordParameter("password") //定義登錄時(shí)的密碼key,即表單中name的值,默認(rèn)是password//設(shè)置的這兩個(gè)用戶名、密碼的key,如果不自己寫登錄頁面的話,可以不用寫,因?yàn)橄到y(tǒng)默認(rèn)提供的頁面就是這個(gè)默認(rèn)值。寫了的話,一定要與表單頁面中定義的name值一致才行。.defaultSuccessUrl("/pages/main")//登錄成功跳轉(zhuǎn)到的頁面或者路徑。當(dāng)然,如果你不是從登錄頁面登錄的,那么攔截之后會進(jìn)入到你的請求路徑(或頁面)中.failureUrl("/login.html")//登錄失敗跳轉(zhuǎn)到的頁面.permitAll() //指和登錄表單相關(guān)的接口 都通過,不攔截.and().authorizeRequests()//開啟授權(quán)請求.antMatchers("/","/pages/main","/login").permitAll()//設(shè)置哪些路徑放行,不需要認(rèn)證 不需要登錄可以訪問的.antMatchers("/security/test1").hasAuthority("admin")//表示當(dāng)前登錄用戶,只有具有權(quán)限名稱為admin時(shí),才能訪問此地址.anyRequest().authenticated()//除開上面的,其他所有請求全部都需要認(rèn)證。.and().csrf().disable();//關(guān)閉csrf防護(hù)}?4.2?驗(yàn)證
重啟項(xiàng)目,然后登錄跳轉(zhuǎn),發(fā)現(xiàn)403,說明成功了,我們的頁面沒有權(quán)限,403表示無權(quán)限禁止訪問。
如果給我們的用戶增加權(quán)限呢,就是再我們的UserDetailsService實(shí)現(xiàn)類中,重寫方法增加這個(gè)權(quán)限即可。
?當(dāng)然呢,正常情況下,不同的用戶會有不同的權(quán)限,我們可以通過在數(shù)據(jù)庫添加權(quán)限,然后查詢數(shù)據(jù)庫的權(quán)限,傳遞過來即可。就不用寫死。
我們再重啟測試一下,發(fā)現(xiàn)就能訪問了。
?4.3 常見的授權(quán)方法
除了上述案例中的使用到了第一個(gè)授權(quán)方法:
1. hasAuthority(String authority)
意思是如果當(dāng)主體具有指定的權(quán)限,則返回true,否則返回false。
2.?hasAnyAuthority(String... authorities)
如果當(dāng)前主體具有任意一個(gè)權(quán)限,則返回true,否則返回false。
3. hasRole(String role)
如果當(dāng)前主體具有指定的角色,則返回true,否則返回false。
需要注意的是如果是hasRole,那么在userDetailsService實(shí)現(xiàn)類中的角色名前面一定要添加ROLE_
4.?hasAnyRole(String... roles)
?如果當(dāng)前主體具備任何一個(gè)角色,則返回true,否則返回false。
?4.4 自定義403頁面
通過以上的授權(quán)方法,我們可以完成授權(quán)功能。當(dāng)我們的用戶沒有相應(yīng)的權(quán)限時(shí),則會出現(xiàn)403白標(biāo)簽頁面。
為了更加友好的展示,我們會選擇自定義403頁面。
具體的做法也很簡單。
4.4.1 修改訪問配置類
增加代碼:
http.exceptionHandling().accessDeniedPage("/unauth");4.4.2 添加對應(yīng)控制器方法
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController;@RestController public class SystemController {@GetMapping("/unauth")public String unauth(){return "當(dāng)前用戶無權(quán)限訪問";} }?4.4.3 重啟測試
待訪問/security/test2接口,我們配置一下,不給測試用戶相應(yīng)的權(quán)限,就無權(quán)限訪問。
.antMatchers("/security/test2").hasAnyAuthority("fang1","fang2")4.4.4 其他寫法
我們的控制器方法,也可以是正常的返回一個(gè)Result對象,這樣如果是前后端分離的,根據(jù)Result對象由前端去生成相應(yīng)的頁面也可以。
或者,后端也可以提供一個(gè)頁面,返回一個(gè)轉(zhuǎn)發(fā)試圖。
import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping;//@RestController @Controller public class SystemController {@GetMapping("/unauth")public String unauth(){// return "當(dāng)前用戶無權(quán)限訪問";return "forward:403.html";} }?
?五、授權(quán)(注解方式)
除了上述的在配置文件中通過配置hasAuthority、hasAnyAuthority、hasRole、hasAnyRole這些方法配置外,我們也可以在對應(yīng)的控制器方法上,添加對應(yīng)的注解來進(jìn)行授權(quán)訪問。
5.1 主啟動(dòng)類上添加注解
@EnableGlobalMethodSecurity(securedEnabled=true)5.2 控制器方法添加相關(guān)授權(quán)注解
@Secured
判斷是否具有某個(gè)角色。
另外需要注意的是這里匹配的字符串需要添加前綴“ROLE_“。
?以上說明擁有teacher或者student角色的用戶才能訪問該方法。
但是如果要求同時(shí)滿足擁有這兩個(gè)角色的用戶才能訪問,@Secured注解就無能為力了。
@PreAuthorize(重點(diǎn))
判斷是否具有某個(gè)角色或權(quán)限,也判斷是否同時(shí)具有某些角色或權(quán)限
它比@Secured的能力更大,@Secured只能判斷是否具有某個(gè)角色
//擁有normal或者admin角色的用戶都可以方法helloUser()方法。 @GetMapping("/helloUser") @PreAuthorize("hasAnyRole('normal','admin')") public String helloUser() { return "hello,user"; }//同時(shí)擁有normal和admin角色的用戶才能訪問 @GetMapping("/helloUser") @PreAuthorize("hasRole('normal') AND hasRole('admin')") public String helloUser() { return "hello,user"; }六、關(guān)于密碼加密補(bǔ)充說明
前面我們代碼中使用到了兩處
一處是配置類中注入了PaswordEncoder的bean對象
第二處是UserDetailsService實(shí)現(xiàn)類中,返回User對象時(shí),第二個(gè)形參中設(shè)置的密碼加密。
?其實(shí),通常而言,在配置類中注入PaswordEncoder的bean對象是必須的,因?yàn)镾pring Security 要求容器中必須有 PasswordEncoder 實(shí)例,才能加密。所以當(dāng)我們手動(dòng)加入自定義登錄邏輯時(shí),要求必須給容器注入PaswordEncoder的bean對象。不寫會報(bào)錯(cuò),如:There is no PasswordEncoder mapped for the id ”null"
當(dāng)然了,如果不想使用它自帶的加密方式,也可以使用自己的。寫一個(gè)類實(shí)現(xiàn)PasswordEncoder接口。
但是第二處,也就是UserDetailsService實(shí)現(xiàn)類中,返回User對象時(shí),第二個(gè)形參其實(shí)最好不要再加密一次。這就不得不提這個(gè)User對象的作用了。總而言之,如果此時(shí)再加密,就相當(dāng)于了解密,也就意味著數(shù)據(jù)庫中必須是明文的形式。如果此時(shí)返回的User對象密碼不加密,也就意味著數(shù)據(jù)庫中的密碼必須是密文的形式。實(shí)際開發(fā)中,肯定是希望數(shù)據(jù)庫中的密碼為密文了,這樣更加安全。比如用戶通過輸入密碼1234567,傳到后臺被spring security攔截,它首先通過配置文件中注入的PaswordEncoder的bean對象進(jìn)行加密,然后內(nèi)部會通過我們UserDetailsService實(shí)現(xiàn)類中查詢數(shù)據(jù)庫返回的User對象,進(jìn)行用戶名和密碼進(jìn)行比對。所以UserDetailsService實(shí)現(xiàn)類中的User對象不要進(jìn)行加密了。
我們修改后重新測試一下,
先手動(dòng)給數(shù)據(jù)庫生成一個(gè)測試用戶
?只有用戶自己知道真實(shí)的密碼是多少,其他人僅通過數(shù)據(jù)庫是無法知曉真實(shí)密碼的。
?然后修改UserDetailsService實(shí)現(xiàn)類
?重啟測試
?
假如我們使用數(shù)據(jù)庫中存儲的密文,進(jìn)行登錄,此時(shí)是不能登錄成功的。
另外,我們不難發(fā)現(xiàn),同一個(gè)字符串,通過加密生成的字符串每次都不一樣,但是盡管每次都不一樣,也都不會匹配失敗。換句話說,同一個(gè)密碼生成的密文每次都不一樣,但是無論是哪個(gè)密文,最終都能解析成功。
七、用戶注銷
7.1 配置類中增加注銷相關(guān)配置
//退出配置http.logout().logoutUrl("/logout")//退出登錄的處理接口隨便寫,系統(tǒng)幫你實(shí)現(xiàn)。和.loginProcessingUrl類似.logoutSuccessUrl("/login.html")//退出成功跳轉(zhuǎn)的頁面或接口.permitAll();?7.2 編寫退出超鏈接
目的是通過點(diǎn)擊超鏈接,跳轉(zhuǎn)到配置類中設(shè)置的退出登錄處理接口。
之前我們一直沒寫主頁面,要不這次我們就編寫一個(gè)主頁面,然后在主頁面完成退出超鏈接跳轉(zhuǎn)吧。
7.3 測試?
?
?
?
八、記住我功能的實(shí)現(xiàn)
什么是記住我功能?
比如說,我們進(jìn)行登錄之后,把瀏覽器關(guān)閉掉。下次再訪問網(wǎng)站時(shí),不需要重新進(jìn)行登錄。
比如說,163郵箱,它有一個(gè)十天內(nèi)免登錄,當(dāng)我們勾選了以后,那么十天內(nèi)都不用重新輸入密碼進(jìn)行登錄了。
正常而言,我們關(guān)閉瀏覽器后,默認(rèn)cookie會消失,不信可以試試。
?
?
?然后關(guān)閉瀏覽器,重新訪問測試接口
提示我們需要重新登錄
?
正常而言,我們關(guān)閉瀏覽器后,默認(rèn)cookie會消失。但是如果設(shè)置了記住我,那么它會生成一個(gè)叫remember me的cookie,這個(gè)cookie包含了用戶信息,且不會消失,直到我們設(shè)置的過期時(shí)間到了才會消失。
因此我們關(guān)閉后,再次請求,它會帶著這個(gè)rememberme(就是token)到我們的服務(wù)器中查詢建立的新的數(shù)據(jù)庫表信息。【系統(tǒng)會在內(nèi)存中自動(dòng)創(chuàng)建表,但是處于安全考慮,我們一般會在自己的數(shù)據(jù)庫建表。】
8.1 數(shù)據(jù)庫建表
CREATE TABLE `persistent_logins` ( `username` varchar(64) NOT NULL, `series` varchar(64) NOT NULL, `token` varchar(64) NOT NULL, `last_used` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`series`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;8.2?編寫配置類
當(dāng)然也可以在原來那個(gè)配置類中寫。也可以新建一個(gè)配置類。都可以。
/*** 自動(dòng)登錄 配置類中,注入數(shù)據(jù)源和配置操作數(shù)據(jù)庫對象*///注入數(shù)據(jù)源@Autowiredprivate DataSource dataSource;//注入操作數(shù)據(jù)庫的對象JdbcTokenRepositoryImpl,用它來創(chuàng)建token// 當(dāng)然最好選擇返回上層接口。我們一般是這樣的,因?yàn)槎鄳B(tài)方便后續(xù)修改維護(hù)。@Beanpublic PersistentTokenRepository persistentTokenRepository(){JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();// 賦值數(shù)據(jù)源jdbcTokenRepository.setDataSource(dataSource);// 自動(dòng)創(chuàng)建表 , 第一次執(zhí)行會創(chuàng)建,以后要執(zhí)行就要?jiǎng)h除掉!//jdbcTokenRepository.setCreateTableOnStartup(true);//這里我們是自己創(chuàng)建的數(shù)據(jù)庫。所以不要這句return jdbcTokenRepository;}@Overrideprotected void configure(HttpSecurity http) throws Exception {.......and().rememberMe()//開啟記住我功能.tokenRepository(persistentTokenRepository())//把操作數(shù)據(jù)庫對象傳進(jìn)來.tokenValiditySeconds(60)//表示自動(dòng)登錄,60s內(nèi)有效.userDetailsService(userDetailsService)//查詢數(shù)據(jù)庫的service......}?8.3 完善登錄頁面
在登錄頁面添加復(fù)選框,要求name值必須為remember-me,否則SpringSecurity找不到。
8.4 測試?
?
?這樣在60s內(nèi)關(guān)閉瀏覽器后,重新打開是不需要登錄的。只有超過這個(gè)時(shí)間才需要重新登錄。
當(dāng)每次自動(dòng)登錄時(shí),會在瀏覽器中將token存cookie值。同時(shí)會在persistent_logins這張表中生成相應(yīng)的數(shù)據(jù)。注意這些都是SpringSecurity幫我們實(shí)現(xiàn)的。
我們關(guān)閉瀏覽器,再次打開發(fā)現(xiàn),不用我們登錄了。這就是因?yàn)殚_啟了remember-me。就是SpringSecurity幫我們存了token到cookie中。當(dāng)然設(shè)置的過期時(shí)間到了還是要重新登錄的,你也可以不設(shè)置過期時(shí)間,永不過期。但是不建議。
我們可以通過如下方法設(shè)置這個(gè)時(shí)間。比如說7天內(nèi)免登錄,那就是60*60*24*7
?.tokenValiditySeconds(60*60*24*7)九、CSRF功能
前面配置文件中,我們配置過一項(xiàng),就是
?那么什么是CSRF呢?
即跨站請求偽造(Cross-site request forgery)
跨站請求位置默認(rèn)開啟。針對 PATCH,POST,PUT 和 DELETE 方法進(jìn)行防護(hù)。
想要實(shí)現(xiàn)該功能,只需要在配置類中開啟CSRF的情況下,在前端中設(shè)置如下:
<input type="hidden"th:if="${_csrf}!=null"th:value="${_csrf.token}"name="_csrf"/>一般我們測試的時(shí)候,免得在前端還要加上這個(gè)代碼。都選擇關(guān)閉CSRF功能。如果你不關(guān)閉,那么在前端表單登錄的代碼中一定要加上上面這段。否則你自己寫的登錄頁面(屬于跨站),POST提交就會被進(jìn)行防護(hù)。
十、踢下線功能
10.1 核心代碼
只需要在配置類中增加session相關(guān)配置
//踢下線配置http.sessionManagement().maximumSessions(1) // 表示同一個(gè)用戶最大登錄客戶端的數(shù)量為1.maxSessionsPreventsLogin(false) // 阻止登錄策略,如果為true,表示已經(jīng)登錄就不允許在別的地方登錄了。如果為false,則表示在其他地方登錄后,就會踢出之前其他地方登錄的該賬號。.expiredSessionStrategy(new SessionInformationExpiredStrategy() {// 方法一:頁面跳轉(zhuǎn)的方式處理//private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();// 當(dāng)發(fā)現(xiàn)session超時(shí),或者session被踢下線之后,要進(jìn)行的處理//@Override//public void onExpiredSessionDetected(SessionInformationExpiredEvent event) throws IOException, ServletException {// redirectStrategy.sendRedirect(event.getRequest(),event.getResponse(),"/forced");//}// 方法二:前后端分離的情況下,一般是返回json數(shù)據(jù)// 可以使用springboot默認(rèn)的jackson的json處理對象,當(dāng)然你也可以使用其他json工具private ObjectMapper objectMapper = new ObjectMapper();@Overridepublic void onExpiredSessionDetected(SessionInformationExpiredEvent event) throws IOException, ServletException {Map<String, Object> map = new HashMap<>();map.put("code",50009);map.put("data",null);map.put("msg","您已在其他地方進(jìn)行了登錄,請核實(shí)是否為本人操作!");String json = objectMapper.writeValueAsString(map);event.getResponse().setContentType("application/json;charset=utf-8");event.getResponse().getWriter().write(json);}});10.2 測試
先在谷歌瀏覽器上測試
?
換用其他瀏覽器登錄同一賬號?
?
?
此時(shí)回到谷歌瀏覽器,點(diǎn)擊刷新頁面,就提示在其他地方進(jìn)行了登錄了。被迫下線!
總結(jié)
以上是生活随笔為你收集整理的一篇文章轻松搞定SpringSecurity权限框架!的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 怎么批量生成100多条短视频素材
- 下一篇: python学习笔记项目_python第