[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.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.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.
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.로그인 요청에 대해 다른 요청 본문 파서 만 사용하려면 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.이 게시물의 또 다른 방법은 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.스프링 부트 애플리케이션에서 JSON 자격 증명으로 로그인하기 위해 fl4l 및 oe.elvik의 답변을 적용했습니다. 나는 주석 기반 bean 설정으로 작업 중이다.
스프링 부트 애플리케이션에서 JSON 자격 증명으로 로그인하기 위해 fl4l 및 oe.elvik의 답변을 적용했습니다. 나는 주석 기반 bean 설정으로 작업 중이다.
참조 된 응답에서 인증 관리자가 삽입되는 사용자 지정 필터가 만들어집니다. 이렇게하려면 인증 관리자가 Spring Bean으로 존재해야합니다. 이를 수행하는 방법에 대한 링크가 있습니다 : https://stackoverflow.com/a/21639553/3950535.
-
==============================
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.다음은 위의 솔루션에 대한 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(); }
from https://stackoverflow.com/questions/19500332/spring-security-and-json-authentication by cc-by-sa and MIT license
'SPRING' 카테고리의 다른 글
[SPRING] 이것은 Tomcat에서 메모리 누출을 일으킬 가능성이 매우 높습니까? (0) | 2018.12.11 |
---|---|
[SPRING] @ComponentScan에서 @Component 제외 (0) | 2018.12.11 |
[SPRING] Spring Data JPA에서 두 개의 테이블 엔티티 조인 (0) | 2018.12.11 |
[SPRING] Spring AOP에서의 프록시 사용 (0) | 2018.12.11 |
[SPRING] 스프링 부트에서 HTTP 응답 캐싱을 사용하는 방법 (0) | 2018.12.11 |