欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Spring?Security實現(xiàn)添加圖片驗證功能

 更新時間:2023年01月04日 14:20:02   作者:bangiao  
這篇文章主要為大家介紹了Spring?Security實現(xiàn)添加圖片驗證功能詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

本章內(nèi)容

Spring security添加圖片驗證方式,在互聯(lián)網(wǎng)上面有很多這種博客,都寫的非常的詳細(xì)了。本篇主要講一些添加圖片驗證的思路。還有前后端分離方式,圖片驗證要怎么去處理?

  • 圖片驗證的思路
  • 簡單的demo

思路

小白: "我們從總體流程上看圖片驗證在認(rèn)證的哪一個階段?"

小黑: "在獲取客戶輸入的用戶名密碼那一階段,而且要在服務(wù)器獲取數(shù)據(jù)庫中用戶名密碼之前。這是一個區(qū)間[獲取請求用戶名密碼, 獲取數(shù)據(jù)庫用戶名密碼)

而在 Spring security中, 可以很明顯的發(fā)現(xiàn)有兩種思路。

  • 第1種思路是在攔截登錄請求準(zhǔn)備認(rèn)證的那個過濾器。
  • 第2種思路是在那個過濾器背后的認(rèn)證器。"

小白: "為什么是這個階段呢? 不能是在判斷密碼驗證之前呢?"

小黑: "你傻啊, 如果在你說的階段, 服務(wù)器需要去數(shù)據(jù)庫中獲取用戶信息, 這相當(dāng)?shù)睦速M系統(tǒng)資源"

小白: "哦哦, 我錯了, 讓我屢屢整個流程應(yīng)該是啥樣"

小白: "我需要事先在后端生成一個驗證碼,然后通過驗證碼返回一張圖片給前端。前端登錄表單添加圖片驗證。用戶輸入圖片驗證后點擊登錄,會存放在request請求中, 后端需要從request請求中讀取到圖片驗證,判斷前后端驗證碼是否相同, 如果圖片驗證碼相同之后才開始從數(shù)據(jù)庫拿用戶信息。否則直接拋出認(rèn)證異常"

簡單點: 數(shù)據(jù)庫獲取用戶賬戶之前, 先進(jìn)行圖片驗證碼驗證

方案

怎么將字符串變成圖片驗證碼?

這輪子肯定不能自己造, 有就拿來吧你

  • kaptcha
  • hutool

kaptcha這么玩

<!--驗證碼生成器-->
<dependency>
    <groupId>com.github.penggle</groupId>
    <artifactId>kaptcha</artifactId>
    <version>2.3.2</version>
    <exclusions>
        <exclusion>
            <artifactId>javax.servlet-api</artifactId>
            <groupId>javax.servlet</groupId>
        </exclusion>
    </exclusions>
</dependency>
@Bean
public DefaultKaptcha captchaProducer() {
    Properties properties = new Properties();
    properties.put("kaptcha.border", "no");
    properties.put("kaptcha.textproducer.char.length","4");
    properties.put("kaptcha.image.height","50");
    properties.put("kaptcha.image.width","150");
    properties.put("kaptcha.obscurificator.impl","com.google.code.kaptcha.impl.ShadowGimpy");
    properties.put("kaptcha.textproducer.font.color","black");
    properties.put("kaptcha.textproducer.font.size","40");
    properties.put("kaptcha.noise.impl","com.google.code.kaptcha.impl.NoNoise");
    //properties.put("kaptcha.noise.impl","com.google.code.kaptcha.impl.DefaultNoise");
    properties.put("kaptcha.textproducer.char.string","acdefhkmnprtwxy2345678");
    DefaultKaptcha kaptcha = new DefaultKaptcha();
    kaptcha.setConfig(new Config(properties));
    return kaptcha;
}
@Resource
private DefaultKaptcha producer;
@GetMapping("/verify-code")
public void getVerifyCode(HttpServletResponse response, HttpSession session) throws Exception {
    response.setContentType("image/jpeg");
    String text = producer.createText();
    session.setAttribute("verify_code", text);
    BufferedImage image = producer.createImage(text);
    try (ServletOutputStream outputStream = response.getOutputStream()) {
        ImageIO.write(image, "jpeg", outputStream);
    }
}

hutool這么玩

@GetMapping("hutool-verify-code")
public void getHtoolVerifyCode(HttpServletResponse response, HttpSession session) throws IOException {
    CircleCaptcha circleCaptcha = CaptchaUtil.createCircleCaptcha(200, 100, 4, 80);
    session.setAttribute("hutool_verify_code", circleCaptcha.getCode());
    response.setContentType(MediaType.IMAGE_PNG_VALUE);
    circleCaptcha.write(response.getOutputStream());
}

這倆隨便挑選一個完事

前端就非常簡單了

