淘先锋技术网

首页 1 2 3 4 5 6 7

1. MD5加密(两次加密)

为了防止用户输入的明文密码在网络传输过程中被窃取,因此设置盐值加密,定义salt变量,通过加密规则对明文密码加密。第一次加密为:从用户端(前端)到后端,盐值设置为1a2b3c4d,加密规则为空字符串+盐值的第0位+第2位+明文密码+第五位+第四位。第二次加密为:从后端到数据库,盐值可从数据库表中字段调用,加密规则暂定与第一次相同。

在Maven中导入jar包

		<dependency>
            <groupId>commons-codec</groupId>
            <artifactId>commons-codec</artifactId>
        </dependency>

在utils包下创建MD5util.java

/**
 * MD5工具类
 * @ClassName: MD5Util
 * 两次加密
 * MD5(MD5(pass明文+固定salt) + salt)
 */
public class MD5Util {

    public static String md5(String str) {
        return DigestUtils.md5Hex(str);
    }

    private static final String salt = "1a2b3c4d";

    /**
     * 第一次加密
     * @param inputPass
     * @return java.lang.String
     **/
    public static String inputPassToFromPass(String inputPass) {
        String str = "" + salt.charAt(0) + salt.charAt(2) + inputPass + salt.charAt(5) + salt.charAt(4);
        return md5(str);
    }

    /**
     * 第二次加密
     * @param formPass
     * @param salt
     * @return java.lang.String
     **/
    public static String formPassToDBPass(String formPass, String salt) {
        String str = "" +salt.charAt(0) + salt.charAt(2) + formPass + salt.charAt(5) + salt.charAt(4);
        return md5(str);
    }

    public static String inputPassToDBPass(String inputPass, String salt) {
        String fromPass = inputPassToFromPass(inputPass);
        String dbPass = formPassToDBPass(fromPass, salt);
        return dbPass;
    }

//    public static void main(String[] args) {
//        String s = inputPassToDBPass("123456", "1a2b3c4d");
//        String s1 = inputPassToFromPass("123456");
//        String s2 = formPassToDBPass(s1, "1a2b3c4d");
//        System.out.println(s);
//        System.out.println(s2);
//    }

}

2. 使用@Valid进行传入参数的验证

2.1使用注解验证

@Valid的详细用法请参考:@Valid用法

Spring Validation验证框架对参数的验证机制提供了@Validated,javax提供了@Valid,配合BindingResult可以直接提供参数验证结果,以检验Controller的入参是否符合规范,该方法可以避免代码的冗余,减少代码量——即重复的进行参数的验证

在Maven中导入jar包

 		<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-validation</artifactId>
        </dependency>

在需要检验的参数处加入@Valid注解

	@PostMapping("/doLogin")
    @ResponseBody
    //在需要检验的参数处加入@Valid注解
    public RespBean doLogin(@Valid LoginVo loginVo, HttpServletRequest request, HttpServletResponse response) {
        log.info("{}", loginVo);
        return tUserService.doLogin(loginVo, request, response);
    }

并在具体需要检验的地方加上具体的检验规则

@Data
public class LoginVo {
    @NotNull
    @IsMobile(required = true)    // 自定义注解
    private String mobile;

    @NotNull
    @Length(min = 32)
    private String password;
}

2.2 自定义注解——验证参数格式

首先提供具体的验证模式

这里我们对手机号进行验证,由于手机都有特定的格式因此我们在util下创建验证的类

/**
 * 手机号码校验类
 */
public class ValidatorUtil {
	//验证格式定义
    private static final Pattern mobile_patten = Pattern.compile("[1]([3-9])[0-9]{9}$");

    /**
     * 手机号码校验
     * @param mobile
     * @return boolean
     **/
    public static boolean isMobile(String mobile) {
        if (StringUtils.isEmpty(mobile)) {
            return false;
        }
        Matcher matcher = mobile_patten.matcher(mobile);
        return matcher.matches();
    }
}

自定义注解IsMobile

