Forráskód Böngészése

登录增加图像验证码

zouguihou 2 éve
szülő
commit
f10d5329bc

+ 6 - 0
pom.xml

@@ -104,6 +104,12 @@
             <groupId>org.springframework.integration</groupId>
             <artifactId>spring-integration-mqtt</artifactId>
         </dependency>
+
+        <dependency>
+            <groupId>org.springframework.social</groupId>
+            <artifactId>spring-social-web</artifactId>
+            <version>1.1.6.RELEASE</version>
+        </dependency>
     </dependencies>
 
     <repositories>  <!-- 配置阿里云镜像仓库 -->

+ 6 - 2
src/main/java/com/welampiot/Main.java

@@ -1,7 +1,11 @@
 package com.welampiot;
 
+import com.alibaba.druid.filter.config.ConfigTools;
+
 public class Main {
-    public static void main(String[] args) {
-        System.out.println("Hello world!");
+    public static void main(String[] args) throws Exception{
+        // 获取RSA加密信息
+        String password="welampiot@2023";
+        ConfigTools.main(new String[]{password});
     }
 }

+ 33 - 0
src/main/java/com/welampiot/controller/ImageController.java

@@ -0,0 +1,33 @@
+package com.welampiot.controller;
+
+import com.welampiot.security.ImageCode;
+import com.welampiot.utils.ImageCodeUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.http.HttpStatus;
+import org.springframework.social.connect.web.HttpSessionSessionStrategy;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.context.request.ServletWebRequest;
+
+import javax.imageio.ImageIO;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+@RestController
+@RequestMapping("/image")
+public class ImageController {
+    private final static Logger log = LoggerFactory.getLogger(ImageController.class);
+
+    @RequestMapping(value = "/getImage",method = RequestMethod.POST)
+    public void login(HttpServletRequest request, HttpServletResponse response) throws IOException {
+        ImageCode imageCode = ImageCodeUtil.createImageCode();
+        ImageCode codeInRedis = new ImageCode(null,imageCode.getCode(),imageCode.getExpireTime());
+        new HttpSessionSessionStrategy().setAttribute(new ServletWebRequest(request), "SESSION_KEY_IMAGE_CODE", codeInRedis);
+        response.setContentType("image/jpeg;charset=utf-8");
+        response.setStatus(HttpStatus.OK.value());
+        ImageIO.write(imageCode.getImage(), "jpeg", response.getOutputStream());
+    }
+}

+ 10 - 2
src/main/java/com/welampiot/security/CustomizeAuthenticationFailureHandler.java

@@ -1,7 +1,7 @@
 package com.welampiot.security;
 
-import com.alibaba.fastjson.JSON;
 import com.welampiot.common.BaseResult;
+import com.welampiot.common.BusinessException;
 import com.welampiot.common.ResultEnum;
 import com.welampiot.utils.WebUtils;
 import org.springframework.security.core.AuthenticationException;
@@ -19,7 +19,7 @@ import java.io.IOException;
 @Component
 public class CustomizeAuthenticationFailureHandler implements AuthenticationFailureHandler {
     @Override
-    public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
+    public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException, BusinessException {
         //返回json数据失败信息
 //        if (e instanceof AccountExpiredException) {
 //            //账号过期
@@ -49,4 +49,12 @@ public class CustomizeAuthenticationFailureHandler implements AuthenticationFail
 //        httpServletResponse.getWriter().write(JSON.toJSONString(BaseResult.fail(ResultEnum.LOGIN_FAIL)));
         WebUtils.renderString(httpServletResponse,BaseResult.fail(ResultEnum.LOGIN_FAIL));
     }
+
+    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, BusinessException e) {
+        if (e instanceof BusinessException) {
+            WebUtils.renderString(response,BaseResult.fail("40101",e.getMessage(),""));
+            return;
+        }
+        WebUtils.renderString(response,BaseResult.fail(ResultEnum.LOGIN_FAIL));
+    }
 }

+ 71 - 0
src/main/java/com/welampiot/security/ValidateImageCodeFilter.java