<form th:action="@{/login}" method="post">
    <div class="input">
        <label for="name">用戶名</label>
        <input type="text" name="username" id="name">
        <span class="spin"></span>
    </div>
    <div class="input">
        <label for="pass">密碼</label>
        <input type="password" name="password" id="pass">
        <span class="spin"></span>
    </div>
    <div class="input">
        <label for="code">驗證碼</label>
        <input type="text" name="code" id="code"><img src="/verify-code" alt="驗證碼">
        <!--<input type="text" name="code" id="code"><img src="/hutool-verify-code" alt="驗證碼">-->
        <span class="spin"></span>
    </div>
    <div class="button login">
        <button type="submit">
            <span>登錄</span>
            <i class="fa fa-check"></i>
        </button>
    </div>
    <div th:text="${session.SPRING_SECURITY_LAST_EXCEPTION}"></div>
</form>

傳統(tǒng)web項目

我們現(xiàn)在根據(jù)上面的思路來設(shè)計設(shè)計該怎么實現(xiàn)這項功能

過濾器方式

/**
 * 使用 OncePerRequestFilter 的方式需要配置匹配器
 */
@RequiredArgsConstructor
public class ValidateCodeFilter extends OncePerRequestFilter {
    private final String login;
    private static final AntPathRequestMatcher requiresAuthenticationRequestMatcher = new AntPathRequestMatcher(this.login,
            "POST");
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        if (requiresAuthenticationRequestMatcher.matches(request)) {
            validateCode(request);
        }
        filterChain.doFilter(request, response);
    }
    private void validateCode(HttpServletRequest request) {
        HttpSession session = request.getSession();
        // 獲取保存在session中的code
        String verifyCode = (String) session.getAttribute("verify_code");
        if (StringUtils.isBlank(verifyCode)) {
            throw new ValidateCodeException("請重新申請驗證碼!");
        }
        // 拿到前端的 code
        String code = request.getParameter("code");
        if (StringUtils.isBlank(code)) {
            throw new ValidateCodeException("驗證碼不能為空!");
        }
        // 對比
        if (!StringUtils.equalsIgnoreCase(code, verifyCode)) {
            throw new AuthenticationServiceException("驗證碼錯誤!");
        }
        // 刪除掉 session 中的 verify_code
        session.removeAttribute("verify_code");
    }
}

雖然OncePerRequestFilter每次瀏覽器請求過來, 都會調(diào)用過濾器. 但是過濾器順序是非常重要的

@Controller
@Slf4j
public class IndexController {
   @GetMapping("login")
   public String login() {
      return "login";
   }
   @GetMapping("")
   @ResponseBody
   public Principal index(Principal principal) {
      return principal;
   }
}
@Configuration
public class SecurityConfig {
	public static final String[] MATCHERS_URLS = {"/verify-code",
			"/css/**",
			"/images/**",
			"/js/**",
			"/hutool-verify-code"};
	public static final String LOGIN_PROCESSING_URL = "/login";
	public static final String LOGIN_PAGE = "/login";
	public static final String SUCCESS_URL = "/index";
	@Bean
	public ValidateCodeFilter validateCodeFilter() {
		return new ValidateCodeFilter(LOGIN_PROCESSING_URL);
	}
//    @Bean
//    public WebSecurityCustomizer webSecurityCustomizer() {
//        return web -> web.ignoring()
//                .antMatchers("/js/**", "/css/**", "/images/**");
//    }
	@Bean
	SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {
		httpSecurity
				.authorizeHttpRequests()
				.antMatchers(MATCHERS_URLS).permitAll()
				.anyRequest()
				.authenticated()
				.and()
				.formLogin()
				.loginPage(LOGIN_PAGE)
				.loginProcessingUrl(LOGIN_PROCESSING_URL)
				.defaultSuccessUrl(SUCCESS_URL, true)
				.permitAll()
				.and()
				.csrf()
				.disable();
		httpSecurity.addFilterBefore(validateCodeFilter(), UsernamePasswordAuthenticationFilter.class);
		return httpSecurity.build();
	}
}

小白: "我在網(wǎng)上看到有些網(wǎng)友并不是繼承的OncePerRequestFilter接口啊?"

小黑: "是的, 有一部分朋友選擇繼承UsernamePasswordAuthenticationFilter"

小黑: "繼承這個過濾器的話, 我們需要配置很多東西, 比較麻煩"

小白: "為什么要有多余的配置?"

小黑: "你想想, 你自定義的過濾器繼承至UsernamePasswordAuthenticationFilter, 自定義的過濾器和原先的過濾器是同時存在的"

小黑: "沒有為你自定義的過濾器配置對應(yīng)的Configurer, 那么它里面啥也沒有全部屬性都是默認(rèn)值, 不說別的, 下面AuthenticationManager至少要配置吧?"

小黑: "他可是沒有任何默認(rèn)值, 這樣會導(dǎo)致下面這行代碼報錯"

小黑: "當(dāng)然如果你有自定義屬于自己的Configurer那沒話說, 比如FormLoginConfigurer"

p>小黑: "默認(rèn)這個函數(shù)需要HttpSecurity調(diào)用的, 我們自定義的Filter并沒有重寫Configurer這個環(huán)節(jié)"

小白: "哦, 我知道了, 那我就是要繼承至UsernamePasswordAuthenticationFilter呢? 我要怎么做?"