@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Constraint(validatedBy = {IsMobileValidator.class})  //这里提供具体的验证方法
public @interface IsMobile {
    boolean required() default true;   // 手机号必填
    String message() default "手机号格式错误";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}

创建IsMobileValidator验证规则

public class IsMobileValidator implements ConstraintValidator<IsMobile, String> {
    private boolean required = false;   // 是否必填
    @Override
    public void initialize(IsMobile constraintAnnotation) {
        required = constraintAnnotation.required();
    }
    @Override
    public boolean isValid(String value, ConstraintValidatorContext constraintValidatorContext) {
        if (required)    // 必填时检验手机号格式
            return ValidatorUtil.isMobile(value);
        else if (StringUtils.isEmpty(value))   // 非必填,未填时直接放行
            return true;
        else
            return ValidatorUtil.isMobile(value);  // 非必填,填了需要检验
    }
}

在使用了@Valid注解后,能够把参数绑定之后的校验结果给到BindingResult实例,出现校验错误时则会抛出异常,下节我们将通过定义全局的异常处理器捕获这些异常


3. 自定义异常并处理BindException

自定义异常

//这里的respBeanEnum主要封装了需要返回给前端的各类信息
@Data
@NoArgsConstructor
@AllArgsConstructor
public class GlobalException extends RuntimeException{
    private RespBeanEnum respBeanEnum;
}

全局异常处理器——定义处理异常方法GlobalExceptionHandler

@RestControllerAdvice
//@ResponseBody      
//指定返回类型为json格式,一般用于向html返回数据,表示该方法的返回结果直接写入 HTTP response body 中,一般在异步获取数据时使用【也就是AJAX】。
public class GlobalExceptionHandler {
    @ExceptionHandler(Exception.class)
    public RespBean ExceptionHandler(Exception e) {
        if (e instanceof GlobalException) {
            GlobalException ex = (GlobalException) e;
            return RespBean.error(ex.getRespBeanEnum());
        } else if (e instanceof BindException) {
            BindException ex = (BindException) e;
            RespBean respBean = RespBean.error(RespBeanEnum.BIND_ERROR);
            respBean.setMessage("参数校验异常:" + ex.getBindingResult().getAllErrors().get(0).getDefaultMessage());
            return respBean;
        }
        return RespBean.error(RespBeanEnum.ERROR);
    }
}

使用自定义异常

//在需要的地方,RespBeanEnum是一个Enum
throws new GlobalException(RespBeanEnum.具体的元素)

4. session、cookie和token

区别
session、cookie和token的区别


4.1 设置uuid,cookie的配置类

(1)cookie工具类(utils下)

package com.example.seckilldemo.utils;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;

/**
 * Cookie工具类
 * @ClassName: CookieUtil
 */
public final class CookieUtil {

    /**
     * 得到Cookie的值, 不编码
     *
     * @param request
     * @param cookieName
     * @return
     */
    public static String getCookieValue(HttpServletRequest request, String cookieName) {
        return getCookieValue(request, cookieName, false);
    }

