天天看點

驗證碼功能實作 - 讓代碼飛

驗證碼功能實作

最近公司對公司小程式進行安全漏洞檢測,發現我們的登入接口存在安全隐患,需要添加圖形驗證碼來進行過濾,要實作圖形驗證碼要考慮兩個問題

1,圖檔展現的形式

 1)二進制傳到前端直接展示

 2)存到一個圖檔位址,傳回url前端直接擷取

2,如何定位 

後端生成一個key,驗證碼,存到緩存中,傳給前端,前端傳驗證碼,key到後端

啥也不說了,直接上代碼

ImageVerificationCode.java

package org.source.dsmh.utils;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Arrays;
import java.util.Random;

import javax.imageio.ImageIO;

import org.apache.commons.lang3.ArrayUtils;

import sun.misc.BASE64Encoder;


public class ImageVerificationCode {

     private  volatile  static  ImageVerificationCode imageCode;

        private  ImageVerificationCode(){}

        public  static  ImageVerificationCode getInstance(){
            if(imageCode==null){
                synchronized (ImageVerificationCode.class){
                    if(imageCode==null){
                        imageCode=new ImageVerificationCode();
                    }
                }
            }
            return  imageCode;
        }
    private static Random r = new Random();    //擷取随機數對象
    //private String[] fontNames = {"宋體", "華文楷體", "黑體", "微軟雅黑", "楷體_GB2312"};   //字型數組
 
    //驗證碼數組
   // private static String codes = "1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
    private static String codes = "1234567890";

    /**
     * 擷取随機的顔色
     *
     * @return
     */
    private Color randomColor() {
        int r = this.r.nextInt(225);  //這裡為什麼是225,因為當r,g,b都為255時,即為白色,為了好辨認,需要顔色深一點。
        int g = this.r.nextInt(225);
        int b = this.r.nextInt(225);
        return new Color(r, g, b);            //傳回一個随機顔色
    }

  

    /**
     * 擷取随機字元串
     *
     * @return
     */
    public  String randomStr() {
        StringBuilder sb=new StringBuilder();
        for(int i=0;i<4;i++) {
            int index = r.nextInt(codes.length());
            char x=codes.charAt(index);
            sb.append(x);
        }         
        return sb.toString();
    }


    
    public  String createImageWithVerifyCode(int width, int height, String word) throws IOException {
        String png_base64="";
        //繪制記憶體中的圖檔
        BufferedImage bufferedImage = new BufferedImage(width,height,BufferedImage.TYPE_INT_RGB);
        //得到畫圖對象
        Graphics graphics = bufferedImage.getGraphics();
        //繪制圖檔前指定一個顔色
        graphics.setColor( Color.white);
        graphics.fillRect(0,0,width,height);
        //繪制邊框
        graphics.setColor(Color.white);
        graphics.drawRect(0, 0, width - 1, height - 1);
        // 步驟四 四個随機數字
        Graphics2D graphics2d = (Graphics2D) graphics;
        graphics2d.setFont(new Font("宋體", Font.BOLD, 18));
        Random random = new Random();
                
        // 定義x坐标
        int x = 5;
        for (int i = 0; i < word.length(); i++) {
            // 随機顔色
            //graphics2d.setColor(new Color(20 + random.nextInt(110), 20 + random.nextInt(110), 20 + random.nextInt(110)));
            graphics2d.setColor(new Color(0,29,38));
            // 旋轉 -30 --- 30度
            int jiaodu = random.nextInt(60) - 30;
            // 換算弧度
            double theta = jiaodu * Math.PI / 180;
            // 獲得字母數字
            char c = word.charAt(i);
            //将c 輸出到圖檔
            graphics2d.rotate(theta, x, 20);
            graphics2d.drawString(String.valueOf(c), x, 20);
            graphics2d.rotate(-theta, x, 20);
            x += 18;
        }
        //儲存驗證碼
        
        // 繪制幹擾線
        graphics.setColor(this.randomColor());
        int x1;
        int x2;
        int y1;
        int y2;
        for (int i = 0; i < 30; i++) {
            x1 = random.nextInt(width);
            x2 = random.nextInt(12);
            y1 = random.nextInt(height);
            y2 = random.nextInt(12);
           // graphics.drawLine(x1, y1, x1 + x2, x2 + y2);
        }
        graphics.dispose();// 釋放資源
        ByteArrayOutputStream baos = new ByteArrayOutputStream();//io流
        ImageIO.write(bufferedImage, "png", baos);//寫入流中
        byte[] bytes = baos.toByteArray();//轉換成位元組
        BASE64Encoder encoder = new BASE64Encoder();
        png_base64 = encoder.encodeBuffer(bytes).trim();
        png_base64= "data:image/jpeg;base64,"+png_base64;
       // png_base64 = png_base64.replaceAll("\n", "").replaceAll("\r", "");//删除 \r\n
        return png_base64;
    }

    
    public static String getRandomString(int length){
       
        Random random=new Random();
        StringBuffer sb=new StringBuffer();
        for(int i=0;i<length;i++){
          int number=random.nextInt(100);
          sb.append(number);
        }
        return sb.toString();
    }
    public static void main(String[] args) throws IOException {
        
        ImageVerificationCode ivc = new ImageVerificationCode();     //用我們的驗證碼類,生成驗證碼類對象
        /*
         * String str=ivc.randomStr(); System.out.println(str);
         * System.out.println(ivc.createImageWithVerifyCode(75, 31,str ));
         */
        
        System.out.println(ivc.getRandomString(5));
    }
}      