小黑: "也行, 這樣就可以不用配置AntPathRequestMatcher了"

public class VerifyCodeFilter extends UsernamePasswordAuthenticationFilter {
   @Override
   public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
      HttpSession session = request.getSession();
      String sessionVerifyCode = (String) session.getAttribute(Constants.VERIFY_CODE);
      String verifyCode = request.getParameter(Constants.VERIFY_CODE);
      if (StrUtil.isBlank(sessionVerifyCode) || StrUtil.isBlank(verifyCode)
            || !StrUtil.equalsIgnoreCase(sessionVerifyCode, verifyCode)) {
         throw new ValidateCodeException("圖片驗證碼錯誤, 請重新獲取");
      }
      return super.attemptAuthentication(request, response);
   }
}
@Bean
public VerifyCodeFilter verifyCodeFilter() throws Exception {
   VerifyCodeFilter verifyCodeFilter = new VerifyCodeFilter();
   verifyCodeFilter.setAuthenticationManager(authenticationConfiguration.getAuthenticationManager());
   return verifyCodeFilter;
}

小黑: "這樣就可以了"

小白: "也不麻煩啊"

小黑: "好吧, 好像是"

小白: "等等, 那SecurityFilterChain呢? 特別是formLogin()函數(shù)要怎么配置?"

httpSecurity.formLogin()
      .loginPage(loginPage)
      .loginProcessingUrl(loginUrl)
      .defaultSuccessUrl("/", true)
      .permitAll();
httpSecurity.addFilterBefore(verifyCodeFilter(), UsernamePasswordAuthenticationFilter.class);

小白: "那我前端表單用戶名和密碼的input標(biāo)簽的name屬性變成userpwd了呢? 也在上面formLogin上配置?"

小黑: "這里就有區(qū)別了, 明顯只能在VerifyCodeFilter Bean上配置"

@Bean
public VerifyCodeFilter verifyCodeFilter() throws Exception {
   VerifyCodeFilter verifyCodeFilter = new VerifyCodeFilter();
   verifyCodeFilter.setAuthenticationManager(authenticationConfiguration.getAuthenticationManager());
   verifyCodeFilter.setUsernameParameter("user");
   verifyCodeFilter.setPasswordParameter("pwd");
   return verifyCodeFilter;
}

小白: "我還以為有多麻煩呢, 就這..."

小黑: "額, 主要是spring security的過濾器不能代替, 只能插入某個過濾器前后位置, 所以如果自定義過濾器就需要我們配置一些屬性"

認(rèn)證器方式

小白: "認(rèn)證器要怎么實現(xiàn)圖片驗證呢?"

小黑: "說到認(rèn)證的認(rèn)證器, 一定要想到DaoAuthenticationProvider"

小黑: "很多人在基于認(rèn)證器實現(xiàn)圖片驗證時, 都重寫additionalAuthenticationChecks, 這是不對的"

小白: "那應(yīng)該重寫哪個方法 ?"

小黑: "應(yīng)該重寫下面那個函數(shù)"

小白: "等一下, 你注意到這個方法的參數(shù)了么? 你這要怎么從request中拿驗證碼?"

小黑: "有別的方法, 看源碼"

public class MyDaoAuthenticationProvider extends DaoAuthenticationProvider {
   @Override
   public Authentication authenticate(Authentication authentication) throws AuthenticationException {
      ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
      assert requestAttributes != null;
      HttpServletRequest request = requestAttributes.getRequest();
      String verifyCode = request.getParameter(Constants.VERIFY_CODE);
      String sessionVerifyCode = (String) request.getSession().getAttribute(Constants.VERIFY_CODE);
      if (StrUtil.isBlank(sessionVerifyCode) && StrUtil.isBlank(verifyCode)
            && !StrUtil.equalsIgnoreCase(sessionVerifyCode, verifyCode)) {
         throw new ValidateCodeException("圖片驗證碼錯誤, 請重新獲取");
      }
      return super.authenticate(authentication);
   }
}

小白: "哦, 我看到了, 沒想到還能這樣"

小白: "那你現(xiàn)在要怎么加入到Spring Security, 讓它代替掉原本的DaoAuthenticationProvider呢?"

小黑: "這里有一個思路, 還記得AuthenticationManager的父子關(guān)系吧, 你看到父親只有一個, 你看到兒子可以有幾個?"

小白: "好像是無數(shù)個, 那我是不是可以這么寫?"

/**
 * 往父類的 AuthenticationManager 里添加 authenticationProvider
 * 在源碼里面是這樣的AuthenticationProvider authenticationProvider = getBeanOrNull(AuthenticationProvider.class);
 *
 * @return
 * @throws Exception
 */
@Bean
public MyDaoAuthenticationProvider authenticationProvider() throws Exception {
    MyDaoAuthenticationProvider authenticationProvider = new MyDaoAuthenticationProvider(Constants.LOGIN_USERNAME, Constants.LOGIN_PASSWORD);
    authenticationProvider.setPasswordEncoder(passwordEncoder());
    authenticationProvider.setUserDetailsService(inMemoryUserDetailsManager());
    return authenticationProvider;
}
// 往子類AuthenticationManager里面添加的 authenticationProvider
httpSecurity.authenticationProvider(authenticationProvider());

