????????這里我對springboot不做過多描述,因?yàn)槲矣X得學(xué)這個(gè)的肯定掌握了springboot這些基礎(chǔ)
導(dǎo)入核心依賴
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring‐boot‐starter‐security</artifactId>
</dependency>
Servlet Context配置
@Configuration
public class WebConfig implements WebMvcConfigurer {
//默認(rèn)Url根路徑跳轉(zhuǎn)到/login,此url為spring security提供
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("redirect:/login");
}
}
Spring Security為我們提供了登錄頁面,這里我是將 "/",路徑設(shè)置為登陸頁面的路徑,方便測試,
也可以自定義登錄頁面,我會在后面說明
application.properties配置文件
server.port=8080
server.servlet.context-path=/security-springboot
spring.application.name = security-springboot
spring.mvc.view.prefix=/WEB-INF/view/
spring.mvc.view.suffix=.jsp
spring.datasource.url=jdbc:mysql://localhost:3306/user_db
spring.datasource.username=root
spring.datasource.password=XXXX
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
? ? ? ? 至于為什么不是yml,這完全不是這里的重點(diǎn),關(guān)于前端用的jsp,各位看官姥爺們也湊合看吧,理解這個(gè)框架就好,最下面的數(shù)據(jù)庫配置這里也可以先不做,后面也會詳細(xì)說明
核心配置來嘍,WebSecurityConfig
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
//定義用戶信息服務(wù)(查詢用戶信息)
/*
@Bean
public UserDetailsService userDetailsService(){
InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
manager.createUser(User.withUsername("zhangsan").password("123").authorities("p1").build());
manager.createUser(User.withUsername("lisi").password("456").authorities("p2").build());
return manager;
}
*/
//密碼編碼器
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
//安全攔截機(jī)制(最重要)
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
// .antMatchers("/r/r1").hasAuthority("p2")
// .antMatchers("/r/r2").hasAuthority("p2")
.antMatchers("/r/**").authenticated()//所有/r/**的請求必須認(rèn)證通過
.anyRequest().permitAll()//除了/r/**,其它的請求可以訪問
.and()
.formLogin()//允許表單登錄
.loginPage("/login-view")//登錄頁面
.loginProcessingUrl("/login")
.successForwardUrl("/login-success")//自定義登錄成功的頁面地址
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
.and()
.logout()
.logoutUrl("/logout")
.logoutSuccessUrl("/login-view?logout");
}
}
????????如果不想連接數(shù)據(jù)庫測試,這里可以先把這些注釋解除掉去掉,用模擬數(shù)據(jù)
controller代碼
@RestController
public class LoginController {
@RequestMapping(value = "/login-success",produces = {"text/plain;charset=UTF-8"})
public String loginSuccess(){
//提示具體用戶名稱登錄成功
return getUsername()+" 登錄成功";
}
/**
* 測試資源1
* @return
*/
@GetMapping(value = "/r/r1",produces = {"text/plain;charset=UTF-8"})
public String r1(){
return "訪問資源1";
}
/**
* 測試資源2
* @return
*/
@GetMapping(value = "/r/r2",produces = {"text/plain;charset=UTF-8"})
public String r2(){
return " 訪問資源2";
}
}
工作原理
Spring Security
所解決的問題就是
安全訪問控制
,而安全訪問控制功能其實(shí)就是對所有進(jìn)入系統(tǒng)的請求進(jìn)行攔截,
校驗(yàn)每個(gè)請求是否能夠訪問它所期望的資源。根據(jù)前邊知識的學(xué)習(xí),可以通過
Filter
或
AOP
等技術(shù)來實(shí)現(xiàn),
Spring
Security
對
Web
資源的保護(hù)是靠
Filter
實(shí)現(xiàn)的,所以從這個(gè)
Filter
來入手,逐步深入
Spring Security
原理。
當(dāng)初始化
Spring Security
時(shí),會創(chuàng)建一個(gè)名為
SpringSecurityFilterChain
的
Servlet
過濾器,類型為
org.springframework.security.web.FilterChainProxy
,它實(shí)現(xiàn)了
javax.servlet.Filter
,因此外部的請求會經(jīng)過此
類,下圖是
Spring Security
過慮器鏈結(jié)構(gòu)圖:
FilterChainProxy
是一個(gè)代理,真正起作用的是
FilterChainProxy
中
SecurityFilterChain
所包含的各個(gè)
Filter
,同時(shí)
這些
Filter
作為
Bean
被
Spring
管理,它們是
Spring Security
核心,各有各的職責(zé),但他們并不直接處理用戶的
認(rèn)
證
,也不直接處理用戶的
授權(quán)
,而是把它們交給了認(rèn)證管理器(
AuthenticationManager
)和決策管理器
(
AccessDecisionManager
)進(jìn)行處理,下圖是
FilterChainProxy
相關(guān)類的
UML
圖示。
spring Security
功能的實(shí)現(xiàn)主要是由一系列過濾器鏈相互配合完成。
下面介紹過濾器鏈中主要的幾個(gè)過濾器及其作用:
SecurityContextPersistenceFilter
這個(gè)
Filter
是整個(gè)攔截過程的入口和出口(也就是第一個(gè)和最后一個(gè)攔截
器),會在請求開始時(shí)從配置好的
SecurityContextRepository
中獲取
SecurityContext
,然后把它設(shè)置給
SecurityContextHolder
。在請求完成后將
SecurityContextHolder
持有的
SecurityContext
再保存到配置好
的
SecurityContextRepository
,同時(shí)清除
securityContextHolder
所持有的
SecurityContext
;
UsernamePasswordAuthenticationFilter
用于處理來自表單提交的認(rèn)證。該表單必須提供對應(yīng)的用戶名和密
碼,其內(nèi)部還有登錄成功或失敗后進(jìn)行處理的
AuthenticationSuccessHandler
和
AuthenticationFailureHandler
,這些都可以根據(jù)需求做相關(guān)改變;
FilterSecurityInterceptor
是用于保護(hù)
web
資源的,使用
AccessDecisionManager
對當(dāng)前用戶進(jìn)行授權(quán)訪問,前
面已經(jīng)詳細(xì)介紹過了;
ExceptionTranslationFilter
能夠捕獲來自
FilterChain
所有的異常,并進(jìn)行處理。但是它只會處理兩類異常:
AuthenticationException
和
AccessDeniedException
,其它的異常它會繼續(xù)拋出。
認(rèn)證流程

1.
用戶提交用戶名、密碼被
SecurityFilterChain
中的
UsernamePasswordAuthenticationFilter
過濾器獲取到,
封裝為請求
Authentication
,通常情況下是
UsernamePasswordAuthenticationToken
這個(gè)實(shí)現(xiàn)類。
2.
然后過濾器將
Authentication
提交至認(rèn)證管理器(
AuthenticationManager
)進(jìn)行認(rèn)證
3.
認(rèn)證成功后,
AuthenticationManager
身份管理器返回一個(gè)被填充滿了信息的(包括上面提到的權(quán)限信息,
身份信息,細(xì)節(jié)信息,但密碼通常會被移除)
Authentication
實(shí)例。
4.
SecurityContextHolder
安全上下文容器將第
3
步填充了信息的
Authentication
,通過
SecurityContextHolder.getContext().setAuthentication(…)
方法,設(shè)置到其中。
可以看出
AuthenticationManager
接口(認(rèn)證管理器)是認(rèn)證相關(guān)的核心接口,也是發(fā)起認(rèn)證的出發(fā)點(diǎn),它
的實(shí)現(xiàn)類為
ProviderManager
。而
Spring Security
支持多種認(rèn)證方式,因此
ProviderManager
維護(hù)著一個(gè)
List<AuthenticationProvider>
列表,存放多種認(rèn)證方式,最終實(shí)際的認(rèn)證工作是由
AuthenticationProvider
完成的。咱們知道
web
表單的對應(yīng)的
AuthenticationProvider
實(shí)現(xiàn)類為
DaoAuthenticationProvider
,它的內(nèi)部又維護(hù)著一個(gè)
UserDetailsService
負(fù)責(zé)
UserDetails
的獲取。最終
AuthenticationProvider
將
UserDetails
填充至
Authentication
。
AuthenticationProvider
通過前面的
Spring Security
認(rèn)證流程
我們得知,認(rèn)證管理器(
AuthenticationManager
)委托
AuthenticationProvider
完成認(rèn)證工作。
AuthenticationProvider
是一個(gè)接口,定義如下:
public interface AuthenticationProvider {
Authentication authenticate(Authentication authentication) throws AuthenticationException;
boolean supports(Class<?> var1);
}
authenticate
()
方法定義了
認(rèn)證的實(shí)現(xiàn)過程
,它的參數(shù)是一個(gè)
Authentication
,里面包含了登錄用戶所提交的用
戶、密碼等。而返回值也是一個(gè)
Authentication
,這個(gè)
Authentication
則是在認(rèn)證成功后,將用戶的權(quán)限及其他信
息重新組裝后生成。
Spring Security
中維護(hù)著一個(gè)
List<AuthenticationProvider>
列表,存放多種認(rèn)證方式,不同的認(rèn)證方式使用不
同的
AuthenticationProvider
。如使用用戶名密碼登錄時(shí),使用
AuthenticationProvider1
,短信登錄時(shí)使用
AuthenticationProvider2
等等這樣的例子很多。
每個(gè)
AuthenticationProvider
需要實(shí)現(xiàn)
supports
()
方法來表明自己支持的認(rèn)證方式,如我們使用表單方式認(rèn)證,
在提交請求時(shí)
Spring Security
會生成
UsernamePasswordAuthenticationToken
,它是一個(gè)
Authentication
,里面
封裝著用戶提交的用戶名、密碼信息。而對應(yīng)的,哪個(gè)
AuthenticationProvider
來處理它?
我們在
DaoAuthenticationProvider
的基類
AbstractUserDetailsAuthenticationProvider
發(fā)現(xiàn)以下代碼:
public boolean supports(Class<?> authentication) {
return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication);
}
也就是說當(dāng)
web
表單提交用戶名密碼時(shí),
Spring Security
由
DaoAuthenticationProvider
處理。
最后,我們來看一下
Authentication
(
認(rèn)證信息
)
的結(jié)構(gòu),它是一個(gè)接口,我們之前提到的
UsernamePasswordAuthenticationToken
就是它的實(shí)現(xiàn)之一:
public interface Authentication extends Principal, Serializable {
Collection<? extends GrantedAuthority> getAuthorities();
Object getCredentials();
Object getDetails();
Object getPrincipal();
boolean isAuthenticated();
void setAuthenticated(boolean var1) throws IllegalArgumentException;
}
(
1
)
Authentication
是
spring security
包中的接口,直接繼承自
Principal
類,而
Principal
是位于
java.security
包中的。它是表示著一個(gè)抽象主體身份,任何主體都有一個(gè)名稱,因此包含一個(gè)
getName()
方法。
(
2
)
getAuthorities()
,權(quán)限信息列表,默認(rèn)是
GrantedAuthority
接口的一些實(shí)現(xiàn)類,通常是代表權(quán)限信息的一系
列字符串。
(
3
)
getCredentials()
,憑證信息,用戶輸入的密碼字符串,在認(rèn)證過后通常會被移除,用于保障安全。
(
4
)
getDetails()
,細(xì)節(jié)信息,
web
應(yīng)用中的實(shí)現(xiàn)接口通常為
WebAuthenticationDetails
,它記錄了訪問者的
ip
地 址和sessionId
的值。
(
5
)
getPrincipal()
,身份信息,大部分情況下返回的是
UserDetails
接口的實(shí)現(xiàn)類,
UserDetails
代表用戶的詳細(xì)
信息,那從
Authentication
中取出來的
UserDetails
就是當(dāng)前登錄用戶信息,它也是框架中的常用接口之一。
UserDetailsService
現(xiàn)在咱們現(xiàn)在知道
DaoAuthenticationProvider
處理了
web
表單的認(rèn)證邏輯,認(rèn)證成功后既得到一個(gè)
Authentication(UsernamePasswordAuthenticationToken
實(shí)現(xiàn)
)
,里面包含了身份信息(
Principal
)。這個(gè)身份 信息就是一個(gè) Object
,大多數(shù)情況下它可以被強(qiáng)轉(zhuǎn)為
UserDetails
對象。
DaoAuthenticationProvider
中包含了一個(gè)
UserDetailsService
實(shí)例,它負(fù)責(zé)根據(jù)用戶名提取用戶信息
UserDetails(
包含密碼
)
,而后
DaoAuthenticationProvider
會去對比
UserDetailsService
提取的用戶密碼與用戶提交
的密碼是否匹配作為認(rèn)證成功的關(guān)鍵依據(jù),因此可以通過將自定義的
UserDetailsService
公開為
spring bean
來定 義自定義身份驗(yàn)證。
很多人把
DaoAuthenticationProvider
和
UserDetailsService
的職責(zé)搞混淆,其實(shí)
UserDetailsService
只負(fù)責(zé)從特定 的地方(通常是數(shù)據(jù)庫)加載用戶信息,僅此而已。而DaoAuthenticationProvider
的職責(zé)更大,它完成完整的認(rèn) 證流程,同時(shí)會把UserDetails
填充至
Authentication
。
上面一直提到
UserDetails
是用戶信息,咱們看一下它的真面目:
public interface UserDetails extends Serializable {
Collection<? extends GrantedAuthority> getAuthorities();
String getPassword();
String getUsername();
boolean isAccountNonExpired();
boolean isAccountNonLocked();
boolean isCredentialsNonExpired();
boolean isEnabled();
}
它和
Authentication
接口很類似,比如它們都擁有
username
,
authorities
。
Authentication
的
getCredentials()
與 UserDetails中的
getPassword()
需要被區(qū)分對待,前者是用戶提交的密碼憑證,后者是用戶實(shí)際存儲的密碼,認(rèn)證 其實(shí)就是對這兩者的比對。Authentication
中的
getAuthorities()
實(shí)際是由
UserDetails
的
getAuthorities()
傳遞而形 成的。還記得Authentication
接口中的
getDetails()
方法嗎?其中的
UserDetails
用戶詳細(xì)信息便是經(jīng)過了 AuthenticationProvider認(rèn)證之后被填充的。
通過實(shí)現(xiàn)
UserDetailsService
和
UserDetails
,我們可以完成對用戶信息獲取方式以及用戶信息字段的擴(kuò)展。
Spring Security
提供的
InMemoryUserDetailsManager(
內(nèi)存認(rèn)證
)
,
JdbcUserDetailsManager(jdbc
認(rèn)證
)
就是 UserDetailsService的實(shí)現(xiàn)類,主要區(qū)別無非就是從內(nèi)存還是從數(shù)據(jù)庫加載用戶
PasswordEncoder
DaoAuthenticationProvider
認(rèn)證處理器通過
UserDetailsService
獲取到
UserDetails
后,它是如何與請求 Authentication中的密碼做對比呢?
在這里
Spring Security
為了適應(yīng)多種多樣的加密類型,又做了抽象,
DaoAuthenticationProvider
通過 PasswordEncoder接口的
matches
方法進(jìn)行密碼的對比,而具體的密碼對比細(xì)節(jié)取決于實(shí)現(xiàn)
public interface PasswordEncoder {
String encode(CharSequence var1);
boolean matches(CharSequence var1, String var2);
default boolean upgradeEncoding(String encodedPassword) {
return false;
}
}
而
Spring Security
提供很多內(nèi)置的
PasswordEncoder
,能夠開箱即用,使用某種
PasswordEncoder
只需要進(jìn)行如
下聲明即可,如下:
@Bean
public PasswordEncoder passwordEncoder() {
return NoOpPasswordEncoder.getInstance();
}
NoOpPasswordEncoder
采用字符串匹配方法,不對密碼進(jìn)行加密比較處理,密碼比較流程如下:
1
、用戶輸入密碼(明文 )
2
、
DaoAuthenticationProvider
獲取
UserDetails
(其中存儲了用戶的正確密碼)
3
、
DaoAuthenticationProvider
使用
PasswordEncoder
對輸入的密碼和正確的密碼進(jìn)行校驗(yàn),密碼一致則校驗(yàn)通
過,否則校驗(yàn)失敗。
NoOpPasswordEncoder
的校驗(yàn)規(guī)則拿 輸入的密碼和
UserDetails
中的正確密碼進(jìn)行字符串比較,字符串內(nèi)容一致
則校驗(yàn)通過,否則 校驗(yàn)失敗。
實(shí)際項(xiàng)目中推薦使用
BCryptPasswordEncoder, Pbkdf2PasswordEncoder, SCryptPasswordEncoder
等,感興趣
的大家可以看看這些
PasswordEncoder
的具體實(shí)現(xiàn)。
授權(quán)流程
通過
快速上手
我們知道,
Spring Security
可以通過
http.authorizeRequests()
對
web
請求進(jìn)行授權(quán)保護(hù)。
Spring
Security
使用標(biāo)準(zhǔn)
Filter
建立了對
web
請求的攔截,最終實(shí)現(xiàn)對資源的授權(quán)訪問。

1.
攔截請求
,已認(rèn)證用戶訪問受保護(hù)的
web
資源將被
SecurityFilterChain
中的
FilterSecurityInterceptor
的子
類攔截。
2.
獲取資源訪問策略
,
FilterSecurityInterceptor
會從
SecurityMetadataSource
的子類
DefaultFilterInvocationSecurityMetadataSource
獲取要訪問當(dāng)前資源所需要的權(quán)限
Collection<ConfigAttribute>
。
SecurityMetadataSource
其實(shí)就是讀取訪問策略的抽象,而讀取的內(nèi)容,其實(shí)就是我們配置的訪問規(guī)則, 讀
取訪問策略如:

(不過后面我們都會從數(shù)據(jù)庫中拿)
3.
最后,
FilterSecurityInterceptor
會調(diào)用
AccessDecisionManager
進(jìn)行授權(quán)決策,若決策通過,則允許訪問資
源,否則將禁止訪問
授權(quán)決策
AccessDecisionManager
采用
投票
的方式來確定是否能夠訪問受保護(hù)資源。

AffirmativeBased
的邏輯是:
(
1
)只要有
AccessDecisionVoter
的投票為
ACCESS_GRANTED
則同意用戶進(jìn)行訪問;
(
2
)如果全部棄權(quán)也表示通過;
(
3
)如果沒有一個(gè)人投贊成票,但是有人投反對票,則將拋出
AccessDeniedException
。
Spring security
默認(rèn)使用的是
AffirmativeBased
。
ConsensusBased
的邏輯是:
(
1
)如果贊成票多于反對票則表示通過。
(
2
)反過來,如果反對票多于贊成票則將拋出
AccessDeniedException
。
(
3
)如果贊成票與反對票相同且不等于
0
,并且屬性
allowIfEqualGrantedDeniedDecisions
的值為
true
,則表 示通過,否則將拋出異常AccessDeniedException
。參數(shù)
allowIfEqualGrantedDeniedDecisions
的值默認(rèn)為
true
。
(
4
)如果所有的
AccessDecisionVoter
都棄權(quán)了,則將視參數(shù)
allowIfAllAbstainDecisions
的值而定,如果該值
為
true
則表示通過,否則將拋出異常
AccessDeniedException
。參數(shù)
allowIfAllAbstainDecisions
的值默認(rèn)為
false
。
UnanimousBased
的邏輯與另外兩種實(shí)現(xiàn)有點(diǎn)不一樣,另外兩種會一次性把受保護(hù)對象的配置屬性全部傳遞
給
AccessDecisionVoter
進(jìn)行投票,而
UnanimousBased
會一次只傳遞一個(gè)
ConfigAttribute
給
AccessDecisionVoter
進(jìn)行投票。這也就意味著如果我們的
AccessDecisionVoter
的邏輯是只要傳遞進(jìn)來的
ConfigAttribute
中有一個(gè)能夠匹配則投贊成票,但是放到
UnanimousBased
中其投票結(jié)果就不一定是贊成了。
UnanimousBased
的邏輯具體來說是這樣的:
(
1
)如果受保護(hù)對象配置的某一個(gè)
ConfigAttribute
被任意的
AccessDecisionVoter
反對了,則將拋出
AccessDeniedException
。
(
2
)如果沒有反對票,但是有贊成票,則表示通過。
(
3
)如果全部棄權(quán)了,則將視參數(shù)
allowIfAllAbstainDecisions
的值而定,
true
則通過,
false
則拋出
AccessDeniedException
自定義認(rèn)證
自定義登錄頁面
在
快速上手
中,你可能會想知道登錄頁面從哪里來的?因?yàn)槲覀儾]有提供任何的
HTML
或
JSP
文件。
Spring
Security
的默認(rèn)配置沒有明確設(shè)定一個(gè)登錄頁面的
URL
,因此
Spring Security
會根據(jù)啟用的功能自動生成一個(gè)登錄
頁面
URL
,并使用默認(rèn)
URL
處理登錄的提交內(nèi)容,登錄后跳轉(zhuǎn)的到默認(rèn)
URL
等等。盡管自動生成的登錄頁面很方便
快速啟動和運(yùn)行,但大多數(shù)應(yīng)用程序都希望定義自己的登錄頁面。
文章來源:http://www.zghlxwxcb.cn/news/detail-822457.html
@Configuration//就相當(dāng)于springmvc.xml文件
public class WebConfig implements WebMvcConfigurer {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("redirect:/login-view");
registry.addViewController("/login-view").setViewName("login");
}
}
//配置安全攔截機(jī)制
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/r/**").authenticated()
.anyRequest().permitAll()
.and()
.formLogin() (1)
.loginPage("/login‐view") (2)
.loginProcessingUrl("/login") (3)
.successForwardUrl("/login‐success") (4)
.permitAll();
}
(
1
)允許表單登錄
(
2
)指定我們自己的登錄頁
,spring security
以重定向方式跳轉(zhuǎn)到
/login-view
(
3
)指定登錄處理的
URL
,也就是用戶名、密碼表單提交的目的路徑
(
4
)指定登錄成功后的跳轉(zhuǎn)
URL
(
5
)我們必須允許所有用戶訪問我們的登錄頁(例如為驗(yàn)證的用戶),這個(gè)
formLogin().permitAll()
方法允許
任意用戶訪問基于表單登錄的所有的
URL
。
問題解決
spring security
為防止
CSRF
(
Cross-site request forgery
跨站請求偽造)的發(fā)生,限制了除了
get
以外的大多數(shù)方法。
解決方法
1
:
屏蔽
CSRF
控制,即
spring security
不再限制
CSRF
。
配置
WebSecurityConfig
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable() //屏蔽CSRF控制,即spring security不再限制CSRF
...
}
連接數(shù)據(jù)庫認(rèn)證
????????創(chuàng)建數(shù)據(jù)庫
CREATE DATABASE `user_db` CHARACTER SET 'utf8' COLLATE 'utf8_general_ci';
CREATE TABLE `t_user` (
`id` bigint(20) NOT NULL COMMENT '用戶id',
`username` varchar(64) NOT NULL,
`password` varchar(64) NOT NULL,
`fullname` varchar(255) NOT NULL COMMENT '用戶姓名',
`mobile` varchar(11) DEFAULT NULL COMMENT '手機(jī)號',
PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC
application.properties配置
spring.datasource.url=jdbc:mysql://localhost:3306/user_db
spring.datasource.username=root
spring.datasource.password=mysql
spring.datasource.driver‐class‐name=com.mysql.jdbc.Driver
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring‐boot‐starter‐jdbc</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql‐connector‐java</artifactId>
<version>5.1.47</version>
</dependency>
pom.xml添加依賴,mysql版本根據(jù)自己情況文章來源地址http://www.zghlxwxcb.cn/news/detail-822457.html
定義模型類型,在model包定義UserDto
@Data
public class UserDto {
private String id;
private String username;
private String password;
private String fullname;
private String mobile;
}
在Dao包定義UserDao:
@Repository
public class UserDao {
@Autowired
JdbcTemplate jdbcTemplate;
public UserDto getUserByUsername(String username){
String sql ="select id,username,password,fullname from t_user where username = ?";
List<UserDto> list = jdbcTemplate.query(sql, new Object[]{username}, new
BeanPropertyRowMapper<>(UserDto.class));
if(list == null && list.size() <= 0){
return null;
}
return list.get(0);
}
}
定義UserDetailService
@Service
public class SpringDataUserDetailsService implements UserDetailsService {
@Autowired
UserDao userDao;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//登錄賬號
System.out.println("username="+username);
//根據(jù)賬號去數(shù)據(jù)庫查詢...
UserDto user = userDao.getUserByUsername(username);
if(user == null){
return null;
}
//這里暫時(shí)使用靜態(tài)數(shù)據(jù)
UserDetails userDetails =
User.withUsername(user.getFullname()).password(user.getPassword()).authorities("p1").build();
return userDetails;
}
}
使用BCryptPasswordEncoder
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
UserDetails
中的密碼存儲
BCrypt
格式
前邊實(shí)現(xiàn)了從數(shù)據(jù)庫查詢用戶信息,所以數(shù)據(jù)庫中的密碼應(yīng)該存儲
BCrypt
格式
會話
獲取用戶身份
編寫
LoginController
,實(shí)現(xiàn)
/r/r1
、
/r/r2
的測試資源,并修改
loginSuccess
方法,注意
getUsername
方法,
Spring
Security
獲取當(dāng)前登錄用戶信息的方法為
SecurityContextHolder.getContext().getAuthentication()
@RestController
public class LoginController {
@RequestMapping(value = "/login-success",produces = {"text/plain;charset=UTF-8"})
public String loginSuccess(){
//提示具體用戶名稱登錄成功
return getUsername()+" 登錄成功";
}
/**
* 測試資源1
* @return
*/
@GetMapping(value = "/r/r1",produces = {"text/plain;charset=UTF-8"})
public String r1(){
return getUsername()+" 訪問資源1";
}
/**
* 測試資源2
* @return
*/
@GetMapping(value = "/r/r2",produces = {"text/plain;charset=UTF-8"})
public String r2(){
return getUsername()+" 訪問資源2";
}
//獲取當(dāng)前用戶信息
private String getUsername(){
String username = null;
//當(dāng)前認(rèn)證通過的用戶身份
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
//用戶身份
Object principal = authentication.getPrincipal();
if(principal == null){
username = "匿名";
}
if(principal instanceof org.springframework.security.core.userdetails.UserDetails){
UserDetails userDetails = (UserDetails) principal;
username = userDetails.getUsername();
}else{
username = principal.toString();
}
return username;
}
}
會話控制
通過以下配置方式對該選項(xiàng)進(jìn)行配置:
@Override
protected void configure(HttpSecurity http) throws Exception {
http.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
}
默認(rèn)情況下,
Spring Security
會為每個(gè)登錄成功的用戶會新建一個(gè)
Session
,就是
ifRequired
。
若選用
never
,則指示
Spring Security
對登錄成功的用戶不創(chuàng)建
Session
了,但若你的應(yīng)用程序在某地方新建了
session
,那么
Spring Security
會用它的。
若使用
stateless
,則說明
Spring Security
對登錄成功的用戶不會創(chuàng)建
Session
了,你的應(yīng)用程序也不會允許新建
session
。并且它會暗示不使用
cookie
,所以每個(gè)請求都需要重新進(jìn)行身份驗(yàn)證。這種無狀態(tài)架構(gòu)適用于
REST API
及其無狀態(tài)認(rèn)證機(jī)制。
會話超時(shí)
server.servlet.session.timeout
=
3600s
session
超時(shí)之后,可以通過
Spring Security
設(shè)置跳轉(zhuǎn)的路徑
http.sessionManagement()
.expiredUrl("/login‐view?error=EXPIRED_SESSION")
.invalidSessionUrl("/login‐view?error=INVALID_SESSION");
expired
指
session
過期,
invalidSession
指傳入的
sessionid
無效
安全會話cookie
我們可以使用
httpOnly
和
secure
標(biāo)簽來保護(hù)我們的會話
cookie
:
httpOnly
:如果為
true
,那么瀏覽器腳本將無法訪問
cookie
secure
:如果為
true
,則
cookie
將僅通過
HTTPS
連接發(fā)送
spring boot
配置文件:
server.servlet.session.cookie.http‐only
=
true
server.servlet.session.cookie.secure
=
true
退出
Spring security
默認(rèn)實(shí)現(xiàn)了
logout
退出,訪問
/logout
,果然不出所料,退出功能
Spring
也替我們做好了。
點(diǎn)擊
“Log Out”
退出 成功。
退出 后訪問其它
url
判斷是否成功退出。
這里也可以自定義退出成功的頁面
.and()
.logout()
.logoutUrl("/logout")
.logoutSuccessUrl("/login‐view?logout");
當(dāng)退出操作出發(fā)時(shí),將發(fā)生:
????????使HTTP Session
無效
????????清除 SecurityContextHolder
????????跳轉(zhuǎn)到 /login
-
view?logout
但是,類似于配置登錄功能,咱們可以進(jìn)一步自定義退出功能:
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
//...
.and()
.logout() (1)
.logoutUrl("/logout") (2)
.logoutSuccessUrl("/login‐view?logout") (3)
.logoutSuccessHandler(logoutSuccessHandler) (4)
.addLogoutHandler(logoutHandler) (5)
.invalidateHttpSession(true); (6)
}
(
1
)提供系統(tǒng)退出支持,使用
WebSecurityConfigurerAdapter
會自動被應(yīng)用
(
2
)設(shè)置觸發(fā)退出操作的
URL (
默認(rèn)是
/logout
).
(
3
)退出之后跳轉(zhuǎn)的
URL
。默認(rèn)是
/login?logout
。
(
4
)定制的
LogoutSuccessHandler
,用于實(shí)現(xiàn)用戶退出成功時(shí)的處理。如果指定了這個(gè)選項(xiàng)那么
logoutSuccessUrl()
的設(shè)置會被忽略。
(
5
)添加一個(gè)
LogoutHandler
,用于實(shí)現(xiàn)用戶退出時(shí)的清理工作
.
默認(rèn)
SecurityContextLogoutHandler
會被添加
為最后一個(gè)
LogoutHandler
。
(
6
)指定是否在退出時(shí)讓
HttpSession
無效。 默認(rèn)設(shè)置為
true
。
注意:如果讓
logout
在
GET
請求下生效,必須關(guān)閉防止
CSRF
攻擊
csrf().disable()
。如果開啟了
CSRF
,必須使用
post
方式請求
/logout
logoutHandler
:
一般來說,
LogoutHandler
的實(shí)現(xiàn)類被用來執(zhí)行必要的清理,因而他們不應(yīng)該拋出異常。
下面是
Spring Security
提供的一些實(shí)現(xiàn):
PersistentTokenBasedRememberMeServices
基于持久化
token
的
RememberMe
功能的相關(guān)清理
TokenBasedRememberMeService
基于
token
的
RememberMe
功能的相關(guān)清理
CookieClearingLogoutHandler
退出時(shí)
Cookie
的相關(guān)清理
CsrfLogoutHandler
負(fù)責(zé)在退出時(shí)移除
csrfToken
SecurityContextLogoutHandler
退出時(shí)
SecurityContext
的相關(guān)清理
鏈?zhǔn)?/span>
API
提供了調(diào)用相應(yīng)的
LogoutHandler
實(shí)現(xiàn)的快捷方式,比如
deleteCookies()
。
到了這里,關(guān)于Spring Boot開發(fā)Spring Security的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!