1.1.配置响应封装类
响应枚举类JsonResponseStatus
JsonResponseStatus响应枚举类用于自定义错误码。
创建响应封装类`JsonResponseBod
JsonResponseBody响应封装类用于以JSON的形式统一输出错误信息。
具体配置请参考utils目录中的JsonResponseStatus和JsonResponseBody。
1.2.业务实现
第一步:创建UserVo,用于接收前端传入的账号和密码参数。
@Data
public class UserVo {
private String mobile;
private String password;
}
第二步:创建UserController,定义登录接口方法。
@Controller
@RequestMapping("/user")
public class UserController {
@RequestMapping("/userLogin")
@ResponseBody
public JsonResponseBody<?> userLogin(UserVo userVo,
HttpServletRequest req,
HttpServletResponse resp){
return userService.userLogin(userVo,req,resp);
}
}
第三步:创建UserServiceImpl
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
@Override
public JsonResponseBody<?> userLogin(UserVo userVo,
HttpServletRequest req,
HttpServletResponse resp) {
...
}
}
1.3.参数校验
登录接口的参数校验流程:
* 判断mobile和password是否为空;
* 判断mobile格式是否正确;
* 根据用户手机号码查询用户是否存在;
* 校验账号;
* 校验密码(明文密码校验);
导入ValidatorUtils.java工具类,通过该工具类中的正则校验方法实现对输入的手机号码进行格式校验,例如:
// 判断手机号码格式是否正确
if(!ValidatorUtils.isMobile(userVo.getMobile())){
...
}
1.4.配置登录页
创建login.js,并定义登录按钮点击事件:
$('#login').click(function () {
...
});
点击登录按钮时,请设置按钮为禁用状态:
$(this).attr("disabled","disabled").css("background","#dbdbdb");
登录成功跳转到首页;登录失败请将登录按钮恢复为可点击状态:
$('#login').removeAttr("disabled").css("background","#C10000");
2.全局异常处理
首先,创建自定义异常类:
@Data
@AllArgsConstructor
@NoArgsConstructor
public class BusinessException extends RuntimeException {
private JsonResponseStatus jsonResponseStatus;
}
通过@RestControllerAdvice+@ExceptionHandler实现全局异常:
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler
public JsonResponseBody<?> exceptionHandler(Exception e){
e.printStackTrace();
JsonResponseBody<?> jsonResponseBody=null;
// 业务类异常
if(e instanceof BusinessException){
...
}else{ //系统内部异常
...
}
return jsonResponseBody;
}
}
3.自定义注解
3.1.JSR303注解验证
通过JSR303实现注解验证,修改UserVo类,添加@NotBlank实现为空验证:
@Data
public class UserVo {
@NotNull(message = "手机号码不能为空!")
private String mobile;
@NotNull(message = "登陆密码不能为空!")
private String password;
}
注意:在这里使用了注解进行非空验证之后,请移除UserServiceImpl类中userLogin方法中的非空判断。
然后,修改UserController中的userLogin接口方法,在入参UserVo之前加入@Valid注解:
@RequestMapping("/userLogin")
@ResponseBody
public JsonResponseBody<?> userLogin(@Valid UserVo userVo,
HttpServletRequest req,
HttpServletResponse resp){
return userService.userLogin(userVo,req,resp);
}
重启项目,登录进行验证测试。
3.2.自定义注解
创建自定义注解类@IsMobile:
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
//自定义校验规则类指定
@Constraint(
validatedBy = {}
)
public @interface IsMobile {
boolean required() default true;
String message() default "手机号码格式错误";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
创建自定义校验规则类MobileValidator,并实现ConstraintValidator接口:
public class MobileValidator implements ConstraintValidator<IsMobile,String> {
private boolean required=false;
@Override
public void initialize(IsMobile constraintAnnotation) {
required = constraintAnnotation.required();
}
@Override
public boolean isValid(String mobile, ConstraintValidatorContext constraintValidatorContext) {
if(required)
return ValidatorUtils.isMobile(mobile);
return false;
}
}
然后,请在自定义注解@IsMobile中设置自定义规则类MobileValidator.class:
@Constraint(
validatedBy = {MobileValidator.class}
)
最后,修改UserVo类,在指定的属性上加入自定义注解@IsMobile:
@Data
public class UserVo {
@NotNull(message = "手机号码不能为空!")
@IsMobile
private String mobile;
...
}
重启项目,登录进行验证测试。
4.密码加密
在未进行加密之前,登录验证采用明文密码传输、数据库密码采用明文密码存储。不安全!!!
两次MD5加密流程:
前端传输:jjpassword=MD5(明文+固定Salt)
后端存储:password=MD5(用户输入+随机Salt)
总之,用户端MD5加密是为了防止用户密码在网络中明文传输,服务端`MD5`加密是为了提高密码安全性,双重保险。
salt为加密所用到的盐,这里本质上就是一个UUID的随机串。
首先,导入utils目录下的MD5Utils.java,通过该类来生成加密用的盐和密文密码,并将盐和密文密码配置到数据库表中,如下:
password字段为密文密码;salt字段为加密用的盐。盐和密码必须配套,不能打乱,不然解密无效。
调整业务代码:
第一步:修改前端login.js加入前端密码生成规则:
...
let password=$('#password').val();
//固定盐
let salt="f1g2h3j4";
//盐混淆
let temp=salt.charAt(1)+""+salt.charAt(5)+password+
salt.charAt(0)+""+salt.charAt(3);
//进行前端第一次加密
let pwd=md5(temp);
...
再次发起ajax请求时,请将第一次加密后的密文密码传入后端接口。
第二步:修改UserServiceImpl中的userLogin方法,添加密码校验逻辑:
@Override
public JsonResponseBody<?> userLogin(UserVo userVo,
HttpServletRequest req,
HttpServletResponse resp) {
//根据user对象的随机盐+前端输入一道加密密码进行加密生成二道加密密码
String pwd = MD5Utils.formPassToDbPass(userVo.getPassword(), user.getSalt());
//判断登陆密码是否正确
if(!user.getPassword().equals(pwd))
throw new BusinessException(JsonResponseStatus.USER_PASSWORD_ERROR);
...
}
重启项目,登录进行验证测试。
5.token令牌
生成Token令牌,并使用cookie+redis的方式存储用户对象。将课件资料中的CookieUtils.java导入到项目中。
第一步:创建RedisConfig配置类:
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String,Object> redisTemplate(RedisConnectionFactory connectionFactory){
RedisTemplate<String,Object> redisTemplate=new RedisTemplate<>();
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
redisTemplate.setConnectionFactory(connectionFactory);
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
}
第二步:创建IRedisService并定义储存和获取token令牌的接口:
public interface IRedisService {
/**
* 将登陆User对象保存到Redis中,并以Token为键
* @param token
* @param user
*/
void setUserToRedis(String token,User user);
/**
* 根据token令牌获取redis中存储的user对象
* @param token
* @return
*/
User getUserByToken(String token);
}
第三步:创建RedisServiceImpl并实现IRedisService接口:
@Service
public class RedisServiceImpl implements IRedisService {
@Autowired
private RedisTemplate<String,Object> redisTemplate;
@Override
public void setUserToRedis(String token, User user) {
redisTemplate.opsForValue().set("user:"+token,user,7200, TimeUnit.SECONDS);
}
@Override
public User getUserByToken(String token) {
return (User) redisTemplate.opsForValue().get("user:"+token);
}
}
注意:Redis将存储用户对象信息,超时时间7200秒。
第四步:修改UserServiceImpl中的userLogin方法,加入cookie+redis的存储机制:
@Override
public JsonResponseBody<?> userLogin(UserVo userVo,
HttpServletRequest req,
HttpServletResponse resp) {
...
//通过UUID生成随机Token令牌
String token = UUID.randomUUID().toString().replace("-","");
//将随机token令牌保存到Cookie中(保存时长7200秒)
CookieUtils.setCookie(req,resp,"userTicket",token,7200);
//将user对象+token绑定保存到redis中
redisService.setUserToRedis(token,user);
//将昵称保存到cookie中
CookieUtils.setCookie(req,resp,"nickname",user.getNickname());
return new JsonResponseBody<>();
}
重启项目,登录进行验证测试。