小黑: "這上面的代碼有問題, AuthenticationManger有父類和子類, 上面這段代碼同時往父類和子類都添加MyDaoAuthenticationProvider, 這樣MyDaoAuthenticationProvider會被執(zhí)行兩次, 但request的流只能執(zhí)行一次, 會報錯"

小黑: "我們可以這么玩"

@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
    // 代碼省略
    // 代碼省略
    // 代碼省略
    // 代碼省略
    // 往子類AuthenticationManager里面添加的 authenticationProvider, 但不能阻止 AuthenticationManger 父類加載 DaoAuthenticationProvider
    AuthenticationManagerBuilder authenticationManagerBuilder = http.getSharedObject(AuthenticationManagerBuilder.class);
    // 但是這種方式可以將 parent Manager 設(shè)置為 null, 所以是可以的
    authenticationManagerBuilder.parentAuthenticationManager(null);
    MyDaoAuthenticationProvider authenticationProvider = new MyDaoAuthenticationProvider(Constants.LOGIN_USERNAME, Constants.LOGIN_PASSWORD);
    authenticationProvider.setPasswordEncoder(passwordEncoder());
    authenticationProvider.setUserDetailsService(inMemoryUserDetailsManager());
    authenticationManagerBuilder.authenticationProvider(authenticationProvider);
    http.authenticationManager(authenticationManagerBuilder.build());
    return http.build();
}

小黑: "SecurityFilterChain表示一個Filter集合, 更直接點就是子類的AuthenticationManager"

小黑: "所以這種玩法是給子類AuthenticationManager添加Provider, 但是它需要手動將parent置為 null, 否則父類的DaoAuthenticationProvider還是會執(zhí)行, 最后報錯信息就不對了, 本來應(yīng)該是驗證碼錯誤, 將會變成用戶名和密碼錯誤"

小黑: "還有就是, 很多人很喜歡在舊版本像下面這么玩"

@Override
@Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
   MyDaoAuthenticationProvider authenticationProvider = new MyDaoAuthenticationProvider(Constants.LOGIN_USERNAME, Constants.LOGIN_PASSWORD);
   authenticationProvider.setPasswordEncoder(passwordEncoder());
   authenticationProvider.setUserDetailsService(inMemoryUserDetailsManager());
   return new ProviderManager(authenticationProvider);
}

小黑: "在新版本也類似的這么搞, 但這樣是有區(qū)別的, 下面這種方式只會加入到spring Bean上下文, 但是不會加入到Spring Security中執(zhí)行, 他是無效的"

@Bean
public ProviderManager providerManager() throws Exception {
   MyDaoAuthenticationProvider authenticationProvider = authenticationProvider();
   return new ProviderManager(authenticationProvider);
}

小黑: "在新版本中, 使用上面那段代碼是一點用都沒有"

public MyDaoAuthenticationProvider authenticationProvider() throws Exception {
   MyDaoAuthenticationProvider authenticationProvider = new MyDaoAuthenticationProvider(Constants.LOGIN_USERNAME, Constants.LOGIN_PASSWORD);
   authenticationProvider.setPasswordEncoder(passwordEncoder());
   authenticationProvider.setUserDetailsService(inMemoryUserDetailsManager());
   return authenticationProvider;
}
// 往子類AuthenticationManager里面添加的 authenticationProvider
httpSecurity.authenticationProvider(authenticationProvider());

小黑: "上面這樣做也是不行, 他還是會存在兩個, 一個是MyDaoAuthenticationProvider(子類), 另一個是DaoAuthenticationProvider(父類)"

小白: "那最好的辦法是什么?"

小黑: "直接將MyDaoAuthenticationProvider添加到Spring Bean上下文"

@Bean
public MyDaoAuthenticationProvider authenticationProvider() throws Exception {
    MyDaoAuthenticationProvider authenticationProvider = new MyDaoAuthenticationProvider(Constants.LOGIN_USERNAME, Constants.LOGIN_PASSWORD);
    authenticationProvider.setPasswordEncoder(passwordEncoder());
    authenticationProvider.setUserDetailsService(inMemoryUserDetailsManager());
    return authenticationProvider;
}

小白: "那還有別的思路么?"

小黑: "還有么? 不清楚了, 萬能網(wǎng)友應(yīng)該知道"

小白: "就這樣設(shè)置就行了? 其他還需不需要配置?"

小黑: "其他和過濾器方式一致"

總結(jié)下

@Bean
public MyDaoAuthenticationProvider authenticationProvider() throws Exception {
// 最好的辦法就是直接MyDaoAuthenticationProvider加入到Spring Bean里面就行了, 其他都不要
MyDaoAuthenticationProvider authenticationProvider = new MyDaoAuthenticationProvider(Constants.LOGIN_USERNAME, Constants.LOGIN_PASSWORD);
authenticationProvider.setPasswordEncoder(passwordEncoder());
authenticationProvider.setUserDetailsService(inMemoryUserDetailsManager());
return authenticationProvider;
}