@@ -0,0 +1,71 @@
+package com.welampiot.security;
+
+import com.welampiot.common.BusinessException;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.social.connect.web.HttpSessionSessionStrategy;
+import org.springframework.social.connect.web.SessionStrategy;
+import org.springframework.stereotype.Component;
+import org.springframework.web.bind.ServletRequestBindingException;
+import org.springframework.web.bind.ServletRequestUtils;
+import org.springframework.web.context.request.ServletWebRequest;
+import org.springframework.web.filter.OncePerRequestFilter;
+
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+
+@Component
+public class ValidateImageCodeFilter extends OncePerRequestFilter {
+        @Autowired
+        //自定义验证失败处理器
+        private CustomizeAuthenticationFailureHandler customizeAuthenticationFailureHandler;
+        private SessionStrategy sessionStrategy = new HttpSessionSessionStrategy();
+
+        @Override
+        protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
+            //请求路径中是否包含login这个关键词 && 发送的请求必须是post
+            if (StringUtils.contains(request.getRequestURI(), "login") && StringUtils.equalsIgnoreCase(request.getMethod(), "post")) {
+                try {
+                //开始验证
+                validateCode(new ServletWebRequest(request));
+                } catch (BusinessException e) {
+                    //如果验证失败,就使用自定义验证处理器
+                    customizeAuthenticationFailureHandler.onAuthenticationFailure(request, response, e);
+                    return;
+                }
+            }
+            filterChain.doFilter(request, response);
+        }
+        //验证实现
+        private void validateCode(ServletWebRequest servletWebRequest) throws ServletRequestBindingException, BusinessException {
+            //从SessionStrategy中拿出验证码
+            ImageCode codeInSession = (ImageCode) sessionStrategy.getAttribute(servletWebRequest,"SESSION_KEY_IMAGE_CODE");
+            //从请求路径中拿出验证码
+            String codeInRequest = ServletRequestUtils.getStringParameter(servletWebRequest.getRequest(), "imageCode");
+            //验证码判空
+            if (StringUtils.isBlank(codeInRequest)) {
+                throw new BusinessException("验证码不能为空 ");
+            }
+            //验证码颁发方验证
+            if (codeInSession == null) {
+                throw new BusinessException("验证码不存在!");
+            }
+            //验证码是否过期
+            if (codeInSession.isExpire()) {
+                sessionStrategy.removeAttribute(servletWebRequest,"SESSION_KEY_IMAGE_CODE");
+                throw new BusinessException("验证码已过期!");
+            }
+            //验证码正确性
+            if (!StringUtils.equalsIgnoreCase(codeInSession.getCode(), codeInRequest)) {
+                throw new BusinessException("验证码不正确!");
+            }
+            //移除服务端的验证码存储
+            sessionStrategy.removeAttribute(servletWebRequest,"SESSION_KEY_IMAGE_CODE");
+  }
+
+
+}

+ 21 - 15
src/main/java/com/welampiot/security/WebSecurityConfig.java

@@ -13,8 +13,7 @@ import org.springframework.security.config.annotation.web.configuration.WebSecur
 import org.springframework.security.core.userdetails.UserDetailsService;
 import org.springframework.security.crypto.password.PasswordEncoder;
 import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
-import org.springframework.security.web.authentication.AuthenticationFailureHandler;
-import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
+import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
 
 import javax.annotation.Resource;
 
@@ -59,6 +58,10 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
     @Autowired
     CustomizeFilterInvocationSecurityMetadataSource securityMetadataSource;
 
+    //图片验证码过滤器
+    @Autowired
+    private ValidateImageCodeFilter validateImageCodeFilter;
+
     @Autowired
     private CustomizeAbstractSecurityInterceptor securityInterceptor;
 
@@ -76,7 +79,9 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
     @Override
     protected void configure(HttpSecurity http) throws Exception {
         http.cors().and().csrf().disable();
-        http.authorizeRequests().antMatchers("/login").permitAll().anyRequest().authenticated().
+        //注册自定义图片验证码过滤器
+        http.addFilterBefore(validateImageCodeFilter, UsernamePasswordAuthenticationFilter.class);
+        http.authorizeRequests().antMatchers("/login","/image/getImage").permitAll().anyRequest().authenticated().
 //                antMatchers("/login").anonymous().
 //                antMatchers("/**").fullyAuthenticated().
                         withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
@@ -88,23 +93,24 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
                     }
                 }).
                 //登出
-                and().logout().logoutSuccessUrl("/logout").
+                and().logout().logoutSuccessUrl("/logout")
 //                permitAll().//允许所有用户
-                logoutSuccessHandler(logoutSuccessHandler).//登出成功处理逻辑
-                deleteCookies("JSESSIONID").//登出之后删除cookie
+                .logoutSuccessHandler(logoutSuccessHandler)//登出成功处理逻辑
+                .deleteCookies("JSESSIONID")//登出之后删除cookie
                 //登入
-                and().formLogin().loginProcessingUrl("/login").
+                .and().formLogin().loginProcessingUrl("/login")
 //                permitAll().//允许所有用户
-                successHandler(authenticationSuccessHandler).//登录成功处理逻辑
-                failureHandler(authenticationFailureHandler).//登录失败处理逻辑
+                .successHandler(authenticationSuccessHandler)//登录成功处理逻辑
+                .failureHandler(authenticationFailureHandler)//登录失败处理逻辑
                 //异常处理(权限拒绝、登录失效等)
