복붙노트

[SPRING] Spring 보안 OAuth2는 JSON을 허용합니다.

SPRING

Spring 보안 OAuth2는 JSON을 허용합니다.

나는 봄 OAuth2로 시작하고있다. POST 본체의 application / json 형식으로 / oauth / token endpoint에 사용자 이름과 암호를 보내고 싶습니다.

curl -X POST -H "Authorization: Basic YWNtZTphY21lc2VjcmV0" -H "Content-Type: application/json" -d '{
"username": "user",
"password": "password",
"grant_type": "password"
}' "http://localhost:9999/api/oauth/token"

그게 가능하니?

나에게 조언을 해줄 수 있니?

해결법

  1. ==============================

    1.해결책 (정확한지는 확실하지 않지만 작동하는 것 같습니다).

    해결책 (정확한지는 확실하지 않지만 작동하는 것 같습니다).

    자원 서버 구성 :

    @Configuration
    public class ServerEndpointsConfiguration extends ResourceServerConfigurerAdapter {
    
        @Autowired
        JsonToUrlEncodedAuthenticationFilter jsonFilter;
    
        @Override
        public void configure(HttpSecurity http) throws Exception {
            http
                .addFilterBefore(jsonFilter, ChannelProcessingFilter.class)
                .csrf().and().httpBasic().disable()
                .authorizeRequests()
                .antMatchers("/test").permitAll()
                .antMatchers("/secured").authenticated();
        }
    }
    

    필터:

    @Component
    @Order(value = Integer.MIN_VALUE)
    public class JsonToUrlEncodedAuthenticationFilter implements Filter {
    
        @Override
        public void init(FilterConfig filterConfig) throws ServletException {
        }
    
        @Override
        public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException,
                ServletException {
            if (Objects.equals(request.getContentType(), "application/json") && Objects.equals(((RequestFacade) request).getServletPath(), "/oauth/token")) {
                InputStream is = request.getInputStream();
                ByteArrayOutputStream buffer = new ByteArrayOutputStream();
    
                int nRead;
                byte[] data = new byte[16384];
    
                while ((nRead = is.read(data, 0, data.length)) != -1) {
                    buffer.write(data, 0, nRead);
                }
                buffer.flush();
                byte[] json = buffer.toByteArray();
    
                HashMap<String, String> result = new ObjectMapper().readValue(json, HashMap.class);
                HashMap<String, String[]> r = new HashMap<>();
                for (String key : result.keySet()) {
                    String[] val = new String[1];
                    val[0] = result.get(key);
                    r.put(key, val);
                }
    
                String[] val = new String[1];
                val[0] = ((RequestFacade) request).getMethod();
                r.put("_method", val);
    
                HttpServletRequest s = new MyServletRequestWrapper(((HttpServletRequest) request), r);
                chain.doFilter(s, response);
            } else {
                chain.doFilter(request, response);
            }
        }
    
        @Override
        public void destroy() {
        }
    }
    

    요청 래퍼 :

    public class MyServletRequestWrapper extends HttpServletRequestWrapper {
        private final HashMap<String, String[]> params;
    
        public MyServletRequestWrapper(HttpServletRequest request, HashMap<String, String[]> params) {
            super(request);
            this.params = params;
        }
    
        @Override
        public String getParameter(String name) {
            if (this.params.containsKey(name)) {
                return this.params.get(name)[0];
            }
            return "";
        }
    
        @Override
        public Map<String, String[]> getParameterMap() {
            return this.params;
        }
    
        @Override
        public Enumeration<String> getParameterNames() {
            return new Enumerator<>(params.keySet());
        }
    
        @Override
        public String[] getParameterValues(String name) {
            return params.get(name);
        }
    }
    

    권한 서버 구성 (/ oauth / token 엔드 포인트에 대한 기본 인증 사용 불가능 :

        @Configuration
    public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {
    
        ...
    
        @Override
        public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
            oauthServer.allowFormAuthenticationForClients(); // Disable /oauth/token Http Basic Auth
        }
    
        ...
    
    }
    
  2. ==============================

    2.OAuth 2 사양에서,

    OAuth 2 사양에서,

    액세스 토큰 요청은 application / x-www-form-urlencoded를 사용해야합니다.

    Spring 보안에서 Resource Owner Password Credential은 Spring Security의 ResourceOwnerPasswordTokenGranter # getOAuth2Authentication에 의해 처리됩니다 :

    protected OAuth2Authentication getOAuth2Authentication(AuthorizationRequest clientToken) {
        Map parameters = clientToken.getAuthorizationParameters();
        String username = (String)parameters.get("username");
        String password = (String)parameters.get("password");
        UsernamePasswordAuthenticationToken userAuth = new UsernamePasswordAuthenticationToken(username, password);
    

    요청 매개 변수에 사용자 이름과 암호를 보낼 수 있습니다.

    JSON을 실제로 사용해야하는 경우 해결 방법이 있습니다. 보시다시피 요청 매개 변수에서 사용자 이름과 암호가 검색됩니다. 따라서 요청을 JSON 본문에서 요청 매개 변수로 전달하면 작동합니다.

    아이디어는 다음과 같습니다.

    희망이 도움이 될 수 있습니다.

  3. ==============================

    3.또한 oauth에 대한 http 기본 인증을 지원하도록 @ jakub-kopřiva 솔루션을 수정할 수 있습니다.

    또한 oauth에 대한 http 기본 인증을 지원하도록 @ jakub-kopřiva 솔루션을 수정할 수 있습니다.

    자원 서버 구성 :

    @Configuration
    public class ServerEndpointsConfiguration extends ResourceServerConfigurerAdapter {
    
        @Autowired
        JsonToUrlEncodedAuthenticationFilter jsonFilter;
    
        @Override
        public void configure(HttpSecurity http) throws Exception {
            http
                .addFilterAfter(jsonFilter, BasicAuthenticationFilter.class)
                .csrf().disable()
                .authorizeRequests()
                .antMatchers("/test").permitAll()
                .antMatchers("/secured").authenticated();
        }
    }
    

    내부 RequestWrapper로 필터링

    @Component
    public class JsonToUrlEncodedAuthenticationFilter extends OncePerRequestFilter {
    
        @Override
        protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
    
            if (Objects.equals(request.getServletPath(), "/oauth/token") && Objects.equals(request.getContentType(), "application/json")) {
    
                byte[] json = ByteStreams.toByteArray(request.getInputStream());
    
                Map<String, String> jsonMap = new ObjectMapper().readValue(json, Map.class);;
                Map<String, String[]> parameters =
                        jsonMap.entrySet().stream()
                                .collect(Collectors.toMap(
                                        Map.Entry::getKey,
                                        e ->  new String[]{e.getValue()})
                                );
                HttpServletRequest requestWrapper = new RequestWrapper(request, parameters);
                filterChain.doFilter(requestWrapper, response);
            } else {
                filterChain.doFilter(request, response);
            }
        }
    
    
        private class RequestWrapper extends HttpServletRequestWrapper {
    
            private final Map<String, String[]> params;
    
            RequestWrapper(HttpServletRequest request, Map<String, String[]> params) {
                super(request);
                this.params = params;
            }
    
            @Override
            public String getParameter(String name) {
                if (this.params.containsKey(name)) {
                    return this.params.get(name)[0];
                }
                return "";
            }
    
            @Override
            public Map<String, String[]> getParameterMap() {
                return this.params;
            }
    
            @Override
            public Enumeration<String> getParameterNames() {
                return new Enumerator<>(params.keySet());
            }
    
            @Override
            public String[] getParameterValues(String name) {
                return params.get(name);
            }
        }
    }
    

    또한 x-www-form-urlencoded 인증을 허용해야합니다.

        @Configuration
    public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {
    
        ...
    
        @Override
        public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
            oauthServer.allowFormAuthenticationForClients();
        }
    
        ...
    
    }
    

    이 방법을 사용하면 다음과 같이 oauth 토큰에 기본 인증을 사용할 수 있고 json으로 토큰을 요청할 수 있습니다.

    머리글:

    Authorization: Basic bG9yaXpvbfgzaWNwYQ==
    

    신체:

    {
        "grant_type": "password", 
        "username": "admin", 
        "password": "1234"
    }
    
  4. ==============================

    4.스프링 시큐리티 5에서는 .oldFormAuthenticationForClients () + JsontoUrlEncodedAuthenticationFilter를 추가해야만 x- 폼 포스트 데이터 외에도 json을 받아들이도록 할 수 있었다. 리소스 서버 등을 등록 할 필요가 없었습니다.

    스프링 시큐리티 5에서는 .oldFormAuthenticationForClients () + JsontoUrlEncodedAuthenticationFilter를 추가해야만 x- 폼 포스트 데이터 외에도 json을 받아들이도록 할 수 있었다. 리소스 서버 등을 등록 할 필요가 없었습니다.

  5. ==============================

    5.@ jakub-kopřiva 솔루션을 수정하여 아래 코드로 권한 부여 서버 만 구현할 수 있습니다.

    @ jakub-kopřiva 솔루션을 수정하여 아래 코드로 권한 부여 서버 만 구현할 수 있습니다.

     @Configuration
     @Order(Integer.MIN_VALUE)
     public class AuthorizationServerSecurityConfiguration
        extends org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerSecurityConfiguration {
    
          @Autowired
          JsonToUrlEncodedAuthenticationFilter jsonFilter;
    
          @Override
          protected void configure(HttpSecurity httpSecurity) throws Exception {
                 httpSecurity
                       .addFilterBefore(jsonFilter, ChannelProcessingFilter.class);
                 super.configure(httpSecurity);
          }
    
    }
    
  6. from https://stackoverflow.com/questions/38165131/spring-security-oauth2-accept-json by cc-by-sa and MIT license