springSecurity 中不能拋出異常UserNameNotFoundException 解析

通過查看源碼可得知:

1. 前言

抽象類中AbstractUserDetailsAuthenticationProvider 接口拋出異常AuthenticationException

下面源碼註釋這麼描述

     *
     * @throws AuthenticationException if the credentials could not be validated
     * (generally a <code>BadCredentialsException</code>, an
     * <code>AuthenticationServiceException</code> or
     * <code>UsernameNotFoundException</code>)
     */
    protected abstract UserDetails retrieveUser(String username,
            UsernamePasswordAuthenticationToken authentication)
            throws AuthenticationException;

AuthenticationException 拋出的情況是 BadCredentialsException,AuthenticationServiceException,UsernameNotFoundException這三個異常。

當UserNameNotFoundException 這個異常的情況下會拋出

可實際情況下我們 查詢的user爲null 拋出了 UserNameNotFoundException 這個異常但是實際並沒有拋出來,拋出的是 AuthenticationException

通過繼續往下查看源碼後明白了,原來是做了對UserNameNotFoundException 處理,轉換成了AuthenticationException 這個異常;

hideUserNotFoundExceptions = true;
...
boolean cacheWasUsed = true;
        UserDetails user = this.userCache.getUserFromCache(username);

        if (user == null) {
            cacheWasUsed = false;

            try {
                user = retrieveUser(username,
                        (UsernamePasswordAuthenticationToken) authentication);
            }
            catch (UsernameNotFoundException notFound) {
                logger.debug("User '" + username + "' not found");

                if (hideUserNotFoundExceptions) {
                    throw new BadCredentialsException(messages.getMessage(
                            "AbstractUserDetailsAuthenticationProvider.badCredentials",
                            "Bad credentials"));
                }
                else {
                    throw notFound;
                }
            }

所以我們沒有拋出UsernameNotFoundException 這個異常,而是將這個異常進行了轉換。

2.解決辦法

如何拋出這個異常,那就是將hideUserNotFoundExceptions 設置爲 false;

2.1設置實現類中

     @Bean
    public DaoAuthenticationProvider authenticationProvider() {
        DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
        provider.setHideUserNotFoundExceptions(false);
        provider.setUserDetailsService(mUserDetailsService);
        provider.setPasswordEncoder(passwordEncoder);
        return provider;
    }

最後在WebSecurityConfig配置即可

 auth.authenticationProvider(daoAuthenticationProvider());

2.2 debug來看一下

設置之前
image.png
設置之後
image.png

拋出的UsernameNotFoundException 異常已經捕獲到了,然後進入if中
最後拋出

new BadCredentialsException(messages.getMessage(
                            "AbstractUserDetailsAuthenticationProvider.badCredentials",
                            "Bad credentials"))

會將異常信息存入session中 , 根據key即可獲取
最後在失敗的處理器中獲取到

@Component
public class MyAuthenctiationFailureHandler extends SimpleUrlAuthenticationFailureHandler {

    public MyAuthenctiationFailureHandler() {
        this.setDefaultFailureUrl("/loginPage");
    }

    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
        logger.info("進入認證失敗處理類");
        HttpSession session = request.getSession();
        AuthenticationException attributes = (AuthenticationException) session.getAttribute("SPRING_SECURITY_LAST_EXCEPTION");
        logger.info("aaaa " + attributes);
        super.onAuthenticationFailure(request, response, exception);
    }
}

這樣子做可以直接在session中獲取到,如果自定義拋出一個異常首先控制檯會報異常錯,其次前臺的通過如ajax獲取錯誤信息,又得寫ajax。

這樣子做直接將信息存入session中,springSecurity直接爲我們封裝到session中了,可以直接根據key獲取到。

如: ${session.SPRING_SECURITY_LAST_EXCEPTION.message}

2.4 判斷密碼

注意
如果用戶名不存在,拋了異常

不要再在密碼驗證其中拋出密碼錯誤異常,不然拋出UserNameNotFoundException 後還會驗證密碼是否正確,如果密碼正確還好,返回true,如果不正確拋出異常。
此時會將 UsernameNotFoundException 異常覆蓋,這裏應該返回false。

源碼如下:

protected final UserDetails retrieveUser(String username,
            UsernamePasswordAuthenticationToken authentication)
            throws AuthenticationException {
        prepareTimingAttackProtection();
        try {
            UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);
            if (loadedUser == null) {
                throw new InternalAuthenticationServiceException(
                        "UserDetailsService returned null, which is an interface contract violation");
            }
            return loadedUser;
        }
        catch (UsernameNotFoundException ex) {
                    // 、、、、、、、 這裏會去匹配密碼是否正確 
            mitigateAgainstTimingAttack(authentication);
            throw ex;
        }
        catch (InternalAuthenticationServiceException ex) {
            throw ex;
        }
        catch (Exception ex) {
            throw new InternalAuthenticationServiceException(ex.getMessage(), ex);
        }
    }

mitigateAgainstTimingAttack 方法

    private void mitigateAgainstTimingAttack(UsernamePasswordAuthenticationToken authentication) {
        if (authentication.getCredentials() != null) {
            String presentedPassword = authentication.getCredentials().toString();
                        //這裏會是自定義密碼加密
            this.passwordEncoder.matches(presentedPassword, this.userNotFoundEncodedPassword);
        }
    }

我的密碼加密器

 @Override
    public boolean matches(CharSequence charSequence, String s) {

        String pwd = charSequence.toString();
        log.info("前端傳過來密碼爲: " + pwd);
        log.info("加密後密碼爲: " + MD5Util.encode(charSequence.toString()));
        log.info("數據庫存儲的密碼: " + s);
        //s 應在數據庫中加密
        if( MD5Util.encode(charSequence.toString()).equals(MD5Util.encode(s))){
            return true;
        }

        //throw new DisabledException("--密碼錯誤--");  //不能拋出異常
        return false;
    }

如下是 我們密碼驗證器裏拋出異常後獲取到的異常

異常

密碼未驗證 之前捕獲到的異常信息
image.png
驗證密碼後捕獲到的異常 (這裏跳到ProviderManager中)
image.png

既然我用戶名不對我就不必要驗證密碼了,所以不應該拋出異常,應該直接返回false。
不然。此處密碼異常會將 用戶不存在進行覆蓋!

3. 驗證頁面代碼

<body>
登錄頁面
<div th:text="${session.SPRING_SECURITY_LAST_EXCEPTION!=null?session.SPRING_SECURITY_LAST_EXCEPTION.message:''}">[...]</div>
<form method="post" action="/login" >
    <input type="text" name="username" /><br>
    <input type="password" name="password" />

    <input type="submit" value="login" />


</form>

密碼不對

用戶名不對

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章