@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
 // 代碼省略
 // 代碼省略
 // 代碼省略
 // 代碼省略
 // 往子類AuthenticationManager里面添加的 authenticationProvider, 但不能阻止 AuthenticationManger 父類加載 DaoAuthenticationProvider
 AuthenticationManagerBuilder authenticationManagerBuilder = http.getSharedObject(AuthenticationManagerBuilder.class);
 // 但是這種方式可以將 parent Manager 設(shè)置為 null, 所以是可以的
 authenticationManagerBuilder.parentAuthenticationManager(null);
 MyDaoAuthenticationProvider authenticationProvider = new MyDaoAuthenticationProvider(Constants.LOGIN_USERNAME, Constants.LOGIN_PASSWORD);
 authenticationProvider.setPasswordEncoder(passwordEncoder());
 authenticationProvider.setUserDetailsService(inMemoryUserDetailsManager());
 authenticationManagerBuilder.authenticationProvider(authenticationProvider);
 http.authenticationManager(authenticationManagerBuilder.build());
 return http.build();
}

都是可以的, 一個往父類的AuthenticationManager添加MyDaoAuthenticationProvider, 另一個往子類添加, 設(shè)置父類為null

前后端分離項目

小白: "前后端分離和傳統(tǒng)web項目的區(qū)別是什么?"

小黑: "請求request和響應(yīng)response都使用JSON傳遞數(shù)據(jù)"

小白: "那我們分析源碼時只要關(guān)注 requestresponse 咯, 只要發(fā)現(xiàn)存在request的讀, 和 response的寫通通都要重寫一邊"

小黑: "是的, 其實很簡單, 無非是圖片驗證碼改用json讀, 認(rèn)證時的讀取usernamepassword也使用json讀, 其次是出現(xiàn)異常需要響應(yīng)response, 也改成json寫, 認(rèn)證成功和失敗需要響應(yīng)到前端也改成json寫"

小白: "哦, 那只要分析過源碼, 就能夠完成前后端分離功能了"

小黑: "所以還講源碼么? "

小白: "不用, 非常簡單"

基于過濾器方式

public class VerifyCodeFilter extends UsernamePasswordAuthenticationFilter {
   @Resource
   private ObjectMapper objectMapper;
   /**
    * 很多人這里同時支持前后端分離, 其實不對, 既然是前后端分離就徹底點
    * 但為了跟上潮流, 我這里也搞前后端分離
    *
    * @param request
    * @param response
    * @return
    * @throws AuthenticationException
    */
   @SneakyThrows
   @Override
   public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
      if (!"POST".equals(request.getMethod())) {
         throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
      }
      String contentType = request.getContentType();
      HttpSession session = request.getSession();
      if (MediaType.APPLICATION_JSON_VALUE.equals(contentType) || MediaType.APPLICATION_JSON_UTF8_VALUE.equals(contentType)) {
         Map map = objectMapper.readValue(request.getInputStream(), Map.class);
         imageJSONVerifyCode(session, map);
         String username = (String) map.get(this.getUsernameParameter());
         username = (username != null) ? username.trim() : "";
         String password = (String) map.get(this.getPasswordParameter());
         password = (password != null) ? password : "";
         UsernamePasswordAuthenticationToken authRequest = UsernamePasswordAuthenticationToken.unauthenticated(username,
               password);
         // Allow subclasses to set the "details" property
         setDetails(request, authRequest);
         return this.getAuthenticationManager().authenticate(authRequest);
      }
      imageVerifyCode(request, session);
      return super.attemptAuthentication(request, response);
   }
   private void imageJSONVerifyCode(HttpSession session, Map map) throws ValidateCodeException {
      String verifyCode = (String) map.get(Constants.VERIFY_CODE);
      String code = (String) session.getAttribute(Constants.VERIFY_CODE);
      if (StrUtil.isBlank(verifyCode) || StrUtil.isBlank(code) || !StrUtil.equalsIgnoreCase(verifyCode, code)) {
         throw new ValidateCodeException("驗證碼錯誤, 請重新獲取驗證碼");
      }
   }
   private void imageVerifyCode(HttpServletRequest request, HttpSession session) throws ValidateCodeException {
      String verifyCode = request.getParameter(Constants.VERIFY_CODE);
      String code = (String) session.getAttribute(Constants.VERIFY_CODE);
      if (StrUtil.isBlank(verifyCode) || StrUtil.isBlank(code) || !StrUtil.equalsIgnoreCase(verifyCode, code)) {
         throw new ValidateCodeException("驗證碼錯誤, 請重新獲取驗證碼");
      }
   }
}

小白: "為什么你要寫imageJSONVerifyCode, imageVerifyCode兩個函數(shù)? 寫一個不就行了?"

小黑: "額, 是的, 把參數(shù)改成兩個String verifyCode, String code也行"

