1. 程式人生 > >SpringSecurity(五)圖片驗證碼的使用

SpringSecurity(五)圖片驗證碼的使用

SpringSecurity預設是沒有圖片驗證碼功能的,假如我們需要在登入介面新增一個圖片驗證碼的功能,我們可以在UsernamePasswordAuthenticationFilter過濾器之前寫一個圖片驗證碼過濾器,圖片驗證碼過濾器的功能:首先判斷請求地址是否需要圖片驗證碼,如果需要就判斷圖片驗證碼是否正確,如果驗證碼正確則繼續往下執行,否則丟擲驗證碼錯誤異常;如果不需要就直接往下執行。

 

圖片驗證碼

使用kaptcha外掛來生成圖片驗證碼,匯入如下依賴

        <dependency>
            <groupId>com.github.penggle</groupId>
            <artifactId>kaptcha</artifactId>
            <version>2.3.2</version>
        </dependency>

kaptcha的一些配置

@Component
public class KaptchaConfig {
    @Bean
    public DefaultKaptcha getDefaultKaptcha(){
        com.google.code.kaptcha.impl.DefaultKaptcha defaultKaptcha = new com.google.code.kaptcha.impl.DefaultKaptcha();
        Properties properties = new Properties();
        properties.setProperty("kaptcha.border", "yes"); // 是否有邊框
        properties.setProperty("kaptcha.border.color", "105,179,90"); // 驗證碼邊框顏色
        // properties.setProperty("kaptcha.textproducer.char.string", "ABCDEFG23456789"); // 驗證碼,不設定預設也存在
        properties.setProperty("kaptcha.noise.color", "red"); // 干擾線的顏色
        properties.setProperty("kaptcha.textproducer.font.color", "blue"); // 字型顏色
        properties.setProperty("kaptcha.image.width", "110");
        properties.setProperty("kaptcha.image.height", "40");
        properties.setProperty("kaptcha.textproducer.font.size", "30");
        properties.setProperty("kaptcha.session.key", "code");
        properties.setProperty("kaptcha.textproducer.char.length", "4");
        properties.setProperty("kaptcha.textproducer.font.names", "宋體,楷體,微軟雅黑");
        Config config = new Config(properties);
        defaultKaptcha.setConfig(config);
        return defaultKaptcha;
    }
}

kaptcha使用

        DefaultKaptcha defaultKaptcha = new DefaultKaptcha ();
        //生產驗證碼字串
        String code = defaultKaptcha.createText();
        //使用生產的驗證碼字串返回一個BufferedImage物件
        BufferedImage image = defaultKaptcha.createImage(code);

省略圖片驗證碼的生成過程,主要有兩個步驟:

1. 使用DefaultKaptcha 類生成BufferedImage 物件,將BufferedImage封裝到ImageCode類中,ImageCode主要有三個屬性:

String code(驗證碼字串)、LocalDateTime expireTime(過期時間)、BufferedImage image(圖片)

2. 將生成的ImageCode存入Session中

 

圖片驗證碼過濾器

為了在某些地址使用圖片驗證碼,我們需要寫一個過濾器。

@Component
public class ImageCodeFilter extends OncePerRequestFilter implements InitializingBean {

    @Autowired
    private AuthenticationFailureHandler authenticationFailureHandler;

    private Set<String> urls = new HashSet<>();

    private AntPathMatcher antPathMatcher = new AntPathMatcher();

    @Override
    public void afterPropertiesSet() throws ServletException {
        super.afterPropertiesSet();
        // 這裡可以設定,哪些地址是需要圖片驗證碼進行驗證的
        urls.add("/authentication/form"); // 登入地址

    }

