por example español ejemplo basada autenticacion rest spring-security spring-boot restful-authentication stateless

example - Asegurar la API REST utilizando tokens personalizados(sin estado, sin interfaz de usuario, sin cookies, sin autenticación básica, sin OAuth, sin página de inicio de sesión)



oauth2 (5)

Hay muchas pautas, ejemplos de códigos que muestran cómo proteger la API REST con Spring Security, pero la mayoría asume un cliente web y habla de la página de inicio de sesión, la redirección, el uso de cookies, etc. Puede ser incluso un filtro simple que busca el token personalizado en el encabezado HTTP podría ser suficiente. ¿Cómo implemento la seguridad para los requisitos a continuación? ¿Hay algún proyecto de github que haga lo mismo? Mi conocimiento sobre la seguridad de primavera es limitado, así que si hay una forma más sencilla de implementarlo con la seguridad de primavera, háganmelo saber.

  • API REST servida por back-end sin estado sobre HTTPS
  • el cliente podría ser una aplicación web, una aplicación móvil, cualquier aplicación de estilo SPA, API de terceros
  • sin autenticación básica, sin cookies, sin interfaz de usuario (sin JSP / HTML / recursos estáticos), sin redirecciones, sin proveedor de OAuth.
  • token personalizado configurado en encabezados HTTPS
  • La validación del token realizada contra el almacenamiento externo (como MemCached / Redis / o incluso cualquier RDBMS)
  • Todas las API deben ser autenticadas, excepto para las rutas seleccionadas (como / login, / signup, / public, etc.)

Uso Springboot, seguridad de primavera, etc. prefiero una solución con configuración de Java (sin XML)


El código protege todos los puntos finales, pero estoy seguro de que puedes jugar con eso :). El token se almacena en Redis usando Spring Boot Starter Security y usted tiene que definir nuestro propio UserDetailsService que pasa a AuthenticationManagerBuilder .

Para abreviar, copie y pegue EmbeddedRedisConfiguration y SecurityConfig y reemplace AuthenticationManagerBuilder por su lógica.

HTTP:

Token de solicitud: envía contenido HTTP auth básico en un encabezado de solicitud. Un token se devuelve en un encabezado de respuesta.

http --print=hH -a user:password localhost:8080/v1/users GET /v1/users HTTP/1.1 Accept: */* Accept-Encoding: gzip, deflate Authorization: Basic dXNlcjpwYXNzd29yZA== Connection: keep-alive Host: localhost:8080 User-Agent: HTTPie/0.9.3 HTTP/1.1 200 OK Cache-Control: no-cache, no-store, max-age=0, must-revalidate Content-Length: 4 Content-Type: text/plain;charset=UTF-8 Date: Fri, 06 May 2016 09:44:23 GMT Expires: 0 Pragma: no-cache Server: Apache-Coyote/1.1 X-Application-Context: application X-Content-Type-Options: nosniff X-Frame-Options: DENY X-XSS-Protection: 1; mode=block x-auth-token: cacf4a97-75fe-464d-b499-fcfacb31c8af

La misma solicitud pero usando token:

http --print=hH localhost:8080/v1/users ''x-auth-token: cacf4a97-75fe-464d-b499-fcfacb31c8af'' GET /v1/users HTTP/1.1 Accept: */* Accept-Encoding: gzip, deflate Connection: keep-alive Host: localhost:8080 User-Agent: HTTPie/0.9.3 x-auth-token: cacf4a97-75fe-464d-b499-fcfacb31c8af HTTP/1.1 200 OK Cache-Control: no-cache, no-store, max-age=0, must-revalidate Content-Length: 4 Content-Type: text/plain;charset=UTF-8 Date: Fri, 06 May 2016 09:44:58 GMT Expires: 0 Pragma: no-cache Server: Apache-Coyote/1.1 X-Application-Context: application X-Content-Type-Options: nosniff X-Frame-Options: DENY X-XSS-Protection: 1; mode=block

Si pasa un nombre de usuario / contraseña o un token incorrecto, obtiene 401.

JAVA

build.gradle esas dependencias en build.gradle

compile("org.springframework.session:spring-session-data-redis:1.0.1.RELEASE") compile("org.springframework.boot:spring-boot-starter-security") compile("org.springframework.boot:spring-boot-starter-web") compile("com.github.kstyrc:embedded-redis:0.6")