@Configuration
public class SecurityConfig {
   @Resource
   private AuthenticationConfiguration authenticationConfiguration;
   @Bean
   PasswordEncoder passwordEncoder() {
      return PasswordEncoderFactories.createDelegatingPasswordEncoder();
   }
   @Bean
   public ObjectMapper objectMapper() throws Exception {
      return new ObjectMapper();
   }
   @Bean
   public VerifyCodeFilter verifyCodeFilter() throws Exception {
      VerifyCodeFilter verifyCodeFilter = new VerifyCodeFilter();
      verifyCodeFilter.setAuthenticationManager(authenticationConfiguration.getAuthenticationManager());
      verifyCodeFilter.setAuthenticationFailureHandler((request, response, exception) -> {
         HashMap<String, Object> map = new HashMap<>();
         map.put("status", 401);
         map.put("msg", exception.getMessage());
         response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
         response.getWriter().write(JSONUtil.toJsonStr(map));
      });
      verifyCodeFilter.setAuthenticationSuccessHandler((request, response, authentication) -> {
         HashMap<String, Object> map = new HashMap<>();
         map.put("status", 200);
         map.put("msg", "登錄成功");
         map.put("user", authentication);
         response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
         response.getWriter().write(JSONUtil.toJsonStr(map));
      });
      return verifyCodeFilter;
   }
   @Bean
   SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {
      httpSecurity
            .authorizeHttpRequests()
            .antMatchers(Constants.MATCHERS_LIST)
            .permitAll()
            .anyRequest()
            .authenticated()
      ;
      httpSecurity.formLogin()
            .loginPage(Constants.LOGIN_PAGE)
            .loginProcessingUrl(Constants.LOGIN_PROCESSING_URL)
            .defaultSuccessUrl(Constants.SUCCESS_URL, true)
            .permitAll();
      httpSecurity.logout()
            .clearAuthentication(true)
            .invalidateHttpSession(true)
            .logoutSuccessHandler((request, response, authentication) -> {
               HashMap<String, Object> map = new HashMap<>();
               map.put("status", 200);
               map.put("msg", "注銷成功");
               map.put("user", authentication);
               response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
               response.getWriter().write(JSONUtil.toJsonStr(map));
            });
      httpSecurity.csrf()
            .disable();
      httpSecurity.addFilterAt(verifyCodeFilter(), UsernamePasswordAuthenticationFilter.class);
      httpSecurity.exceptionHandling()
            .accessDeniedHandler((request, response, accessDeniedException) -> {
               HashMap<String, Object> map = new HashMap<>();
               map.put("status", 401);
               map.put("msg", "您沒有權(quán)限, 拒絕訪問: " + accessDeniedException.getMessage());
//             map.put("msg", "您沒有權(quán)限, 拒絕訪問");
               response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
               response.getWriter().write(JSONUtil.toJsonStr(map));
            })
            .authenticationEntryPoint((request, response, authException) -> {
               HashMap<String, Object> map = new HashMap<>();
               map.put("status", HttpStatus.UNAUTHORIZED.value());
               map.put("msg", "認(rèn)證失敗, 請重新認(rèn)證: " + authException.getMessage());
//             map.put("msg", "認(rèn)證失敗, 請重新認(rèn)證");
               response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
               response.getWriter().write(JSONUtil.toJsonStr(map));
            });
      return httpSecurity.build();
   }
}

注意這兩行代碼, 教你怎么在不使用WebSecurityConfigurerAdapter的情況下拿到AuthenticationManager

@RestController
@Slf4j
public class VerifyCodeController {
   @GetMapping("/verify-code")
   public void getVerifyCode(HttpServletResponse response, HttpSession session) throws Exception {
      GifCaptcha captcha = CaptchaUtil.createGifCaptcha(Constants.IMAGE_WIDTH, Constants.IMAGE_HEIGHT);
      RandomGenerator randomGenerator = new RandomGenerator(Constants.BASE_STR, Constants.RANDOM_LENGTH);
      captcha.setGenerator(randomGenerator);
      captcha.createCode();
      String code = captcha.getCode();
      session.setAttribute(Constants.VERIFY_CODE, code);
      ServletOutputStream outputStream = response.getOutputStream();
      captcha.write(outputStream);
      outputStream.flush();
      outputStream.close();
   }
}
@Controller
@Slf4j
public class IndexController {
   @GetMapping("login")
   public String login() {
      return "login";
   }
   @GetMapping("")
   @ResponseBody
   public Principal myIndex(Principal principal) {
      return principal;
   }
}

基于認(rèn)證器方式

