복붙노트

[SPRING] 스프링 보안 및 JSON 인증

SPRING

스프링 보안 및 JSON 인증

JSON 통신을 완전히 사용하는 spring / spring-mvc 응용 프로그램을 사용했습니다. 이제 JSON을 통해 스프링 보안 3 (LdapAuthenticationProvider를 사용)으로 애플리케이션을 인증해야합니다.

기본 스프링 보안 제출 양식에는 다음과 같은 POST가 필요합니다.

POST /myapp/j_spring_security_check HTTP/1.1
Accept-Encoding: gzip,deflate
Content-Type: application/x-www-form-urlencoded
Content-Length: 32
Host: 127.0.0.1:8080
Connection: Keep-Alive
User-Agent: Apache-HttpClient/4.1.1 (java 1.5)

j_username=myUsername&j_password=myPass

하지만이 JSON 개체를 전달하려면 :

{"j_username":"myUsername","j_password":"myPass"}

나는이 같은 많은 게시물을 읽었고,이 다른 하나 또는 행운이없는이 모든 것은 아약스 케이스에서 위와 같은 POST가 수행된다.

어떤 아이디어?

해결법

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

    1.JSON을 구문 분석 할 자체 보안 필터를 작성할 수 있습니다.

    JSON을 구문 분석 할 자체 보안 필터를 작성할 수 있습니다.

    http://docs.spring.io/spring-security/site/docs/3.0.x/reference/core-web-filters.html

    BasicAuthenticationFilter를 참조로 사용할 수 있습니다.

    http://docs.spring.io/spring-security/site/docs/3.0.x/apidocs/org/springframework/security/web/authentication/www/BasicAuthenticationFilter.html

  2. ==============================

    2.Kevin 제안에 따르면, 이 게시물을 읽은 후 : 1, 2, documentation 3,이 블로그 게시물 덕분에, 인증 전에 JSON을 직접 관리하기 위해 FORM_LOGIN_FILTER를 직접 작성했습니다. 커뮤니티 코드를 붙여 넣습니다.

    Kevin 제안에 따르면, 이 게시물을 읽은 후 : 1, 2, documentation 3,이 블로그 게시물 덕분에, 인증 전에 JSON을 직접 관리하기 위해 FORM_LOGIN_FILTER를 직접 작성했습니다. 커뮤니티 코드를 붙여 넣습니다.

    목표는 고전적인 브라우저 형식의 POST 인증과 JSON 기반 인증을 모두 부여하는 것입니다. 또한 JSON 인증에서 loginSuccesful.htm으로 리디렉션하지 않으려합니다.

    문맥:

    <security:http use-expressions="true" auto-config="false" entry-point-ref="http403EntryPoint">      
        <security:intercept-url pattern="/logs/**" access="denyAll" />
        <!-- ... All other intercept URL -->
    
        <security:custom-filter ref="CustomUsernamePasswordAuthenticationFilter" position="FORM_LOGIN_FILTER "/>
        <security:logout
                invalidate-session="true"
                logout-success-url="/LogoutSuccessful.htm"
                delete-cookies="true"
        />
        <security:session-management>
            <security:concurrency-control max-sessions="1" error-if-maximum-exceeded="true" />
        </security:session-management>
        <security:access-denied-handler error-page="/accessDenied.htm" />
    </security:http>
    
    <bean id="CustomUsernamePasswordAuthenticationFilter" class="path.to.CustomUsernamePasswordAuthenticationFilter">
        <property name="authenticationManager" ref="authenticationManager" />
        <property name="authenticationSuccessHandler" ref="customSuccessHandler"/>
        <property name="authenticationFailureHandler" ref="failureHandler"/>
        <property name="filterProcessesUrl" value="/j_spring_security_check"/>
        <property name="usernameParameter" value="j_username"/>
        <property name="passwordParameter" value="j_password"/>
    </bean>
    
    <bean id="customSuccessHandler" class="path.to.CustomAuthenticationSuccessHandler">
        <property name="defaultTargetUrl" value="/login.htm" />
        <property name="targetUrlParameter" value="/LoginSuccessful.htm" />
    </bean>
    
    <bean id="failureHandler" class="org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler">
        <property name="defaultFailureUrl" value="/login.htm" />
    </bean>
    
    <bean id="http403EntryPoint" class="org.springframework.security.web.authentication.Http403ForbiddenEntryPoint" />
    

    CustomUsernamePasswordAuthenticationFilter 클래스 :

    public class CustomUsernamePasswordAuthenticationFilter extends UsernamePasswordAuthenticationFilter{
        private String jsonUsername;
        private String jsonPassword;
    
        @Override
        protected String obtainPassword(HttpServletRequest request) {
            String password = null; 
    
            if ("application/json".equals(request.getHeader("Content-Type"))) {
                password = this.jsonPassword;
            }else{
                password = super.obtainPassword(request);
            }
    
            return password;
        }
    
        @Override
        protected String obtainUsername(HttpServletRequest request){
            String username = null;
    
            if ("application/json".equals(request.getHeader("Content-Type"))) {
                username = this.jsonUsername;
            }else{
                username = super.obtainUsername(request);
            }
    
            return username;
        }
    
        @Override
        public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response){
            if ("application/json".equals(request.getHeader("Content-Type"))) {
                try {
                    /*
                     * HttpServletRequest can be read only once
                     */
                    StringBuffer sb = new StringBuffer();
                    String line = null;
    
                    BufferedReader reader = request.getReader();
                    while ((line = reader.readLine()) != null){
                        sb.append(line);
                    }
    
                    //json transformation
                    ObjectMapper mapper = new ObjectMapper();
                    LoginRequest loginRequest = mapper.readValue(sb.toString(), LoginRequest.class);
    
                    this.jsonUsername = loginRequest.getUsername();
                    this.jsonPassword = loginRequest.getPassword();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
    
            return super.attemptAuthentication(request, response);
        }
    }
    

    CustomAuthenticationSuccessHandler 클래스 :

    public class CustomAuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {
    
        public void onAuthenticationSuccess(
                HttpServletRequest request,
                HttpServletResponse response,
                Authentication auth
        )throws IOException, ServletException {
    
            if ("application/json".equals(request.getHeader("Content-Type"))) {
                /*
                 * USED if you want to AVOID redirect to LoginSuccessful.htm in JSON authentication
                 */         
                response.getWriter().print("{\"responseCode\":\"SUCCESS\"}");
                response.getWriter().flush();
            } else {
                super.onAuthenticationSuccess(request, response, auth);
            }
        }
    }
    
  3. ==============================

    3.

    public class AuthenticationFilter extends UsernamePasswordAuthenticationFilter {
        @Override
        public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response){
            if (!request.getMethod().equals("POST")) {
                throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
            }
    
            LoginRequest loginRequest = this.getLoginRequest(request);
    
            UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(loginRequest.getUsername(), loginRequest.getPassword());
    
            setDetails(request, authRequest);
    
            return this.getAuthenticationManager().authenticate(authRequest);
        }
    
        private LoginRequest getLoginRequest(HttpServletRequest request) {
            BufferedReader reader = null;
            LoginRequest loginRequest = null;
            try {
                reader = request.getReader();
                Gson gson = new Gson();
                loginRequest = gson.fromJson(reader, LoginRequest.class);
            } catch (IOException ex) {
                Logger.getLogger(AuthenticationFilter.class.getName()).log(Level.SEVERE, null, ex);
            } finally {
                try {
                    reader.close();
                } catch (IOException ex) {
                    Logger.getLogger(AuthenticationFilter.class.getName()).log(Level.SEVERE, null, ex);
                }
            }
    
            if (loginRequest == null) {
                loginRequest = new LoginRequest();
            }
    
            return loginRequest;
        }
    }
    
  4. ==============================

    4.로그인 요청에 대해 다른 요청 본문 파서 만 사용하려면 UsernamePasswordAuthenticationFilter를 확장하고 tryAuthentication 메소드를 재정의하십시오. 기본적으로 UsernamePasswordAuthenticationFilter는 URL로 인코딩 된 데이터를 구문 분석하고 UsernamePasswordAuthenticationToken을 만듭니다. 이제 응용 프로그램에 보내는 내용을 구문 분석 할 파서를 만들어야합니다.

    로그인 요청에 대해 다른 요청 본문 파서 만 사용하려면 UsernamePasswordAuthenticationFilter를 확장하고 tryAuthentication 메소드를 재정의하십시오. 기본적으로 UsernamePasswordAuthenticationFilter는 URL로 인코딩 된 데이터를 구문 분석하고 UsernamePasswordAuthenticationToken을 만듭니다. 이제 응용 프로그램에 보내는 내용을 구문 분석 할 파서를 만들어야합니다.

    다음은 { "username": "someusername", "password": "somepassword"} 구문을 분석하는 예입니다.

    public class CustomUsernamePasswordAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
    
        @Override
        public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
            try {
                BufferedReader reader = request.getReader();
                StringBuffer sb = new StringBuffer();
                String line = null;
                while ((line = reader.readLine()) != null) {
                    sb.append(line);
                }
                String parsedReq = sb.toString();
                if (parsedReq != null) {
                    ObjectMapper mapper = new ObjectMapper();
                    AuthReq authReq = mapper.readValue(parsedReq, AuthReq.class);
                    return new UsernamePasswordAuthenticationToken(authReq.getUsername(), authReq.getPassword());
                }
            } catch (Exception e) {
                System.out.println(e.getMessage());
                throw new InternalAuthenticationServiceException("Failed to parse authentication request body");
            }
            return null;
        }
    
        @Data
        public static class AuthReq {
            String username;
            String password;
        }
    
    }
    

    스 니펫 요청 본문은 문자열로 추출되어 AuthReq 객체에 매핑됩니다 (@Data 주석은 lombok lib에서 제공되며 seters 및 getters를 생성합니다). 기본 AuthenticationProvider로 전달 될 UsernamePasswordAuthenticationToken을 만들 수 있습니다.

    이제 WebSecurityConfigurerAdapter를 확장하고 cnofigure 메서드를 재정 의하여 기존 필터를 바꿀 수 있습니다.

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
                .antMatchers("/", "/login", "/logout").permitAll()
                .anyRequest().authenticated()
            .and().addFilterAt(new CustomUsernamePasswordAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
            .formLogin().loginProcessingUrl("/login")
            .and()
            .csrf().disable();
    }
    

    addFilterAt 메서드를 사용하면 기본 UsernamePasswordAuthenticationFilter를 바꿀 수 있습니다. @EnableWebSecurity 주석을 사용하는 것을 잊지 마세요.

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

    5.이 게시물의 또 다른 방법은 Controller에서 직접 스프링 보안 인증을 직접 관리하는 것입니다. 이 방식으로 JSON 입력을 관리하고 로그인 리디렉션을 피하는 것은 매우 간단합니다.

    이 게시물의 또 다른 방법은 Controller에서 직접 스프링 보안 인증을 직접 관리하는 것입니다. 이 방식으로 JSON 입력을 관리하고 로그인 리디렉션을 피하는 것은 매우 간단합니다.

    @Autowired
    AuthenticationManager authenticationManager;
    
    @ResponseBody
    @RequestMapping(value="/login.json", method = RequestMethod.POST)
    public JsonResponse mosLogin(@RequestBody LoginRequest loginRequest, HttpServletRequest request) {
        JsonResponse response = null;
    
        try {
            UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(loginRequest.getUsername(), loginRequest.getPassword());
            token.setDetails(new WebAuthenticationDetails(request));
    
            Authentication auth = authenticationManager.authenticate(token);
            SecurityContext securityContext = SecurityContextHolder.getContext();
            securityContext.setAuthentication(auth);
    
            if(auth.isAuthenticated()){
                HttpSession session = request.getSession(true);
                session.setAttribute("SPRING_SECURITY_CONTEXT", securityContext);
    
                LoginResponse loginResponse = new LoginResponse();
                loginResponse.setResponseCode(ResponseCodeType.SUCCESS);
                response = loginResponse;   
            }else{
                SecurityContextHolder.getContext().setAuthentication(null);
    
                ErrorResponse errorResponse = new ErrorResponse();
                errorResponse.setResponseCode(ResponseCodeType.ERROR);
                response = errorResponse;
            }   
        } catch (Exception e) {     
            ErrorResponse errorResponse = new ErrorResponse();
            errorResponse.setResponseCode(ResponseCodeType.ERROR);
            response = errorResponse;           
        }
        return response;
    }
    
  6. ==============================

    6.스프링 부트 애플리케이션에서 JSON 자격 증명으로 로그인하기 위해 fl4l 및 oe.elvik의 답변을 적용했습니다. 나는 주석 기반 bean 설정으로 작업 중이다.

    스프링 부트 애플리케이션에서 JSON 자격 증명으로 로그인하기 위해 fl4l 및 oe.elvik의 답변을 적용했습니다. 나는 주석 기반 bean 설정으로 작업 중이다.

    참조 된 응답에서 인증 관리자가 삽입되는 사용자 지정 필터가 만들어집니다. 이렇게하려면 인증 관리자가 Spring Bean으로 존재해야합니다. 이를 수행하는 방법에 대한 링크가 있습니다 : https://stackoverflow.com/a/21639553/3950535.

  7. ==============================

    7.다음 예제를보십시오 : https://github.com/fuhaiwei/springboot_security_restful_api

    다음 예제를보십시오 : https://github.com/fuhaiwei/springboot_security_restful_api

    @EnableWebSecurity
    @EnableGlobalMethodSecurity(prePostEnabled = true)
    public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
    
        @Autowired
        private UserDetailsService userDetailsService;
    
        @Autowired
        private CustomLoginHandler customLoginHandler;
    
        @Autowired
        private CustomLogoutHandler customLogoutHandler;
    
        @Autowired
        private CustomAccessDeniedHandler customAccessDeniedHandler;
    
        protected void configure(AuthenticationManagerBuilder auth) throws Exception {
            auth.userDetailsService(userDetailsService);
        }
    
        protected void configure(HttpSecurity http) throws Exception {
            http.authorizeRequests()
                    .antMatchers("/api/admin/**").hasRole("ADMIN")
                    .antMatchers("/api/basic/**").hasRole("BASIC")
                    .antMatchers("/api/session").permitAll()
                    .antMatchers(HttpMethod.GET).permitAll()
                    .antMatchers("/api/**").hasRole("BASIC");
    
            http.formLogin();
    
            http.logout()
                    .logoutUrl("/api/session/logout")
                    .addLogoutHandler(customLogoutHandler)
                    .logoutSuccessHandler(customLogoutHandler);
    
            http.exceptionHandling()
                    .accessDeniedHandler(customAccessDeniedHandler)
                    .authenticationEntryPoint(customAccessDeniedHandler);
    
            http.csrf()
                    .ignoringAntMatchers("/api/session/**");
    
            http.addFilterBefore(new AcceptHeaderLocaleFilter(), UsernamePasswordAuthenticationFilter.class);
    
            http.addFilterAt(customAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
    
            http.addFilterAfter(new CsrfTokenResponseHeaderBindingFilter(), CsrfFilter.class);
        }
    
        private CustomAuthenticationFilter customAuthenticationFilter() throws Exception {
            CustomAuthenticationFilter filter = new CustomAuthenticationFilter();
            filter.setAuthenticationSuccessHandler(customLoginHandler);
            filter.setAuthenticationFailureHandler(customLoginHandler);
            filter.setAuthenticationManager(authenticationManager());
            filter.setFilterProcessesUrl("/api/session/login");
            return filter;
        }
    
        private static void responseText(HttpServletResponse response, String content) throws IOException {
            response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
            byte[] bytes = content.getBytes(StandardCharsets.UTF_8);
            response.setContentLength(bytes.length);
            response.getOutputStream().write(bytes);
            response.flushBuffer();
        }
    
        @Component
        public static class CustomAccessDeniedHandler extends BaseController implements AuthenticationEntryPoint, AccessDeniedHandler {
            // NoLogged Access Denied
            @Override
            public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException {
                responseText(response, errorMessage(authException.getMessage()));
            }
    
            // Logged Access Denied
            @Override
            public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException {
                responseText(response, errorMessage(accessDeniedException.getMessage()));
            }
        }
    
        @Component
        public static class CustomLoginHandler extends BaseController implements AuthenticationSuccessHandler, AuthenticationFailureHandler {
            // Login Success
            @Override
            public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException {
                LOGGER.info("User login successfully, name={}", authentication.getName());
                responseText(response, objectResult(SessionController.getJSON(authentication)));
            }
    
            // Login Failure
            @Override
            public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException {
                responseText(response, errorMessage(exception.getMessage()));
            }
        }
    
        @Component
        public static class CustomLogoutHandler extends BaseController implements LogoutHandler, LogoutSuccessHandler {
            // Before Logout
            @Override
            public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
    
            }
    
            // After Logout
            @Override
            public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException {
                responseText(response, objectResult(SessionController.getJSON(null)));
            }
        }
    
        private static class AcceptHeaderLocaleFilter implements Filter {
            private AcceptHeaderLocaleResolver localeResolver;
    
            private AcceptHeaderLocaleFilter() {
                localeResolver = new AcceptHeaderLocaleResolver();
                localeResolver.setDefaultLocale(Locale.US);
            }
    
            @Override
            public void init(FilterConfig filterConfig) {
            }
    
            @Override
            public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
                Locale locale = localeResolver.resolveLocale((HttpServletRequest) request);
                LocaleContextHolder.setLocale(locale);
    
                chain.doFilter(request, response);
            }
    
            @Override
            public void destroy() {
            }
        }    
    }
    
    
    
    public class CustomAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
    
        @Override
        public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
            UsernamePasswordAuthenticationToken authRequest;
            try (InputStream is = request.getInputStream()) {
                DocumentContext context = JsonPath.parse(is);
                String username = context.read("$.username", String.class);
                String password = context.read("$.password", String.class);
                authRequest = new UsernamePasswordAuthenticationToken(username, password);
            } catch (IOException e) {
                e.printStackTrace();
                authRequest = new UsernamePasswordAuthenticationToken("", "");
            }
            setDetails(request, authRequest);
            return this.getAuthenticationManager().authenticate(authRequest);
        }
    
    }
    
  8. ==============================

    8.다음은 위의 솔루션에 대한 Java 구성입니다.

    다음은 위의 솔루션에 대한 Java 구성입니다.

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable()
                .addFilterBefore(authenticationFilter(),UsernamePasswordAuthenticationFilter.class)
                .authorizeRequests()
                .anyRequest().authenticated()
                .and()
                .formLogin()
                .loginPage("/login")
                .permitAll();
    }
    
    @Bean
    public AuthenticationFilter authenticationFilter() throws Exception{
        AuthenticationFilter authenticationFilter = new AuthenticationFilter();
        authenticationFilter.setUsernameParameter("username");
        authenticationFilter.setPasswordParameter("password");
        authenticationFilter.setAuthenticationManager(authenticationManager());
        authenticationFilter.setFilterProcessesUrl("/login");
        authenticationFilter.setAuthenticationSuccessHandler(successHandler());
        return authenticationFilter;
    }
    
    @Bean
    public SuccessHandler successHandler(){
        return new SuccessHandler();
    }
    
  9. from https://stackoverflow.com/questions/19500332/spring-security-and-json-authentication by cc-by-sa and MIT license