zoukankan      html  css  js  c++  java
  • SpringSecurity图形验证码认证

    图形验证码认证

    1.添加过滤器认证

    image-20210508102431430

    1.1 生成一张图形验证码

    Kaptcha 是谷歌提供的一个生成图形验证码的 jar 包, 只要简单配置属性就可以生成。
    参考 :https://github.com/penggle/kaptcha

    1. 添加Kaptcha依赖
    <!--短信验证码-->
        <dependency>
          <groupId>com.github.penggle</groupId>
          <artifactId>kaptcha</artifactId>
        </dependency>
    
    1. 生成验证码配置类,在模块中创建KaptchaImageCodeConfig
    @Configuration
    public class KaptchaImageCodeConfig {
      @Bean
      public DefaultKaptcha getDefaultKaptcha(){
        DefaultKaptcha defaultKaptcha = new DefaultKaptcha();
        Properties properties = new Properties();
        properties.setProperty(Constants.KAPTCHA_BORDER, "yes");
        properties.setProperty(Constants.KAPTCHA_BORDER_COLOR, "192,192,192");
        properties.setProperty(Constants.KAPTCHA_IMAGE_WIDTH, "110");
        properties.setProperty(Constants.KAPTCHA_IMAGE_HEIGHT, "36");
        properties.setProperty(Constants.KAPTCHA_TEXTPRODUCER_FONT_COLOR, "blue");
        properties.setProperty(Constants.KAPTCHA_TEXTPRODUCER_FONT_SIZE, "28");
        properties.setProperty(Constants.KAPTCHA_TEXTPRODUCER_FONT_NAMES, "宋体");
        properties.setProperty(Constants.KAPTCHA_TEXTPRODUCER_CHAR_LENGTH, "4");
        // 图片效果
        properties.setProperty(Constants.KAPTCHA_OBSCURIFICATOR_IMPL,
    "com.google.code.kaptcha.impl.ShadowGimpy");
        Config config = new Config(properties);
        defaultKaptcha.setConfig(config);
        return defaultKaptcha;
     }
    }
    
    1. 在CustomLoginController 提供请求接口,将验证码图片数据流写出
     @Autowired
      private DefaultKaptcha defaultKaptcha;
      /**
      * 获取图形验证码
      */
      @RequestMapping("/code/image")
      public void imageCode(HttpServletRequest request, HttpServletResponse response) throws
    IOException {
        // 1. 获取验证码字符串
        String code = defaultKaptcha.createText();
        logger.info("生成的图形验证码是:" + code);
        // 2. 字符串把它放到session中
        request.getSession().setAttribute(SESSION_KEY , code);
        // 3. 获取验证码图片
        BufferedImage image = defaultKaptcha.createImage(code);
        // 4. 将验证码图片把它写出去
        ServletOutputStream out = response.getOutputStream();
        ImageIO.write(image, "jpg", out);
     }
    
    1. 在 SpringSecurityConfig.configure(HttpSecurity http) 放行 /code/image 资源权限
    .antMatchers(securityProperties.getAuthentication().getLoginPage(),
          "/code/image").permitAll()
    

    1.2 实现验证码校验过滤器

    1. 创建ImageCodeValidateFilter,继承OncePerRequestFilter (在所有请求前都被调用一次)
    2. 如果是登录请求(请求地址:/login/form,请求方式:post),校验验证码输入是否正确校验不合法时,提示信息通过自定义异常 ValidateCodeExcetipn抛出,此异常要继承org.springframework.security.core.AuthenticationException,它是认证的父异常类。
      捕获ImageCodeException异常,交给失败处理器 CustomAuthenticationFailureHandler。
    3. 如果非登录请求,则放行请求 filterChain.doFilter(request, response)
    /**
     * @author WGR
     * @create 2021/5/7 -- 16:08
     */
    @Component
    public class ImageCodeValidateFilter extends OncePerRequestFilter {
    
        @Autowired
        SecurityProperties securityProperties;
        @Autowired
    
        CustomAuthenticationFailureHandler customAuthenticationFailureHandler;
    
        @Override
        protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
            // 1. 如果是post方式 的登录请求,则校验输入的验证码是否正确
            if (securityProperties.getAuthentication().getLoginProcessingUrl()
                    .equals(request.getRequestURI())
                    && request.getMethod().equalsIgnoreCase("post")) {
                try {
                   // 校验验证码合法性
                    validate(request);
                } catch (AuthenticationException e) {
                    // 交给失败处理器进行处理异常
                    customAuthenticationFailureHandler.onAuthenticationFailure(request, response, e);
                    // 一定要记得结束
                    return;
                }
            }
            // 放行请求
            filterChain.doFilter(request, response);
        }
    
        private void validate(HttpServletRequest request) {
            // 先获取seesion中的验证码
            String sessionCode = (String) request.getSession().getAttribute(CustomLoginController.SESSION_KEY);
             // 获取用户输入的验证码
            String inpuCode = request.getParameter("code");
            // 判断是否正确
            if (StringUtils.isBlank(inpuCode)) {
                throw new ValidateCodeException("验证码不能为空");
            }
            if (!inpuCode.equalsIgnoreCase(sessionCode)) {
                throw new ValidateCodeException("验证码输入错误");
            }
        }
    }
    

    1.3 创建验证码异常类

    创建ValidateCodeExcetipn 异常类,它继承AuthenticationException特别注意是:org.springframework.security.core.AuthenticationException

    /**
     * @author WGR
     * @create 2021/5/7 -- 16:15
     */
    public class ValidateCodeException extends AuthenticationException {
        public ValidateCodeException(String msg, Throwable t) {
            super(msg, t);
        }
    
        public ValidateCodeException(String msg) {
            super(msg);
        }
    }
    

    1.4 重构SpringSecurityConfig

    将校验过滤器 imageCodeValidateFilter 添加到UsernamePasswordAuthenticationFilter 前面

    http.addFilterBefore(imageCodeValidateFilter,
              UsernamePasswordAuthenticationFilter.class)
     .formLogin()
    

    image-20210508103746733

    2.自定义认证

    2.1 请求的简单流程

    请求过程如图

    image-20210508134858063

    说明:

    Authentication中包含主体权限列表,主体凭据,主体的详细信息,及是否验证成功等。

    AuthenticationProvider被SpringSecurity定义为一个验证过程。

    ProviderManager管理多个AuthenticationProvider。

    当我们自定义的时候,Authentication还可以携带额外的数据

    public interface Authentication extends Principal, Serializable {
    
    	Collection<? extends GrantedAuthority> getAuthorities();
    
    	Object getCredentials();
    
        //允许携带任意对象
    	Object getDetails();
    
    	Object getPrincipal();
    
    	boolean isAuthenticated();
    
    	void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException;
    }
    
    

    其中ProviderManager是由UsernamePasswordAuthenticationFilter调用的,也就是说所有AuthenticationProvider包含的Authentication都来源于UsernamePasswordAuthenticationFilter。

    public Authentication attemptAuthentication(HttpServletRequest request,
    			HttpServletResponse response) throws AuthenticationException {
    		if (postOnly && !request.getMethod().equals("POST")) {
    			throw new AuthenticationServiceException(
    					"Authentication method not supported: " + request.getMethod());
    		}
    
    		String username = obtainUsername(request);
    		String password = obtainPassword(request);
    
    		if (username == null) {
    			username = "";
    		}
    
    		if (password == null) {
    			password = "";
    		}
    
    		username = username.trim();
    
    		UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
    				username, password);
    
    		// Allow subclasses to set the "details" property
    		setDetails(request, authRequest);
    
    		return this.getAuthenticationManager().authenticate(authRequest);
    	}
    
    	protected void setDetails(HttpServletRequest request,
    			UsernamePasswordAuthenticationToken authRequest) {
    		authRequest.setDetails(authenticationDetailsSource.buildDetails(request));
    	}
    
    

    从上面的源码中看出,用户的详细信息是由authenticationDetailsSource构建的

    public class WebAuthenticationDetailsSource implements
    		AuthenticationDetailsSource<HttpServletRequest, WebAuthenticationDetails> {
    
    	// ~ Methods
    	// ========================================================================================================
    
    	/**
    	 * @param context the {@code HttpServletRequest} object.
    	 * @return the {@code WebAuthenticationDetails} containing information about the
    	 * current request
    	 */
    	public WebAuthenticationDetails buildDetails(HttpServletRequest context) {
    		return new WebAuthenticationDetails(context);
    	}
    }
    	public WebAuthenticationDetails(HttpServletRequest request) {
    		this.remoteAddress = request.getRemoteAddr();
    
    		HttpSession session = request.getSession(false);
    		this.sessionId = (session != null) ? session.getId() : null;
    	}
    

    携带了session和ip地址。

    2.2 代码

    /**
     * @author WGR
     * @create 2021/5/8 -- 10:48
     */
    public class MyWebAuthenticationDetails extends WebAuthenticationDetails {
    
        private boolean imageCodeIsRight = false;
    
        public boolean isImageCodeIsRight() {
            return imageCodeIsRight;
        }
    
    
        public MyWebAuthenticationDetails(HttpServletRequest request) {
    
            super(request);
            // 先获取seesion中的验证码
            String sessionCode = (String) request.getSession().getAttribute(CustomLoginController.SESSION_KEY);
            // 获取用户输入的验证码
            String inpuCode = request.getParameter("code");
            System.out.println(inpuCode);
            System.out.println(sessionCode);
            if(inpuCode.equals(sessionCode)){
                imageCodeIsRight = true;
            }
            System.out.println(imageCodeIsRight);
    
        }
    }
    
    /**
     * @author WGR
     * @create 2021/5/8 -- 10:55
     */
    @Component
    public class MyWebAuthenticationDetailsSource implements
            AuthenticationDetailsSource<HttpServletRequest, WebAuthenticationDetails> {
        @Override
        public WebAuthenticationDetails buildDetails(HttpServletRequest context) {
            return new MyWebAuthenticationDetails(context);
        }
    }
    
    
    /**
     * @author WGR
     * @create 2021/5/8 -- 10:41
     */
    @Component
    public class MyAuthenticationProvider extends DaoAuthenticationProvider {
    
        public MyAuthenticationProvider(UserDetailsService userDetailsService, PasswordEncoder passwordEncoder) {
            this.setUserDetailsService(userDetailsService);
            this.setPasswordEncoder(passwordEncoder);
    
        }
    
        @Override
        protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
            MyWebAuthenticationDetails  details = (MyWebAuthenticationDetails)authentication.getDetails();
            if(!details.isImageCodeIsRight()){
                throw new ValidateCodeException("验证码输入错误");
            }
            super.additionalAuthenticationChecks(userDetails, authentication);
        }
    }
    
    

    修改配置类

    @Configuration
    @EnableWebSecurity // 开启springsecurity过滤链 filter
    public class SpringSecurityConfig2 extends WebSecurityConfigurerAdapter {
        Logger logger = LoggerFactory.getLogger(getClass());
    
        @Autowired
        private SecurityProperties securityProperties;
    
    
        @Autowired
        private CustomAuthenticationSuccessHandler2 customAuthenticationSuccessHandler;
    
        @Autowired
        private CustomAuthenticationFailureHandler2 customAuthenticationFailureHandler;
    
    
        @Autowired
        AuthenticationDetailsSource<HttpServletRequest, WebAuthenticationDetails> myAuthenticationDetailsSource;
    
        @Autowired
        AuthenticationProvider authenticationProvider;
    
        @Autowired
        private DataSource dataSource;
    
        @Bean
        public PasswordEncoder passwordEncoder() {
            // 明文+随机盐值》加密存储
            return new BCryptPasswordEncoder();
        }
    
        @Bean
        public JdbcTokenRepositoryImpl jdbcTokenRepository(){
           JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
           jdbcTokenRepository.setDataSource(dataSource);
           return jdbcTokenRepository;
        }
    
        /**
         * 认证管理器:
         * 1. 认证信息(用户名,密码)
         *
         * @param auth
         * @throws Exception
         */
        @Override
        protected void configure(AuthenticationManagerBuilder auth) throws Exception {
            auth.authenticationProvider(authenticationProvider);
        }
    
    
        @Override
        protected void configure(HttpSecurity http) throws Exception {
    //        http.httpBasic() // 采用 httpBasic认证方式
            http.formLogin().authenticationDetailsSource(myAuthenticationDetailsSource) // 表单登录方式
                    .loginPage(securityProperties.getAuthentication().getLoginPage())
                    .loginProcessingUrl(securityProperties.getAuthentication().getLoginProcessingUrl()) // 登录表单提交处理url, 默认是/login
                    .usernameParameter(securityProperties.getAuthentication().getUsernameParameter()) //默认的是 username
                    .passwordParameter(securityProperties.getAuthentication().getPasswordParameter())  // 默认的是 password
                    .successHandler(customAuthenticationSuccessHandler)
                    .failureHandler(customAuthenticationFailureHandler)
                    .and()
                    .authorizeRequests() // 认证请求
                    .antMatchers(securityProperties.getAuthentication().getLoginPage(),"/code/image").permitAll() // 放行/login/page不需要认证可访问
                     //这里图片不放行的话就会看不见
                    .anyRequest().authenticated() //所有访问该应用的http请求都要通过身份认证才可以访问
                    .and()
            .rememberMe().tokenRepository(jdbcTokenRepository()).tokenValiditySeconds(60*60*24*7)
            ; // 注意不要少了分号
        }
    
        /**
         * * 释放静态资源
         * * @param web
         * 
         */
        @Override
        public void configure(WebSecurity web) {
            web.ignoring().antMatchers("/dist/**", "/modules/**", "/plugins/**");
        }
    
    
    }
    
    
  • 相关阅读:
    CSS-常用hack
    CSS触发haslayout的方法
    CSS最大最小宽高兼容
    CSS-文字超出自动显示省略号
    [LeetCode][JavaScript]Number of Islands
    [LeetCode][JavaScript]Search a 2D Matrix II
    [LeetCode][JavaScript]Search a 2D Matrix
    [LeetCode][JavaScript]Candy
    [LeetCode][JavaScript]Wildcard Matching
    [LeetCode][JavaScript]Sliding Window Maximum
  • 原文地址:https://www.cnblogs.com/dalianpai/p/14744704.html
Copyright © 2011-2022 走看看