    @Override
    protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
        boolean action = false;
        // 判斷請求地址是否需要圖片驗證碼
        for (String url : urls) {
            if (antPathMatcher.match(url, httpServletRequest.getRequestURI())) {
                action = true;
                break;
            }
        }
        if (action) {
            try {
                // 驗證驗證碼是否正確
                validate(httpServletRequest);
            } catch (ImageCodeException e) {
                // 驗證碼錯誤則丟擲異常
                authenticationFailureHandler.onAuthenticationFailure(httpServletRequest, httpServletResponse, e);
                return;
            }
        }
        filterChain.doFilter(httpServletRequest, httpServletResponse);
    }

    private void validate(HttpServletRequest request) {
        ImageCode imageCodeSession = (ImageCode)request.getSession().getAttribute(ValidateCodeProcessor.SESSION_KEY_PREFIX + "IMAGE");
        String imageCodeRequest = request.getParameter("imageCode");
        if (imageCodeRequest == null || imageCodeRequest.isEmpty()) {
            throw new ImageCodeException("圖片驗證碼不能為空");
        }
        if (imageCodeSession == null) {
            throw new ImageCodeException("驗證碼不存在");
        }
        if (imageCodeSession.isExpired()) {
            request.getSession().removeAttribute(ValidateCodeProcessor.SESSION_KEY_PREFIX + "IMAGE");
            throw new ImageCodeException("驗證碼已過期");
        }
        if(!imageCodeRequest.equalsIgnoreCase(imageCodeSession.getCode())) {
            throw new ImageCodeException("驗證碼錯誤");
        }
        request.getSession().removeAttribute(ValidateCodeProcessor.SESSION_KEY_PREFIX + "IMAGE");
    }
}

 

配置過濾器

將ImageCodeFilter過濾器設定在UsernamePasswordAuthenticationFilter之前

@Configuration
@EnableWebSecurity
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private MyUserService myUserService;

    @Autowired
    private MyAuthenticationSuccessHandler authenticationSuccessHandler;
    @Autowired
    private MyAuthenticationFailHandler authenticationFailHandler;

    @Autowired
    private ImageCodeFilter imageCodeFilter;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.addFilterBefore(imageCodeFilter, UsernamePasswordAuthenticationFilter.class) // 將ImageCodeFilter過濾器設定在UsernamePasswordAuthenticationFilter之前
                .authorizeRequests()
                .antMatchers("/authentication/*","/login/*","/code/*") // 不需要登入就可以訪問
                .permitAll()
                .antMatchers("/user/**").hasAnyRole("USER") // 需要具有ROLE_USER角色才能訪問
                .antMatchers("/admin/**").hasAnyRole("ADMIN") // 需要具有ROLE_ADMIN角色才能訪問
                .anyRequest().authenticated()
                .and()
                    .formLogin()
                    .loginPage("/authentication/login") // 訪問需要登入才能訪問的頁面,如果未登入,會跳轉到該地址來
                    .loginProcessingUrl("/authentication/form")
                    .successHandler(authenticationSuccessHandler)
                    .failureHandler(authenticationFailHandler)
                ;
    }

    // 密碼加密方式
    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }
    // 重寫方法,自定義使用者
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//        auth.inMemoryAuthentication().withUser("lzc").password(new BCryptPasswordEncoder().encode("123456")).roles("ADMIN","USER");
//        auth.inMemoryAuthentication().withUser("zhangsan").password(new BCryptPasswordEncoder().encode("123456")).roles("USER");
        auth.userDetailsService(myUserService); // 注入MyUserService,這樣SpringSecurity會呼叫裡面的loadUserByUsername(String s)
    }
}

 

登入頁面

<form th:action="@{/authentication/form}" method="post">
                <div class="form-group">
                    <label for="username">Username</label>
                    <input type="text" class="form-control" id="username" name="username" placeholder="Enter username">
                </div>
                <div class="form-group">
                    <label for="Password">Password:</label>
                    <input type="password" class="form-control" id="Password" name="password" placeholder="Enter password">
                </div>
                <div class="form-group">
                    <label for="imageCode">imageCode:</label>
                    <input type="text" class="form-control" id="imageCode" name="imageCode" placeholder="Enter imageCode">
                    <img src="/code/image">
                </div>
                <div class="form-group" th:if="${param.error}">
                    <p th:if="${session.SPRING_SECURITY_LAST_EXCEPTION}">
                        <p th:text="${session.SPRING_SECURITY_LAST_EXCEPTION.message}"></p>
                    </p>
                </div>
                <button type="submit" class="btn btn-primary">Submit</button>
            </form>

 

程式碼地址  https://github.com/923226145/SpringSecurity/tree/master/chapter4