Spring Security(6)

湘王發表於2022-11-27

您好,我是湘王,這是我的部落格園,歡迎您來,歡迎您再來~

 

Spring Security使用MySQL儲存cookie記錄雖然方便,但是目前更多的主流網際網路應用都是用NoSQL來儲存非業務資料的,Spring Security也應該可以實現這個功能。之前Spring Security官方並不支援使用NoSQL來儲存cookie,但這個問題對於一個愛鑽研的碼農來說應該只是個小CASE——畢竟只要有程式碼,就沒有搞不定的問題——JdbcTokenRepositoryImpl的啟發,檢視其原始碼,可以發現JdbcDaoSupport只是用來提供資料來源,無實際意義,PersistentTokenRepository才是要實現的介面。

JdbcTokenRepositoryImpl的原始碼非常簡單,看懂了就能照著寫出Mongo的實現。題外話:閱讀原始碼是個能夠很快提高開發能力的捷徑,如Spring框架、Spark原始碼等等。

下面就來開始我們們的NoSQL改造DIY。

首先安裝並執行mongodb(我用的是mongodb-4.2.6),可以是虛擬機器,也可以是Docker。

再修改專案的pom.xml檔案,增加mongodb依賴:

 

 

 

 

透過模仿JdbcDaoSupport,來自定義自己的MongoDaoSupport:

/**
 * MongoDaoSupport
 *
 * @author 湘王
 */
@Component
public class MongoDaoSupport<T> {
    @Autowired
    private MongoTemplate mongoTemplate;

    // 插入資料
    public boolean insert(PersistentRememberMeToken token) {
        if (Objects.isNull(token)) {
            return false;
        }

        Object object = mongoTemplate.save(token);
        if (Objects.nonNull(object)) {
            return true;
        }

        return false;
    }
}

 

 

然後再在MongoDaoSupport中增加兩個重要的方法,讓它支援Mongodb:

    // 查詢資料
    public PersistentRememberMeToken getTokenBySeries(String series) {
//        // 如果是透過字串方式諸葛插入欄位值,那麼透過mongoTemplate.findOne()得到的就是一個LinkedHashMap
//        LinkedHashMap<String, String> map = (LinkedHashMap<String, String>) mongoTemplate
//                .findOne(query, Object.class, "collectionName");
//        return new PersistentRememberMeToken(map.get("username"), map.get("series"),
//                map.get("tokenValue"), DateUtils.format()map.get("date"));
        Query query = new Query(Criteria.where("series").is(series));
//        // 這裡原路返回PersistentRememberMeToken物件,不會是LinkedHashMap
//        Object object = mongoTemplate.findOne(query, PersistentRememberMeToken.class);
//        return (PersistentRememberMeToken) obejct;
        return mongoTemplate.findOne(query, PersistentRememberMeToken.class);
    }

    // 更新資料
    public boolean updateToken(String series, String tokenValue, Date lastUsed) {
        Query query = new Query(Criteria.where("series").is(series));
        Update update = new Update();
        update.set("tokenValue", tokenValue);
        update.set("date", lastUsed);
//        // 這裡不能用DateUtils.parse(new Date()),否則getTokenBySeries()方法會丟擲非法引數異常
//        update.set("date", DateUtils.parse(new Date()));
        Object object = mongoTemplate.updateMulti(query, update, PersistentRememberMeToken.class);
        if (Objects.nonNull(object)) {
            return true;
        }
        return false;
    }

 

 

然後再定義MongoTokenRepositoryImpl:

/**
 * 自定義實現token持久化到mongodb
 *
 * @author 湘王
 */
public class MongoTokenRepositoryImpl implements PersistentTokenRepository {
    @Autowired
    private MongoDaoSupport<PersistentRememberMeToken> mongoDaoSupport;

    @Override
    public void createNewToken(PersistentRememberMeToken token) {
        mongoDaoSupport.insert(token);
    }

    @Override
    public void updateToken(String series, String tokenValue, Date lastUsed) {
        mongoDaoSupport.updateToken(series, tokenValue, lastUsed);
    }

    @Override
    public PersistentRememberMeToken getTokenForSeries(String series) {
        return mongoDaoSupport.getTokenBySeries(series);
    }

    @Override
    public void removeUserTokens(String username) {
    }
}

 

 

接著在WebSecurityConfiguration中用自定義的MongoTokenRepositoryImpl代替JdbcTokenRepositoryImpl:

// NoSQL方式實現記住我
@Bean
public PersistentTokenRepository persistentTokenRepository() {
    // 自定義mongo方式實現
    MongoTokenRepositoryImpl mongoTokenRepository = new MongoTokenRepositoryImpl();
    return mongoTokenRepository;
}

 

 

或者就直接(需要透過@Service註解注入):

 

 

 

 

執行postman測試,可以看到透過MongoDB實現了對cookie資訊的儲存與修改。

如果mongodb中多出了_class欄位,可以加上額外的配置:

/**
 * 去除_class欄位
 *
 * @author 湘王
 */
@Configuration
public class MongoConfiguration implements InitializingBean {
    @Autowired
    @Lazy
    private MappingMongoConverter mappingMongoConverter;

    @Override
    public void afterPropertiesSet() {
        mappingMongoConverter
                .setTypeMapper(new DefaultMongoTypeMapper(null));
    }
}

 

 

多次執行可發現訪問記錄值的規律:

1、同一使用者會有多條訪問記錄

如果每次都明確執行login方法,那麼每次都會產生不同的記錄,否則只會更新同一條記錄的tokenValue和date值;

token有效且未執行login方法,那麼將更新最後一次產生的記錄的tokenValue和date值。

2、這說明token條數是與login方法執行次數一一對應的;

3、只要token不失效,僅更新同一條記錄series的token值。

 

訪問資料記錄:

 

 

 

不管是Mongodb還是別的NoSQL,比如Redis,原理都是一樣的。

 

 


 

 

感謝您的大駕光臨!諮詢技術、產品、運營和管理相關問題,請關注後留言。歡迎騷擾,不勝榮幸~

 

相關文章