1.用户端:PASS = MD5(明文+固定Salt) 防止用户明文密码在网络中传输
2.服务端:PASS = MD5(用户输入+随机Salt) 防止被脱裤
引入MD5工具类,添加MD5Util
<dependency>
<groupId>commons-codecgroupId>
<artifactId>commons-codecartifactId>
dependency>
<dependency>
<groupId>org.apache.commonsgroupId>
<artifactId>commons-lang3artifactId>
<version>3.6version>
dependency>
MD5Util
public class MD5Util {
public static String md5(String src){
return DigestUtils.md5Hex(src);
}
private static final String salt ="1a2b3c4d";
//这个是在网络中传输的,哪怕被截获到了。反向获取的话还是找不到真正的密码的
public static String inputPassToFormPass(String inputPass){
//拼个串在做md5.当然这个拼接自定义的。
String str = ""+salt.charAt(0)+salt.charAt(2)+inputPass+salt.charAt(5)+salt.charAt(4);
return md5(str);
}
//把传上来的密码加上随机 salt再次md5存入mysql
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);
}
//把明文二次MD5为存入数据库
public static String inputPassToDbPass(String input,String saltDB){
String formPass = inputPassToFormPass(input);
return formPassToDBPass(formPass,saltDB);
}
先pom依赖
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-validationartifactId>
dependency>
先在参数中加上注解
doLogin(@Valid LoginVo loginVo)
在实体类中校验
@NotNull
private String mobile;
@NotNull
@Length(min=32)
private String password;
如何自定义一个验证器,比如检验手机号格式
先定义校验注解
@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 { };
}
再定义一个真正校验的
IsMobileValidator.class
要实现 ConstraintValidator 接口
public class IsMobileValidator implements ConstraintValidator<IsMobile, String> {
private boolean required = false;
//获取注解中的required(这个是不是必须的意思)
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);
}
}
}
现在这样会有异常,定义一个全局异常拦截
拦截所有异常,如果是绑定异常就返回对应的错误
如果是其他异常就返回服务端错误
@ControllerAdvice
@ResponseBody
public class GlobalExceptionHandler {
@ExceptionHandler(value = Exception.class)//拦截什么异常就写什么
public Result exceptionHandler(HttpServletRequest request,Exception e){
//拦截绑定异常
if(e instanceof BindException){
BindException ex = (BindException) e;
List errors = ex.getAllErrors();
ObjectError error = errors.get(0);
String msg = error.getDefaultMessage();
return Result.error(CodeMsg.BIND_ERROR.fillArgs(msg));
}else {//其他异常就返回一个错误
return Result.error(CodeMsg.SERVER_ERROR);
}
}
之后我们就可以抛出异常然后让其捕获就可以了
我们创建一个全局异常,然后抛出,可以在上面捕获在进行指定输出
public class GlobalException extends RuntimeException {
private static final long serialVersionUID=1L;
private CodeMsg cm;
public GlobleException(CodeMsg cm){
super(cm.toString());
this.cm = cm;
}
public static long getSerialVersionUID() {
return serialVersionUID;
}
public CodeMsg getCm() {
return cm;
}
public void setCm(CodeMsg cm) {
this.cm = cm;
}
}
登录成功 给这个用户生成一个UUID,也就是token,传递给客户端。
客户端每次都上传这个 token就能获取到用户信息了。
这个token对应哪个 用户信息存到 redis中
将信息存到第三方缓存中
cookie增加成功,添加成功 token
获取 过去时间和前缀的。
public class MiaoshaUserKey extends BasePrefix{
public static int TOKEN_EXPIRE = 3600*24*2;//设置token过期时间为2天
public MiaoshaUserKey(int expireSeconds,String prefix) {
super(expireSeconds,prefix);
}
public static MiaoshaUserKey token = new MiaoshaUserKey(TOKEN_EXPIRE,"tk");
}
登录成功后获取 token,在根据token去redis中读取 用户信息,再延长token过期时间
控制器
@RequestMapping("/to_list")
public String toLogin(Model model,
@CookieValue(value = MiaoshaUserService.COOKI_NAME_TOKEN,required = false)String cookieToken,
@RequestParam(value = MiaoshaUserService.COOKI_NAME_TOKEN,required = false)String paramToken,
HttpServletResponse response){
if (StringUtils.isEmpty(cookieToken)&&StringUtils.isEmpty(paramToken)){
return "do_login";
}
//优先取cookie中的token,两种都取是因为可能手机端是发的参数而不是cookie中。required = false 表示可以传值为空
String token = StringUtils.isEmpty(paramToken)?cookieToken:paramToken;
MiaoshaUser user =userService.getByToken(response,token);
model.addAttribute("user",user);
return "to_list";
}
Service
@Service
public class MiaoshaUserService {
public static final String COOKI_NAME_TOKEN = "token";
@Autowired
MiaoshaUserDao miaoshaUserDao;
@Autowired
RedisService redisService;
public MiaoshaUser getByMobile(String mobile) {
return miaoshaUserDao.getByMobile(mobile);
}
public MiaoshaUser getByToken(HttpServletResponse response, String token) {
if(StringUtils.isEmpty(token)) {
return null;
}
MiaoshaUser user = redisService.get(MiaoshaUserKey.token, token, MiaoshaUser.class);
//延长有效期
if(user != null) {
addCookie(response, token, user);
}
return user;
}
public boolean login(HttpServletResponse response, LoginVo loginVo) {
if(loginVo == null) {
throw new GlobleException(CodeMsg.SERVER_ERROR);
}
String mobile = loginVo.getMobile();
String formPass = loginVo.getPassword();
//判断手机号是否存在
MiaoshaUser user = getByMobile(mobile);
if(user == null) {
throw new GlobleException(CodeMsg.MOBILE_NOT_EXIST);
}
//验证密码
String dbPass = user.getPassword();
String saltDB = user.getSalt();
String calcPass = MD5Util.formPassToDBPass(formPass, saltDB);
if(!calcPass.equals(dbPass)) {
throw new GlobleException(CodeMsg.PASSWORD_ERROR);
}
//生成cookie
String token = UUIDUtil.uuid();
//随机生成 token加入到cookie中
addCookie(response, token, user);
return true;
}
private void addCookie(HttpServletResponse response, String token, MiaoshaUser user) {
//存到redis中,key是 tk:token值,值是序列化的用户
redisService.set(MiaoshaUserKey.token, token, user);
Cookie cookie = new Cookie(COOKI_NAME_TOKEN, token);
//设置cookie过期时间为toeken过期时间
cookie.setMaxAge(MiaoshaUserKey.token.expireSeconds());
cookie.setPath("/");
response.addCookie(cookie);
}
redis中的key
写一个配置类
重写 addArgumentResolvers方法
这个方法就是给 控制器中的定义的参数赋值
先定义
UserArgumentResolver
@Service
public class UserArgumentResolver implements HandlerMethodArgumentResolver{
@Autowired
MiaoshaUserService userService;
@Override
public boolean supportsParameter(MethodParameter methodParameter) {
//如果参数类型是这个实体类返回 true,才能执行下面的那个函数
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.COOKI_NAME_TOKEN);
String cookieToken = getCookieValue(request,MiaoshaUserService.COOKI_NAME_TOKEN);
if(StringUtils.isEmpty(cookieToken)&& StringUtils.isEmpty(paramToken)){
return null;
}
String token = StringUtils.isEmpty(paramToken) ? cookieToken : paramToken;
MiaoshaUser user = userService.getByToken(response, token);
return user;
}
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;
}
}
再在配置类中注入
@Configuration
public class WebConfig extends WebMvcConfigurerAdapter{
@Autowired
UserArgumentResolver userArgumentResolver;
@Override
public void addArgumentResolvers(List argumentResolvers) {
argumentResolvers.add(userArgumentResolver);
}
}
这么写后就不用在控制器中做判断了
控制器方法就可以写成这样了。
因为重写了对参数赋值的方法,如果在cookie中或者参数中找到了 token就直接从 redis中获取并
赋值给参数中的 user
@RequestMapping("/to_list")
public String toLogin(Model model,
MiaoshaUser user) {
model.addAttribute("user", user);
return "goods_list";
}
这样如果修改cookie获取方式直接修改这个地方就行,不需要修改业务代码。
分布式Session就是生成token对应用户,存在redis中。
根据token获取用户,获取出信息。
这个地方重写了框架的方法,将这些获取写到了这个方法里,
与业务分离,还有对redis的操作,对每个业务的前缀的设置+过期时间。
期间遇到了一个问题
参考
https://blog.csdn.net/qq_21853607/article/details/72802080
不然总是报错说没有注入