前言

Spring security增加图片验证形式,在互联网下面有很多这种博客,都写的十分的具体了。本篇次要讲一些增加图片验证的思路。还有前后端拆散形式,图片验证要怎么去解决?

本章内容

  1. 图片验证的思路
  2. 简略的demo

思路

小白: "咱们从总体流程上看图片验证在认证的哪一个阶段?"

小黑: "在获取客户输出的用户名明码那一阶段,而且要在服务器获取数据库中用户名明码之前。这是一个区间[获取申请用户名明码, 获取数据库用户名明码)

而在 Spring security中, 能够很显著的发现有两种思路。

  • 第1种思路是在拦挡登录申请筹备认证的那个过滤器。
  • 第2种思路是在那个过滤器背地的认证器。"

小白: "为什么是这个阶段呢? 不能是在判断明码验证之前呢?"

小黑: "你傻啊, 如果在你说的阶段, 服务器须要去数据库中获取用户信息, 这相当的节约系统资源"

小白: "哦哦, 我错了, 让我每每整个流程应该是啥样"

小白: "我须要当时在后端生成一个验证码,而后通过验证码返回一张图片给前端。前端登录表单增加图片验证。用户输出图片验证后点击登录,会寄存在request申请中, 后端须要从request申请中读取到图片验证,判断前后端验证码是否雷同, 如果图片验证码雷同之后才开始从数据库拿用户信息。否则间接抛出认证异样"

简略点: 数据库获取用户账户之前, 先进行图片验证码验证

计划

怎么将字符串变成图片验证码?

这轮子必定不能自己造, 有就拿来吧你

  • 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>
@Beanpublic 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;}
@Resourceprivate 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>

传统web我的项目

咱们当初依据下面的思路来设计设计该怎么实现这项性能

过滤器形式

/** * 应用 OncePerRequestFilter 的形式须要配置匹配器 */@RequiredArgsConstructorpublic 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每次浏览器申请过去, 都会调用过滤器. 然而过滤器程序是十分重要的

@Controller@Slf4jpublic class IndexController {   @GetMapping("login")   public String login() {      return "login";   }   @GetMapping("")   @ResponseBody   public Principal index(Principal principal) {      return principal;   }}
@Configurationpublic 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();    }}

小白: "我在网上看到有些网友并不是继承的OncePerRequestFilter接口啊?"

小黑: "是的, 有一部分敌人抉择继承UsernamePasswordAuthenticationFilter"

小黑: "继承这个过滤器的话, 咱们须要配置很多货色, 比拟麻烦"

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

小黑: "你想想, 你自定义的过滤器继承至UsernamePasswordAuthenticationFilter, 自定义的过滤器和原先的过滤器是同时存在的"

小黑: "没有为你自定义的过滤器配置对应的Configurer, 那么它外面啥也没有全副属性都是默认值, 不说别的, 上面AuthenticationManager至多要配置吧?"

小黑: "他可是没有任何默认值, 这样会导致上面这行代码报错"

小黑: "当然如果你有自定义属于本人的Configurer那没话说, 比方FormLoginConfigurer"

小黑: "默认这个函数须要HttpSecurity调用的, 咱们自定义的Filter并没有重写Configurer这个环节"

小白: "哦, 我晓得了, 那我就是要继承至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);   }}
@Beanpublic VerifyCodeFilter verifyCodeFilter() throws Exception {   VerifyCodeFilter verifyCodeFilter = new VerifyCodeFilter();   verifyCodeFilter.setAuthenticationManager(authenticationConfiguration.getAuthenticationManager());   return verifyCodeFilter;}

小黑: "这样就能够了"

小白: "也不麻烦啊"

小黑: "好吧, 如同是"

小白: "等等, 那SecurityFilterChain呢? 特地是formLogin()函数要怎么配置?"

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

小白: "那我前端表单用户名和明码的input标签的name属性变成userpwd了呢? 也在下面formLogin上配置?"

小黑: "这里就有区别了, 显著只能在VerifyCodeFilter Bean上配置"

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

小白: "我还认为有多麻烦呢, 就这..."

小黑: "额, 次要是spring security的过滤器不能代替, 只能插入某个过滤器前后地位, 所以如果自定义过滤器就须要咱们配置一些属性"

认证器形式

小白: "认证器要怎么实现图片验证呢?"

小黑: "说到认证的认证器, 肯定要想到DaoAuthenticationProvider"

小黑: "很多人在基于认证器实现图片验证时, 都重写additionalAuthenticationChecks, 这是不对的"

小白: "那应该重写哪个办法?"

小黑: "应该重写上面那个函数"

小白: "等一下, 你留神到这个办法的参数了么? 你这要怎么从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);   }}

小白: "哦, 我看到了, 没想到还能这样"

小白: "那你当初要怎么退出到Spring Security, 让它代替掉本来的DaoAuthenticationProvider呢?"

小黑: "这里有一个思路, 还记得AuthenticationManager的父子关系吧, 你看到父亲只有一个, 你看到儿子能够有几个?"

小白: "如同是无数个, 那我是不是能够这么写?"