public class MyDaoAuthenticationProvider extends DaoAuthenticationProvider {
   @Resource
   private ObjectMapper objectMapper;
   private final String loginUsername;
   private final String loginPassword;
   public MyDaoAuthenticationProvider(String loginUsername, String loginPassword) {
      this.loginUsername = loginUsername;
      this.loginPassword = loginPassword;
   }
   @SneakyThrows
   @Override
   public Authentication authenticate(Authentication authentication) throws AuthenticationException {
      ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
      assert requestAttributes != null;
      HttpServletRequest request = requestAttributes.getRequest();
      String contentType = request.getContentType();
      String verifyCode = (String) request.getSession().getAttribute(Constants.VERIFY_CODE);
      if (MediaType.APPLICATION_JSON_VALUE.equals(contentType) || MediaType.APPLICATION_JSON_UTF8_VALUE.equals(contentType)) {
         Map map = this.objectMapper.readValue(request.getInputStream(), Map.class);
         String code = (String) map.get(Constants.VERIFY_CODE);
         imageVerifyCode(verifyCode, code);
         String username = (String) map.get(loginUsername);
         String password = (String) map.get(loginPassword);
         UsernamePasswordAuthenticationToken authenticationToken = UsernamePasswordAuthenticationToken
               .unauthenticated(username, password);
         return super.authenticate(authenticationToken);
      }
      String code = request.getParameter(Constants.VERIFY_CODE);
      imageVerifyCode(verifyCode, code);
      return super.authenticate(authentication);
   }
   private void imageVerifyCode(String verifyCode, String code) throws ValidateCodeException {
      if (StrUtil.isBlank(verifyCode) || StrUtil.isBlank(code) || !StrUtil.equalsIgnoreCase(verifyCode, code)) {
         throw new ValidateCodeException("驗證碼錯誤, 請重新獲取驗證碼");
      }
   }
}
@Slf4j
@Configuration
public class SecurityConfig {
   private static final String NOOP_PASSWORD_PREFIX = "{noop}";
   private static final Pattern PASSWORD_ALGORITHM_PATTERN = Pattern.compile("^\\{.+}.*$");
   @Resource
   private SecurityProperties properties;
   @Bean
   PasswordEncoder passwordEncoder() {
      return PasswordEncoderFactories.createDelegatingPasswordEncoder();
   }
   @Bean
   public ObjectMapper objectMapper() {
      return new ObjectMapper();
   }
   @Bean
   @Lazy
   public InMemoryUserDetailsManager inMemoryUserDetailsManager() {
      SecurityProperties.User user = properties.getUser();
      List<String> roles = user.getRoles();
      return new InMemoryUserDetailsManager(
            User.withUsername(user.getName()).password(getOrDeducePassword(user, passwordEncoder()))
                  .roles(StringUtils.toStringArray(roles)).build());
   }
   private String getOrDeducePassword(SecurityProperties.User user, PasswordEncoder encoder) {
      String password = user.getPassword();
      if (user.isPasswordGenerated()) {
         log.warn(String.format(
               "%n%nUsing generated security password: %s%n%nThis generated password is for development use only. "
                     + "Your security configuration must be updated before running your application in "
                     + "production.%n",
               user.getPassword()));
      }
      if (encoder != null || PASSWORD_ALGORITHM_PATTERN.matcher(password).matches()) {
         return password;
      }
      return NOOP_PASSWORD_PREFIX + password;
   }
   @Bean
   public MyDaoAuthenticationProvider authenticationProvider() throws Exception {
      MyDaoAuthenticationProvider authenticationProvider = new MyDaoAuthenticationProvider(Constants.LOGIN_USERNAME, Constants.LOGIN_PASSWORD);
      authenticationProvider.setPasswordEncoder(passwordEncoder());
      authenticationProvider.setUserDetailsService(inMemoryUserDetailsManager());
      return authenticationProvider;
   }
   @Bean
   SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
      http
            .authorizeHttpRequests()
            .antMatchers(Constants.MATCHERS_LIST)
            .permitAll()
            .anyRequest()
            .authenticated()
      ;
      http.formLogin()
            .loginPage(Constants.LOGIN_PAGE)
            .loginProcessingUrl(Constants.LOGIN_PROCESSING_URL)
            .successHandler(new MyAuthenticationSuccessHandler())
            .failureHandler(new MyAuthenticationFailureHandler())
            .permitAll();
      http.logout()
            .clearAuthentication(true)
            .invalidateHttpSession(true)
            .logoutSuccessHandler(new MyLogoutSuccessHandler());
      http.csrf()
            .disable();
      http.exceptionHandling(exceptionHandlingConfigurer -> {
         exceptionHandlingConfigurer.authenticationEntryPoint(new MyAuthenticationEntryPoint());
         exceptionHandlingConfigurer.accessDeniedHandler(new MyAccessDeniedHandler());
      })
      ;
      return http.build();
   }
   private static class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
      @Override
      public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
         HashMap<String, Object> map = new HashMap<>();
         map.put("status", 200);
         map.put("msg", "認(rèn)證成功");
         map.put("user_info", authentication.getPrincipal());
         response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
         response.getWriter().write(JSONUtil.toJsonStr(map));
      }
   }
   private static class MyAuthenticationFailureHandler implements AuthenticationFailureHandler {
      @Override
      public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
         log.error("認(rèn)證失敗", exception);
         exception.printStackTrace();
         HashMap<String, Object> map = new HashMap<>();
         map.put("status", 401);
         map.put("msg", "認(rèn)證失敗");
         map.put("exception", exception.getMessage());
         response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
         response.getWriter().write(JSONUtil.toJsonStr(map));
      }
   }
   private static class MyAuthenticationEntryPoint implements AuthenticationEntryPoint {
      @Override
      public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
         log.error("認(rèn)證失效", authException);
         HashMap<String, Object> map = new HashMap<>();
         map.put("status", HttpStatus.UNAUTHORIZED.value());
         map.put("msg", "認(rèn)證失敗, 請重新認(rèn)證: " + authException.getMessage());
//             map.put("msg", "認(rèn)證失敗, 請重新認(rèn)證");
         response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
         response.getWriter().write(JSONUtil.toJsonStr(map));
      }
   }
   private static class MyAccessDeniedHandler implements AccessDeniedHandler {
      @Override
      public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
         log.error("沒有權(quán)限", accessDeniedException);
         HashMap<String, Object> map = new HashMap<>();
         map.put("status", 401);
         map.put("msg", "您沒有權(quán)限, 拒絕訪問: " + accessDeniedException.getMessage());
//             map.put("msg", "您沒有權(quán)限, 拒絕訪問");
         response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
         response.getWriter().write(JSONUtil.toJsonStr(map));
      }
   }
   private static class MyLogoutSuccessHandler implements LogoutSuccessHandler {
      @Override
      public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
         HashMap<String, Object> map = new HashMap<>();
         map.put("status", 200);
         map.put("msg", "注銷成功");
         map.put("user", authentication);
         response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
         response.getWriter().write(JSONUtil.toJsonStr(map));
      }
   }
}

