{"id":3579,"date":"2021-10-06T22:36:27","date_gmt":"2021-10-06T13:36:27","guid":{"rendered":"https:\/\/www.skyer9.pe.kr\/wordpress\/?p=3579"},"modified":"2021-10-09T16:01:00","modified_gmt":"2021-10-09T07:01:00","slug":"spring-boot-acl","status":"publish","type":"post","link":"https:\/\/www.skyer9.pe.kr\/wordpress\/?p=3579","title":{"rendered":"Spring Boot ACL"},"content":{"rendered":"<h1>Spring Boot ACL<\/h1>\n<p><a href=\"https:\/\/www.baeldung.com\/spring-security-acl\">\ucc38\uc870<\/a><\/p>\n<p><a href=\"https:\/\/derekpark.tistory.com\/101\">\ucc38\uc870<\/a><\/p>\n<p><a href=\"https:\/\/shout.setfive.com\/2015\/11\/02\/spring-boot-authentication-with-custom-http-header\/\">\ucc38\uace0<\/a><\/p>\n<h2>\ubaa9\ud45c<\/h2>\n<p>\uc778\uc99d\ud0a4\ub97c \ubc1c\uae09\ud558\uace0, \ubc1c\uae09\ub41c \uc778\uc99d\ud0a4\ub97c \uae30\ubc18\uc73c\ub85c API Access \ub97c \ud5c8\uc6a9\ud55c\ub2e4.<\/p>\n<pre><code class=\"language-javascript\">\/\/ $ curl -H &quot;X-Authorization: $some_secret_token&quot; http:\/\/localhost\/user\n\n$.ajax({\n    url: &#039;http:\/\/localhost\/user&#039;,\n    headers: { &#039;X-Authorization&#039;: &#039;$some_secret_token&#039; }\n});<\/code><\/pre>\n<h2>\uc778\uc99d \ub370\uc774\ud0c0 \uc0dd\uc131<\/h2>\n<pre><code class=\"language-sql\">create table tbl_api_user(\n    id bigint(20) unsigned not null auto_increment,\n    primary key (id),\n    unique key unique_id (id),\n    access_domain varchar(255) NOT NULL,\n    access_token VARCHAR(100) NOT NULL,\n    access_expire varchar(10),\n    modify_time timestamp not null default current_timestamp on update current_timestamp,\n    insert_time timestamp not null default current_timestamp\n);\n\nINSERT INTO tbl_api_user(access_domain, access_token, access_expire)\nVALUES(&#039;http:\/\/localhost:8080&#039;, LEFT(REPLACE(UUID(), &#039;-&#039;, &#039;&#039;), 50), &#039;2099-12-31&#039;);\n\nSELECT * FROM tbl_api_user;<\/code><\/pre>\n<h2>CORS \uc124\uc815<\/h2>\n<pre><code class=\"language-java\">public class CorsFilter implements Filter {\n\n    @Override\n    public void init(FilterConfig filterConfig) throws ServletException {\n\n    }\n\n    @Override\n    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {\n        HttpServletResponse response = (HttpServletResponse) servletResponse;\n        HttpServletRequest request = (HttpServletRequest) servletRequest;\n\n        response.setHeader(&quot;Access-Control-Allow-Origin&quot;, &quot;*&quot;);\n        response.setHeader(&quot;Access-Control-Allow-Methods&quot;, &quot;GET,POST,DELETE,PUT,OPTIONS&quot;);\n        response.setHeader(&quot;Access-Control-Allow-Headers&quot;, &quot;*&quot;);\n        response.setHeader(&quot;Access-Control-Allow-Credentials&quot;, &quot;true&quot;);\n        response.setHeader(&quot;Access-Control-Max-Age&quot;, &quot;3600&quot;);\n\n        if (&quot;OPTIONS&quot;.equals(request.getMethod())) {\n            \/\/ for CORS preflight channel\n            response.setStatus(HttpServletResponse.SC_OK);\n        } else {\n            filterChain.doFilter(request, response);\n        }\n    }\n\n    @Override\n    public void destroy() {\n\n    }\n}<\/code><\/pre>\n<h2>RSA \ud0a4 \uc778\uc99d \uc124\uc815<\/h2>\n<pre><code class=\"language-java\">public class RsaAuthToken extends AbstractAuthenticationToken {\n\n    private Integer principal;\n    private Object credentials;\n\n    private final String accessToken;\n    private final String remoteHost;\n\n    public RsaAuthToken(String accessToken, String remoteHost) {\n        super(null);\n        this.accessToken = accessToken;\n        this.remoteHost = remoteHost;\n    }\n\n    @Override\n    public Object getCredentials() {\n        return null;\n    }\n\n    @Override\n    public Object getPrincipal() {\n        return principal;\n    }\n\n    public void setPrincipal(Integer principal) {\n        this.principal = principal;\n    }\n\n    public String getAccessToken() {\n        return accessToken;\n    }\n\n    public String getRemoteHost() {\n        return remoteHost;\n    }\n}<\/code><\/pre>\n<pre><code class=\"language-java\">public class RsaHeaderKeyFilter extends OncePerRequestFilter {\n\n    public RsaHeaderKeyFilter() {\n        Logger log = LoggerFactory.getLogger(RsaHeaderKeyFilter.class);\n        log.info(&quot;RsaHeaderKeyFilter init&quot;);\n    }\n\n    @Override\n    protected void doFilterInternal(HttpServletRequest request,\n                                    HttpServletResponse response, FilterChain filterChain)\n            throws ServletException, IOException {\n\n        String accessToken = request.getHeader(&quot;X-Authorization&quot;);\n        String referer = request.getHeader(&quot;referer&quot;);          \/\/ TODO : referer hijacking \uc740 \ub098\uc911\uc5d0 \uc0dd\uac01\ud558\uc790.\n\n        if (&quot;localhost&quot;.equals(request.getServerName())) {\n            referer = request.getScheme() + &quot;:\/\/&quot; + request.getServerName() + &quot;:&quot; + request.getServerPort();\n        }\n\n        if (accessToken != null &amp;&amp; referer != null) {\n            Authentication auth = new RsaAuthToken(accessToken, referer);\n            SecurityContextHolder.getContext().setAuthentication(auth);\n\n            System.out.println(&quot;xAuth : &quot; + accessToken);\n            System.out.println(&quot;referer : &quot; + referer);\n        }\n\n        filterChain.doFilter(request, response);\n    }\n}<\/code><\/pre>\n<h2>Spring Security \uc124\uc815<\/h2>\n<pre><code class=\"language-java\">@Component\npublic class TokenAuthenticationProvider implements AuthenticationProvider {\n\n    \/\/ Service \ub97c \uc8fc\uc785\ubc1b\uace0, \uc11c\ube44\uc2a4\uc5d0 Spring Boot Cache \ub97c \uc801\uc6a9\ud558\uc5ec \ubd80\ud558\ub97c \uc904\uc77c \uc218 \uc788\ub2e4.\n    private final ApiUserRepository userRepository;\n\n    @Autowired\n    public TokenAuthenticationProvider(ApiUserRepository userRepository) {\n        this.userRepository = userRepository;\n    }\n\n    @Override\n    public Authentication authenticate(Authentication authentication) throws AuthenticationException {\n\n        RsaAuthToken rsaAuthToken = (RsaAuthToken) authentication;\n        List&lt;ApiUser&gt; userList = userRepository.findByAccessToken(rsaAuthToken.getAccessToken());\n\n        if(userList.isEmpty()){\n            throw new UnknownUserException(&quot;Invalid access token : &quot; + rsaAuthToken.getAccessToken());\n        }\n\n        for (ApiUser user : userList) {\n            if (rsaAuthToken.getRemoteHost().startsWith(user.getAccessDomain())) {\n\n                DateTimeFormatter formatter = DateTimeFormatter.ofPattern(&quot;yyyy-MM-dd&quot;);\n                if (user.getAccessExpire().compareTo(LocalDate.now().format(formatter)) &gt; 0) {\n                    rsaAuthToken.setPrincipal(user.getId());\n\n                    return rsaAuthToken;\n                }\n            }\n        }\n\n        throw new UnknownUserException(&quot;Access denied : &quot; + rsaAuthToken.getRemoteHost());\n    }\n\n    @Override\n    public boolean supports(Class&lt;?&gt; authentication) {\n        return RsaAuthToken.class.isAssignableFrom(authentication);\n    }\n}<\/code><\/pre>\n<pre><code class=\"language-java\">@EnableWebSecurity\npublic class WebSecurityConfig extends WebSecurityConfigurerAdapter {\n\n    private final TokenAuthenticationProvider authenticationProvider;\n\n    public WebSecurityConfig(TokenAuthenticationProvider authenticationProvider) {\n        this.authenticationProvider = authenticationProvider;\n    }\n\n    @Bean\n    CorsFilter corsFilter() {\n        return new CorsFilter();\n    }\n\n    @Bean\n    RsaHeaderKeyFilter rsaHeaderKeyFilter() {\n        return new RsaHeaderKeyFilter();\n    }\n\n    @Override\n    protected void configure(HttpSecurity http) throws Exception {\n\n        \/\/ curl -v &quot;http:\/\/localhost:8080\/health\/index.html&quot;\n        \/\/ curl -v http:\/\/localhost:8080\/v1\/esrestaurant\/?q=%EA%B9%80%EC%B9%98\n        \/\/ curl -v -H &quot;X-Authorization: XXXXXXXXXXXXXXXXXXXXX&quot; &quot;http:\/\/localhost:8080\/v1\/esrestaurant\/?q=%EA%B9%80%EC%B9%98%EC%B0%8C%EA%B0%9C&amp;s=10&amp;p=0&amp;las=35.48608721246216&amp;lae=38.49363416030283&amp;los=125.93452195073536&amp;loe=127.94537104338305&quot;\n        \/\/ curl -v -H &quot;X-Authorization: XXXXXXXXXXXXXXXXXXXXX&quot; &quot;https:\/\/nb.skyer9.pe.kr:28080\/v1\/esrestaurant\/?q=%EA%B9%80%EC%B9%98%EC%B0%8C%EA%B0%9C&amp;s=10&amp;p=0&amp;las=35.48608721246216&amp;lae=38.49363416030283&amp;los=125.93452195073536&amp;loe=127.94537104338305&quot;\n\n        http\n                .addFilterBefore(corsFilter(), SessionManagementFilter.class)\n                .addFilterBefore(rsaHeaderKeyFilter(), BasicAuthenticationFilter.class)\n                .authorizeRequests().antMatchers(&quot;\/health\/**&quot;).permitAll()\n                .anyRequest().authenticated();\n    }\n\n    @Override\n    public void configure(AuthenticationManagerBuilder auth) throws Exception {\n        auth.authenticationProvider(authenticationProvider);\n    }\n}<\/code><\/pre>\n","protected":false},"excerpt":{"rendered":"<p>Spring Boot ACL \ucc38\uc870 \ucc38\uc870 \ucc38\uace0 \ubaa9\ud45c \uc778\uc99d\ud0a4\ub97c \ubc1c\uae09\ud558\uace0, \ubc1c\uae09\ub41c \uc778\uc99d\ud0a4\ub97c \uae30\ubc18\uc73c\ub85c API Access \ub97c \ud5c8\uc6a9\ud55c\ub2e4. \/\/ $ curl -H &quot;X-Authorization: $some_secret_token&quot; http:\/\/localhost\/user $.ajax({ url: &#039;http:\/\/localhost\/user&#039;, headers: { &#039;X-Authorization&#039;: &#039;$some_secret_token&#039; } }); \uc778\uc99d \ub370\uc774\ud0c0 \uc0dd\uc131 create table tbl_api_user( id bigint(20) unsigned not null auto_increment, primary key (id), unique key unique_id (id), access_domain varchar(255) NOT NULL,\u2026 <span class=\"read-more\"><a href=\"https:\/\/www.skyer9.pe.kr\/wordpress\/?p=3579\">Read More &raquo;<\/a><\/span><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[29,1],"tags":[],"class_list":["post-3579","post","type-post","status-publish","format-standard","hentry","category-spring-boot-2-5","category-uncategorized"],"_links":{"self":[{"href":"https:\/\/www.skyer9.pe.kr\/wordpress\/index.php?rest_route=\/wp\/v2\/posts\/3579","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=3579"}],"version-history":[{"count":6,"href":"https:\/\/www.skyer9.pe.kr\/wordpress\/index.php?rest_route=\/wp\/v2\/posts\/3579\/revisions"}],"predecessor-version":[{"id":3588,"href":"https:\/\/www.skyer9.pe.kr\/wordpress\/index.php?rest_route=\/wp\/v2\/posts\/3579\/revisions\/3588"}],"wp:attachment":[{"href":"https:\/\/www.skyer9.pe.kr\/wordpress\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=3579"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.skyer9.pe.kr\/wordpress\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=3579"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.skyer9.pe.kr\/wordpress\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=3579"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}