/** * 往父类的 AuthenticationManager 里增加 authenticationProvider * 在源码外面是这样的AuthenticationProvider authenticationProvider = getBeanOrNull(AuthenticationProvider.class); * * @return * @throws Exception */@Beanpublic MyDaoAuthenticationProvider authenticationProvider() throws Exception {    MyDaoAuthenticationProvider authenticationProvider = new MyDaoAuthenticationProvider(Constants.LOGIN_USERNAME, Constants.LOGIN_PASSWORD);    authenticationProvider.setPasswordEncoder(passwordEncoder());    authenticationProvider.setUserDetailsService(inMemoryUserDetailsManager());    return authenticationProvider;}// 往子类AuthenticationManager外面增加的 authenticationProviderhttpSecurity.authenticationProvider(authenticationProvider());

小黑: "这下面的代码有问题, AuthenticationManger有父类和子类, 下面这段代码同时往父类和子类都增加MyDaoAuthenticationProvider, 这样MyDaoAuthenticationProvider会被执行两次, 但request的流只能执行一次, 会报错"

小黑: "咱们能够这么玩"

@BeanSecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {    // 代码省略    // 代码省略    // 代码省略    // 代码省略    // 往子类AuthenticationManager外面增加的 authenticationProvider, 但不能阻止 AuthenticationManger 父类加载 DaoAuthenticationProvider    AuthenticationManagerBuilder authenticationManagerBuilder = http.getSharedObject(AuthenticationManagerBuilder.class);    // 然而这种形式能够将 parent Manager 设置为 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还是会执行, 最初报错信息就不对了, 原本应该是验证码谬误, 将会变成用户名和明码谬误"

小黑: "还有就是, 很多人很喜爱在旧版本像上面这么玩"

@Override@Beanpublic AuthenticationManager authenticationManagerBean() throws Exception {   MyDaoAuthenticationProvider authenticationProvider = new MyDaoAuthenticationProvider(Constants.LOGIN_USERNAME, Constants.LOGIN_PASSWORD);   authenticationProvider.setPasswordEncoder(passwordEncoder());   authenticationProvider.setUserDetailsService(inMemoryUserDetailsManager());   return new ProviderManager(authenticationProvider);}
小黑: "在新版本也相似的这么搞, 但这样是有区别的, 上面这种形式只会退出到spring Bean上下文, 然而不会退出到Spring Security中执行, 他是有效的"
@Beanpublic 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外面增加的 authenticationProviderhttpSecurity.authenticationProvider(authenticationProvider());

小黑: "下面这样做也是不行, 他还是会存在两个, 一个是MyDaoAuthenticationProvider(子类), 另一个是DaoAuthenticationProvider(父类)"

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

小黑: "间接将MyDaoAuthenticationProvider增加到Spring Bean上下文"

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

小白: "那还有别的思路么?"

小黑: "还有么? 不分明了, 万能网友应该晓得"

小白: "就这样设置就行了? 其余还需不需要配置?"

小黑: "其余和过滤器形式统一"

总结下
@Beanpublic 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;}

@BeanSecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {// 代码省略// 代码省略// 代码省略// 代码省略// 往子类AuthenticationManager外面增加的 authenticationProvider, 但不能阻止 AuthenticationManger 父类加载 DaoAuthenticationProviderAuthenticationManagerBuilder authenticationManagerBuilder = http.getSharedObject(AuthenticationManagerBuilder.class);// 然而这种形式能够将 parent Manager 设置为 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, 另一个往子类增加, 设置父类为null

前后端拆散我的项目

小白: "前后端拆散和传统web我的项目的区别是什么?"

小黑: "申请request和响应response都应用JSON传递数据"

小白: "那咱们剖析源码时只有关注 requestresponse 咯, 只有发现存在request的读, 和 response的写统统都要重写一边"

小黑: "是的, 其实很简略, 无非是图片验证码改用json读, 认证时的读取usernamepassword也应用json读, 其次是出现异常须要响应response, 也改成json写, 认证胜利和失败须要响应到前端也改成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两个函数? 写一个不就行了?"

小黑: "额, 是的, 把参数改成两个String verifyCode, String code也行"

@Configurationpublic 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", "您没有权限, 回绝拜访: " + accessDeniedException.getMessage());//             map.put("msg", "您没有权限, 回绝拜访");               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", "认证失败, 请从新认证: " + authException.getMessage());//             map.put("msg", "认证失败, 请从新认证");               response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);               response.getWriter().write(JSONUtil.toJsonStr(map));            });      return httpSecurity.build();   }}

留神这两行代码, 教你怎么在不应用WebSecurityConfigurerAdapter的状况下拿到AuthenticationManager

@RestController@Slf4jpublic 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@Slf4jpublic class IndexController {   @GetMapping("login")   public String login() {      return "login";   }   @GetMapping("")   @ResponseBody   public Principal myIndex(Principal principal) {      return principal;   }}

基于认证器形式

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@Configurationpublic 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", "认证胜利");         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("认证失败", exception);         exception.printStackTrace();         HashMap<String, Object> map = new HashMap<>();         map.put("status", 401);         map.put("msg", "认证失败");         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("认证生效", authException);         HashMap<String, Object> map = new HashMap<>();         map.put("status", HttpStatus.UNAUTHORIZED.value());         map.put("msg", "认证失败, 请从新认证: " + authException.getMessage());//             map.put("msg", "认证失败, 请从新认证");         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("没有权限", accessDeniedException);         HashMap<String, Object> map = new HashMap<>();         map.put("status", 401);         map.put("msg", "您没有权限, 回绝拜访: " + accessDeniedException.getMessage());//             map.put("msg", "您没有权限, 回绝拜访");         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));      }   }}