-                and().exceptionHandling().accessDeniedHandler(accessDeniedHandler).//权限拒绝处理逻辑
-                authenticationEntryPoint(authenticationEntryPoint).//匿名用户访问无权限资源时的异常处理
+                .and().exceptionHandling().accessDeniedHandler(accessDeniedHandler)//权限拒绝处理逻辑
+                .authenticationEntryPoint(authenticationEntryPoint)//匿名用户访问无权限资源时的异常处理
                 //会话管理
-                and().sessionManagement().
-                maximumSessions(1).//同一账号同时登录最大用户数
-                expiredSessionStrategy(sessionInformationExpiredStrategy);//会话失效(账号被挤下线)处理逻辑
-        http.addFilterBefore(securityInterceptor, FilterSecurityInterceptor.class);
+                .and().sessionManagement()
+                .maximumSessions(1)//同一账号同时登录最大用户数
+                .expiredSessionStrategy(sessionInformationExpiredStrategy)//会话失效(账号被挤下线)处理逻辑
+                ;
+//        http.addFilterBefore(securityInterceptor, FilterSecurityInterceptor.class);
     }
 
     /**

+ 69 - 0
src/main/java/com/welampiot/utils/ImageCodeUtil.java

@@ -0,0 +1,69 @@
+package com.welampiot.utils;
+
+import com.welampiot.security.ImageCode;
+
+import java.awt.*;
+import java.awt.image.BufferedImage;
+import java.util.Random;
+
+public class ImageCodeUtil {
+    /**
+      * 创建图片验证码
+      * @return
+      */
+    public static ImageCode createImageCode() {
+         int width = 100; // 验证码图片宽度
+         int height = 36; // 验证码图片长度
+         int length = 4; // 验证码位数
+         int expireIn = 120; // 验证码有效时间 120s
+
+        BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
+        Graphics g = image.getGraphics();
+        Random random = new Random();
+        g.setColor(getRandColor(200,250));
+        g.fillRect(0,0,width,height);
+        g.setFont(new Font("Times New Roman",Font.ITALIC, 35));
+        g.setColor(getRandColor(160,200));
+        for(int i = 0; i< 155; i++){
+            int x = random.nextInt(width);
+            int y = random.nextInt(height);
+            int xl = random.nextInt(12);
+            int yl = random.nextInt(12);
+            g.drawLine(x, y, x + xl, y + yl);
+        }
+
+        StringBuilder sRand = new StringBuilder();
+        String rand = null;
+        for(int i = 0; i<length; i++){
+            int anInt = random.nextInt(57);
+            if(anInt >= 10) {
+                if(anInt + 65 >=91 && anInt + 65 <= 96) {
+                    anInt += 6;
+                }
+                char ch = (char) (anInt + 65);
+                rand = String.valueOf(ch);
+            } else {
+                rand = String.valueOf(anInt);
+            }
+            sRand.append(rand);
+            g.setColor(new Color(20 + random.nextInt(110), 20 + random.nextInt(110), 20 + random.nextInt(110)));
+            g.drawString(rand, 15 * i + 15, 28);
+        }
+        g.dispose();
+        return new ImageCode(image, sRand.toString(),expireIn);
+    }
+
+    private static Color getRandColor(int fc, int bc) {
+        Random random = new Random();
+        if(fc > 255) {
+            fc = 255;
+        }
+        if (bc > 255) {
+            bc = 255;
+        }
+        int r = fc + random.nextInt(bc - fc);
+        int g = fc + random.nextInt(bc - fc);
+        int b = fc + random.nextInt(bc - fc);
+        return new Color(r, g, b);
+    }
+}

+ 4 - 3
src/main/resources/prod/application.yml

@@ -4,9 +4,6 @@ server:
     context-path: /api
 
 spring:
-  # 指定哪个文件,比如dev.yml local.yml
-  #  profiles:
-  #    active: @spring.active@
   application:
     name: welampiot
   datasource:
@@ -17,6 +14,10 @@ spring:
       url: jdbc:mysql://139.196.213.241:3306/welampiot?characterEncoding=utf-8&useSSL=false
       username: welampiot
       password: XPDejO2KMJ5WZB5a8G3oOIJPkibY1v3Vm9PDI2C+8DQM15FGGQUCGlLGAxiMbBMphX5HNNXy6iAbbZC9GkKzWw==
+      initialSize: 5
+      minIdle: 5
+      maxActive: 100
+      maxWait: 60000
       # encrypt config
       filters: config
       connect-properties: