1. 程式人生 > >Spring Security-- 驗證碼功能的實現

Spring Security-- 驗證碼功能的實現

turn stringbu overflow .net 內容 一個 子類 異常 too

spring security4 添加驗證碼

http://www.itwendao.com/article/detail/165400.html

http://www.itdadao.com/articles/c15a754492p0.html

https://stackoverflow.com/questions/29783059/redirect-in-a-filter-with-spring-boot/29789630

在spring security的內置login處理是無法滿足要求的,需要自己進行各種定制。這裏介紹login中實現驗證碼的實現。

實現方法

可以有三種方法可以實現驗證碼的功能

第一種

自定義一個filter,放在SpringSecurity過濾器之前,在用戶登錄的時候會先經過這個filter,然後在這個filter中實現對驗證碼進行驗證的功能,這種方法不推薦,因為它已經脫離了SpringSecurity

第二種

自定義一個filter讓它繼承自UsernamePasswordAuthenticationFilter,然後重寫attemptAuthentication方法在這個方法中實現驗證碼的功能,如果驗證碼錯誤就拋出一個繼承自AuthenticationException的驗證嗎錯誤的異常比如(CaptchaException),然後這個異常就會被SpringSecurity捕獲到並將異常信息返回到前臺,這種實現起來比較簡單。

@Override  
public Authentication attemptAuthentication(HttpServletRequest request,  
        HttpServletResponse response) throws AuthenticationException {  

    String requestCaptcha = request.getParameter(this.getCaptchaFieldName());  
    String genCaptcha = (String)request.getSession().getAttribute("code");  

    logger.info("開始校驗驗證碼,生成的驗證碼為:"+genCaptcha+" ,輸入的驗證碼為:"+requestCaptcha);  

    if( !genCaptcha.equals(requestCaptcha)){  
        throw new CaptchaException(  
                this.messageSource.getMessage("AbstractUserDetailsAuthenticationProvider.badCaptcha",null,"Default",null));  
    }  
    return super.attemptAuthentication(request, response);  
}  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

接著在配置文件中配置:

<bean id="loginFilter" class="com.zrhis.system.security.DefaultUsernamePasswordAuthenticationFilter">  
    <property name="authenticationManager"  ref="authenticationManager"></property>  
    <property name="authenticationSuccessHandler">  
        <bean class="org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler">  
            <property name="defaultTargetUrl" value="/index.jsp"></property>  
        </bean>  
    </property>  
    <property name="authenticationFailureHandler">  
        <bean class="org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler">  
            <property name="defaultFailureUrl" value="/login.jsp"></property>  
        </bean>  
    </property>  
</bean>  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

最後在http中加入custom-filter配置,將這個filter放在SpringSecurity的FORM_LOGIN_FILTER之前.

<custom-filter ref="loginFilter" before="FORM_LOGIN_FILTER"/>  
  • 1

第三種

直接替換掉SpringSecurity的UsernamePasswordAuthenticationFilter,這種比較復雜,但是更為合理,也是我現在正在用的。
如果用這種方法那麽http 中的auto-config就必須去掉,而form-login配置也必須去掉,因為這個不需要了,裏面的屬性都需要我們自行註入。

首先需要創建並配置一個login.jsp作為登錄頁面EntryPoint

<bean id="authenticationEntryPoint" class="org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint">  
    <property name="loginFormUrl" value="/login.jsp" />  
</bean>  
  • 1
  • 2
  • 3

然後在http中配置下

<sec:http access-decision-manager-ref="accessDecisionManager"  
        entry-point-ref="authenticationEntryPoint">  
  • 1
  • 2

然後我們來寫CaptchaAuthenticationFilter,同樣需要繼承自UsernamePasswordAuthenticationFilter

public class CaptchaAuthenticationFilter extends UsernamePasswordAuthenticationFilter{  

    public static final String SPRING_SECURITY_FORM_CAPTCHA_KEY = "j_captcha";  
    public static final String SESSION_GENERATED_CAPTCHA_KEY = Constant.SESSION_GENERATED_CAPTCHA_KEY;  

    private String captchaParameter = SPRING_SECURITY_FORM_CAPTCHA_KEY;  

    public Authentication attemptAuthentication(HttpServletRequest request,  
            HttpServletResponse response) throws AuthenticationException {  

        String genCode = this.obtainGeneratedCaptcha(request);  
        String inputCode = this.obtainCaptcha(request);  
        if(genCode == null)  
            throw new CaptchaException(this.messages.getMessage("LoginAuthentication.captchaInvalid"));  
        if(!genCode.equalsIgnoreCase(inputCode)){  
            throw new CaptchaException(this.messages.getMessage("LoginAuthentication.captchaNotEquals"));  
        }  

        return super.attemptAuthentication(request, response);  
    }  

