定制 Spring Security 錯誤提示信息

使用過 Spring Security 框架的同學應(yīng)該都見過這個提示:壞的憑證荚坞,對應(yīng)的英文提示為 Bad credentials虎谢。不知道是不是機翻蚁鳖,反正我看著十分別扭∮邪穑客戶拿到你做的系統(tǒng)是尖,輸入錯誤的用戶名或密碼時,看到壞的憑證這樣的提示肯定是一頭霧水泥耀。

下面就來教大家如何定制 Spring Security 的錯誤提示信息饺汹。

分析

spring-security-core-5.0.7.RELEASE.jarorg.springframework.security 包下,有一組名為 messages[_language_country].properties 的配置文件爆袍。

messages_zh_CN.properties 內(nèi)容如下:

AbstractAccessDecisionManager.accessDenied=不允許訪問
AbstractLdapAuthenticationProvider.emptyPassword=壞的憑證
AbstractSecurityInterceptor.authenticationNotFound=未在SecurityContext中查找到認證對象
AbstractUserDetailsAuthenticationProvider.badCredentials=壞的憑證
AbstractUserDetailsAuthenticationProvider.credentialsExpired=用戶憑證已過期
AbstractUserDetailsAuthenticationProvider.disabled=用戶已失效
AbstractUserDetailsAuthenticationProvider.expired=用戶帳號已過期
AbstractUserDetailsAuthenticationProvider.locked=用戶帳號已被鎖定
AbstractUserDetailsAuthenticationProvider.onlySupports=僅僅支持UsernamePasswordAuthenticationToken
AccountStatusUserDetailsChecker.credentialsExpired=用戶憑證已過期
AccountStatusUserDetailsChecker.disabled=用戶已失效
AccountStatusUserDetailsChecker.expired=用戶帳號已過期
AccountStatusUserDetailsChecker.locked=用戶帳號已被鎖定
AclEntryAfterInvocationProvider.noPermission=給定的Authentication對象({0})根本無權(quán)操控領(lǐng)域?qū)ο?{1})
AnonymousAuthenticationProvider.incorrectKey=展示的AnonymousAuthenticationToken不含有預期的key
BindAuthenticator.badCredentials=壞的憑證
BindAuthenticator.emptyPassword=壞的憑證
CasAuthenticationProvider.incorrectKey=展示的CasAuthenticationToken不含有預期的key
CasAuthenticationProvider.noServiceTicket=未能夠正確提供待驗證的CAS服務(wù)票根
ConcurrentSessionControlAuthenticationStrategy.exceededAllowed=已經(jīng)超過了當前主體({0})被允許的最大會話數(shù)量
DigestAuthenticationFilter.incorrectRealm=響應(yīng)結(jié)果中的Realm名字({0})同系統(tǒng)指定的Realm名字({1})不吻合
DigestAuthenticationFilter.incorrectResponse=錯誤的響應(yīng)結(jié)果
DigestAuthenticationFilter.missingAuth=遺漏了針對'auth' QOP的首繁、必須給定的摘要取值; 接收到的頭信息為{0}
DigestAuthenticationFilter.missingMandatory=遺漏了必須給定的摘要取值; 接收到的頭信息為{0}
DigestAuthenticationFilter.nonceCompromised=Nonce令牌已經(jīng)存在問題了,{0}
DigestAuthenticationFilter.nonceEncoding=Nonce未經(jīng)過Base64編碼; 相應(yīng)的nonce取值為 {0}
DigestAuthenticationFilter.nonceExpired=Nonce已經(jīng)過期/超時
DigestAuthenticationFilter.nonceNotNumeric=Nonce令牌的第1部分應(yīng)該是數(shù)字陨囊,但結(jié)果卻是{0}
DigestAuthenticationFilter.nonceNotTwoTokens=Nonce應(yīng)該由兩部分取值構(gòu)成弦疮,但結(jié)果卻是{0}
DigestAuthenticationFilter.usernameNotFound=用戶名{0}未找到
JdbcDaoImpl.noAuthority=沒有為用戶{0}指定角色
JdbcDaoImpl.notFound=未找到用戶{0}
LdapAuthenticationProvider.badCredentials=壞的憑證
LdapAuthenticationProvider.credentialsExpired=用戶憑證已過期
LdapAuthenticationProvider.disabled=用戶已失效
LdapAuthenticationProvider.expired=用戶帳號已過期
LdapAuthenticationProvider.locked=用戶帳號已被鎖定
LdapAuthenticationProvider.emptyUsername=用戶名不允許為空
LdapAuthenticationProvider.onlySupports=僅僅支持UsernamePasswordAuthenticationToken
PasswordComparisonAuthenticator.badCredentials=壞的憑證
#PersistentTokenBasedRememberMeServices.cookieStolen=Invalid remember-me token (Series/token) mismatch. Implies previous cookie theft attack.
ProviderManager.providerNotFound=未查找到針對{0}的AuthenticationProvider
RememberMeAuthenticationProvider.incorrectKey=展示RememberMeAuthenticationToken不含有預期的key
RunAsImplAuthenticationProvider.incorrectKey=展示的RunAsUserToken不含有預期的key
SubjectDnX509PrincipalExtractor.noMatching=未在subjectDN\: {0}中找到匹配的模式
SwitchUserFilter.noCurrentUser=不存在當前用戶
SwitchUserFilter.noOriginalAuthentication=不能夠查找到原先的已認證對象

