programing

스프링 보안 및 JSON 인증

muds 2023. 3. 12. 11:25
반응형

스프링 보안 및 JSON 인증

봄/봄-mvc에 JSON 통신을 모두 사용하는 어플리케이션이 있습니다.여기서 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"}

이런 투고를 많이 읽었는데, 이 투고든 운이 없는 투고든, 모든 AJAX의 경우 위와 같이 POST를 합니다.

아이디어 있어요?

의 ★★★★★★★★★★★★★★★★★★★★★★★★★★★★★」
그리고 이 게시물 1, 2, 문서 3을 읽고 이 블로그 게시물 덕분에
JSON은 FORM_LOGIN_FILTER를 사용합니다.
지역 사회를 위해 코드를 붙입니다.

목표는 JSON 기반 인증을 사용한 기존 브라우저 형식의 POST 인증을 모두 부여하는 것입니다.또한 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" />

커스텀 사용자명 패스워드AuthenticationFilter 클래스:

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);
    }
}

커스텀 인증Success Handler 클래스:

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);
        }
    }
}
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;
    }
}

JSON을 해석하는 독자적인 보안 필터를 작성할 수 있습니다.

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

Basic Authentication Filter 는, 다음의 참조로서 사용할 수 있습니다.

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

" "를 하십시오.UsernamePasswordAuthenticationFilter「」를 덮어씁니다.attemptAuthentication디폴트로는 " " 입니다.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 )@Datalombok lib, seters, getters)를 사용하다 수 것보다 더 많이.UsernamePasswordAuthenticationToken 상태가 .AuthenticationProvider.

으로, 「 」를 확장할 수 되었습니다.WebSecurityConfigurerAdapter 오래된 filtercnofig 메서드를 .

@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();
}

★★★★★★★★★★★★★★★★ addFilterAtmethod you replace defaultUsernamePasswordAuthenticationFilter사용하는 것을 잊지 마세요.@EnableWebSecurity석입니니다다

게시물에 따르면 다른 방법은 컨트롤러에서 직접 스프링 보안 인증을 수동으로 관리하는 것입니다.
이렇게 하면 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;
}

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);
    }

}

Spring Boot 어플리케이션에서 JSON credential을 사용하여 로그인하기 위해 fl4l과 oe.elvik의 답변을 적용했습니다.주석 기반의 콩 구성을 사용하고 있습니다.

참조된 응답에서는 인증 매니저가 삽입되는 커스텀필터가 생성됩니다.이를 위해서는 인증 매니저가 스프링빈으로 존재해야 합니다.다음은 그 방법에 대한 링크입니다.https://stackoverflow.com/a/21639553/3950535

위의 솔루션의 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();
}

언급URL : https://stackoverflow.com/questions/19500332/spring-security-and-json-authentication

반응형