Authentication idea

In this paper, Shiro verification code integration will be used as a case to provide a unified idea for the integration of third-party authentication. In the future, the integration of mobile verification code and email verification code can use this idea, only need to make specific modifications.

Before you can integrate third-party authentication, you must complete the previous integration with Spring Boot.

The idea is to separate captcha authentication from Shiro authentication, which is not hosted by Shiro but controlled by us developers. During the authentication, the verification code is verified first. If the verification succeeds, Shiro authenticates the user name and password.

Certification development

  1. Create the captcha utility class

    public class VerifyCodeUtils {
    
        // Algerian fonts need to be installed if they are not already in the system. The fonts are only displayed in uppercase, removing the confusing 1,0, I, and o characters
        public static final String VERIFY_CODES = "23456789ABCDEFGHJKLMNPQRSTUVWXYZ";
        private static Random random = new Random();
    
        /** * Use the system default character source to generate the verification code *@paramVerifySize Specifies the length of the verification code@return* /
        public static String generateVerifyCode(int verifySize){
            return generateVerifyCode(verifySize, VERIFY_CODES);
        }
    
        /** * Generate captcha * using the specified source@paramVerifySize Specifies the length of the verification code@paramSources Verification code character source *@return* /
        public static String generateVerifyCode(int verifySize, String sources){
            if(sources == null || sources.length() == 0){
                sources = VERIFY_CODES;
            }
            int codesLen = sources.length();
            Random rand = new Random(System.currentTimeMillis());
            StringBuilder verifyCode = new StringBuilder(verifySize);
            for(int i = 0; i < verifySize; i ++){
                verifyCode.append(sources.charAt(rand.nextInt(codesLen - 1)));
            }
            return verifyCode.toString();
        }
    
        /** * Generates a random captcha file and returns the captcha value *@param w
         * @param h
         * @param outputFile
         * @param verifySize
         * @return
         * @throws IOException
         */
        public static String outputVerifyImage(int w, int h, File outputFile, int verifySize) throws IOException{
            String verifyCode = generateVerifyCode(verifySize);
            outputImage(w, h, outputFile, verifyCode);
            return verifyCode;
        }
    
        /** * outputs a random captcha image stream and returns the captcha value *@param w
         * @param h
         * @param os
         * @param verifySize
         * @return
         * @throws IOException
         */
        public static String outputVerifyImage(int w, int h, OutputStream os, int verifySize) throws IOException{
            String verifyCode = generateVerifyCode(verifySize);
            outputImage(w, h, os, verifyCode);
            return verifyCode;
        }
    
        /** * Generates the specified captcha image file *@param w
         * @param h
         * @param outputFile
         * @param code
         * @throws IOException
         */
        public static void outputImage(int w, int h, File outputFile, String code) throws IOException{
            if(outputFile == null) {return;
            }
            File dir = outputFile.getParentFile();
            if(! dir.exists()){ dir.mkdirs(); }try{
                outputFile.createNewFile();
                FileOutputStream fos = new FileOutputStream(outputFile);
                outputImage(w, h, fos, code);
                fos.close();
            } catch(IOException e){
                throwe; }}/** * outputs the specified captcha image stream *@param w
         * @param h
         * @param os
         * @param code
         * @throws IOException
         */
        public static void outputImage(int w, int h, OutputStream os, String code) throws IOException{
            int verifySize = code.length();
            BufferedImage image = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
            Random rand = new Random();
            Graphics2D g2 = image.createGraphics();
            g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
            Color[] colors = new Color[5];
            Color[] colorSpaces = new Color[] { Color.WHITE, Color.CYAN,
                    Color.GRAY, Color.LIGHT_GRAY, Color.MAGENTA, Color.ORANGE,
                    Color.PINK, Color.YELLOW };
            float[] fractions = new float[colors.length];
            for(int i = 0; i < colors.length; i ++){
                colors[i] = colorSpaces[rand.nextInt(colorSpaces.length)];
                fractions[i] = rand.nextFloat();
            }
            Arrays.sort(fractions);
    
            g2.setColor(Color.GRAY);// Set the border color
            g2.fillRect(0.0, w, h);
    
            Color c = getRandColor(200.250);
            g2.setColor(c);// Set the background color
            g2.fillRect(0.2, w, h-4);
    
            // Draw interference lines
            Random random = new Random();
            g2.setColor(getRandColor(160.200));// Set the color of the line
            for (int i = 0; i < 20; i ++) {
                int x = random.nextInt(w - 1);
                int y = random.nextInt(h - 1);
                int xl = random.nextInt(6) + 1;
                int yl = random.nextInt(12) + 1;
                g2.drawLine(x, y, x + xl + 40, y + yl + 20);
            }
    
            // Add noise
            float yawpRate = 0.05 f;/ / noise ratio
            int area = (int) (yawpRate * w * h);
            for (int i = 0; i < area; i ++) {
                int x = random.nextInt(w);
                int y = random.nextInt(h);
                int rgb = getRandomIntColor();
                image.setRGB(x, y, rgb);
            }
    
            shear(g2, w, h, c);// Distort the image
    
            g2.setColor(getRandColor(100.160));
            int fontSize = h - 4;
            Font font = new Font("Algerian", Font.ITALIC, fontSize);
            g2.setFont(font);
            char[] chars = code.toCharArray();
            for(int i = 0; i < verifySize; i ++){
                AffineTransform affine = new AffineTransform();
                affine.setToRotation(Math.PI / 4 * rand.nextDouble() * (rand.nextBoolean() ? 1 : -1), (w / verifySize) * i + fontSize/2, h/2);
                g2.setTransform(affine);
                g2.drawChars(chars, i, 1, ((w-10) / verifySize) * i + 5, h/2 + fontSize/2 - 10);
            }
    
            g2.dispose();
            ImageIO.write(image, "jpg", os);
        }
    
        private static Color getRandColor(int fc, int bc) {
            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);
        }
    
        private static int getRandomIntColor(a) {
            int[] rgb = getRandomRgb();
            int color = 0;
            for (int c : rgb) {
                color = color << 8;
                color = color | c;
            }
            return color;
        }
    
        private static int[] getRandomRgb() {
            int[] rgb = new int[3];
            for (int i = 0; i < 3; i ++) {
                rgb[i] = random.nextInt(255);
            }
            return rgb;
        }
    
        private static void shear(Graphics g, int w1, int h1, Color color) {
            shearX(g, w1, h1, color);
            shearY(g, w1, h1, color);
        }
    
        private static void shearX(Graphics g, int w1, int h1, Color color) {
    
            int period = random.nextInt(2);
    
            boolean borderGap = true;
            int frames = 1;
            int phase = random.nextInt(2);
    
            for (int i = 0; i < h1; i ++) {
                double d = (double) (period >> 1)
                        * Math.sin((double) i / (double) period
                        + (6.2831853071795862 D * (double) phase)
                        / (double) frames);
                g.copyArea(0, i, w1, 1, (int) d, 0);
                if (borderGap) {
                    g.setColor(color);
                    g.drawLine((int) d, i, 0, i);
                    g.drawLine((int) d + w1, i, w1, i); }}}private static void shearY(Graphics g, int w1, int h1, Color color) {
    
            int period = random.nextInt(40) + 10; / / 50;
    
            boolean borderGap = true;
            int frames = 20;
            int phase = 7;
            for (int i = 0; i < w1; i ++) {
                double d = (double) (period >> 1)
                        * Math.sin((double) i / (double) period
                        + (6.2831853071795862 D * (double) phase)
                        / (double) frames);
                g.copyArea(i, 0.1, h1, 0, (int) d);
                if (borderGap) {
                    g.setColor(color);
                    g.drawLine(i, (int) d, i, 0);
                    g.drawLine(i, (int) d + h1, i, h1); }}}}Copy the code
  2. Build the captcha interface

    @GetMapping("verifyCode")
    public Response verifyCode(a) {
        try {
            List<String> data = userService.getVerifyCode();
    
            if (data == null || data.size() == 0) {
                return Response.error(ResponseEnum.VERIFY_CODE_GENERATE_ERROR);
            }
            return Response.ok().data("codeKey", data.get(0)).data("codeBase64", data.get(1));
        } catch (IOException e) {
            throw newVerifyCodeGenerateException(ResponseEnum.VERIFY_CODE_GENERATE_ERROR); }}Copy the code

    GetVerifyCode interface implementation:

    public List<String> getVerifyCode(a) throws IOException {
        List<String> data = new ArrayList<>();
    
        // Generates a verification code of 4 characters
        String codeKey = UUID.randomUUID().toString();
        String code = VerifyCodeUtils.generateVerifyCode(4);
    
        // The verification code is stored in the cache, and 60s is set to expire
        redisUtils.set(codeKey, code, 60);
    
        // Base64 conversion verification code, width 100, height 30
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        VerifyCodeUtils.outputImage(100.30, byteArrayOutputStream, code);
        String codeBase64 = "data:image/png; base64," + Base64Utils.encodeToString(byteArrayOutputStream.toByteArray());
    
        // Load data
        data.add(codeKey);
        data.add(codeBase64);
    
        return data;
    }
    Copy the code
  3. Shiro release verification code interface

    @Configuration
    public class ShiroConfig {
    
        @Bean
        public ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager defaultWebSecurityManager) {
            ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
    
            shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager);
    
            shiroFilterFactoryBean.setLoginUrl("/user/login");
    
            Map<String, String> map = new HashMap<>();
            map.put("/user/register"."anon");
            map.put("/user/login"."anon");
            // Permit verification code interface
            map.put("/user/verifyCode"."anon")
            map.put("/ * *"."authc");
    
            shiroFilterFactoryBean.setFilterChainDefinitionMap(map);
    
            return shiroFilterFactoryBean;
        }
    
        @Bean
        public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("getRealm") Realm realm) {... }@Bean
        public Realm getRealm(a) {... }}Copy the code
  4. Example Modify the user login DTO

    @Data
    public class UserLoginDto implements Serializable {
     
        private static final long serialVersionUID = 1L;
    
        private String username;
    
        private String password;
    
        private String codeKey;
        
        private String code;
    
    }
    Copy the code
  5. Example Modify the user login interface

    @PostMapping("login")
    public Response login(@RequestBody UserLoginDto userLoginDto) {
        int result = userService.compareVerifyCode(userLoginDto.getCodeKey(), userLoginDto.getCode());
    
        // Check whether the captcha in the cache is expired
        if (result == -1) {
            return Response.error(ResponseEnum.VERIFY_CODE_EXPIRED_ERROR);
        }
    
        // Check whether the verification code is matched. Shiro performs authentication after the match
        if (result == 1) {
            Subject subject = SecurityUtils.getSubject();
    
            try {
                // Login authentication
                subject.login(new UsernamePasswordToken(userLoginDto.getUsername(), userLoginDto.getPassword()));
                // The authentication succeeded
                return Response.ok().message("Login successful");
            } catch (UnknownAccountException e) {
                return Response.error(ResponseEnum.UNKNOWN_ACCOUNT_ERROR);
            } catch (IncorrectCredentialsException e) {
                returnResponse.error(ResponseEnum.INCORRECT_CREDENTIALS_ERROR); }}else {
            // The verification code is incorrect
            returnResponse.error(ResponseEnum.VERIFY_CODE_CONTENT_ERROR); }}Copy the code

    CompareVerifyCode interface implementation:

    public Integer compareVerifyCode(String codeKey, String code) {
        // Check whether the captcha in the cache is expired
        if(! redisUtils.hasKey(codeKey)) {return -1;
        }
    
        // Obtain the original verification code
        String originalCode = (String) redisUtils.get(codeKey);
    
        return originalCode.equalsIgnoreCase(code) ? 1 : 0;
    }
    Copy the code