Spring Security 通過 org.springframework.security.core.SpringSecurityMessageSource 類讀取該組配置文件,將其作為 MessageSource 使用:

/**
 * The default <code>MessageSource</code> used by Spring Security.
 * <p>
 * All Spring Security classes requiring message localization will by default use this
 * class. However, all such classes will also implement <code>MessageSourceAware</code> so
 * that the application context can inject an alternative message source. Therefore this
 * class is only used when the deployment environment has not specified an alternative
 * message source.
 * </p>
 *
 * @author Ben Alex
 */
public class SpringSecurityMessageSource extends ResourceBundleMessageSource {

    public SpringSecurityMessageSource() {
        setBasename("org.springframework.security.messages");
    }

    public static MessageSourceAccessor getAccessor() {
        return new MessageSourceAccessor(new SpringSecurityMessageSource());
    }
}

Spring Security 的很多類都會調(diào)用 SpringSecurityMessageSource.getAccessor() 方法來獲取 MessageSourceAccessor 的對象蜘醋,進而使用 getMessage(...) 方法訪問前述的配置文件信息:

/**
 * Helper class for easy access to messages from a MessageSource,
 * providing various overloaded getMessage methods.
 *
 * <p>Available from ApplicationObjectSupport, but also reusable
 * as a standalone helper to delegate to in application objects.
 *
 * @author Juergen Hoeller
 * @since 23.10.2003
 * @see ApplicationObjectSupport#getMessageSourceAccessor
 */
public class MessageSourceAccessor {

    private final MessageSource messageSource;

    @Nullable
    private final Locale defaultLocale;


    /**
     * Create a new MessageSourceAccessor, using LocaleContextHolder's locale
     * as default locale.
     * @param messageSource the MessageSource to wrap
     * @see org.springframework.context.i18n.LocaleContextHolder#getLocale()
     */
    public MessageSourceAccessor(MessageSource messageSource) {
        this.messageSource = messageSource;
        this.defaultLocale = null;
    }

    /**
     * Retrieve the message for the given code and the default Locale.
     * @param code code of the message
     * @param defaultMessage String to return if the lookup fails
     * @return the message
     */
    public String getMessage(String code, String defaultMessage) {
        String msg = this.messageSource.getMessage(code, null, defaultMessage, getDefaultLocale());
        return (msg != null ? msg : "");
    }

    //……
}

踩坑

SpringSecurityMessageSource 類的注釋是這樣說的:

The default MessageSource used by Spring Security.
All Spring Security classes requiring message localization will by default use this class. However, all such classes will also implement MessageSourceAware so that the application context can inject an alternative message source. Therefore this class is only used when the deployment environment has not specified an alternative message source.

Spring Security 默認使用的 MessageSource胁塞。
所有需要本地化信息的 Spring Security 類默認使用該類。不過,這些類也都實現(xiàn)了 MessageSourceAware啸罢,以便應(yīng)用程序上下文可以注入一個可供替代的信息源(message source)编检。因此這個類只在部署環(huán)境沒有另一個信息源時使用。

再結(jié)合官方手冊的說明 https://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle/#localization扰才,我們知道允懂,可以提供一個名為 messageSource 的 bean,該 bean 通過 MessageSourceAware 接口自動注入到 Spring Security 的類:

<bean id="messageSource"
    class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
<property name="basename" value="classpath:messages"/>
</bean>

接下來只需要拷貝一份 messages_zh_CN.properties 配置文件到我們的 classpath 即 Maven 目錄結(jié)構(gòu)的 resources 下衩匣,修改錯誤提示信息即可蕾总。

然而上述配置在我的 Spring Boot 2.0.4.RELEASE 集成環(huán)境下并沒有起到預期的效果!@拍蟆生百!

在代碼中注入上述配置的 messageSource bean,調(diào)用 getMessage(...) 方法確實能拿到我們自定義的錯誤提示信息柄延,但是 Spring Security 返回給頁面的提示仍然是壞的憑證……

解決方案

