example json spring spring-mvc spring-security

json - example - spring security rest



Spring Security y JSON Authentication (7)

Aquí está la configuración java para las soluciones anteriores:

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

Tengo una aplicación en Spring / Spring-mvc que usa totalmente las comunicaciones JSON. Ahora necesito autenticar mi aplicación con spring security 3 (que usa LdapAuthenticationProvider) a través de JSON.

El formulario de envío predeterminado para el período de vencimiento de primavera requiere un POST como este:

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

Pero quiero pasar un objeto JSON como este:

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

Leo muchos posts como this , este otro o este sin suerte, en todos los casos ajax se hace un POST como el anterior.

¿Algunas ideas?


De acuerdo con las sugerencias de Kevin,
y después de leer estas publicaciones: 1 , 2 , documentación 3 , y gracias a this publicación en el blog,
Escribí mi propio FORM_LOGIN_FILTER para administrar directamente JSON antes de la autenticación.
Pego mi código para la comunidad.

El objetivo es otorgar tanto la autenticación POST clásica del navegador como la autenticación basada en JSON. También en la autenticación JSON quiero evitar el redireccionamiento a loginSuccesful.htm

En contexto:

<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" />

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

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


Mira este ejemplo: 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); } }


Otra forma, de acuerdo con this publicación, es administrar manualmente la autenticación de seguridad de primavera directamente en el controlador.
De esta manera es muy sencillo administrar la entrada JSON y evitar el redireccionamiento de inicio de sesión:

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



Si solo desea un analizador de cuerpo de solicitud diferente para la solicitud de inicio de sesión, simplemente amplíe UsernamePasswordAuthenticationFilter y anule el método de attemptAuthentication . De forma predeterminada UsernamePasswordAuthenticationFilter analizará los datos codificados de url y crear UsernamePasswordAuthenticationToken partir de él. Ahora solo necesita hacer un analizador que analizará todo lo que envíe a la aplicación.

Aquí hay un ejemplo que analizará {"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; } }

En el cuerpo de la solicitud de fragmento, se extrae a la cadena y se asigna al objeto AuthReq (la anotación @Data es de lombok lib, generará seters y getters). De lo que puede hacer UsernamePasswordAuthenticationToken que se pasará a AuthenticationProvider defecto.

Ahora puede extender WebSecurityConfigurerAdapter y anular el método cnofigure para reemplazar el filtro anterior.

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

Con el método addFilterAt , se reemplaza el nombre de UsernamePasswordAuthenticationFilter predeterminado addFilterAt . No te olvides de usar la anotación @EnableWebSecurity .


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