登录界面处理
两次MD5实现
数据库录入
JSR303校验
全局异常处理
分布式Session
两次MD5实现
创建了一个MD5的工具类,方便调用。
用户端:pass= MD5(明文+固定salt)
服务端:pass=MD5(用户输入+随机salt)
1、为什么实现两次MD5
第一次因为http传输是明文的,为了防止用户的密码直接在网络上进行传输,所以进行第一次,而第二次防止数据库被盗之后,通过一些方法反破解,所以再加一道MD5以及随机salt的拼接。
第一次MD5
public static String md5(String src) {
return DigestUtils.md5Hex(src);
}
private static final String salt = "1a2b3c4d";
public static String inputPassToFormPass(String inputPass) {
String str = ""+salt.charAt(0)+salt.charAt(2) + inputPass +salt.charAt(5) + salt.charAt(4);
System.out.println(str);
return md5(str);
}
第二次md5
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);
}
构建数据库
无太大问题,数据库中储存了动态salt的值!![在这里插入图片描述]
JSR303校验
spring中已经集成的validation
@NotNull:不能为空
@Email:必须为email格式
@Length():规定长度
自定义校验器@IsMobile,根据@NotNull仿写
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER })
@Retention(RUNTIME)
@Documented
@Constraint(validatedBy = {IsMobileValidator.class })
public @interface IsMobile {
boolean required() default true;
String message() default "手机号码格式错误";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
}
Constraint一个限制,系统看到IsMobile这个注解后会调用IsMobileValidator.class这个类进行校验,需继承ConstraintValidator接口,初始化方法拿到注解,然后isvalid方法中进行判断格式,是否为空等条件
public class IsMobileValidator implements ConstraintValidator<IsMobile, String> {
private boolean required = false;
public void initialize(IsMobile constraintAnnotation) {
required = constraintAnnotation.required();
}
public boolean isValid(String value, ConstraintValidatorContext context) {
if(required) {
return ValidatorUtil.isMobile(value);
}else {
if(StringUtils.isEmpty(value)) {
return true;
}else {
return ValidatorUtil.isMobile(value);
}
}
}
}
全局异常处理
1.为什么要进行全局异常处理
刚开始我们对账号密码进行判断时,返回的时codemsg,但是真实业务中,我们应该就是要么登陆成功,或者失败,不应把这些信息给到用户,所以我们定义异常,把这些信息抛出,最后返回true or false。
public String login(HttpServletResponse response, LoginVo loginVo) {
if(loginVo == null) {
throw new GlobalException(CodeMsg.SERVER_ERROR);
}
String mobile = loginVo.getMobile();
String formPass = loginVo.getPassword();
//判断手机号是否存在
MiaoshaUser user = getById(Long.parseLong(mobile));
if(user == null) {
throw new GlobalException(CodeMsg.MOBILE_NOT_EXIST);
}
//验证密码
String dbPass = user.getPassword();
String saltDB = user.getSalt();
String calcPass = MD5Util.formPassToDBPass(formPass, saltDB);
if(!calcPass.equals(dbPass)) {
throw new GlobalException(CodeMsg.PASSWORD_ERROR);
}
分布式Session
为什么要实现分布式Session
因为我们是一个秒杀系统,肯定会有多台服务器,用户的请求有可能随机落入到不同的服务器中,这样的结果将会导致用户的session丢失。传统做法时进行Session同步,将用户的session的信息同步到每一台服务器上,但这样的方式弊端也很明显,就是占用内存,以及如果服务器数量特别庞大,那么操作难度和成本也会变得相当高。
我们的做法是,用户登陆成功后给这个用户加上一个token(UUID,累死sessionId)来标识这个用户,写到cookie当中传给客户端,随后客户端在登录时都会上传这个token,然后服务端通过这个token来取到用户对应的session信息。跟原生的容器实现session的原理是一样的。
过程
1.我们先通过UUID创建一个token,但我们要知道token对应的是哪一个用户,所以我们要把token和用户信息存入redis缓存中
String token= UUIDUtil.uuid();
addCookie(response, token, user);
return token
2.生成Cookie,我们创建了一个addcookie方法,就是把token信息存入cookie,然后把cookie方法写入客户端,这样客户端每次可以通过cookie中的token值去缓存里面找我们的用户信息了
private void addCookie(HttpServletResponse response, String token, MiaoshaUser user) {
redisService.set(MiaoshaUserKey.token, token, user);
Cookie cookie = new Cookie(COOKI_NAME_TOKEN, token);
cookie.setMaxAge(MiaoshaUserKey.token.expireSeconds());
cookie.setPath("/");
response.addCookie(cookie);
}
3.两个注解使用
@CookieValue:这个注解能够根据参数value在Cookie中获取值
@RequestParam:该注解让我们在Request中能获取参数,解决的主要是,移动手机端不使用Cookie存值的问题
4.延长有效期,在从缓存中取用户信息时,再重新设置一个cookie
问题
我们登陆进去之后,还要进入商品详情页,秒杀页面,这时都要判断用户的session即用户是否登陆,这样十分繁琐,所以我们要把这个方式剥离出来。
思路:直接将user对象注入到我们的controller方法中。这样直接由用户信息了,不需要进行判断了,如下解决
WebMvcConfigurerAdapter
1.这个项目中我们使用了WebMvcConfigurerAdapter,去重写里面的addAgumentReslover方法
@Configuration
public class WebConfig extends WebMvcConfigurerAdapter{
@Autowired
UserArgumentResolver userArgumentResolver;
@Autowired
AccessInterceptor accessInterceptor;
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
argumentResolvers.add(userArgumentResolver);
}
- 然后在UserArgumentResolver中实现我们的参数添加逻辑,继承HandlerMethodArgumentResolver 重写supportsParameter()和resolveArgument()方法,我们要实现输入MiaoshaUser。
@Service
public class UserArgumentResolver implements HandlerMethodArgumentResolver {
@Autowired
MiaoShaUserService miaoShaUserService;
@Override
public boolean supportsParameter(MethodParameter methodParameter) {
//这个方法判断参数类型是否支持
Class<?> clazz = methodParameter.getParameterType();
return clazz == MiaoShaUser.class;
}
@Override
public Object resolveArgument(MethodParameter methodParameter, ModelAndViewContainer modelAndViewContainer,
NativeWebRequest nativeWebRequest, WebDataBinderFactory webDataBinderFactory) throws Exception {
//这个方法实现对参数的处理
HttpServletRequest request = nativeWebRequest.getNativeRequest(HttpServletRequest.class);
HttpServletResponse response = nativeWebRequest.getNativeResponse(HttpServletResponse.class);
String paramToken = request.getParameter(miaoShaUserService.COOKIE_NAME_TOKEN);
String cookieToken = getCookieValue(request, miaoShaUserService.COOKIE_NAME_TOKEN);
if(StringUtils.isEmpty(paramToken) && StringUtils.isEmpty(cookieToken)){
return null;
}
String token = StringUtils.isEmpty(paramToken) ? cookieToken : paramToken;
return miaoShaUserService.getByToken(response,token);
}
private String getCookieValue(HttpServletRequest request,String cookieName){
Cookie[] cookies = request.getCookies();
for(Cookie cookie : cookies){
if(cookie.getName().equals(cookieName)){
return cookie.getValue();
}
}
return null;
}
}
先判断参数类型,然后对参数的处理一是从request中获取token值,二是从cookie中拿取token值,根据token值来获取到对应的user。
这样就实现了代码的简化。
分布式Session总结
此图转载自https://blog.csdn.net/qq_46225886/article/details/107256719,方便理解