{"id":2472,"date":"2021-07-29T06:26:17","date_gmt":"2021-07-28T21:26:17","guid":{"rendered":"https:\/\/www.skyer9.pe.kr\/wordpress\/?p=2472"},"modified":"2021-07-29T06:35:56","modified_gmt":"2021-07-28T21:35:56","slug":"spring-boot-brute-force%eb%9e%9c%eb%8d%a4-%eb%ac%b8%ec%9e%90%ec%97%b4-%ea%b3%b5%ea%b2%a9-%eb%8c%80%ec%9d%91","status":"publish","type":"post","link":"https:\/\/www.skyer9.pe.kr\/wordpress\/?p=2472","title":{"rendered":"Spring Boot Brute Force(\ub79c\ub364 \ubb38\uc790\uc5f4 \uacf5\uaca9) \ub300\uc751"},"content":{"rendered":"<h1>Spring Boot Brute Force(\ub79c\ub364 \ubb38\uc790\uc5f4 \uacf5\uaca9) \ub300\uc751<\/h1>\n<p>10 \ud68c \uc774\uc0c1 \ub85c\uadf8\uc778 \uc2e4\ud328 \ubc1c\uc0dd\uc2dc, 24\uc2dc\uac04\ub3d9\uc548 \ub85c\uadf8\uc778 \uc2dc\ub3c4\ub97c \ucc28\ub2e8\ud569\ub2c8\ub2e4.<\/p>\n<h2>\ubaa9\ud45c<\/h2>\n<p>Brute Force(\ub79c\ub364 \ubb38\uc790\uc5f4 \uacf5\uaca9) \uc5d0 \ub300\uc751\ud569\ub2c8\ub2e4.<\/p>\n<h2>\uae30\ucd08 \ud504\ub85c\uc81d\ud2b8<\/h2>\n<p><a href=\"https:\/\/www.skyer9.pe.kr\/wordpress\/?p=2294\">Spring Boot OAuth2 Authorization Server<\/a> \ub97c \uae30\ucd08 \ud504\ub85c\uc81d\ud2b8\ub85c \ud569\ub2c8\ub2e4.<\/p>\n<h2>build.gradle \uc218\uc815<\/h2>\n<pre><code class=\"language-gradle\">dependencies {\n    \/\/ ......\n    implementation &#039;com.google.guava:guava:20.0&#039;\n    \/\/ ......\n}<\/code><\/pre>\n<h2>\uc18c\uc2a4 \ucd94\uac00<\/h2>\n<p>AuthenticationFailureListener.java<\/p>\n<pre><code class=\"language-java\">@RequiredArgsConstructor\n@Component\npublic class AuthenticationFailureListener implements\n        ApplicationListener&lt;AuthenticationFailureBadCredentialsEvent&gt; {\n\n    private final HttpServletRequest request;\n\n    private final LoginAttemptService loginAttemptService;\n\n    @Override\n    public void onApplicationEvent(AuthenticationFailureBadCredentialsEvent e) {\n        final String xfHeader = request.getHeader(&quot;X-Forwarded-For&quot;);\n        if (xfHeader == null) {\n            loginAttemptService.loginFailed(request.getRemoteAddr());\n        } else {\n            loginAttemptService.loginFailed(xfHeader.split(&quot;,&quot;)[0]);\n        }\n    }\n}<\/code><\/pre>\n<p>AuthenticationSuccessEventListener.java<\/p>\n<pre><code class=\"language-java\">@RequiredArgsConstructor\n@Component\npublic class AuthenticationSuccessEventListener implements\n        ApplicationListener&lt;AuthenticationSuccessEvent&gt; {\n\n    private final HttpServletRequest request;\n\n    private final LoginAttemptService loginAttemptService;\n\n    @Override\n    public void onApplicationEvent(final AuthenticationSuccessEvent e) {\n        final String xfHeader = request.getHeader(&quot;X-Forwarded-For&quot;);\n        if (xfHeader == null) {\n            loginAttemptService.loginSucceeded(request.getRemoteAddr());\n        } else {\n            loginAttemptService.loginSucceeded(xfHeader.split(&quot;,&quot;)[0]);\n        }\n    }\n}<\/code><\/pre>\n<p>LoginAttemptService.java<\/p>\n<pre><code class=\"language-java\">@Service\npublic class LoginAttemptService {\n\n    private final int MAX_ATTEMPT = 10;\n    private LoadingCache&lt;String, Integer&gt; attemptsCache;\n\n    public LoginAttemptService() {\n        super();\n        attemptsCache = CacheBuilder.newBuilder().\n                expireAfterWrite(1, TimeUnit.DAYS).build(new CacheLoader&lt;String, Integer&gt;() {\n            public Integer load(String key) {\n                return 0;\n            }\n        });\n    }\n\n    public void loginSucceeded(String key) {\n        attemptsCache.invalidate(key);\n    }\n\n    public void loginFailed(String key) {\n        int attempts = 0;\n        try {\n            attempts = attemptsCache.get(key);\n        } catch (ExecutionException e) {\n            attempts = 0;\n        }\n        attempts++;\n        attemptsCache.put(key, attempts);\n    }\n\n    public boolean isBlocked(String key) {\n        try {\n            return attemptsCache.get(key) &gt;= MAX_ATTEMPT;\n        } catch (ExecutionException e) {\n            return false;\n        }\n    }\n}<\/code><\/pre>\n<p>CustomUserDetailService.java<\/p>\n<pre><code class=\"language-java\">@RequiredArgsConstructor\n@Service\npublic class CustomUserDetailService implements UserDetailsService {\n\n    private final UserRepository userRepository;\n\n    private final AccountStatusUserDetailsChecker detailsChecker = new AccountStatusUserDetailsChecker();\n\n    private final LoginAttemptService loginAttemptService;\n\n    private final HttpServletRequest request;\n\n    @Override\n    public UserDetails loadUserByUsername(String name) throws UsernameNotFoundException {\n        String ip = getClientIP();\n        if (loginAttemptService.isBlocked(ip)) {\n            throw new UsernameNotFoundException(&quot;IP blocked&quot;);\n        }\n\n        User user = userRepository.findByUid(name).orElseThrow(() -&gt; new UsernameNotFoundException(&quot;user is not exists&quot;));\n        detailsChecker.check(user);\n        return user;\n    }\n\n    private String getClientIP() {\n        String xfHeader = request.getHeader(&quot;X-Forwarded-For&quot;);\n        if (xfHeader == null){\n            return request.getRemoteAddr();\n        }\n        return xfHeader.split(&quot;,&quot;)[0];\n    }\n}<\/code><\/pre>\n","protected":false},"excerpt":{"rendered":"<p>Spring Boot Brute Force(\ub79c\ub364 \ubb38\uc790\uc5f4 \uacf5\uaca9) \ub300\uc751 10 \ud68c \uc774\uc0c1 \ub85c\uadf8\uc778 \uc2e4\ud328 \ubc1c\uc0dd\uc2dc, 24\uc2dc\uac04\ub3d9\uc548 \ub85c\uadf8\uc778 \uc2dc\ub3c4\ub97c \ucc28\ub2e8\ud569\ub2c8\ub2e4. \ubaa9\ud45c Brute Force(\ub79c\ub364 \ubb38\uc790\uc5f4 \uacf5\uaca9) \uc5d0 \ub300\uc751\ud569\ub2c8\ub2e4. \uae30\ucd08 \ud504\ub85c\uc81d\ud2b8 Spring Boot OAuth2 Authorization Server \ub97c \uae30\ucd08 \ud504\ub85c\uc81d\ud2b8\ub85c \ud569\ub2c8\ub2e4. build.gradle \uc218\uc815 dependencies { \/\/ &#8230;&#8230; implementation &#039;com.google.guava:guava:20.0&#039; \/\/ &#8230;&#8230; } \uc18c\uc2a4 \ucd94\uac00 AuthenticationFailureListener.java @RequiredArgsConstructor @Component public class AuthenticationFailureListener implements\u2026 <span class=\"read-more\"><a href=\"https:\/\/www.skyer9.pe.kr\/wordpress\/?p=2472\">Read More &raquo;<\/a><\/span><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[29],"tags":[],"class_list":["post-2472","post","type-post","status-publish","format-standard","hentry","category-spring-boot-2-5"],"_links":{"self":[{"href":"https:\/\/www.skyer9.pe.kr\/wordpress\/index.php?rest_route=\/wp\/v2\/posts\/2472","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.skyer9.pe.kr\/wordpress\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.skyer9.pe.kr\/wordpress\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.skyer9.pe.kr\/wordpress\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.skyer9.pe.kr\/wordpress\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=2472"}],"version-history":[{"count":2,"href":"https:\/\/www.skyer9.pe.kr\/wordpress\/index.php?rest_route=\/wp\/v2\/posts\/2472\/revisions"}],"predecessor-version":[{"id":2474,"href":"https:\/\/www.skyer9.pe.kr\/wordpress\/index.php?rest_route=\/wp\/v2\/posts\/2472\/revisions\/2474"}],"wp:attachment":[{"href":"https:\/\/www.skyer9.pe.kr\/wordpress\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=2472"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.skyer9.pe.kr\/wordpress\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=2472"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.skyer9.pe.kr\/wordpress\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=2472"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}