以上就是Spring Security實現(xiàn)添加圖片驗證功能的詳細(xì)內(nèi)容,更多關(guān)于Spring Security添加圖片驗證的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • IDEA將Maven項目中指定文件夾下的xml等文件編譯進(jìn)classes的方法

    IDEA將Maven項目中指定文件夾下的xml等文件編譯進(jìn)classes的方法

    這篇文章主要介紹了IDEA將Maven項目中指定文件夾下的xml等文件編譯進(jìn)classes的方法,幫助大家更好的利用IDEA進(jìn)行Java的開發(fā)學(xué)習(xí),感興趣的朋友可以了解下
    2021-01-01
  • JAVA的反射機(jī)制你了解多少

    JAVA的反射機(jī)制你了解多少

    這篇文章主要為大家詳細(xì)介紹了JAVA的反射機(jī)制,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來幫助
    2022-02-02
  • SpringBoot如何手寫一個starter并使用這個starter詳解

    SpringBoot如何手寫一個starter并使用這個starter詳解

    starter是SpringBoot中的一個新發(fā)明,它有效的降低了項目開發(fā)過程的復(fù)雜程度,對于簡化開發(fā)操作有著非常好的效果,下面這篇文章主要給大家介紹了關(guān)于SpringBoot如何手寫一個starter并使用這個starter的相關(guān)資料,需要的朋友可以參考下
    2022-12-12
  • SpringBoot實現(xiàn)自定義條件注解的代碼示例

    SpringBoot實現(xiàn)自定義條件注解的代碼示例

    在Spring Boot中,條件注解是一種非常強(qiáng)大的工具,它可以根據(jù)特定的條件來選擇是否加載某個類或某個Bean,文將介紹如何在Spring Boot中實現(xiàn)自定義條件注解,并提供一個示例代碼,需要的朋友可以參考下
    2023-06-06
  • java文件復(fù)制代碼片斷(java實現(xiàn)文件拷貝)

    java文件復(fù)制代碼片斷(java實現(xiàn)文件拷貝)

    本文介紹java實現(xiàn)文件拷貝的代碼片斷,大家可以直接放到程序里運行
    2014-01-01
  • JVM Tomcat性能實戰(zhàn)(推薦)

    JVM Tomcat性能實戰(zhàn)(推薦)

    下面小編就為大家?guī)硪黄狫VM Tomcat性能實戰(zhàn)(推薦)。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2016-05-05
  • KotlinScript構(gòu)建SpringBootStarter保姆級教程

    KotlinScript構(gòu)建SpringBootStarter保姆級教程

    這篇文章主要為大家介紹了KotlinScript構(gòu)建SpringBootStarter的保姆級教程,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-09-09
  • SpringCloud Alibaba框架介紹

    SpringCloud Alibaba框架介紹

    spring cloud是一個基于springboot實現(xiàn)的微服務(wù)架構(gòu)開發(fā)工具,目前主流的SpringCloud分為SpringCloud Netflix和阿里云開源的SpringCloud Alibaba兩個系列,本文主要介紹SpringCloud Alibaba框架,感興趣的朋友可以參考一下
    2023-04-04
  • java.lang.NullPointerException異常問題解決方案

    java.lang.NullPointerException異常問題解決方案

    這篇文章主要介紹了java.lang.NullPointerException異常問題解決方案,本篇文章通過簡要的案例,講解了該項技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下
    2021-08-08
  • java jni調(diào)用c函數(shù)實例分享(java調(diào)用c函數(shù))

    java jni調(diào)用c函數(shù)實例分享(java調(diào)用c函數(shù))

    Java代碼中調(diào)用C/C++代碼,當(dāng)然是使用JNI,JNI是Java native interface的簡寫,可以譯作Java原生接口,下面看實例吧
    2013-12-12

最新評論