package com.eactive.eai.authserver.custom; import java.nio.charset.StandardCharsets; import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.Principal; import java.security.SecureRandom; import java.security.Signature; import java.time.LocalDateTime; import java.util.Base64; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import javax.crypto.Cipher; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.math.NumberUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.security.oauth2.common.OAuth2AccessToken; import org.springframework.security.oauth2.common.util.OAuth2Utils; import org.springframework.security.oauth2.provider.ClientDetails; import org.springframework.security.oauth2.provider.OAuth2Authentication; import org.springframework.security.oauth2.provider.OAuth2Request; import org.springframework.security.oauth2.provider.endpoint.TokenEndpoint; import org.springframework.security.oauth2.provider.token.TokenEnhancer; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseBody; import com.eactive.eai.adapter.http.dynamic.filter.JwtAuthException; import com.eactive.eai.authserver.config.IssueLimitOAuth2Exception; import com.eactive.eai.authserver.config.RequestContextData; import com.eactive.eai.authserver.dao.TokenIssuanceLogDAO; import com.eactive.eai.authserver.service.OAuth2Manager; import com.eactive.eai.authserver.util.BeanUtils; import com.eactive.eai.authserver.vo.ClientVO; import com.eactive.eai.common.dao.DAOException; import com.eactive.eai.common.logger.EAIDBLogControl; import com.eactive.eai.common.util.Logger; import com.eactive.eai.common.util.MessageUtil; import com.eactive.eai.common.util.UUID; import com.eactive.eai.data.entity.onl.authserver.TokenIssuanceLog; import com.fasterxml.jackson.databind.ObjectMapper; @Controller public class KjbMGOAuth2Controller { public static Logger logger = Logger.getLogger(Logger.LOGGER_DEFAULT); private static final String HEADER_TRACEID = "traceId"; private static final String SERVICE_CODE_ACCESS_TOKEN = "00"; // 임시설정 @Autowired private TokenIssuanceLogDAO tokenIssuanceLogDAO; @RequestMapping(value = "/mapi/oauth2/token", method = RequestMethod.POST, consumes = "application/json", produces = "application/json; charset=UTF-8") @ResponseBody public ResponseEntity tokenJson(@RequestBody KjbMGOAuth2AccessTokenRequest tokenRequest, HttpServletRequest request, HttpServletResponse response) { return issueToken(tokenRequest, request, response); } @RequestMapping(value = "/mapi/oauth2/token", method = RequestMethod.POST, consumes = "application/x-www-form-urlencoded", produces = "application/json; charset=UTF-8") @ResponseBody public ResponseEntity tokenForm(@RequestParam Map params, HttpServletRequest request, HttpServletResponse response) { KjbMGOAuth2AccessTokenRequest tokenRequest = new KjbMGOAuth2AccessTokenRequest(); tokenRequest.setGrantType(params.get("grant_type")); tokenRequest.setClientId(params.get("client_id")); tokenRequest.setClientSecret(params.get("client_secret")); tokenRequest.setScope(params.get("scope")); tokenRequest.setResource(params.get("resource")); return issueToken(tokenRequest, request, response); } @GetMapping(value = "/mapi/oauth2/token", produces = "application/json; charset=UTF-8") @ResponseBody public ResponseEntity tokenGet(@RequestParam Map params, HttpServletRequest request, HttpServletResponse response) { KjbMGOAuth2AccessTokenRequest tokenRequest = new KjbMGOAuth2AccessTokenRequest(); tokenRequest.setGrantType(params.get("grant_type")); tokenRequest.setClientId(params.get("client_id")); tokenRequest.setClientSecret(params.get("client_secret")); tokenRequest.setScope(params.get("scope")); tokenRequest.setResource(params.get("resource")); return issueToken(tokenRequest, request, response); } private ResponseEntity issueToken( KjbMGOAuth2AccessTokenRequest tokenRequest, HttpServletRequest request, HttpServletResponse response) { if (logger.isDebug()) { logger.debug(tokenRequest.toString()); } RequestContextData data = new RequestContextData(); data.setClientId(tokenRequest.getClientId()); data.setIpAddress(extractIpAddress(request)); data.setGrantType(tokenRequest.getGrantType()); data.setScope(tokenRequest.getScope()); data.setUsername("none"); data.setResource("none"); RequestContextData.ThreadLocalRequestContext.set(data); KjbMGOAuth2AccessTokenResponse responseToken = new KjbMGOAuth2AccessTokenResponse(); String grantType = tokenRequest.getGrantType(); String clientId = tokenRequest.getClientId(); String clientSecret = tokenRequest.getClientSecret(); String scopes = tokenRequest.getScope(); try { if (!StringUtils.equals(tokenRequest.getGrantType(), "client_credentials")) { throw new JwtAuthException( String.format("%d%s%s", HttpStatus.BAD_REQUEST.value(), SERVICE_CODE_ACCESS_TOKEN, "02"), "Invalid Mandatory Field {grantType}"); } String[] scopeArr = org.springframework.util.StringUtils.tokenizeToStringArray(scopes, " "); Set scopeSet = new HashSet(); for (String scope : scopeArr) { scopeSet.add(scope); } String[] resourceArr = org.springframework.util.StringUtils.tokenizeToStringArray(tokenRequest.getResource(), ","); Set resourceSet = new HashSet(); for (String resource : resourceArr) { resourceSet.add(resource); } ClientDetails clientDetails = OAuth2Manager.getInstance().getClientDeatilsStore().get(clientId); verifyClient(clientDetails, clientId, clientSecret, scopeSet); String traceId = UUID.randomUUID().toString().replace("-", ""); response.setHeader(HEADER_TRACEID, traceId); HashMap authorizationParameters = new HashMap(); authorizationParameters.put(OAuth2Utils.GRANT_TYPE, grantType); authorizationParameters.put(OAuth2Utils.CLIENT_ID, clientId); authorizationParameters.put("client_secret", clientSecret); Set responseType = new HashSet(); responseType.add(tokenRequest.getGrantType()); OAuth2Request authorizationRequest = new OAuth2Request(authorizationParameters, clientId, null, true, scopeSet, resourceSet, "", responseType, null); Principal principal = new OAuth2Authentication(authorizationRequest, null); ResponseEntity result = tokenEndpoint().postAccessToken(principal, authorizationParameters); OAuth2AccessToken token = result.getBody(); if(StringUtils.isNotBlank(token.getTokenType()) && StringUtils.equalsIgnoreCase(token.getTokenType(), "bearer")) { responseToken.setTokenType("Bearer"); } else { responseToken.setTokenType(token.getTokenType()); } responseToken.setAccessToken(token.getValue()); responseToken.setExpiresIn(Long.valueOf(token.getExpiresIn())); responseToken.setrefreshToken(""); responseToken.setExpiresOn(System.currentTimeMillis() + (token.getExpiresIn() * 1000)); responseToken.setScope(tokenRequest.getScope()); responseToken.setResource(tokenRequest.getResource()); // 성공 시 JSON 문자열로 변환하여 반환 ObjectMapper mapper = new ObjectMapper(); String successJson = mapper.writeValueAsString(responseToken); return ResponseEntity.ok(successJson); } catch (JwtAuthException e) { logger.info("Token request[/mapi/oauth2/token] => clientId: {}, grantType: {}, scope: {}", clientId, grantType, scopes); logger.error(e.getMessage()); String errorJson = MessageUtil.makeJsonErrorMessage(MessageUtil.ERROR_CODE_AUTH_FAIL, e.getMessage()); int statusCode = NumberUtils.toInt(StringUtils.left(e.getCode(), 3), HttpStatus.UNAUTHORIZED.value()); logTokenIssuance(false ,false, e.getMessage(), null); return ResponseEntity.status(statusCode).body(errorJson); } catch (Exception e) { logger.info("Token request[/mapi/oauth2/token] => clientId: {}, grantType: {}, scope: {}", clientId, grantType, scopes); logger.error(e.getMessage()); String errorJson = MessageUtil.makeJsonErrorMessage(MessageUtil.ERROR_CODE_AUTH_FAIL, "Unauthorized. [Unknown]"); logTokenIssuance(false ,false, e.getMessage(), null); return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(errorJson); }finally { RequestContextData.ThreadLocalRequestContext.clear(); } } private String extractIpAddress(HttpServletRequest request) { String ipAddress = request.getHeader("X-Forwarded-For"); if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) { ipAddress = request.getHeader("Proxy-Client-IP"); } if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) { ipAddress = request.getHeader("WL-Proxy-Client-IP"); } if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) { ipAddress = request.getHeader("HTTP_CLIENT_IP"); } if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) { ipAddress = request.getHeader("HTTP_X_FORWARDED_FOR"); } if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) { ipAddress = request.getRemoteAddr(); } return ipAddress; } private void verifyClient(ClientDetails clientDetails, String clientId, String clientSecret, Set scopeSet) throws JwtAuthException { if (StringUtils.isBlank(clientId)) { throw new JwtAuthException( String.format("%d%s%s", HttpStatus.BAD_REQUEST.value(), SERVICE_CODE_ACCESS_TOKEN, "02"), "Invalid Mandatory Field {client_id}"); } if (clientDetails == null) { throw new JwtAuthException( String.format("%d%s%s", HttpStatus.UNAUTHORIZED.value(), SERVICE_CODE_ACCESS_TOKEN, "00"), "Unauthorized. [client not found]"); } if (StringUtils.isBlank(clientSecret)) { throw new JwtAuthException( String.format("%d%s%s", HttpStatus.BAD_REQUEST.value(), SERVICE_CODE_ACCESS_TOKEN, "02"), "Invalid Mandatory Field {client_secret}"); } if (!StringUtils.equals(clientDetails.getClientSecret(), clientSecret)) { throw new JwtAuthException( String.format("%d%s%s", HttpStatus.UNAUTHORIZED.value(), SERVICE_CODE_ACCESS_TOKEN, "00"), "Unauthorized. [Bad client credentials(Client Secret is not match)]"); } if (scopeSet.isEmpty()) { throw new JwtAuthException( String.format("%d%s%s", HttpStatus.BAD_REQUEST.value(), SERVICE_CODE_ACCESS_TOKEN, "02"), "Invalid Mandatory Field {scope}"); } if (!clientDetails.getScope().containsAll(scopeSet)) { throw new JwtAuthException( String.format("%d%s%s", HttpStatus.UNAUTHORIZED.value(), SERVICE_CODE_ACCESS_TOKEN, "00"), "Unauthorized. [Bad client credentials(Include unacceptable scope)]"); } } private TokenEndpoint tokenEndpoint() { return BeanUtils.getBean("tokenEndpoint", TokenEndpoint.class); } private void logTokenIssuance(boolean isSuccess, boolean isRestrictionExempt, String resultMessage, String accessToken) { try { if (!EAIDBLogControl.isEnable()) { logger.warn("DB logging is disabled. Skipping token issuance logging."); return; } RequestContextData data = RequestContextData.ThreadLocalRequestContext.get(); OAuth2Manager.getInstance(); TokenIssuanceLog log = new TokenIssuanceLog(); String clientId = data.getClientId(); log.setClientId(clientId); if(StringUtils.isNotBlank(clientId)) { ClientVO clientVO = OAuth2Manager.getInstance().getClientInfo(clientId); log.setAppName(clientVO.getClientName()); log.setOrgId(clientVO.getOrgId()); log.setOrgName(clientVO.getOrgName()); } log.setGrantType(data.getGrantType()); log.setScope(data.getScope()); log.setIpAddress(data.getIpAddress()); log.setSuccessYn(isSuccess ? "Y" : "N"); log.setIssuanceDateTime(LocalDateTime.now()); log.setRestrictionExemptYn(isRestrictionExempt ? "Y" : "N"); log.setResultMessage(resultMessage); log.setAccessToken(accessToken); // Add access token value to the log tokenIssuanceLogDAO.saveTokenIssuanceLog(log); } catch (Throwable th) { logger.error("Error while logging token issuance: " + th.getMessage(), th); } } public static void main(String[] args) throws Exception { // First generate a public/private key pair KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA"); // generator.initialize(512, new SecureRandom()); //generator.initialize(1024, new SecureRandom()); generator.initialize(2048, new SecureRandom()); KeyPair pair = generator.generateKeyPair(); // The private key can be used to sign (not encrypt!) a message. The public key // holder can then verify the message. String message = "sSGJDwlJsel5WeXodzd3J3MQ20KYCZpL|2022-12-21T14:36:19+07:00"; // Let's sign our message Signature privateSignature = Signature.getInstance("SHA256withRSA"); privateSignature.initSign(pair.getPrivate()); privateSignature.update(message.getBytes(StandardCharsets.UTF_8)); // System.out.println("private key=" + Base64.getEncoder().encodeToString(pair.getPrivate().getEncoded())); byte[] signature = privateSignature.sign(); //System.out.println("signature=" + new String(signature, StandardCharsets.UTF_8)); // System.out.println("signature=" + Base64.getEncoder().encodeToString(signature)); // Let's check the signature Signature publicSignature = Signature.getInstance("SHA256withRSA"); publicSignature.initVerify(pair.getPublic()); publicSignature.update(message.getBytes(StandardCharsets.UTF_8)); boolean isCorrect = publicSignature.verify(signature); // System.out.println("public key=" + Base64.getEncoder().encodeToString(pair.getPublic().getEncoded())); // System.out.println("Signature correct: " + isCorrect); // The public key can be used to encrypt a message, the private key can be used // to decrypt it. // Encrypt the message Cipher encryptCipher = Cipher.getInstance("RSA"); encryptCipher.init(Cipher.ENCRYPT_MODE, pair.getPublic()); byte[] cipherText = encryptCipher.doFinal(message.getBytes(StandardCharsets.UTF_8)); // Now decrypt it Cipher decriptCipher = Cipher.getInstance("RSA"); decriptCipher.init(Cipher.DECRYPT_MODE, pair.getPrivate()); String decipheredMessage = new String(decriptCipher.doFinal(cipherText), StandardCharsets.UTF_8); // System.out.println(decipheredMessage); } }