    protected String obtainCaptcha(HttpServletRequest request){  
        return request.getParameter(this.captchaParameter);  
    }  

    protected String obtainGeneratedCaptcha (HttpServletRequest request){  
        return (String)request.getSession().getAttribute(SESSION_GENERATED_CAPTCHA_KEY);  
    }  

}  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30

在配置文件中配置CaptchaAuthenticationFilter

<bean id="captchaAuthenticaionFilter" class="com.zrhis.system.security.CaptchaAuthenticationFilter">  
    <property name="authenticationManager" ref="authenticationManager" />  
    <property name="authenticationFailureHandler" ref="authenticationFailureHandler" />  
    <property name="authenticationSuccessHandler" ref="authenticationSuccessHandler" />  
    <property name="filterProcessesUrl" value="/login.do" />  
</bean>  

<bean id="authenticationSuccessHandler" class="com.zrhis.system.security.SimpleLoginSuccessHandler">  
    <property name="defaultTargetUrl" value="/WEB-INF/app.jsp"></property>  
    <property name="forwardToDestination" value="true"></property>  
</bean>  
<bean id="authenticationFailureHandler" class="org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler">  
    <property name="defaultFailureUrl" value="/login.jsp" />  
</bean>  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

從配置文件中就可以看出來authenticationManager、authenticationFailureHandler、authenticationSuccessHandler、filterProcessesUrl等都需要我們自行註入了。
filterProcessesUrl定義的是登錄驗證的地址,默認的是j_spring_security_check這裏我們改成login.do

authenticationSuccessHandler中的defaultTargetUrl定義的是登錄成功後跳轉到的頁面

authenticationFailureHandler中的defaultTargetUrl定義的是登錄失敗後跳轉到的頁面

我們的首頁app.jsp在/WEB-INF下所以需要使用服務器跳轉,所以需要將forwardToDestination設為true,因為客戶端跳轉是不能直接訪問WEB-INF下的內容的。

最後在http中將FORM_LOGIN_FILTER替換掉,最終http中完整的配置就變成了下面的內容

<sec:http access-decision-manager-ref="accessDecisionManager"  
    entry-point-ref="authenticationEntryPoint">  

    <sec:access-denied-handler ref="accessDeniedHandler"/>  

    <sec:session-management invalid-session-url="/login.jsp" />  

    <sec:custom-filter ref="filterSecurityInterceptor" before="FILTER_SECURITY_INTERCEPTOR"/>  
    <sec:custom-filter ref="captchaAuthenticaionFilter" position="FORM_LOGIN_FILTER"/>  
</sec:http>  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

custom-filter中before是在這個filter之前,after是之後,position是替換。

http://www.infocool.net/kb/WWW/201701/268891.html

 在使用Spring Security框架過程中,經常會有這樣的需求,即在登錄驗證時,附帶增加額外的數據,如驗證碼、用戶類型等。下面將介紹如何實現。

  註:我的工程是在Spring Boot框架基礎上的,使用xml方式配置的話請讀者自行研究吧。

  • 實現自定義的WebAuthenticationDetails

  該類提供了獲取用戶登錄時攜帶的額外信息的功能,默認實現WebAuthenticationDetails提供了remoteAddress與sessionId信息。開發者可以通過Authentication的getDetails()獲取WebAuthenticationDetails。我們編寫自定義類CustomWebAuthenticationDetails繼承自WebAuthenticationDetails,添加我們關心的數據(以下是一個token字段)。

技術分享
package com.cgs.courses.service;

import javax.servlet.http.HttpServletRequest;

import org.springframework.security.web.authentication.WebAuthenticationDetails;

public class CustomWebAuthenticationDetails extends WebAuthenticationDetails {
    /**
     * 
     */
    private static final long serialVersionUID = 6975601077710753878L;
    private final String token;

    public CustomWebAuthenticationDetails(HttpServletRequest request) {
        super(request);
        token = request.getParameter("token");
    }

