token是"令牌","代币"等一些说法,关于token的作用的说法也比较多,其实在token的使用中保证业务的安全性上具有不可忽略的作用,比如防止表单的重复提交,token的生成可以用session的随机id,也可以用随机数等,前者可以直接将session的ID以token的形式传入,并且封装为需要的cookie放进response中返回给服务端,下面是一个关于随机数生成token的简单的业务应用场景:用户忘记密码时验证问题答案重置自己的密码的过程。
(1)TokenCache类
将生成的token的值加载到本地缓存中:
1 /** 2 * @author 大神爱吃茶 3 * */ 4 public class TokenCache { 5 6 //创建日志 7 private static Logger logger = LoggerFactory.getLogger(TokenCache.class); 8 9 //token的前缀 10 public static final String TOKEN_PREFIX = "token_"; 11 12 //设置本地缓存,其中expireAfterAccess是有效期 13 //初始化的值是10000,如果超过10000的话就会使用LRU算法 14 //声明了localCache的key和value值都是String类型的 15 private static LoadingCache<String,String> localCache = CacheBuilder.newBuilder(). 16 initialCapacity(1000). 17 maximumSize(10000). 18 expireAfterAccess(12, TimeUnit.HOURS). 19 build(new CacheLoader<String, String>() { 20 //下面这个方法是默认的数据加载实现,当调用get取值的时候,如果key没有对应的值,就调用这个方法来进行加载 21 //即如果get没有拿到数据,就用load来加载缓存 22 @Override 23 public String load(String s) throws Exception { 24 //在这里可以初始化加载数据的缓存信息,读取数据库中信息或者是加载文件中的某些数据信息 25 return "null"; 26 } 27 }); 28 29 public static void setKey(String key,String value){ 30 localCache.put(key, value); 31 } 32 33 public static String getKey(String key){ 34 String value = null; 35 try{ 36 value = localCache.get(key); 37 if("null".equals(value)){ 38 return null; 39 } 40 return value; 41 }catch (Exception e){ 42 logger.error("localCache get error", e); 43 } 44 return null; 45 } 46 }
(2)业务接口和实现类即方法
①忘记密码接口:
1 /** 2 * 忘记密码接口,返回这个用户的问题给他 3 * */ 4 @RequestMapping(value = "forget_get_question.do",method = RequestMethod.POST) 5 @ResponseBody 6 public ServerResponse<String> forgetPasswordGetQuestion(String username){ 7 ServerResponse forgetPwdGetQuestionResponse = iUserService.selectUserQuestion(username); 8 return forgetPwdGetQuestionResponse; 9 }
②校验问题的答案是否正确接口
1 /** 2 * 校验问题的答案是否准确接口 3 * */ 4 @RequestMapping(value = "forget_check_answer.do",method = RequestMethod.POST) 5 @ResponseBody 6 public ServerResponse<String> forgetPasswordCheckAnswer(String username,String question,String answer){ 7 return iUserService.checkAnswer(username,question,answer); 8 }
checkAnswer方法:并且将生成的forgetPwdToken返回给前端供后面验证!
1 public ServerResponse<String> checkAnswer(String username,String question,String answer){ 2 int resultCount = userMapper.checkAnswer(username,question,answer); 3 if(resultCount > 0){ 4 //生成一个UUID序列,然后再考虑将之加到缓存中去 5 String forgetPwdToken = UUID.randomUUID().toString(); 6 //setKey的过程localCache.put(key, value)会把key和对应的token的值加进去 7 TokenCache.setKey(TokenCache.TOKEN_PREFIX+username, forgetPwdToken); 8 return ServerResponse.createBySuccess(forgetPwdToken); 9 } 10 return ServerResponse.createByError("问题的答案错误"); 11}
③忘记密码重置密码接口
1 /** 2 * 忘记密码,重置密码接口 3 * */ 4 @RequestMapping(value = "forget_reset_password.do",method = RequestMethod.POST) 5 @ResponseBody 6 public ServerResponse<String> forgetPasswordRestPassword(String username,String passwordNew,String forgetToken){ 7 return iUserService.forgetPwdRestPwd(username,passwordNew,forgetToken); 8 }
forgetPwdRestPwd方法:
1 /** 2 * 忘记密码,重置密码 3 * */ 4 //是真要传入通过checkAnswer验证了答案之后返回的token的 5 public ServerResponse<String> forgetPwdRestPwd(String username,String passwordNew,String forgetPwdToken){ 6 //先进行校验 7 if(StringUtils.isBlank(forgetPwdToken)){ 8 return ServerResponse.createByError("参数错误,token需要传递"); 9 } 10 //如果用户名为空的话用户可能就会直接获取token_和forgetToken的内容,对username再进行校验 11 ServerResponse ifUsernameIsNullResponse = this.checkValid(username, Const.USERNAME); 12 if(ifUsernameIsNullResponse.isSuccess()){ 13 //因为checkValid的时候,校验成功是用户不存在的情况 14 return ServerResponse.createByError("用户不存在"); 15 } 16 //在本地缓存中取TokenCache.TOKEN_PREFIX + username对应的token的值 17 String token = TokenCache.getKey(TokenCache.TOKEN_PREFIX + username); 18 //StringUtils的equals(a,b)方法即使a为空也不会报异常,但如果是普通的object的equals方法就会报空指针异常 19 if(StringUtils.isBlank(token)){ 20 return ServerResponse.createByError("token无效或者已过期"); 21 } 22 //需要在此处传进forgetPwdToken来与缓存中的token来比较验证是否是在同一时间段的该用户在修改自己的密码保证安全性 23 if(StringUtils.equals(forgetPwdToken, token)){ 24 String md5Password = MD5Util.MD5EncodeUtf8(passwordNew); 25 //更新用户的密码 26 int resultCount = userMapper.updatePasswordByUsername(username,md5Password); 27 if(resultCount > 0 ){ 28 return ServerResponse.createBySuccess("修改密码成功"); 29 } 30 }else { 31 return ServerResponse.createByError("token错误,请重新获取重置密码的token"); 32 } 33 return ServerResponse.createByError("修改密码失败"); 34 }
在上面,验证完用户密码的正确与否就是最后的修改重置密码的最后一步了,所以需要验证是否是相同的用户在修改自己的密码,这样能防止恶意的篡改,使用生成的token能保证安全性,先生成后存到缓存中,并传回前端,这样保证在后面需要的时候再传入此token与缓存中的token进行比较验证是否正确。在规定的时效内修改密码保证是相同的用户。