    /**
     * 得到Cookie的值,
     *
     * @param request
     * @param cookieName
     * @return
     */
    public static String getCookieValue(HttpServletRequest request, String cookieName, boolean isDecoder) {
        Cookie[] cookieList = request.getCookies();
        if (cookieList == null || cookieName == null) {
            return null;
        }
        String retValue = null;
        try {
            for (int i = 0; i < cookieList.length; i++) {
                if (cookieList[i].getName().equals(cookieName)) {
                    if (isDecoder) {
                        retValue = URLDecoder.decode(cookieList[i].getValue(), "UTF-8");
                    } else {
                        retValue = cookieList[i].getValue();
                    }
                    break;
                }
            }
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return retValue;
    }

    /**
     * 得到Cookie的值,
     *
     * @param request
     * @param cookieName
     * @return
     */
    public static String getCookieValue(HttpServletRequest request, String cookieName, String encodeString) {
        Cookie[] cookieList = request.getCookies();
        if (cookieList == null || cookieName == null) {
            return null;
        }
        String retValue = null;
        try {
            for (int i = 0; i < cookieList.length; i++) {
                if (cookieList[i].getName().equals(cookieName)) {
                    retValue = URLDecoder.decode(cookieList[i].getValue(), encodeString);
                    break;
                }
            }
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return retValue;
    }

    /**
     * 设置Cookie的值 不设置生效时间默认浏览器关闭即失效,也不编码
     */
    public static void setCookie(HttpServletRequest request, HttpServletResponse response, String cookieName,
                                 String cookieValue) {
        setCookie(request, response, cookieName, cookieValue, -1);
    }

    /**
     * 设置Cookie的值 在指定时间内生效,但不编码
     */
    public static void setCookie(HttpServletRequest request, HttpServletResponse response, String cookieName,
                                 String cookieValue, int cookieMaxage) {
        setCookie(request, response, cookieName, cookieValue, cookieMaxage, false);
    }

    /**
     * 设置Cookie的值 不设置生效时间,但编码
     */
    public static void setCookie(HttpServletRequest request, HttpServletResponse response, String cookieName,
                                 String cookieValue, boolean isEncode) {
        setCookie(request, response, cookieName, cookieValue, -1, isEncode);
    }

    /**
     * 设置Cookie的值 在指定时间内生效, 编码参数
     */
    public static void setCookie(HttpServletRequest request, HttpServletResponse response, String cookieName,
                                 String cookieValue, int cookieMaxage, boolean isEncode) {
        doSetCookie(request, response, cookieName, cookieValue, cookieMaxage, isEncode);
    }

    /**
     * 设置Cookie的值 在指定时间内生效, 编码参数(指定编码)
     */
    public static void setCookie(HttpServletRequest request, HttpServletResponse response, String cookieName,
                                 String cookieValue, int cookieMaxage, String encodeString) {
        doSetCookie(request, response, cookieName, cookieValue, cookieMaxage, encodeString);
    }

    /**
     * 删除Cookie带cookie域名
     */
    public static void deleteCookie(HttpServletRequest request, HttpServletResponse response,
                                    String cookieName) {
        doSetCookie(request, response, cookieName, "", -1, false);
    }

    /**
     * 设置Cookie的值,并使其在指定时间内生效
     *
     * @param cookieMaxage cookie生效的最大秒数
     */
    private static final void doSetCookie(HttpServletRequest request, HttpServletResponse response,
                                          String cookieName, String cookieValue, int cookieMaxage, boolean isEncode) {
        try {
            if (cookieValue == null) {
                cookieValue = "";
            } else if (isEncode) {
                cookieValue = URLEncoder.encode(cookieValue, "utf-8");
            }
            Cookie cookie = new Cookie(cookieName, cookieValue);
            if (cookieMaxage > 0)
                cookie.setMaxAge(cookieMaxage);
            if (null != request) {// 设置域名的cookie
                String domainName = getDomainName(request);
                System.out.println(domainName);
                if (!"localhost".equals(domainName)) {
                    cookie.setDomain(domainName);
                }
            }
            cookie.setPath("/");
            response.addCookie(cookie);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 设置Cookie的值,并使其在指定时间内生效
     *
     * @param cookieMaxage cookie生效的最大秒数
     */
    private static final void doSetCookie(HttpServletRequest request, HttpServletResponse response,
                                          String cookieName, String cookieValue, int cookieMaxage, String encodeString) {
        try {
            if (cookieValue == null) {
                cookieValue = "";
            } else {
                cookieValue = URLEncoder.encode(cookieValue, encodeString);
            }
            Cookie cookie = new Cookie(cookieName, cookieValue);
            if (cookieMaxage > 0) {
                cookie.setMaxAge(cookieMaxage);
            }
            if (null != request) {// 设置域名的cookie
                String domainName = getDomainName(request);
                System.out.println(domainName);
                if (!"localhost".equals(domainName)) {
                    cookie.setDomain(domainName);
                }
            }
            cookie.setPath("/");
            response.addCookie(cookie);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 得到cookie的域名
     */
    private static final String getDomainName(HttpServletRequest request) {
        String domainName = null;
        // 通过request对象获取访问的url地址
        String serverName = request.getRequestURL().toString();
        if (serverName == null || serverName.equals("")) {
            domainName = "";
        } else {
            // 将url地下转换为小写
            serverName = serverName.toLowerCase();
            // 如果url地址是以http://开头  将http://截取
            if (serverName.startsWith("http://")) {
                serverName = serverName.substring(7);
            }
            int end = serverName.length();
            // 判断url地址是否包含"/"
            if (serverName.contains("/")) {
                //得到第一个"/"出现的位置
                end = serverName.indexOf("/");
            }

            // 截取
            serverName = serverName.substring(0, end);
            // 根据"."进行分割
            final String[] domains = serverName.split("\\.");
            int len = domains.length;
            if (len > 3) {
                // www.xxx.com.cn
                domainName = domains[len - 3] + "." + domains[len - 2] + "." + domains[len - 1];
            } else if (len <= 3 && len > 1) {
                // xxx.com or xxx.cn
                domainName = domains[len - 2] + "." + domains[len - 1];
            } else {
                domainName = serverName;
            }
        }

        if (domainName != null && domainName.indexOf(":") > 0) {
            String[] ary = domainName.split("\\:");
            domainName = ary[0];
        }
        return domainName;
    }
}

(2)uuid工具类(utils下)

/**
 * UUID工具类
 * @ClassName: UUIDUtil
 */
public class UUIDUtil {

    public static String uuid() {
        return UUID.randomUUID().toString().replace("-", "");
    }
}

4.2 使用

以cookie,session判断用户是否登录成功

		//在登陆时,生成cookie
        String ticket = UUIDUtil.uuid();
        request.getSession().setAttribute(ticket, user);
        CookieUtil.setCookie(request, response, "userTicket", ticket);

登陆成功后跳转

//获取Cookie中的值,查session
@Controller
@RequestMapping("/goods")
public class GoodsController {
    @RequestMapping("/toList")                              // userTicket 为自定义的cookie
    public String toList(HttpSession session, Model model, @CookieValue("userTicket") String ticket) {
        if (StringUtils.isEmpty(ticket))
            return "login";
        User user = (User) session.getAttribute(ticket);
        if (user == null)
            return "login";
        model.addAttribute("user", user);   // 将user传递到前端页面
        return "goodsList";
    }
}

5 分布式Session问题

5.1 存在的问题

  • 之前的代码在我们之后一台应用系统,所有操作都在一台Tomcat上,没有什么问题。当我们部署多台
    系统,配合Nginx的时候会出现用户登录的问题

  • 但当Nginx 使用默认负载均衡策略(轮询),请求将会按照时间顺序逐一分发到后端应用上。也就是说刚开始我们在 Tomcat1 登录之后,用户信息放在 Tomcat1 的 Session 里。过了一会,请求又被 Nginx 分发到了 Tomcat2 上,这时 Tomcat2 上 Session 里还没有用户信息,于是又要登录。
    在这里插入图片描述

5.2 解决方案

具体参鉴session分布式解决方法

  • Session复制
    • 优点
      1. 无需修改代码,只需要修改Tomcat配置
    • 缺点
      1. Session同步传输占用内网带宽
      2. 多台Tomcat同步性能指数级下降
      3. Session占用内存,无法有效水平扩展
  • 前端存储
    • 优点
      1. 不占用服务端内存
    • 缺点
      1. 存在安全风险
      2. 数据大小受cookie限制
      3. 占用外网带宽
  • Session粘滞
    • 优点
      1. 无需修改代码
      2. 服务端可以水平扩展
    • 缺点
      1. 增加新机器,会重新Hash,导致重新登录
      2. 应用重启,需要重新登录
  • 后端集中存储
    • 优点
      1. 安全
      2. 容易水平扩展
    • 缺点
      1. 增加复杂度
      2. 需要修改代码