拷貝一份 messages_zh_CN.properties 配置文件到我們的 classpath 即 Maven 目錄結(jié)構(gòu)的 resources 下的 org/springframework/security 目錄中蚀浆,修改錯誤提示信息即可。

不需要配置 messageSource 的 bean搜吧,此方案直接暴力覆蓋了 Spring Security 的配置文件市俊,Spring Security 通過自己的默認配置加載的就是我們的 messages[_language_country].properties

題外話

百度 + 谷歌 + 官方手冊 + Spring Security 源碼赎败,此問題耗費了我兩個多小時的寶貴時間秕衙,而且我隱約記得之前也解決過這個問題。

好記性不如爛筆頭僵刮,特此記下据忘,利人利己,何樂而不為呢搞糕。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末勇吊,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子窍仰,更是在濱河造成了極大的恐慌汉规,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,839評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件驹吮,死亡現(xiàn)場離奇詭異针史,居然都是意外死亡,警方通過查閱死者的電腦和手機碟狞,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評論 2 382
  • 文/潘曉璐 我一進店門啄枕,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人族沃,你說我怎么就攤上這事频祝∶诓危” “怎么了?”我有些...
    開封第一講書人閱讀 153,116評論 0 344
  • 文/不壞的土叔 我叫張陵常空,是天一觀的道長沽一。 經(jīng)常有香客問我,道長漓糙,這世上最難降的妖魔是什么铣缠? 我笑而不...
    開封第一講書人閱讀 55,371評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮兼蜈,結(jié)果婚禮上攘残,老公的妹妹穿的比我還像新娘拙友。我一直安慰自己为狸,他們只是感情好,可當我...
    茶點故事閱讀 64,384評論 5 374
  • 文/花漫 我一把揭開白布遗契。 她就那樣靜靜地躺著辐棒,像睡著了一般。 火紅的嫁衣襯著肌膚如雪牍蜂。 梳的紋絲不亂的頭發(fā)上漾根,一...
    開封第一講書人閱讀 49,111評論 1 285
  • 那天,我揣著相機與錄音鲫竞,去河邊找鬼辐怕。 笑死,一個胖子當著我的面吹牛从绘,可吹牛的內(nèi)容都是我干的寄疏。 我是一名探鬼主播,決...
    沈念sama閱讀 38,416評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼僵井,長吁一口氣:“原來是場噩夢啊……” “哼陕截!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起批什,我...
    開封第一講書人閱讀 37,053評論 0 259
  • 序言:老撾萬榮一對情侶失蹤农曲,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后驻债,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體乳规,經(jīng)...
    沈念sama閱讀 43,558評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,007評論 2 325
  • 正文 我和宋清朗相戀三年合呐,在試婚紗的時候發(fā)現(xiàn)自己被綠了暮的。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,117評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡合砂,死狀恐怖青扔,靈堂內(nèi)的尸體忽然破棺而出源织,到底是詐尸還是另有隱情,我是刑警寧澤微猖,帶...
    沈念sama閱讀 33,756評論 4 324
  • 正文 年R本政府宣布谈息,位于F島的核電站,受9級特大地震影響凛剥,放射性物質(zhì)發(fā)生泄漏侠仇。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,324評論 3 307
  • 文/蒙蒙 一犁珠、第九天 我趴在偏房一處隱蔽的房頂上張望逻炊。 院中可真熱鬧,春花似錦犁享、人聲如沸余素。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,315評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽桨吊。三九已至,卻和暖如春凤巨,著一層夾襖步出監(jiān)牢的瞬間视乐,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,539評論 1 262
  • 我被黑心中介騙來泰國打工敢茁, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留佑淀,地道東北人。 一個月前我還...
    沈念sama閱讀 45,578評論 2 355
  • 正文 我出身青樓彰檬,卻偏偏與公主長得像伸刃,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子僧叉,可洞房花燭夜當晚...
    茶點故事閱讀 42,877評論 2 345

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理奕枝,服務(wù)發(fā)現(xiàn),斷路器瓶堕,智...
    卡卡羅2017閱讀 134,600評論 18 139
  • Spring Boot 參考指南 介紹 轉(zhuǎn)載自:https://www.gitbook.com/book/qbgb...
    毛宇鵬閱讀 46,748評論 6 342
  • SpringMVC原理分析 Spring Boot學習 5隘道、Hello World探究 1、POM文件 1郎笆、父項目...
    jack_jerry閱讀 1,267評論 0 1
  • 1.1 Spring IoC容器和bean簡介 本章介紹了Spring Framework實現(xiàn)的控制反轉(zhuǎn)(IoC)...
    起名真是難閱讀 2,568評論 0 8
  • app.module.ts: 因為缺少icon谭梗,所以可以去git上面下載下來,我是放在assets里面宛蚓,然后在in...
    云小諾閱讀 3,523評論 0 1