    public String getToken() {
        return token;
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append(super.toString()).append("; Token: ").append(this.getToken());
        return sb.toString();
    }
}
技術分享

  註:在登錄頁面,可將token字段放在form表單中,也可以直接加在url的參數中,進而把額外數據發送給後臺。

  • 實現自定義的AuthenticationDetailsSource

  該接口用於在Spring Security登錄過程中對用戶的登錄信息的詳細信息進行填充,默認實現是WebAuthenticationDetailsSource,生成上面的默認實現WebAuthenticationDetails。我們編寫類實現AuthenticationDetailsSource,用於生成上面自定義的CustomWebAuthenticationDetails。

技術分享
package com.cgs.courses.service;

import javax.servlet.http.HttpServletRequest;

import org.springframework.security.authentication.AuthenticationDetailsSource;
import org.springframework.security.web.authentication.WebAuthenticationDetails;
import org.springframework.stereotype.Component;

@Component
public class CustomAuthenticationDetailsSource implements AuthenticationDetailsSource<HttpServletRequest, WebAuthenticationDetails> {

@Override
public WebAuthenticationDetails buildDetails(HttpServletRequest context) {
return new CustomWebAuthenticationDetails(context);
}
}
技術分享

  • 配置使用自定義的AuthenticationDetailsSource

  只要看這一句.formLogin().authenticationDetailsSource(authenticationDetailsSource)

技術分享
    @Autowired
    private AuthenticationDetailsSource<HttpServletRequest, WebAuthenticationDetails> authenticationDetailsSource;

    protected void configure(HttpSecurity http) throws Exception {
        http
            .headers()
                .cacheControl()
                .contentTypeOptions()
                .httpStrictTransportSecurity()
                .xssProtection()
                .and()
            .authorizeRequests()
                .antMatchers(
                    "/css/**",
                    "/js/**")
                .permitAll()
                .antMatchers("/**")
                .authenticated()
                .and()
            .formLogin()
                .loginPage("/login")
                .permitAll()
                .defaultSuccessUrl("/todo.html", true)
                .authenticationDetailsSource(authenticationDetailsSource)
            .and()
                .logout()
                .logoutUrl("/logout")
                .logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
                .logoutSuccessUrl("/login")
                .and()
            .csrf().disable();
    }
技術分享

  • 實現自定義的AuthenticationProvider

  AuthenticationProvider提供登錄驗證處理邏輯,我們實現該接口編寫自己的驗證邏輯。

技術分享
package com.cgs.courses.service;

import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.stereotype.Component;

@Component
public class CustomAuthenticationProvider implements AuthenticationProvider {
    
    @Override
    public Authentication authenticate(Authentication authentication) 
            throws AuthenticationException {
        CustomWebAuthenticationDetails details = (CustomWebAuthenticationDetails) authentication.getDetails();  // 如上面的介紹,這裏通過authentication.getDetails()獲取詳細信息
        // System.out.println(details); details.getRemoteAddress(); details.getSessionId(); details.getToken();
// 下面是驗證邏輯,驗證通過則返回UsernamePasswordAuthenticationToken,
// 否則,可直接拋出錯誤(AuthenticationException的子類,在登錄驗證不通過重定向至登錄頁時可通過session.SPRING_SECURITY_LAST_EXCEPTION.message獲取具體錯誤提示信息)
if (驗證通過) {
return UsernamePasswordAuthenticationToken(省略參數);
} else {
throw new AuthenticationException的子類("你要顯示的錯誤信息")
} } @Override public boolean supports(Class<?> authentication) { return authentication.equals(UsernamePasswordAuthenticationToken.class); } }
技術分享

  • 配置使用自定義的AuthenticationProvider
技術分享
[email protected]
private AuthenticationProvider authenticationProvider;

[email protected] public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { auth.authenticationProvider(authenticationProvider); }

http://www.cnblogs.com/phoenix-smile/p/5666686.html

    @Bean
    public AuthenticationFailureHandler authenticationFailureHandler() {
        ExceptionMappingAuthenticationFailureHandler failureHandler = new ExceptionMappingAuthenticationFailureHandler();
        Map<String, String> failureUrlMap = new HashMap<>();
        failureUrlMap.put(AccountExpiredException.class.getName(), LoginAuthenticationFailureHandler.EXPIRE_URL);
        failureUrlMap.put(BadCredentialsException.class.getName(), LoginAuthenticationFailureHandler.PASS_ERROR_URL);
        failureUrlMap.put(CaptchaException.class.getName(), LoginAuthenticationFailureHandler.KAPTCHA_ERROR_URL);
        failureHandler.setExceptionMappings(failureUrlMap);
        return failureHandler;
    }

Spring Security-- 驗證碼功能的實現