Luego la configuración de Redis

@Configuration @EnableRedisHttpSession public class EmbeddedRedisConfiguration { private static RedisServer redisServer; @Bean public JedisConnectionFactory connectionFactory() throws IOException { redisServer = new RedisServer(Protocol.DEFAULT_PORT); redisServer.start(); return new JedisConnectionFactory(); } @PreDestroy public void destroy() { redisServer.stop(); } }

Configuración de seguridad:

@Configuration @EnableWebSecurity @Order(SecurityProperties.ACCESS_OVERRIDE_ORDER) public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired UserService userService; @Override protected void configure(AuthenticationManagerBuilder builder) throws Exception { builder.userDetailsService(userService); } @Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() .anyRequest().authenticated() .and() .requestCache() .requestCache(new NullRequestCache()) .and() .httpBasic(); } @Bean public HttpSessionStrategy httpSessionStrategy() { return new HeaderHttpSessionStrategy(); } }

Usualmente en los tutoriales se encuentra AuthenticationManagerBuilder usando inMemoryAuthentication pero hay muchas más opciones (LDAP, ...) Solo eche un vistazo a la definición de la clase. Estoy usando userDetailsService que requiere el objeto UserDetailsService .

Y finalmente mi servicio al usuario usando CrudRepository .

@Service public class UserService implements UserDetailsService { @Autowired UserRepository userRepository; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { UserAccount userAccount = userRepository.findByEmail(username); if (userAccount == null) { return null; } return new User(username, userAccount.getPassword(), AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_USER")); } }


Mi aplicación de ejemplo hace exactamente esto: asegurar los puntos finales REST usando Spring Security en un escenario sin estado. Las llamadas REST individuales se autentican usando un encabezado HTTP. La información de autenticación se almacena en el servidor en una memoria caché en memoria y proporciona la misma semántica que las ofrecidas por la sesión HTTP en una aplicación web típica. La aplicación utiliza la infraestructura completa de Spring Security con un código personalizado mínimo. Sin filtros desnudos, sin código fuera de la infraestructura de Spring Security.

La idea básica es implementar los siguientes cuatro componentes de Spring Security:

  1. org.springframework.security.web.AuthenticationEntryPoint para atrapar llamadas REST que requieren autenticación pero faltan el token de autenticación requerido y, por lo tanto, denegar las solicitudes.
  2. org.springframework.security.core.Authentication para contener la información de autenticación requerida para la API REST.
  3. org.springframework.security.authentication.AuthenticationProvider para realizar la autenticación real (en una base de datos, un servidor LDAP, un servicio web, etc.).
  4. org.springframework.security.web.context.SecurityContextRepository para mantener el token de autenticación entre solicitudes HTTP. En la muestra, la implementación guarda el token en una instancia de EHCACHE.

El ejemplo usa la configuración XML pero puede encontrar fácilmente la configuración Java equivalente.


Otro ejemplo de proyecto que utiliza JWT - Jhipster

Intente generar una aplicación de Microservice utilizando JHipster. Genera una plantilla con integración fuera de la caja entre Spring Security y JWT.

https://jhipster.github.io/security/



Tienes razón, no es fácil y no hay muchos buenos ejemplos. Los ejemplos que vi lo hicieron para que no pudieras usar otros elementos de seguridad el uno al lado del otro. Hice algo similar recientemente, esto es lo que hice.

Necesita un token personalizado para mantener el valor de su encabezado

public class CustomToken extends AbstractAuthenticationToken { private final String value; //Getters and Constructor. Make sure getAutheticated returns false at first. //I made mine "immutable" via: @Override public void setAuthenticated(boolean isAuthenticated) { //It doesn''t make sense to let just anyone set this token to authenticated, so we block it //Similar precautions are taken in other spring framework tokens, EG: UsernamePasswordAuthenticationToken if (isAuthenticated) { throw new IllegalArgumentException(MESSAGE_CANNOT_SET_AUTHENTICATED); } super.setAuthenticated(false); } }

Necesita un filtro de seguridad de primavera para extraer el encabezado y pedirle al administrador que lo autentique, algo así como texto enfatizado

public class CustomFilter extends AbstractAuthenticationProcessingFilter { public CustomFilter(RequestMatcher requestMatcher) { super(requestMatcher); this.setAuthenticationSuccessHandler((request, response, authentication) -> { /* * On success the desired action is to chain through the remaining filters. * Chaining is not possible through the success handlers, because the chain is not accessible in this method. * As such, this success handler implementation does nothing, and chaining is accomplished by overriding the successfulAuthentication method as per: * http://docs.spring.io/autorepo/docs/spring-security/3.2.4.RELEASE/apidocs/org/springframework/security/web/authentication/AbstractAuthenticationProcessingFilter.html#successfulAuthentication(javax.servlet.http.HttpServletRequest,%20javax.servlet.http.HttpServletResponse,%20javax.servlet.FilterChain,%20org.springframework.security.core.Authentication) * "Subclasses can override this method to continue the FilterChain after successful authentication." */ }); } @Override public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException { String tokenValue = request.getHeader("SOMEHEADER"); if(StringUtils.isEmpty(tokenValue)) { //Doing this check is kinda dumb because we check for it up above in doFilter //..but this is a public method and we can''t do much if we don''t have the header //also we can''t do the check only here because we don''t have the chain available return null; } CustomToken token = new CustomToken(tokenValue); token.setDetails(authenticationDetailsSource.buildDetails(request)); return this.getAuthenticationManager().authenticate(token); } /* * Overriding this method to maintain the chaining on authentication success. * http://docs.spring.io/autorepo/docs/spring-security/3.2.4.RELEASE/apidocs/org/springframework/security/web/authentication/AbstractAuthenticationProcessingFilter.html#successfulAuthentication(javax.servlet.http.HttpServletRequest,%20javax.servlet.http.HttpServletResponse,%20javax.servlet.FilterChain,%20org.springframework.security.core.Authentication) * "Subclasses can override this method to continue the FilterChain after successful authentication." */ @Override protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException { //if this isn''t called, then no auth is set in the security context holder //and subsequent security filters can still execute. //so in SOME cases you might want to conditionally call this super.successfulAuthentication(request, response, chain, authResult); //Continue the chain chain.doFilter(request, response); } }

Registre su filtro personalizado en la cadena de seguridad de primavera

@Configuration public static class ResourceEndpointsSecurityConfig extends WebSecurityConfigurerAdapter { //Note, we don''t register this as a bean as we don''t want it to be added to the main Filter chain, just the spring security filter chain protected AbstractAuthenticationProcessingFilter createCustomFilter() throws Exception { CustomFilter filter = new CustomFilter( new RegexRequestMatcher("^/.*", null)); filter.setAuthenticationManager(this.authenticationManagerBean()); return filter; } @Override protected void configure(HttpSecurity http) throws Exception { http //fyi: This adds it to the spring security proxy filter chain .addFilterBefore(createCustomFilter(), AnonymousAuthenticationFilter.class) } }

Un proveedor de autenticación personalizado para validar ese token extraído con el filtro.

public class CustomAuthenticationProvider implements AuthenticationProvider { @Override public Authentication authenticate(Authentication auth) throws AuthenticationException { CustomToken token = (CustomToken)auth; try{ //Authenticate token against redis or whatever you want //This i found weird, you need a Principal in your Token...I use User //I found this to be very redundant in spring security, but Controller param resolving will break if you don''t do this...anoying org.springframework.security.core.userdetails.User principal = new User(...); //Our token resolved to a username so i went with this token...you could make your CustomToken take the principal. getCredentials returns "NO_PASSWORD"..it gets cleared out anyways. also the getAuthenticated for the thing you return should return true now return new UsernamePasswordAuthenticationToken(principal, auth.getCredentials(), principal.getAuthorities()); } catch(Expection e){ //TODO throw appropriate AuthenticationException types throw new BadCredentialsException(MESSAGE_AUTHENTICATION_FAILURE, e); } } @Override public boolean supports(Class<?> authentication) { return CustomToken.class.isAssignableFrom(authentication); } }

Finalmente, registre su proveedor como un bean para que el administrador de autenticación lo encuentre en alguna clase @Configuration. Probablemente podrías simplemente @Component también, prefiero este método

@Bean public AuthenticationProvider createCustomAuthenticationProvider(injectedDependencies) { return new CustomAuthenticationProvider(injectedDependencies); }