LocalAccountPicCodeServiceImpl.java

package org.source.dsmh.service.impl;

import java.util.concurrent.locks.ReentrantLock;

import org.apache.log4j.Logger;
import org.source.dsmh.service.LocalDataService;
import org.source.dsmh.utils.AccountConstant;
import org.source.dsmh.utils.DataTemplate;
import org.source.dsmh.utils.ImageVerificationCode;
import org.source.dsmh.utils.mongodb.LogMsg;
import org.source.dsmh.utils.redis.RedisOperator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.alibaba.druid.util.StringUtils;
import com.alibaba.fastjson.JSONObject;

/****
 * 圖檔驗證碼*/
@Service("local-account-picCode")
public class LocalAccountPicCodeServiceImpl implements LocalDataService {

    private Logger log = Logger.getLogger(LocalAccountPicCodeServiceImpl.class);

    @Autowired
    private RedisOperator redisOperator;

    public static final String PICCODE_PREFIX = "account:code";
    
    
    
    private final ReentrantLock lock=new ReentrantLock();

    @Override
    public DataTemplate localData(String paramJson, String function, String method, String appUser) {

        this.log.info(LogMsg.getLogMsgForInfo(function, method, paramJson, method));         

        /**
         * @info 完成參數轉換
         */
        JSONObject jsonObject = null;
        if (!StringUtils.isEmpty(paramJson)) {
            try {
                jsonObject = JSONObject.parseObject(paramJson);
            } catch (Exception e) {
                e.printStackTrace();
                log.error(LogMsg.getLogMsgForError(function, method, paramJson, "", AccountConstant.PARAM_JSON_ERROR),
                        e);
                return DataTemplate.error(AccountConstant.PARAM_JSON_ERROR);
            }
        } else {
            jsonObject = new JSONObject();
        }
      /**
         * @info 随機生成驗證碼--将驗證碼發送至使用者手機
         */
        
         lock.lock();
        try {
                long time=System.currentTimeMillis();
                String key=time+ImageVerificationCode.getInstance().getRandomString(5);
                final int width = 75; // 圖檔寬度
                final int height = 31; // 圖檔高度

                String words = ImageVerificationCode.getInstance().randomStr();
                // 建立驗證碼圖檔并傳回圖檔上的字元串
                String code = ImageVerificationCode.getInstance().createImageWithVerifyCode(width, height, words);
                log.info("驗證碼内容: " + words);
                this.redisOperator.setCache(PICCODE_PREFIX, "code:" + key, words);
                log.info(LogMsg.getLogMsgForInfo(function, method, PICCODE_PREFIX+":code:"+key+"驗證碼:"+words, AccountConstant.SUCCESS));    
                JSONObject result = new JSONObject();
                result.put("code", code);                                
                result.put("picCodeKey", key);
                return DataTemplate.ok(result);            
        } catch (Exception e) {
            e.printStackTrace();
            log.error(LogMsg.getLogMsgForError(function, method, paramJson, "", "擷取驗證碼失敗"), e);
            return DataTemplate.error("擷取驗證碼失敗");
        }finally {
            lock.unlock();
        }

    }
}      

 LocalAccountLoginvalidateServiceImpl.java

  String picCode=jsonObject.getString("picCode");
    if (StringUtils.isEmpty(picCode)){
            return DataTemplate.error("驗證碼不能為空");
       }
      String picCodeKey=jsonObject.getString("picCodeKey");             try {
                                                            
                //擷取緩存中驗證碼值,判斷驗證碼是否正确
                 String valid = this.redisOperator.getCache(PICCODE_PREFIX, "code:" + picCodeKey);
                 
                  if (StringUtils.isEmpty(picCode) || !picCode.equalsIgnoreCase(valid)) {
                        log.error(LogMsg.getLogMsgForError(function, method, paramJson, "緩存驗證碼:"+valid, AccountConstant.VALID_ERROR_MESSAGE));
                        return DataTemplate.error(AccountConstant.VALID_ERROR_MESSAGE);
                  }                 
                                                    
            } catch (Exception e) {
                log.error(LogMsg.getLogMsgForError(function, method, paramJson, "", AccountConstant.VALID_ERROR_MESSAGE));
                return DataTemplate.error(AccountConstant.VALID_ERROR_MESSAGE);
            }