天天看點

程式員的硬核幹貨:SpringSecurity與認證緩存,實在是太頂了!

作者:程式員進階碼農II

Spring Security與認證緩存

關于Spring Security的基本介紹就到這裡。

完整地介紹SpringSecurity架構的各項功能不是本書的目标,我們關注的是在使用者認證場景下認證緩存的使用,以及如何實作高效的無狀态認證。講到這裡,你可能會有疑問,為什麼我們要重點分析使用者認證場景呢?因為當應用程式上的調用數達到最大值時,Spring Security性能就會成為主要關注點之一。

程式員的硬核幹貨:SpringSecurity與認證緩存,實在是太頂了!

安全性處理對性能的影響

預設情況下,Spring Security為每個新請求建立一個新會話,并每次準備一個新的安全上下文。這在執行使用者驗證時開銷很大,會降低系統性能。

本節對這一問題進一步展開。

假設,我們有一個API會對每個請求進行身份驗證。假設在沒有使用緩存的情況下來了解這個問題。我們知道,每次使用者認證都需要通路資料庫來擷取使用者名和密碼,并對它們進行驗證,系統通路資料庫的日志如代碼清單7-3所示。

代碼清單7-3 通路資料庫日志

o.s.jdbc.core.JdbcTemplate : Executing prepared SQL query

o.s.jdbc.core.JdbcTemplate : Executing prepared SQL

statement [select username,password,enabled from users where username

= ?]

o.s.jdbc.datasource.DataSourceUtils : Fetching JDBC Connection

from DataSource

o.s.j.datasource.SimpleDriverDataSource : Creating new JDBC Driver

Connection to

[jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=false]

o.s.jdbc.core.JdbcTemplate : Executing prepared SQL queryo.s.jdbc.core.JdbcTemplate : Executing prepared SQL

statement [select username,authority from authorities where username =

?]

o.s.jdbc.datasource.DataSourceUtils : Fetching JDBC Connection

from DataSource

o.s.j.datasource.SimpleDriverDataSource : Creating new JDBC Driver

Connection to

[jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=false]

...

可以看到每次認證請求都需要執行資料庫操作。關于這些日志我們在下節的案例分析中還會進一步展開。現在我們隻需要知道,如果資料庫頻繁被通路,可能會導緻不必要的負載。這時候,解決這個問題的一種有效方案是将使用者身份驗證緩存一個特定的時間長度。

認證緩存

Spring Security專門針對使用者認證緩存的需求,提供了一個UserCache接口,如代碼清單7-4所示。

代碼清單7-4 UserCache接口定義代碼

public interface UserCache {

//從緩存中擷取使用者資訊

UserDetails getUserFromCache(String username);

//把使用者資訊放入緩存中

void putUserInCache(UserDetails user);

//從緩存中移除使用者資訊

void removeUserFromCache(String username);

}

UserCache接口比較簡單,而Spring Security也提供了一組該接口的實作類,如圖7-3所示。

程式員的硬核幹貨:SpringSecurity與認證緩存,實在是太頂了!

圖7-3 UserCache接口實作類圖

可以看到,這裡存在一個基于Spring緩存所實作的SpringCacheBasedUserCache,該類的實作過程如代碼清單7-5所示。

代碼清單7-5 自定義SpringCacheBasedUserCache類代碼

public class SpringCacheBasedUserCache implements UserCache {

private final Cache cache;

public UserDetails getUserFromCache(String username) {

Cache.ValueWrapper element = username != null ?

cache.get(username) : null;

if (element == null) {

return null;

}

else {

return (UserDetails) element.get();

}

}

public void putUserInCache(UserDetails user) {

cache.put(user.getUsername(), user);

}

public void removeUserFromCache(UserDetails user) {

this.removeUserFromCache(user.getUsername());

}

public void removeUserFromCache(String username) { cache.evict(username);

}

}

對上述代碼中的Cache接口及其實作類我們已經在第6章中做了詳細的介紹。可以看到,SpringCacheBasedUserCache本質上隻是對這一接口中的操作做一層簡單的封裝而已,相當于實作一種自定義Cache。

我們再來看UserCache的另一個實作類EhCacheBasedUserCache,該類代碼如代碼清單7-6所示。

代碼清單7-6 自定義EhCacheBasedUserCache類代碼

public class EhCacheBasedUserCache implements UserCache,

InitializingBean {

private Ehcache cache;

public Ehcache getCache() {

return cache;

}

public UserDetails getUserFromCache(String username) {

Element element = cache.get(username);

if (element == null) {

return null;

}

else {

return (UserDetails) element.getValue();

}

}

public void putUserInCache(UserDetails user) {

Element element = new Element(user.getUsername(), user);

cache.put(element);

} public void removeUserFromCache(UserDetails user) {

this.removeUserFromCache(user.getUsername());

}

public void removeUserFromCache(String username) {

cache.remove(username);

}

public void setCache(Ehcache cache) {

this.cache = cache;

}

}

注意,EhCacheBasedUserCache與SpringCacheBasedUserCache的唯一差別就是前者使用了Ehcache來代替Spring自己的Cache,并提供了一個setCache()方法入口。

本文給大家講解的内容是springboot内置緩存:為安全控制添加認證緩存,Spring Security與認證緩存

  • 下文給大家講解的是springboot内置緩存:為安全控制添加認證緩存,Spring Security與認證緩存案例分析

繼續閱讀