ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Keycloak] Sample java & jsp 구성 및 테스트
    Tutorials & Tips/KeyCloak Server 2022. 12. 27. 16:13

     

    SingleID OIDC 연계가이드_20210618.pdf
    0.28MB

     

    keycloak.zip
    2.14MB

     

     

     

    keycloak.zip 파일을 보게되면 ROOT 파일 내부 파일 구성을 볼 수 있습니다.

     

     

    톰캣 서버 자체에서 띄우기 위해서는

    사전에 설치해 놓은 tomcat-9.0 폴더 안 /ROOT 폴더로 파일을 다 옮긴 후

    SSOAuth.java 파일의 경로를수정해 주어야 합니다.

     

     

    setting.ini 파일을 보게되면 각 uri의 값들은

    Keycloak admin console에서 Realm Settings -> Endpoints -> OpenID Endponit Configuration에

    나와있는 값들을 넣어주면 됩니다.

    <%
        // --------------------------------------------------------------
        // Same values for All SP (Do not Edit)
        // -------------------------------------------------------------- 
        String checkUri = "https://idp.kc.com:8443/realms/SpringBootKeycloak";   // Check Connection URI
        String authorizeUri = "https://idp.kc.com:8443/realms/SpringBootKeycloak/protocol/openid-connect/auth";   // Authorize URI
        String tokenTargetUri = "https://idp.kc.com:8443/realms/SpringBootKeycloak/protocol/openid-connect/token";  //  Token URI
        String jwksUri = "https://idp.kc.com:8443/realms/SpringBootKeycloak/protocol/openid-connect/certs";   // RemoteJWKSet URI
    
        // --------------------------------------------------------------
        // Values that received from IdP (Edit needed)
        // Different values for each SP
        // --------------------------------------------------------------
        String clientId = "login";     // Client ID
        String clientSecret = "lbwNBEOOBMwZx9aPfUWTKslfLBdnWwh0"; // Client Secret
        
        // --------------------------------------------------------------
        // SP's address (Edit needed)
        // --------------------------------------------------------------
        String redirectUri = "http://localhost:8081/redirect.jsp";  // SP's rediect uri
        String mainUrl = "http://localhost:8081/main.jsp";          // SP's main uri (portal, home, etc ...)
    	
        String oidcSessionIframeEndpoint = "https://idp.kc.com:8443/realms/SpringBootKeycloak/protocol/openid-connect/login-status-iframe.html";
        String scope = "openid";
        String oauth2GrantType = "code";
    %>

     

    1. [nbf] 에러

    claims 속성중에 [nbf] 속성이 빠져있어서 나는 에러다

     

    [해결방안]

    claims에 [nbf] 속성을 추가해줍니다.

    nbf 속성 추가 방법 - Keycloak admin console에서 Client scopes에서 Create Client scope로 생성합니다.

     

     

    nbf라는 이름으로 Client scopes를 생성한 다음 Mappers를 추가해줍니다.

     

    Configure a new mapper를 선택하면 여러가지 속성으로 mapper를 생성할 수 있습니다.

    여기서는 Hardcoded claim을 선택해서 claim을 생성해 줍니다.

    nbf라는 이름으로 claim 속성을 추가해 주었습니다.

     

     

    Clients를 login을 선택한 후에 Client scopes

     

    Add client scope를 누르게되면 Client scope 방금 만들었던 nbf 속성을 추가해 줄 수 있습니다.

    값을 추가할 때는 default값으로 꼭 넣어주어야 합니다.

     

    claims 속성중에 [nbf] 속성이 빠져있어서 나던 오류가 해결됩니다.

     

    2. iss 에러

    claim 속성중에 iss 값이 달라서 나는 에러이다.

    [해결방안]

    SSOAuth.java 소스를 보게되면 processResponse 메소드에서 수정해주어야 할 부분이 있습니다.

    소스를 내리다보면 중간에

    DefaultJWTClaimsVerifier defaultJWTClaimsVerifier = new DefaultJWTClaimsVerifier<>(new JWTClaimsSet.Builder().issuer(tokenTargetUri).build(), new HashSet<>(Arrays.asList("nbf", "exp")));

    소스를 볼 수있습니다.

    여기에서 issuer(tokenTargetUri)issuer(checkUri)로 수정해주면 됩니다.

    public void processResponse(Map<String, Object> params) throws IllegalAccessException, UnsupportedEncodingException {
            // grant_type이 authorization_code이면 cookie에서 state 값을 검증한다.
            if ("authorization_code".equals(String.valueOf(params.get("grant_type")))) {
                Cookie[] cookies = request.getCookies();
                boolean founded = false;
                for (Cookie c : cookies) {
                    if ("state".equals(c.getName())) {
                        founded = true;
                        print("SSOAuth.processResponse state cookie name : " + c.getName() + ", value = " + c.getValue());
                        print("SSOAuth.processResponse state parameter : " + params.get("state"));
                        if (null == params.get("state") || null == c.getValue()) {
                            throw new IllegalAccessException("state 값 검증에 실패하였습니다.");
                        }
                        if (!URLDecoder.decode(c.getValue(), "UTF-8").equals(params.get("state"))) {
                            print("SSOAuth.processResponse cookie 내 state 값 검증 실패");
                            throw new IllegalAccessException("state 값 검증에 실패하였습니다.");
                        }
                        Map<String, String> map = new HashMap<String, String>();
                        for (String sss : String.valueOf(params.get("state")).split("&")) {
                            String[] sa = sss.split("=");
                            map.put(sa[0], sa[1]);
                        }
                        returnUrl = map.get("returnUrl");
                    }
                }
                if (!founded) {
                    throw new IllegalAccessException("state 값 검증에 실패하였습니다.");
                }
            } else {
                returnUrl = mainUrl;
            }
    
            JsonNode jwtJsonNode = simplePost(tokenTargetUri, params);
            print("jwtJsonNode : " + jwtJsonNode.toPrettyString());
            String accessToken = (String) jwtJsonNode.getObject().get("access_token");
            String refreshToken = (String) jwtJsonNode.getObject().get("refresh_token");
            String idToken = (String) jwtJsonNode.getObject().get("id_token"); // JWT format string
            if (null != idToken && !"".equals(idToken)) {
                ConfigurableJWTProcessor<SecurityContext> jwtProcessor = new DefaultJWTProcessor<>();
                try {
                    RestOperationsResourceRetriever restOperationsResourceRetriever = new RestOperationsResourceRetriever();
                    JWKSource<SecurityContext> keySource =  new RemoteJWKSet<>(new URL(jwksUri), restOperationsResourceRetriever);
                    JWSAlgorithm expectedJWSAlg = JWSAlgorithm.RS256;
                    JWSKeySelector<SecurityContext> keySelector = new JWSVerificationKeySelector<>(expectedJWSAlg, keySource);
                    jwtProcessor.setJWSKeySelector(keySelector);
                    DefaultJWTClaimsVerifier<SecurityContext> defaultJWTClaimsVerifier = new DefaultJWTClaimsVerifier<>(new JWTClaimsSet.Builder().issuer(checkUri).build(), new HashSet<>(Arrays.asList("nbf", "exp")));
    
                    defaultJWTClaimsVerifier.setMaxClockSkew(120);
                    jwtProcessor.setJWTClaimsSetVerifier(defaultJWTClaimsVerifier);
    
                    SecurityContext ctx = null; // optional context parameter, not required here
                    JWTClaimsSet claimsSet = jwtProcessor.process(idToken, ctx);
    
                    if (null != claimsSet) {
                        String userId = claimsSet.getClaim("sub").toString();
                        String jwtString = claimsSet.toString();
                        this.attributes = new HashMap<>(claimsSet.getClaims());
                        this.attributes.put("jwt", jwtString);
                        this.attributes.put("userId", userId);
                        this.attributes.put("idToken", idToken);
                        this.attributes.put("accessToken", accessToken);
                        this.attributes.put("refreshToken", refreshToken);
                        print("SSOAuth.processResponse URLDecoded.state : " + params.get("state"));
                    } else {
                        print("SSOAuth.processResponse : jwsClaims is NULL");
                    }
                } catch(MalformedURLException | JOSEException | BadJOSEException | ParseException e) {
                    this.IsError = true;
                    this.ErrorMessage = e.getMessage();
                    e.printStackTrace();
                } finally {
                    print("SSOAuth.processResponse : IsError : " + this.IsError);
                    print("SSOAuth.processResponse : ErrorMessage : " + this.ErrorMessage);
                }
            }
        }

     

     

    3. SSOAuth.java 소스를 수정한 후 javac 컴파일을 실행 에러

     

    cmd창에서 javac SSOAuth.java 입력하면

    인코딩을 비롯한 package를 찾지못하는 에러가 같이 나옵니다.

    여기에서 못찾는 package는 nimbus, unirest, HttpServlet을 찾지 못합니다.

    [해결방안]

    WEB-INF/lib 파일에 javax.servlet-api-4.0.1 jar 파일을 추가해줍니다.

     

    javax.servlet-api-4.0.1.jar
    0.09MB

     

    컴파일 할때 -cp 옵션을 주어서 참조할 클래스 파일들을 파일 경로를 지정해준 다음 컴파일 해줍니다.

    classpath(cp) path(파일 절대 경로)

    컴파일러가 컴파일 하기 위해서 필요로 하는 참조할 클래스 파일들을 찾기 위해서 컴파일시 파일 경로를 지정해 주는 옵션

     

    javac -cp  C:\tools\apache-tomcat-9.0.65\webapps\ROOT\WEB-INF\lib\nimbus-jose-jwt-9.1.2.jar;C:\tools\apache-tomcat-9.0.65\webapps\ROOT\WEB-INF\lib\unirest-java-3.11.11.jar;C:\tools\apache-tomcat-9.0.65\webapps\ROOT\WEB-INF\lib\javax.servlet-api-4.0.1.jar SSOAuth.java -encoding utf-8 

     

    cp 옵션을 주어서 성공적으로 컴파일을 하고나면 class파일이 생성되는 것을 볼 수 있습니다.

     

     

    1. nbf 에러

    2.iss 에러

    3. javac 컴파일 실행 에러를 해결하고 나면 main.jsp에

    - access_token 

    - refresh_token

    - JWT

    - idToken

    - userId  값을 받아오는 것을 볼 수 있습니다.

    'Tutorials & Tips > KeyCloak Server' 카테고리의 다른 글

    [Keycloak] Schema Extention 및 Claim 추가 설정  (0) 2022.12.27
    Keycloak spring 예제 연동  (0) 2022.12.13
    Keycloak postgresql 연동  (0) 2022.12.02
    KeyCloak 설치 및 실행  (0) 2022.12.02

    댓글

Designed by Tistory.