书单推荐:成为Java顶级程序员架构师 ,这20来本(高薪)必看点击获取
Ç°ÑÔ
´ÓÈ¥Äê10Ô·ݵ½ÏÖÔÚæµÄûʱ¼äд²©¿ÍÁË£¬½ñÌì¾Í˦¸ø´ó¼ÒÒ»¸ö¸É»õ°É£¡£¡£¡
½üÀ´ºÜ¶àÈËÎʵ½ÏÂÃæµÄÎÊÌâ
- ÎÒÃDz»ÏëÔÚÿ¸öController·½·¨ÊÕµ½×Ö·û´®±¨ÎĺóÔÙµ÷ÓÃÒ»´Î½âÃÜ£¬ËäÈ»¿ÉÒÔÍê³É£¬µ«ÊǺÜlow£¬ÇÒÈç¹ûÏë²»ÔÙʹÓüӽâÃÜ£¬ÐÞ¸ÄÆðÀ´ºÜÊÇÂé·³¡£
- ÎÒÃÇÏëÔÚʹÓÃRest¹¤¾ß»òswaggerÇëÇóµÄʱºò²»½øÐмӽâÃÜ£¬¶øÔÚappµ÷ÓõÄʱºò´¦Àí¼Ó½âÃÜ£¬Õâ¿ÉÈçºÎ²Ù×÷¡£
Õë¶ÔÒÔÉϵÄÎÊÌ⣬ÏÂÃæÖ±½Ó¸ø³ö½â¾ö·½°¸£º
ʵÏÖ˼·
- APPµ÷ÓÃAPIµÄʱºò£¬Èç¹ûÐèÒª¼Ó½âÃܵĽӿڣ¬ÐèÒªÔÚhttpHeaderÖиø³ö¼ÓÃÜ·½Ê½£¬Èçheader[encodeMethod]¡£
- Rest¹¤¾ß»òswaggerÇëÇóµÄʱºòÎÞÐèÖ¸¶¨´Ëheader¡£
- ºó¶ËAPIÊÕµ½requestºó£¬ÅжÏheaderÖеÄencodeMethod×ֶΣ¬Èç¹ûÓÐÖµ£¬ÔòÈÏΪÊÇÐèÒª½âÃÜ£¬·ñÔò¾ÍÈÏΪÊÇÃ÷ÎÄ¡£
Ô¼¶¨
ΪÁ˾«¼ò·ÖÏí¼¼Êõ£¬ÏÈÔ¼¶¨Ö»´¦ÀíPOSTÉÏ´«JSON(application/json)Êý¾ÝµÄ¼Ó½âÃÜ´¦Àí¡£
ÇëÇó½âÃÜʵÏÖ·½Ê½
1. Ïȶ¨Òåcontroller
@Controller @RequestMapping("/api/demo") public class MyDemoController { @RequestDecode @ResponseBody @RequestMapping(value = "user", method = RequestMethod.POST) public ResponseDto addUser( @RequestBody User user ) throws Exception { //TODO ... } }
/** * ½âÃÜÇëÇóÊý¾Ý */ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface RequestDecode { SecurityMethod method() default SecurityMethod.NULL; }
¿ÉÒÔ¿´µ½ÕâÀïµÄController¶¨ÒåµÄºÜÆÕͨ£¬Ö»ÓÐÒ»¸ö¶îÍâµÄ×Ô¶¨Òå×¢½âRequestDecode£¬Õâ¸ö×¢½âÊÇΪÁËÏÂÃæµÄRequestBodyAdviceµÄʹÓá£
2. ½¨Éè×Ô¼ºµÄRequestBodyAdvice
ÓÐÁËÉÏÃæµÄÈë¿Ú¶¨Ò壬½ÓÏÂÀ´´¦Àí½âÃÜÕâ¼þÊ£¬Ä¿µÄºÜÃ÷È·£º
1. ÊÇ·ñÐèÒª½âÃÜÅжÏhttpHeaderÖеÄencodeMethod×ֶΡ£
2. ÔÚ½øÈëcontroller֮ǰ¾Í½âÃÜÍê³É£¬ÊÇcontroller´¦ÀíÂß¼ÎÞ¸ÐÖª¡£
DecodeRequestBodyAdvice.java
@Slf4j @Component @ControllerAdvice(basePackages = "com.xxx.hr.api.controller") public class DecodeRequestBodyAdvice implements RequestBodyAdvice { @Value("${hrapi.aesKey}") String aesKey; @Value("${hrapi.googleKey}") String googleKey; @Override public boolean supports(MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) { return methodParameter.getMethodAnnotation(RequestDecode.class) != null && methodParameter.getParameterAnnotation(RequestBody.class) != null; } @Override public Object handleEmptyBody(Object body, HttpInputMessage request, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) { return body; } @Override public HttpInputMessage beforeBodyRead(HttpInputMessage request, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) throws IOException { RequestDecode requestDecode = parameter.getMethodAnnotation(RequestDecode.class); if (requestDecode == null) { return request;//controller·½·¨²»ÒªÇó¼Ó½âÃÜ } String appId = request.getHeaders().getFirst(com.xxx.hr.bean.constant.HttpHeaders.APP_ID);//ÕâÀïÊÇÀ©Õ¹£¬¿ÉÒÔÖªµÀÀ´Ô´·½£¨È翪·Åƽ̨ʹÓã© String encodeMethod = request.getHeaders().getFirst(com.xxx.hr.bean.constant.HttpHeaders.ENCODE_METHOD); if (StringUtils.isEmpty(encodeMethod)) { return request; } SecurityMethod encodeMethodEnum = SecurityMethod.getByCode(encodeMethod); //ÕâÀïÁé»îµÄ¿ÉÒÔÖ§³Öµ½¶àÖÖ¼Ó½âÃÜ·½Ê½ switch (encodeMethodEnum) { case NULL: break; case AES: { InputStream is = request.getBody(); ByteBuf buf = PooledByteBufAllocator.DEFAULT.heapBuffer(); int ret = -1; int len = 0; while((ret = is.read()) > 0) { buf.writeByte(ret); len ++; } String body = buf.toString(0, len, xxxSecurity.DEFAULT_CHARSET); buf.release(); String temp = null; try { temp = XxxSecurity.aesDecodeData(body, aesKey, googleKey, new CheckCallBack() { @Override public boolean isRight(String data) { return data != null && (data.startsWith("{") || data.startsWith("[")); } }); log.info("½âÃÜÍê³É: {}", temp); return new DecodedHttpInputMessage(request.getHeaders(), new ByteArrayInputStream(temp.getBytes("UTF-8"))); } catch (DecodeException e) { log.warn("½âÃÜʧ°Ü appId: {}, Name:{} ´ý½âÃÜÃÜÎÄ: {}", appId, partnerName, body, e); throw e; } } } return request; } @Override public Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) { return body; } static class DecodedHttpInputMessage implements HttpInputMessage { HttpHeaders headers; InputStream body; public DecodedHttpInputMessage(HttpHeaders headers, InputStream body) { this.headers = headers; this.body = body; } @Override public InputStream getBody() throws IOException { return body; } @Override public HttpHeaders getHeaders() { return headers; } } }
ÖÁ´Ë¼Ó½âÃÜÍê³ÉÁË¡£
¡ª¡ª¡ª¡ª¡ª¡ª¡ª¡ª-»ªÀö·Ö¸îÏß ¡ª¡ª¡ª¡ª¡ª¡ª¡ª¡ª¡ª¨C
ÏìÓ¦¼ÓÃÜ
ÏÂÃ渽¼þÒ»ÏÂÏìÓ¦¼ÓÃܹý³Ì£¬Ä¿µÄ
1. ControllerÂß¼´úÂëÎÞ¸ÐÖª
2. ¿ÉÒÔÒ»¼ü¿ª¹ØÏìÓ¦¼ÓÃÜ
¶¨ÒåController
@ResponseEncode @ResponseBody @RequestMapping(value = "employee", method = RequestMethod.GET) public ResponseDto<UserEEInfo> userEEInfo( @ApiParam("Óû§±àºÅ") @RequestParam(HttpHeaders.APPID) Long userId ) { //TODO ... }
/** * ¼ÓÃÜÏìÓ¦Êý¾Ý */ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface ResponseEncode { SecurityMethod method() default SecurityMethod.NULL; }
ÕâÀïµÄController¶¨ÒåµÄÒ²ºÜÆÕͨ£¬Ö»ÓÐÒ»¸ö¶îÍâµÄ×Ô¶¨Òå×¢½âResponseEncode£¬Õâ¸ö×¢½âÊÇΪÁËÏÂÃæµÄResponseBodyAdviceµÄʹÓá£
½¨Éè×Ô¼ºµÄResponseBodyAdvice
ÕâÀïÔ¼¶¨½«ÏìÓ¦µÄDTOÐòÁл¯ÎªJSON¸ñʽÊý¾Ý£¬È»ºóÔÙ¼ÓÃÜ£¬×îºóÔÚÏìÓ¦¸øÇëÇ󷽡£
@Slf4j @Component @ControllerAdvice(basePackages = "com.xxx.hr.api.controller") public class EncodeResponseBodyAdvice implements ResponseBodyAdvice { @Autowired PartnerService partnerService; @Override public boolean supports(MethodParameter returnType, Class converterType) { return returnType.getMethodAnnotation(ResponseEncode.class) != null; } @Override public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) { ResponseEncode responseEncode = returnType.getMethodAnnotation(ResponseEncode.class); String uid = request.getHeaders().getFirst(HttpHeaders.PARTNER_UID); if (uid == null) { uid = request.getHeaders().getFirst(HttpHeaders.APP_ID); } PartnerConfig config = partnerService.getConfigByAppId(uid); if (responseEncode.method() == SecurityMethod.NULL || responseEncode.method() == SecurityMethod.AES) { if (config == null) { return ResponseDto.rsFail(ResponseCode.E_403, "ÉÌ»§²»´æÔÚ"); } String temp = JSON.toJSONString(body); log.debug("´ý¼ÓÃÜÊý¾Ý: {}", temp); String encodedBody = XxxSecurity.aesEncodeData(temp, config.getEncryptionKey(), config.getGoogleKey()); log.debug("¼ÓÃÜÍê³É: {}", encodedBody); response.getHeaders().set(HttpHeaders.ENCODE_METHOD, HttpHeaders.VALUE.AES); response.getHeaders().set(HttpHeaders.HEADER_CONTENT_TYPE, HttpHeaders.VALUE.APPLICATION_BASE64_JSON_UTF8); response.getHeaders().remove(HttpHeaders.SIGN_METHOD); return encodedBody; } return body; } }
ÍØÕ¹
ÓÉÉÏÃæµÄʵÏÖ£¬ÈçºÎʵÏÖRSAÑé֤ǩÃûÄØ£¿Õâ¸ö¾Í¼òµ¥ÁË£¬Çë¿´·Ö½â¡£
Ä¿µÄ»¹ÊǺܼòµ¥£¬½øÀ´¼õÉÙ¶ÔÒµÎñÂß¼µÄÈëÇÖ¡£
Ê×ÏÈÉ趨һÏÂÄÇЩÇëÇóÐèÒªÑé֤ǩÃû
@RequestSign @ResponseEncode @ResponseBody @RequestMapping(value = "employee", method = RequestMethod.GET) public ResponseDto<UserEEInfo> userEEInfo( @RequestParam(HttpHeaders.UID) String uid ) { //TODO ... }
ÕâÀﻹÊÇʹÓÃÒ»¸ö×¢½âRequestSign£¬È»ºóÔÙʵÏÖÒ»¸öSignInterceptor¼´¿ÉÍê³É£º
@Slf4j @Component public class SignInterceptor implements HandlerInterceptor { @Autowired PartnerService partnerService; @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { HandlerMethod method = (HandlerMethod) handler; RequestSign requestSign = method.getMethodAnnotation(RequestSign.class); if (requestSign == null) { return true; } String appId = request.getHeader(HttpHeaders.APP_ID); ValidateUtils.notTrimEmptyParam(appId, "Header[appId]"); PartnerConfig config = partnerService.getConfigByAppId(appId); ValidateUtils.notNull(config, Code.E_400, "ÉÌ‘ô²»´æÔÚ"); String partnerName = partnerService.getPartnerName(appId); String sign = request.getParameter(HttpHeaders.SIGN); String signMethod = request.getParameter(HttpHeaders.SIGN_METHOD); signMethod = (signMethod == null) ? "RSA" : signMethod; Map<String, String[]> parameters = request.getParameterMap(); ValidateUtils.notTrimEmptyParam(sign, "sign"); if ("RSA".equals(signMethod)) { sign = sign.replaceAll(" ", "+"); boolean isOK = xxxxSecurity.signVerifyRequest(parameters, config.getRsaPublicKey(), sign, config.getSecurity()); if (isOK) { log.info("ÑéÖ¤ÉÌ»§Ç©Ãûͨ¹ý {}[{}] ", appId, partnerName); return true; } else { log.warn("ÑéÖ¤ÉÌ»§Ç©Ãûʧ°Ü {}[{}] ", appId, partnerName); } } else { throw new SignVerifyException("Ôݲ»Ö§³Ö¸ÃÇ©Ãû"); } throw new SignVerifyException("Ç©ÃûУÑéʧ°Ü"); } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { } }
¸÷¸öö¾Ù¶¨Ò壺
//¼Ó½âÃÜ¡¢Ç©ÃûË㷨ö¾Ù public enum SecurityMethod { NULL, AES, RSA, DES, DES3, SHA1, MD5 ; }
×¢½â¶¨Ò壺
/** * ÇëÇóÊý¾ÝÊý¾ÝÐèÒª½âÃÜ */ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface RequestDecode { SecurityMethod method() default SecurityMethod.NULL; } /** * ÇëÇóÊý¾ÝÐèÒªÑéÇ© */ @Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited public @interface RequestSign { SecurityMethod method() default SecurityMethod.RSA; } /** * Êý¾ÝÏìÓ¦ÐèÒª¼ÓÃÜ */ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface ResponseEncode { SecurityMethod method() default SecurityMethod.NULL; } /** * ÏìÓ¦Êý¾ÝÐèÒªÉú³ÉÇ©Ãû */ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited public @interface ResponseSign { SecurityMethod method() default SecurityMethod.NULL; }
aesDecodeData
/** * AES ½âÃÜÊý¾Ý * * @param data ´ý½âÃÜÊý¾Ý * @param aesKey AES ÃÜÔ¿(BASE64) * @param googleAuthKey GoogleAuthKey(BASE64) * @param originDataSign ÔʼÊý¾Ýmd5Ç©Ãû * @return */ public static String aesDecodeDataEx(String data, String aesKey, String googleAuthKey, String originDataSign) { return aesDecodeData(data, aesKey, googleAuthKey, System.currentTimeMillis(), null, originDataSign); } public static String aesDecodeData(String data, String aesKey, String googleAuthKey, long tm, CheckCallBack checkCallBack, String originDataSign) { DecodeException lastError = null; long timeWindow = googleAuth.getTimeWindowFromTime(tm); int window = googleAuth.getConfig().getWindowSize(); for (int i = -((window - 1) / 2); i <= window / 2; ++i) { String googleCode = googleAuth.calculateCode16(Base64.decodeBase64(googleAuthKey), timeWindow + i); log.debug((timeWindow + i) + " googleCode: " + googleCode); byte[] code = googleCode.getBytes(DEFAULT_CHARSET); byte[] iv = new byte[16]; System.arraycopy(code, 0, iv, 0, code.length); try { String newKey = convertKey(aesKey, iv); String decodedData = AES.decode(data, newKey, Base64.encodeBase64String(iv)); if (checkCallBack != null && !checkCallBack.isRight(decodedData)) { continue; } if (originDataSign != null) { String sign = DigestUtils.md5Hex(decodedData); if (!sign.equalsIgnoreCase(originDataSign)) { continue; } } return decodedData; } catch (DecodeException e) { lastError = e; } } if (lastError == null) { lastError = new DecodeException("Decode Failed, Error Password!"); } throw lastError; }
signVerifyRequest
static boolean signVerifyRequest(Map<String, String[]> parameters, String rsaPublicKey, String sign, String security) throws SignVerifyException { String preSignData = getHttpPreSignData(parameters, security); log.debug("´ýÑéÇ©×Ö·û´®£º" + preSignData); return RSA.verify(preSignData.getBytes(DEFAULT_CHARSET), rsaPublicKey, sign); }
GoogleAuth
public class GoogleAuth { private GoogleAuthenticatorConfig config; private GoogleAuthenticator googleAuthenticator; public GoogleAuth() { GoogleAuthenticatorConfig.GoogleAuthenticatorConfigBuilder gacb = new GoogleAuthenticatorConfig.GoogleAuthenticatorConfigBuilder() .setTimeStepSizeInMillis(TimeUnit.MINUTES.toMillis(2)) .setWindowSize(3) .setCodeDigits(8) .setKeyRepresentation(KeyRepresentation.BASE64); config = gacb.build(); googleAuthenticator = new GoogleAuthenticator(config); } public GoogleAuthenticatorConfig getConfig(){ return config; } public void setConfig(GoogleAuthenticatorConfig c) { config = c; googleAuthenticator = new GoogleAuthenticator(config); } /** * ÈÏÖ¤ * @param encodedKey(Base 32/64) * @param code * @return ÊÇ·ñͨ¹ý */ public boolean authorize(String encodedKey, int code) { return googleAuthenticator.authorize(encodedKey, code); } /** * Éú³É GoogleAuth Code * @param keyBase64 * @return */ public int getCodeValidCode(String keyBase64) { int code = googleAuthenticator.getTotpPassword(keyBase64); return code; } public long getTimeWindowFromTime(long time) { return time / this.config.getTimeStepSizeInMillis(); } private static String formatLabel(String issuer, String accountName) { if (accountName == null || accountName.trim().length() == 0) { throw new IllegalArgumentException("Account name must not be empty."); } StringBuilder sb = new StringBuilder(); if (issuer != null) { if (issuer.contains(":")) { throw new IllegalArgumentException("Issuer cannot contain the \':\' character."); } sb.append(issuer); sb.append(":"); } sb.append(accountName); return sb.toString(); } public String getOtpAuthTotpURL(String keyBase64) throws EncoderException{ return getOtpAuthTotpURL("MLJR", "myname@mljr.com", keyBase64); } /** * Éú³ÉGoogleAuthÈÏÖ¤µÄURL,±ãÓÚÉú³É¶þάÂë * @param issuer * @param accountName * @param keyBase32 * @return */ public String getOtpAuthTotpURL(String issuer, String accountName, String keyBase32) throws EncoderException { StringBuilder url = new StringBuilder(); url.append("otpauth://") .append("totp") .append("/").append(formatLabel(issuer, accountName)); Map<String, String> parameter = new HashMap<String, String>(); /** * https://github.com/google/google-authenticator/wiki/Key-Uri-Format * The secret parameter is an arbitrary key value encoded in Base32 according to RFC 3548. */ parameter.put("secret", keyBase32); if (issuer != null) { if (issuer.contains(":")) { throw new IllegalArgumentException("Issuer cannot contain the \':\' character."); } parameter.put("issuer", issuer); } parameter.put("algorithm", "SHA1"); parameter.put("digits", String.valueOf(config.getCodeDigits())); parameter.put("period", String.valueOf(TimeUnit.MILLISECONDS.toSeconds(config.getTimeStepSizeInMillis()))); URLCodec urlCodec = new URLCodec(); if (!parameter.isEmpty()) { url.append("?"); for(String key : parameter.keySet()) { String value = parameter.get(key); if (value == null){ continue; } value = urlCodec.encode(value); url.append(key).append("=").append(value).append("&"); } } return url.toString(); } private static final String DEFAULT_RANDOM_NUMBER_ALGORITHM = "SHA1PRNG"; private static final String DEFAULT_RANDOM_NUMBER_ALGORITHM_PROVIDER = "SUN"; private static final String HMAC_HASH_FUNCTION = "HmacSHA1"; private static final String HMAC_MD5_FUNCTION = "HmacMD5"; /** * »ùÓÚʱ¼ä Éú³É16λµÄ code * @param key * @param tm * @return */ public String calculateCode16(byte[] key, long tm) { // Allocating an array of bytes to represent the specified instant // of time. byte[] data = new byte[8]; long value = tm; // Converting the instant of time from the long representation to a // big-endian array of bytes (RFC4226, 5.2. Description). for (int i = 8; i-- > 0; value >>>= 8) { data[i] = (byte) value; } // Building the secret key specification for the HmacSHA1 algorithm. SecretKeySpec signKey = new SecretKeySpec(key, HMAC_HASH_FUNCTION); try { // Getting an HmacSHA1 algorithm implementation from the JCE. Mac mac = Mac.getInstance(HMAC_HASH_FUNCTION); // Initializing the MAC algorithm. mac.init(signKey); // Processing the instant of time and getting the encrypted data. byte[] hash = mac.doFinal(data); // Building the validation code performing dynamic truncation // (RFC4226, 5.3. Generating an HOTP value) int offset = hash[hash.length - 1] & 0xB; // We are using a long because Java hasn't got an unsigned integer type // and we need 32 unsigned bits). long truncatedHash = 0; for (int i = 0; i < 8; ++i) { truncatedHash <<= 8; // Java bytes are signed but we need an unsigned integer: // cleaning off all but the LSB. truncatedHash |= (hash[offset + i] & 0xFF); } truncatedHash &= Long.MAX_VALUE; truncatedHash %= 10000000000000000L; // module with the maximum validation code value. // Returning the validation code to the caller. return String.format("%016d", truncatedHash); } catch (InvalidKeyException e) { throw new GoogleAuthenticatorException("The operation cannot be " + "performed now."); } catch (NoSuchAlgorithmException ex) { // We're not disclosing internal error details to our clients. throw new GoogleAuthenticatorException("The operation cannot be " + "performed now."); } } }
GoogleAuthÆäËû´úÂë ¿´ÕâÀï
ÒÔÉϾÍÊDZ¾ÎĵÄÈ«²¿ÄÚÈÝ£¬Ï£Íû¶Ô´ó¼ÒµÄѧϰÓÐËù°ïÖú£¬Ò²Ï£Íû´ó¼Ò¶à¶àÖ§³Ö½Å±¾Ö®¼Ò¡£