精华内容
下载资源
问答
  • java 如何保证接口安全性

    万次阅读 2017-12-07 09:55:45
    在开发过程中,肯定会有和第三方或者app端的接口调用。在调用的时候,如何来保证非法链接或者恶意攻击呢? 1.签名  根据用户名或者用户id,结合用户的ip或者设备号,生成一个token。在请求后台,后台获取http的...

      在开发过程中,肯定会有和第三方或者app端的接口调用。在调用的时候,如何来保证非法链接或者恶意攻击呢?

    1.签名

        根据用户名或者用户id,结合用户的ip或者设备号,生成一个token。在请求后台,后台获取http的head中的token,校验是否合法(和数据库或者redis中记录的是否一致,在登录或者初始化的时候,存入数据库/redis)

    在使用Base64方式的编码后,Token字符串还是有20多位,有的时候还是嫌它长了。由于GUID本身就有128bit,在要求有良好的可读性的前提下,很难进一步改进了。那我们如何产生更短的字符串呢?还有一种方式就是较少Token的长度,不用GUID,而采用一定长度的随机数,例如64bit,再用Base64编码表示:

        var rnd = new Random();
        var tokenData = userIp+userId;
        rnd.NextBytes(tokenData);
        var token = Convert.ToBase64String(tokenData).TrimEnd('=');

    由于这里只用了64bit,此时得到的字符串为Onh0h95n7nw的形式,长度要短一半。这样就方便携带多了。但是这种方式是没有唯一性保证的。不过用来作为身份认证的方式还是可以的(如网盘的提取码)。

    2.加密

       客户端和服务器都保存一个秘钥,每次传输都加密,服务端根据秘钥解密。

       

    客户端:

        1、设置一个key(和服务器端相同)

        2、根据上述key对请求进行某种加密(加密必须是可逆的,以便服务器端解密)

        3、发送请求给服务器

    服务器端:

        1、设置一个key

        2、根据上述的key对请求进行解密(校验成功就是「信任」的客户端发来的数据,否则拒绝响应)

        3、处理业务逻辑并产生结果

        4、将结果反馈给客户端

    3.第三方支持

      比如spring security-oauth 

    有兴趣的,可以参考这篇帖子

    http://wwwcomy.iteye.com/blog/2230265

    展开全文
  • 用户登录接口的设计,首先要考虑的是防止用户的账号被...但是我们真正的目的是为了保证用户的账号安全,屏蔽攻击者的恶意调用的同时,又要保证真实用户的正常使用,此时就应该提供重置登录密码的操作,密码重置成...

           用户登录接口的设计,首先要考虑的是防止用户的账号被暴力破解,常用的是使用谷歌图形验证码,同时还需要对用户每天的“连续”登录错误次数进行限制,假设一个账号用户每天连续登陆的错误次数是十次,那么当用户第十一次输入错误时,就应该锁住当前用户的账户。但是我们真正的目的是为了保证用户的账号安全,屏蔽攻击者的恶意调用的同时,又要保证真实用户的正常使用,此时就应该提供重置登录密码的操作,密码重置成功之后,用户在当天又能恢复登录。其次需要考虑的是用户登录密码在传输过程中的安全性的问题,虽说现在的HTTPS 的安全性已经足够,但是对于一些安全性很高的项目,仍旧需要有自己的加密方式来保护用户的敏感信息,比如我这里使用的RSA加密方法,具体的论述以及使用,请参考我的另一篇博客:https://blog.csdn.net/weixin_42023666/article/details/89706659

            下面废话也不多说,直接上代码,当然了,还是老规矩,只分享service层的代码来陈述具体的思路,至于全套代码无法提供(还请谅解)。另外代码中的注释已经有足够多的解释,千万别忘了他们。

     

    
    package sy.service.impl;
    
    import java.text.ParseException;
    import java.text.SimpleDateFormat;
    import java.util.Calendar;
    import java.util.Date;
    import java.util.HashMap;
    import java.util.Map;
    import java.util.UUID;
    
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpSession;
    
    import org.apache.commons.lang.StringUtils;
    import org.apache.log4j.Logger;
    import org.springframework.stereotype.Service;
    
    import com.alibaba.fastjson.JSON;
    
    import DateUtil;
    import sy.util.mobile.MobileUtil;
    
    @Service("userService")
    public class UserServiceImpl implements UserService {
    
    	private static final Logger logger = Logger.getLogger(UserServiceImpl.class);
    
    	/**
    	登录时,当用户输入手机号后,就调用该接口判断当前手机号是否注册,
    	如果是已经注册过的,后台就会生成rsa公私钥对并将公钥返回给前端;
    	如果没有注册,就提醒用户当前手机号未注册,以此来提高用户体验
    	*/
    	@Override
    	public Result findUser(String mobile, HttpServletRequest request) {
    
    		Result result = new Result();
    
    		// 判断传入的手机号格式是否正确
    		if (StringUtils.isBlank(mobile)) {
    			result.setSuccess(false);
    			result.setMsg("缺少必要参数");
    			return result;
    		}
    		mobile = mobile.trim();
    
    		// 判断传入的手机号格式是否正确
    		if (mobile.length() != 11 || !MobileUtil.isMobileNum(mobile)) {
    			result.setSuccess(false);
    			result.setMsg("传入的手机号格式不正确");
    			return result;
    		}
    
    		//查询当前手机号是否注册,此处省略具体的数据库相关的操作
    		if(未注册){//伪代码,实际使用需换成你自己的dao层查询
    			result.setSuccess(false);
    			result.setMsg("当前手机号没有注册");
    			return result;
    		}
    
    		String rsaPublicKey = "login_rsa_public_" + mobile;
    		String rsaPrivateKey = "login_rsa_private_" + mobile;
    
    		Long rsa = RedisUtils.ttl(rsaPublicKey);
    		if (rsa > 60 *15+5) {// 原来的公钥有效时间大于15分钟,继续使用
    			result.setStatus(RedisUtils.get(rsaPublicKey));//该字段保存生成的rsa公钥并返回给前端
    		} else {
    			Map<Integer, String> map = RSAEncrypt.genKeyPair();
    			if (map != null && map.get(0) != null) {
    				RedisUtils.set(rsaPublicKey, map.get(0), 60 * 120 + 120);// 保存公钥
    				RedisUtils.set(rsaPrivateKey, map.get(1), 60 * 120 + 150);// 保存私钥,2小时有效
    				result.setStatus(map.get(0));
    			}
    		}
    		
    		// 登录失败超过三次就要输入验证码,判断当前用户是否需要设置验证码
    		String times = RedisUtils.get( mobile + "_login_error_times");
    		if (times != null && new Integer(times).intValue() >= 3) {
    			result.setPage(1);//告诉前端,此次请求需要输入谷歌图形验证码;此时公钥其实已经一并返回给前端,
    				//如果前端没有携带谷歌图形验证码就直接调用登录接口,请求依旧会失败,因为登录接口有做检验,有判断当前用户今日登录次数是否超次数
    		}
    		
    		result.setSuccess(true);
    
    		return result;
    	}
    
    	/**
    	用户登录接口service层的代码,按照惯例,controller层就不贴出来了
    	mobile是登录的手机号,pwd是使用rsa公钥加密后的密码,code是谷歌图形验证码
    	*/
    	@Override
    	public Result login(String mobile,String pwd,String code, HttpServletRequest request) {
    		Result result = new Result();
    
    		// 判断请求参数是否正确
    		if (StringUtils.isBlank(mobile) || StringUtils.isBlank(pwd)) {
    			result.setSuccess(false);
    			result.setMsg("缺少必要的参数");
    			return result;
    		}
    
    		mobile = mobile.trim();
    		// 判断传入的手机号格式是否正确
    		if (mobile.length() != 11 || !MobileUtil.isMobileNum(mobile)) {
    			result.setSuccess(false);
    			result.setMsg("传入的手机号格式不正确");
    			return result;
    		}
    
    		// 判断是否有图片验证码
    		String kaptcha = RedisUtils.get("login_captcha_" + mobile);
    		if (kaptcha != null && code != null && !code.toLowerCase().equals(kaptcha)) {
    			result.setPage(1);
    			result.setSuccess(false);
    			result.setMsg("您输入的验证码有误,请重新输入");
    			return result;
    		}
    
    		String rsaPublicKey = "login_rsa_public_" + mobile;
    		String rsaPrivateKey = "login_rsa_private_" + mobile;
    
    		// 从redis数据库获取私钥
    		String privateKey = RedisUtils.get(rsaPrivateKey);
    		if (StringUtils.isBlank(privateKey)) {
    
    			RedisUtils.del(rsaPublicKey);
    			RedisUtils.del(rsaPrivateKey);
    
    			result.setPage(1);
    			result.setSuccess(false);
    			result.setMsg("密钥验证失败,请输入新的验证码后再登录");
    			result.setStatus("1");
    			return result;
    		}
    
    		// 判断当前手机号的登录失败次数,防止有人暴力破解用户的密码
    		String limitKey =  mobile + "_login_error_times";
    		String limitTimes = RedisUtils.get(limitKey);
    		Integer times = 1;
    		if (limitTimes != null) {
    			if (new Integer(limitTimes).intValue() >= 6) {
    				result.setSuccess(false);
    				result.setMsg("当前账号今日登录失败次数超过6次,为保证您的账号安全,系统已锁定当前账号,您可明天再登录或立即重置密码后使用新密码登录!");
    				return result;
    			}
    			times = new Integer(limitTimes) + 1;
    
    			if (kaptcha == null && times > 3) {
    				result.setPage(1);//该字段如果为1,表示前端需要用户输入谷歌图形验证码接口
    				result.setSuccess(false);
    				result.setMsg("您没有输入验证码");
    				return result;
    			}
    		}
    
    		// 使用私钥解密后的密码
    		pwd = RSAEncrypt.decrypt(pwd, privateKey);
    		if (pwd == null) {
    			result.setSuccess(false);
    			result.setMsg("解密失败,您传入的数据有误");
    			result.setStatus("1");
    			return result;
    		}
    
    		//核心业务隐身符,此处通过手机号获取用户的密码,然后通过对应的加密方式来检验密码是否一致性
    		if(密码不正确){//伪代码
    			// 记录密码输入错误数
    			RedisUtils.set(limitKey, times + "", DateUtil.getTodaySurplusTime());
    
    			if (times > 2) {
    				result.setPage(1);// 密码输错两次,就需要输入谷歌图形验证码
    			}
    
    			result.setSuccess(false);
    			result.setMsg("登录密码错误");
    			return result;
    		}
    		
    		//密码登录成功,执行登录成功后的核心业务,此处省略
    		
    		
    		// 删除登录失败次数的标识
    		if (limitTimes != null) {
    			RedisUtils.del(limitKey);
    		}
    		// 删除验证码
    		if (kaptcha != null) {
    			RedisUtils.del("login_captcha_" + mobile);
    		}
    		// 删除保存的公钥私钥
    		RedisUtils.del(rsaPublicKey);
    		RedisUtils.del(rsaPrivateKey);
    				
    		result.setSuccess(true);
    
    		return result;
    	}
    	
    	/**
    	获取用户重置登录密码所需要的手机短信验证码的接口
    	mobile是手机号,contents是谷歌图形验证码(该值可能为null)
    	*/
    	@Override
    	public Result sendMobileCode(String mobile, String contents, HttpServletRequest request) {
    		Result result = new Result();
    
    		if (StringUtils.isBlank(mobile)) {
    			result.setSuccess(false);
    			result.setMsg("请求参数不全");
    			return result;
    		}
    
    		mobile = mobile.trim();
    		// 判断传入的手机号格式是否正确
    		if (mobile.length() != 11 || !MobileUtil.isMobile(mobile)) {
    			result.setSuccess(false);
    			result.setMsg("传入的手机号格式不正确");
    			return result;
    		}
    
    		//业务代码隐身符1,此处需要用该手机号去查询当前帐号是否注册,只有注册的号码才能调用重置密码的手机短信验证码
    		if(手机号未注册){
    			result.setSuccess(false);
    			result.setMsg("当前手机号未注册,请先注册");
    			return result;
    		}
    
    		String kapchatKey = "kaptcha_reset_login_" + mobile;//记录此次的谷歌图形验证码,如果有的话
    		String mobileKey = "mobile_reset_login_" + mobile;//记录此次的重置登录密码的手机短信验证码
    		String todayKey = "mobile_code_times_" + mobile;//记录今日发送短信验证码的次数
    
    		// 验证码十分钟内有效,2分钟后才能重新发送
    		Long times = RedisUtils.ttl(mobileKey);
    		if (times > 60 * 18) {
    			result.setSuccess(false);
    			result.setMsg("距离您上次发送验证码不足两分钟,请两分钟后再尝试获取");
    			return result;
    		}
    		
    		// 判断当前手机号今天发送密码次数是否已达上线,每天9条,具体条数根据自己的需求制定
    		String todayTimes = RedisUtils.get(todayKey);
    		int todayCount = 1;
    		if (todayTimes != null) {
    			todayCount = new Integer(todayTimes);
    			if (todayCount >= 9) {
    				result.setSuccess(false);
    				result.setMsg("当前手机号今日发送验证码已达上限,请明日再来");
    				return result;
    			}
    			todayCount++;
    		}
    		
    		//今天调用接口超过三次,就需要调谷歌图形验证码,这也是对短信接口的一种保护机制
    		if(todayCount>3){
    			result.setPage(1);//只要今日获取验证码次数超过三次,之后每次调用短信验证码接口时都要先调用谷歌验证码
    			
    			if(StringUtils.isBlank(contents)){
    				result.setSuccess(false);
    				result.setMsg("为保证您账号安全,请输入图形验证码获取手机短信");
    				return result;
    			}
    			
    			// 检验图形验证码
    			String kapchat = RedisUtils.get(kapchatKey);
    			if (kapchat == null) {
    				result.setMsg("图形验证码已失效,请重新输入");
    				result.setSuccess(false);
    				return result;
    			} else if (!kapchat.equals(contents.toLowerCase())) {
    				result.setSuccess(false);
    				result.setMsg("您输入的验证码错误,请重新输入");
    				return result;
    			}
    		}else if(todayCount==3){
    			result.setPage(1);
    		}
    
    		String msg = null;
    		Map<Integer, String> map = null;
    		String publicKey = null;
    		String rsaPublicKey = "rsa_public_reset_login_"  + mobile;// redis保存的公钥的key
    		String rsaPrivateKey = "rsa_private_reset_login_"  + mobile;// redis中保存私钥的key
    
    		try {
    			
    			//判断原来的公钥是否可以使用,如果可以,就不需要重新生成新的rsa公钥私钥对
    			Long rsa = RedisUtils.ttl(rsaPublicKey);
    			if (rsa > 60 * 20 + 10) {// 原来的公钥有效时间大于20分钟,继续使用
    				publicKey = RedisUtils.get(rsaPublicKey);
    			} else {
    				// 生成公钥和私钥
    				map = RSAEncrypt.genKeyPair();
    				if (map == null || map.get(0) == null) {
    					result.setSuccess(false);
    					result.setMsg("生成公钥私钥失败,请联系管理员");
    					return result;
    				}
    			}
    
    			// 要发送的验证码
    			String code = (int) ((Math.random() * 9 + 1) * 100000) + "";
    		
    			// 业务代码隐身符2,此处省略调用第三方接口给用户手机发送验证码的操作
    			
    			if(短信发送成功){
    				result.setSuccess(true);
    				result.setMsg("您的手机验证码发送成功,请注意查收,本验证码20分钟内有效");
    
    				// 保存私钥,返回公钥
    				if (map != null) {
    					RedisUtils.set(rsaPublicKey, map.get(0), 60 * 90 + 250);// 2小时有效
    					RedisUtils.set(rsaPrivateKey, map.get(1), 60 * 90 + 300);// 2小时有效
    					result.setStatus(map.get(0));
    				}
    				if (publicKey != null) {
    					result.setStatus(publicKey);
    				}
    
    				// 保存验证码
    				RedisUtils.set(mobileKey, code, 60 * 20 + 5);
    
    				// 记录本号码发送验证码次数
    				RedisUtils.set(todayKey, todayCount + "", MobileUtil.getTodaySurplusTime());
    
    				// 删除图形验证码
    				RedisUtils.del(kapchatKey);
    				
    			} else {
    				result.setSuccess(false);
    				result.setMsg("短信验证码发送失败,请联系管理员:" + msg);
    			}
    
    		} catch (Exception e) {
    			result.setSuccess(false);
    			result.setMsg("获取短信验证码异常:" + e.getMessage());
    		}
    		return result;
    	}
    
    
    	/**
    	通过手机号重置用户登录密码的service层代码
    	mobile是用户手机号,contents是短信验证码,pwd是新的登录密码
    	*/
    	@Override
    	public Result resetSecret(String mobile, String contents, String pwd,HttpServletRequest request) {
    		Result result = new Result();
    
    		if (StringUtils.isBlank(mobile) || StringUtils.isBlank(contents) || StringUtils.isBlank(pwd)) {
    			result.setSuccess(false);
    			result.setMsg("缺少必要的参数");
    			return result;
    		}
    
    		mobile = mobile.trim();
    		// 判断传入的手机号格式是否正确
    		if (mobile.length() != 11 || !MobileUtil.isMobile(mobile)) {
    			result.setSuccess(false);
    			result.setMsg("传入的手机号格式不正确");
    			return result;
    		}
    
    		//此处可以再次考虑判断当前的手机号是否已经注册,防止前端调错了接口
    		//······
    		
    		
    		String mobileKey = "mobile_reset_login_" + mobile;// 存储到redis中的验证码的key
    		String rsaPublicKey = "rsa_public_reset_login_" + mobile;// 存储到redis的公钥的key
    		String rsaPrivateKey = "rsa_private_reset_login_" + mobile;// 存储到redis的私钥的key
    
    		// 校验短信验证码
    		String code = RedisUtils.get(mobileKey);
    		if (code == null) {
    			result.setPage(1);// 需要重新获取验证码
    			result.setSuccess(false);
    			result.setMsg("当前验证码已失效,请获取最新验证码后再进行此操作");
    			return result;
    		} else if (!code.equals(contents)) {
    			result.setSuccess(false);
    			result.setMsg("您输入的验证码不正确,请重新输入(不用重新获取)");
    			return result;
    		}
    
    		// 获取保存的私钥
    		String privateKey = RedisUtils.get(rsaPrivateKey);
    		if (privateKey == null) {
    			result.setSuccess(false);
    			result.setMsg("没有获取到私钥信息,请重新获取验证码");
    			return result;
    		}
    
    		// 私钥解析密码
    		pwd = RSAEncrypt.decrypt(pwd, privateKey);
    		if (pwd == null) {
    			result.setSuccess(false);
    			result.setMsg("非法请求,传入的密码无法解析");
    			return result;
    		}
    
    		//此处的密码应该是用户的密码明文,可以对用户输入新密码格式进行后端校验
    		
    
    		// 删除缓存的key
    		RedisUtils.del(mobileKey);
    		RedisUtils.del(RsaPublicKey);
    		RedisUtils.del(RsaPrivateKey);
    		
    		// 删除用户今日登录失败次数的标识,如果有
    		RedisUtils.del( mobile + "_login_error_times");
    
    		/* 以下几行是添加操作流水 */
    
    		result.setSuccess(true);
    		result.setMsg("密码重置成功");
    		return result;
    	}
    
    }
    

     

    下面是工具类 MobileUtil.java

    package util;
    
    import java.util.regex.Pattern;
    
    import javax.servlet.http.HttpServletRequest;
    
    import org.apache.commons.lang.StringUtils;
    
    /**
        * @ClassName: MobileUtil  
        * @author hqq  
        *
     */
    public class MobileUtil {
    	
    	/**
    	 * 正则表达式:验证手机号
    	 */
    	private static final String REGEX_MOBILE = "^((13[0-9])|(15[^4,\\D])|(18[0-3,5-9])|(17[0-9]))\\d{8}$";
    	
    	
    
    
    	/**
    	 * 判断是否是手机号格式,如果传入的是空串,返回false
    	 * @param mobile
    	 * @return 校验通过返回true,否则返回false
    	 */
    	public static boolean isMobile(String mobile) {
    		if(StringUtils.isBlank(mobile)){
    			return false;
    		} 
    		
    		return Pattern.matches(REGEX_MOBILE, mobile);
    	}
    
    	/**
    	 * 获取今日的剩余时间,单位是秒
    	 * @return
    	 */
    	public static Integer getTodaySurplusTime(){
    		Calendar c = Calendar.getInstance();
    		long now = c.getTimeInMillis();
    		c.add(Calendar.DAY_OF_MONTH, 1);
    		c.set(Calendar.HOUR_OF_DAY, 0);
    		c.set(Calendar.MINUTE, 0);
    		c.set(Calendar.SECOND, 0);
    		c.set(Calendar.MILLISECOND, 0);
    		long millis = c.getTimeInMillis() - now+2000;
    
    		return (int)(millis/1000);
    	}
    }
    

    另外里面涉及到的短信验证码接口和谷歌图形验证码的接口,可以参考一下我的另外几篇博客:

     

    Jedis常用工具类,包含一些具有事务的设置值的方法

    springmvc使用谷歌captcha生成图片验证码,并将验证码图片以二进制流的方式返回给前端(app和pc端都能调用)

    Java后端防止获取短信验证码接口被恶意调用的代码实现

    Java后端生成RSA随机密钥对,并实现前端(app和web)使用公钥加密,后端使用私钥解密

    Java后端如何保证用户注册登录接口的安全性之用户注册篇

     

     

    展开全文
  • java接口安全性解决方式

    千次阅读 2019-09-09 17:27:43
    在前后端分离的开发中,后台提供的接口如何能保证访问权限安全? 解决方案: 主要是身份验证、数据加密、访问控制(访问频率、访问次序,每个IP次数) 一、.签名 根据用户名或者用户id,结合用户的ip或者设备号,...

    问题:
    在前后端分离的开发中,后台提供的接口如何能保证访问权限安全?

    解决方案:
    主要是身份验证、数据加密、访问控制(访问频率、访问次序,每个IP次数)

    一、.签名
    根据用户名或者用户id,结合用户的ip或者设备号,生成一个token。在请求后台,后台获取http的head中的token,校验是否合法(和数据库或者redis中记录的是否一致,在登录或者初始化的时候,存入数据库/redis) 在使用Base64方式的编码后,Token字符串还是有20多位,有的时候还是嫌它长了。由于GUID本身就有128bit,在要求有良好的可读性的前提下,很难进一步改进了。那我们如何产生更短的字符串呢?还有一种方式就是较少Token的长度,不用GUID,而采用一定长度的随机数,例如64bit,再用Base64编码表示: var rnd = new Random(); var tokenData = userIp+userId; rnd.NextBytes(tokenData); var token = Convert.ToBase64String(tokenData).TrimEnd(’=’); 由于这里只用了64bit,此时得到的字符串为Onh0h95n7nw的形式,长度要短一半。这样就方便携带多了。但是这种方式是没有唯一性保证的。不过用来作为身份认证的方式还是可以的(如网盘的提取码)。

    二、.加密 客户端和服务器都保存一个秘钥,每次传输都加密,服务端根据秘钥解密。

    客户端: 1、设置一个key(和服务器端相同) 2、根据上述key对请求进行某种加密(加密必须是可逆的,以便服务器端解密) 3、发送请求给服务器

    服务器端: 1、设置一个key 2、根据上述的key对请求进行解密(校验成功就是「信任」的客户端发来的数据,否则拒绝响应) 3、处理业务逻辑并产生结果 4、将结果反馈给客户端

    三、.第三方支持 比如spring security-oauth 进行设置

    展开全文
  • //由此来分析该ip的用户是否是正常的用户,如果调用太频繁,比如连续一周或数周都在调用该接口,系统可以暂时禁用该ip发来的请求,或者降低该手机号获取短信验证码的次数 //一般大网站通常都得使用大数据来...

           现在的面试难免显得有些教科书式,面试官往往会问应试者一些跟自己现有项目压根用不上的新技术,以及一些所谓的基础知识,然后招到的往往又都是理论知识很丰富,前言技术又很熟悉,但是处理起实际业务时就只能呵呵的人才,还美其名曰人才储备。我个人认为,公司招人的第一准则是新入职者适应能力强,能以最快的速度上岗干活,适应期过后,其工作效率起码不能比原有岗位的开发者工作效率低太多。至于所谓人才储备,那应该是发展前景良好的公司才需要考虑的问题,小公司能生存下去已很不易。这就好比给一辆十万不到的小轿车装上宝马x5的轮胎,即便能用也无法发挥出轮胎的真正性能,最终的结果也只能是搞得双方都不愉快。因此,一个合格的面试官,他为公司寻找的应该是胜任公司现有项目的开发人才,而不是理论全才,况且,真要是技术很牛逼的大神,哪个不是眼高于顶的人物,小庙又哪里留的住。

            个人愚见,要寻找适合公司当前岗位的员工,要么是拿你在当前公司当下项目遇到的实际问题去面试求职者,通过他们的回答其实已经能够分辨出他们是否能够接受当前项目;要么是拿最普遍常用的项目模块去问他们具体的实现方式,就算他没有开发过该功能,拥有实际开发经验和没有实际开发经验的人的回答其实是很明显的。就以接下来要分享的用户登录注册模块为例,普通的面试官着眼点通常都是用户密码的加密方式云云,什么传输时使用md5加密一遍,数据库保存的密码是后台根据前端传来的密码进行二次加密后的密码等等。

           但是实际开发中,后端开发人员需要考虑的问题还要复杂很多。网站的注册登录接口作为完全暴露的接口,便意味着不管是真实用户还是攻击者都可以调用,也因为无法明确判断访问注册接口的请求来自何人,一般的后端开发通常都只能默认访问当前注册接口的人都是正常用户,以此来保证真实用户能正常使用注册登录功能。但是这样做登录注册接口是很不负责任的,攻击者只需要写一个for循环开个多线程调用你的注册登录接口,你的网站就得崩溃,此时就需要考虑使用图形验证码机制和ip黑名单机制。同一个ip的用户连续注册多少次后,就必须输入图形验证码(由于调用图形验证码接口的次数也是完全暴露的,此时也要对这个用户的日访问次数做限制,达到上限就禁止调用),这只是第一重安全限制,第二重安全限制需要考虑到的是验证码机制被破解后,注册接口仍旧能被调用,此时也要对访问这个注册接口的IP进行日访问量限制。但是对于注册,现在大部分的网站都是使用手机号注册,此时需要加上短信验证码功能(短信验证码接口也是要防恶意调用的,详情请看我的另一篇博客https://blog.csdn.net/weixin_42023666/article/details/89680342,此处不赘述),因为有了短信验证码,所以图形验证码可以考虑不使用。 以为这样就万事大吉了吗?还差得远呢。前面说的IP是指外网的固定IP,我们知道一个外网IP可以映射多个内网IP,一般的公司通常都只拉一条网线,那么同一个公司的不同员工访问我们的网站,我们获取到的IP都是一样的,此时如果员工张三恶意操作导致该IP进我们网站的黑名单,那么同公司的李四也会因此无法访问注册,而李四只是第一次点击我们的网站来注册,这时该怎么办?笔者能力浅薄,暂时也无法回答这个问题。

           另外,用户注册时填写的登录密码,后台是需要校验的,主要是为了判断密码是否符合我们定义的规则(还是那句话,永远不要相信前端传来的敏感数据),这个时候就需要保证后端能够正确得到用户的密码明文,详细的理由我已经在上一篇博客https://blog.csdn.net/weixin_42023666/article/details/89706659中说明,此处不赘述,下面直接上代码。注意,下面分享的只有业务层的代码,并且省去了数据库相关的业务,目的只是为了分享一个思路。

     

    package demo;
    
    import java.text.ParseException;
    import java.text.SimpleDateFormat;
    import java.util.Calendar;
    import java.util.Date;
    import java.util.HashMap;
    import java.util.Map;
    import java.util.UUID;
    
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpSession;
    
    import org.apache.commons.lang.StringUtils;
    import org.apache.log4j.Logger;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    import org.springframework.ui.Model;
    
    import com.alibaba.fastjson.JSON;
    import com.jpush.JPushClientExample;
    
    
    /**
     * @ClassName: UserServiceImpl
     * @author hqq
     */
    @Service("userService")
    public class UserServiceImpl implements UserService {
    
    	private static final Logger logger = Logger.getLogger(UserServiceImpl.class);
    	
    	//获取短信验证码
    	@Override
    	public Result sendMobileCode(String mobile, String type, String imgCode, HttpServletRequest request) {
    		//mobile字段是传入的用户名,即手机号,必传字段;
    		//type是传入的需要发送的短信类型,必传字段,如登录,注册等(也可以不区分,按个人爱好喽);
    		//imgCode是传入的图形验证码,非必传字段,因为只有今日第四次调用该接口时才需要校验图形验证码
    		
    		Result result = new Result();
     
    		//校验请求参数是否正确
    		if (StringUtils.isBlank(mobile) || StringUtils.isBlank(type)) {
    			result.setSuccess(false);
    			result.setMsg("请求参数不全");
    			return result;
    		}
     
    		mobile = mobile.trim();
    		// 判断传入的手机号格式是否正确
    		if (mobile.length() != 11 || !MobileUtil.isMobile(mobile)) {
    			result.setSuccess(false);
    			result.setMsg("手机号格式不正确");
    			return result;
    		}
     
    		// 要发送的短信验证码,生成六位数字验证码
    		String mobileCode = (int) ((Math.random() * 9 + 1) * 100000) + "";
     
    		String modelCode = null;//我这里使用的是阿里云的短信服务,调用阿里云的短信接口需要传入一个模板code参数,这个code再你申请短信模板时就会产生,且固定的值
    		
    		//判断当前要发送的是哪种类型的短信,不同的类型的验证码应该进行区分,这样可以提高用户体验;区分的参数由调用者(前端开发人员)传入,通常不会出现参数不存在的问题
    		switch (type) {
    			case "register":// 发送注册的短信验证码
    			
    				//核心业务隐身符1,用手机号去自己数据库查询当前用户是否存在,如果存在,则不能发送该类手机验证码,提示用户直接登录
    				//······
    				if(手机号已注册){//伪代码
    					result.setSuccess(false);
    					result.setMsg("当前手机号已注册,请直接登录");
    					return result;
    				}
    				
    				modelCode = "SMS_123456788";
    				break;
    				
    			case "reset":// 发送重置登录密码的短信验证码
    			
    				//核心业务隐身符1,用手机号去自己数据库查询当前用户是否存在,如果不存在,则不能发送该类手机验证码,提示用户注册
    				//·······
    				if(手机号未注册){//伪代码
    					result.setSuccess(false);
    					result.setMsg("当前手机号未注册,请先注册");
    					return result;
    				}
     
    				modelCode = "SMS_111111111";
    				break;
    	
    			default:
    				result.setSuccess(false);
    				result.setMsg("非法请求");
    				return result;
    		}
     
    		String mobileKey = type+"_mobile_" + mobile;
    		String todayKey = "today_mobile_code_times_" + mobile;
     
    		// 验证码三十分钟内有效,并且距离上一次发送要超过2分钟的时间才能重新发送
    		Long times = RedisUtils.ttl(mobileKey);
    		if (times > 60 * 28) {
    			result.setSuccess(false);
    			result.setMsg("距离您上次发送验证码不足两分钟,请两分钟后再尝试获取");
    			return result;
    		}
    		
    		// 判断当前手机号今天发送密码次数是否已达上线,每天15条(具体条数根据自己的需求调用)
    		String todayTimes = RedisUtils.get(todayKey);
    		int todayCount = 1;
    		if (todayTimes != null) {
    			todayCount = new Integer(todayTimes);
    			if (todayCount >= 15) {
    				
    				//此时还可以记录当前用户的手机号,ip,调用的短信验证码类型到表中,方便系统记录与分析。系统可以分析该用户该周该月调用短信接口的次数
    				//由此来分析该ip的用户是否是正常的用户,如果调用太频繁,比如连续一周或数周都在调用该接口,系统可以暂时禁用该ip发来的请求,或者降低该手机号获取短信验证码的次数
    				//一般大网站通常都得使用大数据来监控了,而小网站,就没必要整的这么复杂了
    				
    				result.setSuccess(false);
    				result.setMsg("当前手机号今日发送验证码已达上限,请明日再来");
    				return result;
    			}
    			todayCount++;
    		}
    		
    		//今天发送短信超过三次,再次调用接口时,需要调谷歌图形验证码
    		if(todayCount>3){
    			result.setStatus(1);//只要今日获取验证码次数超过三次,之后每次获取都要谷歌验证码,这个标识返回给前端,
    													//前端看到这个值,需要调用谷歌图形验证码,待用户输入图形验证码后才能调用该接口
    			
    			if(StringUtils.isBlank(imgCode)){
    				result.setSuccess(false);
    				result.setMsg("为保证您账号安全,本次请求需要输入图形验证码");
    				return result;
    			}
    			
    			// 检验图形验证码
    			String kapchatKey =type+ "_kaptcha_" + mobile;
    			String kapchat = RedisUtils.get(kapchatKey);//获取redis数据库保存的谷歌图形验证码
    			if (kapchat == null) {
    				result.setMsg("图形验证码已失效,请重新输入");
    				result.setSuccess(false);
    				return result;
    			} else if (!kapchat.equals(imgCode.toLowerCase())) {
    				result.setSuccess(false);
    				result.setMsg("您输入的验证码错误,请重新输入");
    				return result;
    			}
    		}else if(todayCount==3){
    			result.setStatus(1);//这已是第三次调用,下次调用时,就得传入谷歌验证码
    		}
     
    		String msg = "";//发送短信验证码是否成功与失败
     		
    		try {
    			
    			String public=null;
    			String private=null;
    			Map<Integer, String> map=null;
    			
    			//注册的验证码,同时还需要返回公钥
    			if("register".equals(type)){
    					public = "register_rsa_public_" + mobile;// redis保存的公钥的key
    					private = "register_rsa_private_" + mobile;// redis中保存私钥的key
    					if (RedisUtils.ttl(public) > 60 * 30 + 10) {// 原来的公钥有效时间大于30分钟,继续使用,避免浪费资源来频繁生成密钥对
    						result.setStatus(RedisUtils.get(public));
    					} else {
    						// 生成公钥和私钥
    						map = RSAEncrypt.genKeyPair();
    						if (map == null || map.get(0) == null) {
    							result.setSuccess(false);
    							result.setMsg("生成公钥私钥失败,请联系管理员");//通常生成公钥私钥失败的情况是不会发生的,如果发生了,极有可能是jar包的问题
    							return dg;
    						}
    					}
    			}
    			
    			
    			//发送短信验证码,请求成功后返回指定标识,请求失败,可以返回失败的信息,方便开发人员排查bug
    			//此处使用的是阿里云的短信服务,你也可以使用其他的短信服务,此处不做赘述
    			msg = MobileCodeUtils.sendCode(mobile, modelCode, mobileCode);//这个工具类和方法不分享
    				
    			logger.info("手机号:" + mobile + " 的验证码是:" + mobileCode);
     
    			if (msg != null && "SUCCESS".equals(msg)) {	
    				if(map!=null){
    					RedisUtils.set(public, map.get(0), 60 * 60 * 2 + 10);// 2小时有效
    					RedisUtils.set(private, map.get(1), 60 * 60 * 2 + 20);// 2小时有效
    					result.setStatus(map.get(0));
    				}
    				
    				result.setSuccess(true);
    				result.setMsg("您的手机验证码发送成功,请注意查收,本验证码30分钟内有效");
     
    				// 保存验证码到redis
    				RedisUtils.set(mobileKey, mobileCode, 60 * 30 + 5);//redis中的code比实际要多5秒
     
    				// 记录本号码发送验证码次数
    				RedisUtils.set(todayKey, todayCount + "", MobileUtil.getSurplusTime());
     
    				// 删除图形验证码
    				RedisUtils.del(kapchatKey);
     
    			} else {
    				result.setSuccess(false);
    				result.setMsg("短信验证码发送失败:" + msg);
    				result.setStatus(null);
    				return result;
    			}
     
    		} catch (Exception e) {
    			result.setSuccess(false);
    			result.setMsg("获取短信验证码异常:" + e.getMessage());
    			logger.info("获取手机验证码异常:" + e.getMessage());
    			return result;
    		}
    		
    		//此处考虑添加操作流水,记录哪个手机号,哪个ip,哪个时间调用了哪种类型的接口
    		//········
     
    		return result;
    	}
    
        //注册接口
    	@Override
    	public Result register(String mobile,String password,String code, HttpServletRequest request) {
    		Result result = new Result();
    		
    		
    		//判断当前IP今日访问注册接口的次数是否已达上限,如果是,就限制访问
    		String ip=IpUtil.getIpAddr(request);//获取当前的ip地址值
    		Long incr = RedisUtils.incrExpire(ip,MobileUtil.getSurplusTime());
    		if(incr>200){//每日注册上限可根据各自的业务安排来完成
    			
    			//此处可以做一个记录,比如当前IP调用注册接口今日已达上限时,表中记录超标次数+1;明天再超标,记录数就再+1;记录本月该ip调用
    			//该接口的上限次数,系统可以相应减少该ip每日的访问次数或直接拉入黑名单;记住每日只需+1;不是每次超标后的请求+1;
    			//·········
    			
    			result.setSuccess(false);
    			result.setMsg("您今日访问此接口次数已达上限,请明日再来");
    			return result;
    		}
    
    		if (StringUtils.isBlank(mobile) || StringUtils.isBlank(password) || StringUtils.isBlank(code)) {
    			result.setSuccess(false);
    			result.setMsg("缺少必要的参数");
    			return result;
    		}
    
    		mobile = mobile.trim();
    		// 判断传入的手机号格式是否正确
    		if (mobile.length() != 11 || !MobileUtil.isMobileNum(mobile)) {
    			result.setSuccess(false);
    			result.setMsg("传入的手机号格式不正确");
    			return result;
    		}
    
    		String mobileKey = "register_mobile_" + mobile;
    
    		// 判断当前手机号是否已注册,防止重复注册,此时需要去数据库查询是否有该手机号
    		//·······
    		
    		if (手机号已注册) {//伪代码
    			RedisUtils.del(mobileKey);// 删除保存的手机验证码
    
    			result.setMsg("当前手机号已注册,请直接登录");
    			result.setSuccess(false);
    			return result;
    		}
    
    		// 判断验证码是否正确,请记住,一定要将短信验证码的校验和具体的注册业务放在一起,我遇到的搞笑的做法是,
    		//将校验短信验证码的操作放在另一个接口中单独进行,然后由前端控制校验成功后才调用真正的注册接口。
    		//如果按照这种方法做注册,攻击者只需要绕开前端的校验,编写个for循环直接调用注册接口,你的用户表的数据不用半个小时就可能被撑爆
    		String redisCode = RedisUtils.get(mobileKey);
    		if (redisCode == null) {
    			result.setStatus(1);// 需要重新获取手机验证码
    			result.setSuccess(false);
    			result.setMsg("当前验证码已失效,请重新获取");
    			return result;
    		} else if (!redisCode.equals(code.trim())) {
    			result.setSuccess(false);
    			result.setMsg("您输入的验证码错误,请重新输入");
    			return result;
    		}
    
    		// 获取当前私钥
    		// 从redis数据库获取私钥
    		String private = "register_rsa_private_" + mobile;
    		String public = "register_rsa_public_" + mobile;
    		String privateKey = RedisUtils.get(private);
    		if (StringUtils.isBlank(privateKey)) {
    			RedisUtils.del(mobileKey);
    			RedisUtils.del(private);
    			RedisUtils.del(public);
    
    			result.setStatus(2);// 公钥失效,通知前端提醒用户再次获取短信验证码
    			result.setSuccess(false);
    			result.setMsg("密钥验证失败,请重新获取验证码后再注册");
    			return result;
    		}
    
    		// 使用私钥解密后的登录密码
    		password = RSAEncrypt.decrypt(password.trim(), privateKey);
    		if (password == null) {
    			RedisUtils.del(mobileKey);
    			RedisUtils.del(private);
    			RedisUtils.del(public);
    
    			result.setStatus(3);// 私钥解密密码失败,通常为非法请求,按实际业务选择处理
    			result.setSuccess(false);
    			result.setMsg("私钥解密失败,您的请求被拒绝处理");
    			return result;
    		}
    
    		// 判断用户登录的密码是否合法。使用公钥加密用户注册时的密码的另一个原因,是希望传输过程中密码不是密码显示,
    		//而传到后台的时候系统还能获取到明文密码,然后对该密码格式进行后端的校验。还是那句老话,永远不要过度相信
    		//前端传来的数据,尤其是敏感信息
    		if (!MobileUtil.certifyLoginSecret(password)) {
    			result.setSuccess(false);
    			result.setMsg("登录密码格式不正确,请输入某某格式的密码");//密码格式错误
    			return result;
    		}
    
    		//校验成功,执行真正的用户注册操作,比如往用户表中添加手机号,密码等信息,需记住不能直接将明码保存到数据库中,
    		//当用户登录时,只需将用户传入的密码用你注册时的加密规律加密一遍之后,再与数据库保存到的密码比对是否一致即可。具体根据你自己的业务来添加
    		//········
    
    		// 删除验证码、公钥、私钥
    		RedisUtils.del(mobileKey);
    		RedisUtils.del(private);
    		RedisUtils.del(public);
    
    		//最后别忘了添加操作流水
    		//·······
    		
    
    		result.setSuccess(true);
    		result.setMsg("注册成功,请前往登录");
    
    		return result;
    	}
    
    }
    

     

    工具类RedisUtils.java类请参照我的这篇博客https://blog.csdn.net/weixin_42023666/article/details/89287418

    手机短信接口的安全防护可参照这篇博客https://blog.csdn.net/weixin_42023666/article/details/89680342

    第三方短信工具类的此处不分享,大家只需要复用自己原来的短信接口即可。

     

    工具类MobileUtil.java的代码如下:

    package sy.util.mobile;
    
    import java.util.regex.Pattern;
    
    import javax.servlet.http.HttpServletRequest;
    
    import org.apache.commons.lang.StringUtils;
    
    /**
        * @ClassName: MobileUtil  
        * @author hqq  
        *
     */
    public class MobileUtil {
    	
    	/**
    	 * 正则表达式:验证手机号
    	 */
    	private static final String REGEX_MOBILE = "^((13[0-9])|(15[^4,\\D])|(18[0-3,5-9])|(17[0-9]))\\d{8}$";
    	
    
    	/**
    	 * 判断是否是手机号格式,如果传入的是空串,返回false
    	 * @param mobile
    	 * @return 校验通过返回true,否则返回false
    	 */
    	public static boolean isMobile(String mobile) {
    		if(StringUtils.isBlank(mobile)){
    			return false;
    		} 
    		
    		return Pattern.matches(REGEX_MOBILE, mobile);
    	}
    
    	/**
    	 * 获取今日的剩余时间,单位是秒
    	 * @return
    	 */
    	public static Integer getTodaySurplusTime(){
    		Calendar c = Calendar.getInstance();
    		long now = c.getTimeInMillis();
    		c.add(Calendar.DAY_OF_MONTH, 1);
    		c.set(Calendar.HOUR_OF_DAY, 0);
    		c.set(Calendar.MINUTE, 0);
    		c.set(Calendar.SECOND, 0);
    		c.set(Calendar.MILLISECOND, 0);
    		long millis = c.getTimeInMillis() - now+2000;
    
    		return (int)(millis/1000);
    	}
    }
    

     

    暂时分享到这里,等有更完美的方案再补充!下一篇分享用户登录接口相关的。

    展开全文
  • 在有状态SessionBean中,用累加器,以对话状态存储起来,创建EJB对象,并将当前的计数器初始化,调用每一个EJB对象的count()方法,保证Bean正常被激活和钝化,EJB对象是用完毕,从内存中清除…… Java Socket 聊天...
  • Java 提供接口服务,安全怎么保证

    千次阅读 2019-05-22 20:57:02
    1、通信使用https 2、请求签名,防止参数被篡改 3、身份确认机制,每次请求都要验证是否合法 4、APP中使用ssl pinning防止抓包操作 5、对所有请求和响应都进行加解密操作 6.限流(令牌桶、信号量) ...
  • 在前后端分离的开发中,后台提供的接口如何能保证访问权限安全?主要是身份验证、数据加密、访问控制(访问频率、访问访问次序,每个IP次数) 一、.签名 根据用户名或者用户id,结合用户的ip或者设备号,生成一个...
  • Java基础知识面试题(2020最新版)

    万次阅读 多人点赞 2020-02-19 12:11:27
    文章目录Java概述何为编程什么是Javajdk1.5之后的三大版本JVM、JRE和JDK的关系什么是跨平台?原理是什么Java语言有哪些特点什么是字节码?采用字节码的最大好处是什么什么是Java程序的主类?应用程序和小程序的...
  • 因为服务没用户系统,但是我只想让有我Token的人访问(一个微信小程序)。 Token放在请求头Authorization 里 我就想问下,这个Token会被别人抓到吗? ...被别人抓到这个Token不就没什么意义了么。...
  • 接口如何保证 API 的安全性

    千次阅读 2021-05-08 11:51:27
    接口如何保证 API 的安全性的问题 1. 接口协议采用 Https 协议 SSL+证书 443 2. 使用 MD5 对我们的接口实现验证签名 防止接口参数不能能篡改 移动 App 项目 3. 对我们的数据实现加密 rsa 非对称加密 不能别人...
  • Java面试题大全(2020版)

    万次阅读 多人点赞 2019-11-26 11:59:06
    发现网上很多Java面试题都没有答案,所以花了很长时间搜集整理出来了这套Java面试题大全,希望对大家有帮助哈~ 本套Java面试题大全,全的不能再全,哈哈~ 一、Java 基础 1. JDK 和 JRE 有什么区别? JDK:Java ...
  • Java语言特点

    万次阅读 多人点赞 2019-06-30 19:37:45
    对C++来说进行了简化和一定的提高,如:使用接口代替了复杂的多重继承以及取消了指针,还通过实现垃圾自动回收机制,大大简化了程序员的资源释放管理工作。 提供了丰富的类库和API文档,以及第三方开发包工具包,...
  • 保证http请求的安全性,首先我们得知道,http有哪些安全隐患,再针对每个问题,寻找对策。 先有问题,才有解决方法,不要空谈技术。 假设我们的电脑上被人偷偷安装了窃听软件,或者我们使用的路由器,代理服务器被...
  • Java接口的幂等

    千次阅读 2020-03-17 11:16:54
    通过条件限制 update tablexxx set avaiamount=avaiamount-#subAmount# where avaiamount-#subAmount# >= 0 要求:quality-#subQuality# >= ,这个情景适合不用版本号,只更新是做数据安全校验,适合库存模型,扣...
  • Java 提供接口安全问题

    千次阅读 2017-08-02 10:50:57
    问题: Java给别人提供接口,接口安全怎么保证? 1.请求的合法校验,考虑用token方式保证接口不被其他人访问。 2.数据校验,白名单方式验证数据,确保不出现异常数据和注入攻击。 3.数据加密,对数据进行加密...
  • 公司内部的接口,当然是涉及到比较隐秘信息的时候,调用方需要持有一个私钥,调用的时候将传入的参数通过私钥进行的加密,若加密后的内容能够被公钥解密,那么则能够通过。 二、调用第三方的接口 1.通信使用https...
  • Java集合面试题

    万次阅读 多人点赞 2019-06-25 14:46:19
    Java 集合框架的基础接口有哪些? Collection ,为集合层级的根接口。一个集合代表一组对象,这些对象即为它的元素。Java 平台不提供这个接口任何直接的实现。 Set ,是一个不能包含重复元素的集合。这个接口对...
  • 怎么保证接口数据的传输安全

    千次阅读 2019-10-10 23:48:43
    前后端分离的项目离不开后端对外提供接口,而请求这些接口的数据时,安全性应当保证。 二、解决方法 1、对请求做身份认证(数据签名) 如果不对请求进行签名认证,那么可以简单的通过fiddler等工具轻...
  • 2020最新Java常见面试题及答案

    万次阅读 多人点赞 2019-10-26 15:53:35
    Java最新常见面试题 + 答案汇总 1、面试题模块汇总 面试题包括以下十九个模块:Java 基础、容器、多线程、反射、对象拷贝、Java Web 模块、异常、网络、设计模式、Spring/Spring MVC、Spring Boot/Spring Cloud、...
  • JAVA上百实例源码以及开源项目

    千次下载 热门讨论 2016-01-03 17:37:40
    Java源代码实现部分,比较有意思,也具参考。像坐标控制、旋转矩阵、定时器、生成图像、数据初始化、矩阵乘法、坐标旋转、判断是否是顺时针方向排列、鼠标按下、放开时的动作等,都可在本源码中得以体现。 Java...
  • java面试题2019_java面试题及答案_java面试题库

    千次阅读 多人点赞 2019-05-16 09:31:30
    1、一个.java源文件中是否可以包括多个类(不是内部类)?有什么限制? 2、Java有没有goto? 3、&和&&的区别? 4、switch语句能否作用在byte上,能否作用在long上,能否作用在String上? 5、short s1 = ...
  • 数据传输安全性保证

    千次阅读 2018-07-07 17:43:42
    现代软件开发中,无论是基于敏捷式的软件开发,或者基于前后端分离的软件开发,都离不开提供对外接口,而请求这些接口数据的安全性应当得到保证。 方法 保证传输数据的安全性,主要有以下方法: 数据加密 ...
  • Java并发编程中,如果要保证代码的安全性,则必须保证代码的原子性、可见性和有序性。 在 Java并发12:并发三特性-原子性、可见性和有序性概述及问题示例中,对并发中的三个特性(原子性、可见性和有序性)进行了...
  • 史上最全面Java面试汇总(面试题+答案)

    万次阅读 多人点赞 2018-07-06 14:09:25
    JAVA面试精选【Java基础第一部分】 JAVA面试精选【Java基础第二部分】 JAVA面试精选【Java基础第三部分】 JAVA面试精选【Java算法与编程一】 JAVA面试精选【Java算法与编程二】 Java高级工程师—面试(1) ...
  • JWT是一种用于双方之间传递安全信息的简洁的、URL安全的表述声明规范。JWT作为一个开放的标准(RFC 7519),定义了一种简洁的,自包含的方法用于通信双方之间以Json对象的形式安全的传递信息。因为数字签名的存在...
  • Java常用接口

    千次阅读 2018-08-07 11:56:07
    Comparable ,Collection,Set, List, Map, ...Collections类中包含很多对实现Collection接口的容器各种操作的静态方法. 当然, 其中最长用的莫过于排序了(Collections.sort(List l). 例: public class Compare1{ ...
  • Java线程池】Java线程池汇总,看这一篇文章就够了 (1)引言1:Java线程池 在面向对象编程中,创建和销毁对象是很费时间的,因为创建一个对象要获取内存资源或者其它更多资源。在Java中更是如此,虚拟机将试图...
  • JAVA中ArrayList如何保证线程安全

    万次阅读 2016-11-03 11:17:35
    保证线程安全的三种方法: 不要跨线程访问共享变量 使共享变量是final类型的 将共享变量的操作加上同步 一开始就将类设计成线程安全的, 比在后期重新修复它,更容易. 编写多线程程序, 首先保证它是正确的, 其次再...
  • swagger添加权限验证,保证API(接口安全性

    千次阅读 多人点赞 2020-11-26 22:17:50
    当我们使用swagger,进行接口测试,怕接口安全,担心暴露。可采用两种方式 1.环境权限配置 对swagger文档配置只在测试环境可访问,生产环境不可访问。 @Profile({"dev","test"}) 如以上配置,则只有在dev以及...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 235,939
精华内容 94,375
关键字:

java怎么保证接口的安全性

java 订阅