27 jun 2019

Securizando APIs (II) [Roles]: SpringBoot + Security + JWT + Auth en Base de datos

Introduccion


Vamos a continuar/ampliar el anterior post que os dejo aqui.
En la anterior entrada vimos la manera, de todas las posibles soluciones que puedes tener con Spring Security, de poder tener una arquitectura de microservicios, haciendo que uno de esos microservicios sea unod e Authentication, mediante una conexión en BD y posterior validación de las peticiones REST que se generen a través de un filter que haga precisamente eso, un filtro que extraiga la cabecera, recupere el token y lo valide.
Ahora ampliamos esa primera entrega, con la introdución a la autorización (¿A qué tenemos persmiso para acceder?) y lo resolvemos con los ROLES.
Como siempre os dejo el acceso al código de la aplicación desde mi GitHub.

Algo de teoría


Aunque no es mi fuerte, y no me gusta, pero algo hay que tener claro antes de empezar.
Spring Security nos permite proteger nuestros endpoints con las etiquetas @PreAuthorize y @Secured indicando los roles que tienen acceso.
Podéis suponer que no son lo mismo, aunque comparten cosas:

  • @PreAuthorize es una anotación más nueva que @Secured también es más flexible.
  • @PreAuthorize soporta Spring Expression Language (SpEL) pudiendo utilizar expresiones como hasRole, permitAl. 
    • Con @Secured solo podemos referencia los ROLES permitidos.
  • @Secured cuando se tienen varios roles en la lista que le pasemos se va a comportar como un OR. Con @PreAuthorize, admite expresiones (pudiendo usar AND, OR, NOT). Ejemplo:
    • @Secured({«ROLE_ADMIN», «ROLE_USER»}) Acceso a todos usuarios con rol admin O user.
    • @PreAuthorize(«hasRole(‘ROLE_ADMIN’) AND hasRole(‘ROLE_USER’)») Acceso a todos usuarios con rol admin Y user.
    • @Secured({«ROLE_ADMIN»}) y @PreAuthorize(«hasRole(‘ROLE_ADMIN’)») son equivalentes.


A codificar!

Controller securizado


Vamos a añadir un controller (a modo de prueba) para saber cómo se debe securizar con lo explicado antes.

/**
 * UserController
 */
@RestController
public class UserController {
    
    @PreAuthorize("hasRole('ROLE_USER') OR hasRole('ROLE_ADMIN')")
    @GetMapping("/userSecured")
    public String listUsersSecured() {

        return "listUsersSecured User or Admin";

    }

    @PreAuthorize("hasRole('ROLE_ADMIN')")
    @GetMapping("/userSecuredAdmin")
    public String listUsersSecuredAdmin() {

        return "listUsersSecured Admin";

    }

    @GetMapping("/users")
    public String listUsers() {

        return "listUsers";

    }

    @PreAuthorize("hasRole('ROLE_SVC')")
    @GetMapping("/userService")
    public String listUsersService() {

        return "listUsersService";

    }

}


Podemos ver 3 Endpoints, uno dice que puedes acceder si tienes el ROLE_USER o ROLE_ADMIN. El segundo solo para ROLE_ADMIN y el ultimo es para una supuesta llamada interna entre microservicios ROLE_SVC.

Cambios en el modelo


Ahora vamos a tener que hacer algunos cambios para que la tabla USER y una futura tabla ROLES las relacionemos. Esa relacion es N a N, porque un usuario podrá tener N roles, pero esos roles pueden estar presentes en N usuarios.
Por lo que nos sale una tercera tabla para relacionarlo, pero por suerte eso lo hace spring por nosotros.
* No voy a profundizar en JPA de cómo sería hacerlo a bajo nivel.


ROLES


@Entity
public class Role {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long id;

    @Column
    private String name;

    @Column
    private String description;

    public long getId() {
        return id;
    }

    public void setId(long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }

    public Role(String name, String description) {
        this.name = name;
        this.description = description;
    }

    public Role() {
    }
}


USERS
@Entity
public class Usuario {
    @Id
    @GeneratedValue
    private long id;
    public String username;
    public String password;

    @ManyToMany(fetch = FetchType.EAGER, cascade = { CascadeType.MERGE, CascadeType.REFRESH })
    @JoinTable(name = "USER_ROLES", joinColumns = { @JoinColumn(name = "USER_ID") }, inverseJoinColumns = {
            @JoinColumn(name = "ROLE_ID") })
    private Set roles;

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    @Override
    public String toString() {
        return "Usuario [id=" + id + ", password=" + password + ", username=" + username + "]";
    }

    public Usuario() {
    }

    public long getId() {
        return id;
    }

    public void setId(long id) {
        this.id = id;
    }

    public Usuario(String username, String password) {
        this.username = username;
        this.password = password;
    }

    public Set getRoles() {
        return roles;
    }

    public void setRoles(Set roles) {
        this.roles = roles;
    }

    public Usuario(String username, String password, Set roles) {
        this.username = username;
        this.password = password;
        this.roles = roles;
    }

}


Podemos ver el cambio desde la tabla de usuario, para poder obtener de un usuario los ROLES asociados que tiene, que es lo que verdaderamente nos importa.
Luego por ejemplo para dar de alta un usuario haremos algo del estilo


   Role role1 = new Role("ROLE_ADMIN", "Admin role");
   roleRepo.save(role1);
   Role role2 = new Role("ROLE_USER", "User role");
   roleRepo.save(role2);
   Role role3 = new Role("ROLE_SVC", "Service role");
   roleRepo.save(role3);

   Set roles = new HashSet<>();

   roles.add(role2);
   Usuario usuario1 = new Usuario("usuario", bCryptPasswordEncoder.encode("usuario"), roles); // Solo user
   userRepo.save(usuario1);

   roles.add(role1);
   Usuario usuario2 = new Usuario("admin", bCryptPasswordEncoder.encode("admin"),roles); // User & Admin
   userRepo.save(usuario2);

   roles.clear();
   roles.add(role3);
   Usuario usuario3 = new Usuario("service", bCryptPasswordEncoder.encode("service"),roles);
   userRepo.save(usuario3);


Configuramos


La parte de la configuración es sencilla, realmente la podríamos haber tenido activada sin que por ello la aplicación no fuese 100% funcional.

@Configuration
// @EnableGlobalMethodSecurity(securedEnabled = true) // Este es el método 1 de
// securización a usar: @Secured
@EnableGlobalMethodSecurity(prePostEnabled = true) // Este es el método 2 de securización a usar: @PreAuthorize
public class SecurityConfig extends WebSecurityConfigurerAdapter {

.....


}


Modificamos el Token


Para que con el token viaje el ROL debemos hacer un pequeño cambio. Aquí la variedad de soluciones es amplia, pero vamos a modificar la interfaz de UserDetails, para una vez obtenido el usuario de la BBDD podamos añadir la información correspondiente a los permisos.

Vamos a cambiar esta parte:

@Override
 public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
  Usuario usuario = usuarioRepository.findByUsername(username);
  if (usuario == null) {
   throw new UsernameNotFoundException(username);
  }
  return UserDetailsMapper.build(usuario);
  }


Vemos que aparece un UserDetailsMapper.
/**
 * UserDetailsMapper
 */
public class UserDetailsMapper {
    public static UserDetails build(Usuario user) {
        return new org.springframework.security.core.userdetails.User(user.getUsername(), user.getPassword(),
                getAuthorities(user));
    }

    private static Set getAuthorities(Usuario retrievedUser) {
        Set roles = retrievedUser.getRoles();
        Set authorities = new HashSet<>();
        roles.forEach(role -> authorities.add(new SimpleGrantedAuthority( role.getName())));
        return authorities;
    }
}


Como vemos la parte importante está en :
return new org.springframework.security.core.userdetails.User(user.getUsername(), user.getPassword(),getAuthorities(user));

Ese ultimo parámetro antes lo mandabamos vacío, porque todos tenían el mismo ROL, y ahora ya no, por lo que debemos sacar la lista de ROLES.


¿Y qué añadimos al TOKEN?


Pues tampoco tanto.
 @Override
 protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain,
   Authentication auth) throws IOException, ServletException {

  final String authorities = auth.getAuthorities().stream().map(GrantedAuthority::getAuthority)
    .collect(Collectors.joining(","));

  String token = Jwts.builder()
  .setIssuedAt(new Date())
  .setIssuer(SecurityConstants.ISSUER_INFO)
  .setSubject(((User) auth.getPrincipal()).getUsername())
    .claim(SecurityConstants.AUTHORITIES_KEY, authorities)
  .setExpiration(new Date(System.currentTimeMillis() + SecurityConstants.TOKEN_EXPIRATION_TIME))
  .signWith(SignatureAlgorithm.HS512, SecurityConstants.SUPER_SECRET_KEY)
  .compact();

  response.addHeader(SecurityConstants.HEADER_AUTHORIZACION_KEY,
    SecurityConstants.TOKEN_BEARER_PREFIX + " " + token);

  response.setContentType("application/json");
  response.setCharacterEncoding("UTF-8");
  response.getWriter().write("{\"" + SecurityConstants.TOKEN_PREFIX + "\":\"" + token + "\"}");
 }

Vemos como hemos añadido la parte de  .claim(SecurityConstants.AUTHORITIES_KEY, authorities)
Con la authorities que ahora sí, las hemos obtenido desde nuestro usuario.


¿Y para validarlo?


recordamos que teníamos otro filtro, que hacía eso precisamente. Cuando nos llegue un TOKEN, lo "ABRIMOS", buscamos la parte donde viaja el ROL y a security debemos pasarle los authorities. Pues añadimos esta parte:

   if (user != null) {

    final Collection authorities = Arrays
      .stream(claims.get(SecurityConstants.AUTHORITIES_KEY).toString().split(",")).map(SimpleGrantedAuthority::new)
      .collect(Collectors.toList());
    return new UsernamePasswordAuthenticationToken(user, "", authorities);
   }




Resumen

Como hemos visto, lo que le interesa a Security para poder authorizar son los authorities (permisos) que tiene ese asignados esa petición de recurso y que hemos decidido que viaje en el token.
Se podría por ejemplo que viaje solo el username, por ejemplo, se valida el token y si es válido, en otro servicio se le podrían buscar los permisos y que no viaje en el token...

Las configuraciones son variadas. Aquí solo expongo una de las tantas formas que me parece más clara para poder tenerlo en una arquitectura de microservicios.


Espero que sirva de ayuda y aclare alguna que otra duda.

Read More

20 jun 2019

Securizando APIs: SpringBoot + Security + JWT + Auth en Base de datos

1. Introducción

Dentro de la cantidad de "sobreinformación" que existe actualmente, voy a intentar dar una píldoras resumidas de los pasos a seguir para securizar vuestra API creada con SringBoot.
Vamos a usar JSON Web Tokens (JWT), activaremos Spring Security con la configuración adecuada, y vamos a securizarlo usando una Base de datos H2 como ejemplo de uso de JPA (A modo de ejemplo debería bastar)
Las configuraciones en detalle de cada uno de las piezas, las dejo a discreción del lector en profundizar en ellas.

Os dejo el código fuente de este tutorial en github para vuestra consulta.

2. JWT

Cogiendo la definición de la wikipedia, no vamos a repetirla, destacar solamente:
La mayor ventaja de los tokens sobre otros sistemas es el hecho de que no tenga estado (stateless). El backend no necesita mantener un registro de los tokens. Cada token es compacto y auto contenido. Contiene todos los datos necesarios para comprobar su validez, así como la información del usuario para las diferentes peticiones.

3. Vamos a por el código

La estructura del proyecto lo he dividido en la parte que pega al modelo (la cual está asociada con JPA a un ejemplo con H2 y se debería cambiar por el sistema que tenga cada uno) y la otra parte de Security que es la que sería común a cualquier microservicio, incluídos aquellos que tengan que hacer uso del JWT cuando quedan expuestos.

En la parte de Security no vamos a hacer uso de ningún controller, realmente no nos hace falta, si no que a través de los Filters que nos proporciona el propio framework de Spring y de Security podemos ser capaces de  tener las piezas necesarias para devolver y analizar un JWT.

Por ultimo tenemos una serie de ficheros de configuración (appication.properties, SecurityConfig y SecurityConstants)

4. A programar!


4.1 Autenticación

Lo primero que vamos a analizar es la Authenticación (Respondemos a la pregunta de ¿quién? ).
public class JWTAuthenticationFilter extends UsernamePasswordAuthenticationFilter {

 private AuthenticationManager authenticationManager;

 public JWTAuthenticationFilter(AuthenticationManager authenticationManager) {
  this.authenticationManager = authenticationManager;

  // Sin esta parte la url que atendería sería la de /login
  setFilterProcessesUrl(SecurityConstants.SIGNIN_URL);

 }

 @Override
 public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
   throws AuthenticationException {
  try {
   Usuario credenciales = new ObjectMapper().readValue(request.getInputStream(), Usuario.class);

   return authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(
     credenciales.getUsername(), credenciales.getPassword(), new ArrayList<>()));
  } catch (IOException e) {
   throw new RuntimeException(e);
  }
 }

 @Override
 protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain,
   Authentication auth) throws IOException, ServletException {

  String token = Jwts.builder().setIssuedAt(new Date()).setIssuer(SecurityConstants.ISSUER_INFO)
    .setSubject(((User) auth.getPrincipal()).getUsername()).claim("roles", "user") 
    .setExpiration(new Date(System.currentTimeMillis() + SecurityConstants.TOKEN_EXPIRATION_TIME))
    .signWith(SignatureAlgorithm.HS512, SecurityConstants.SUPER_SECRET_KEY).compact();

  response.addHeader(SecurityConstants.HEADER_AUTHORIZACION_KEY,
    SecurityConstants.TOKEN_BEARER_PREFIX + " " + token);

  response.setContentType("application/json");
  response.setCharacterEncoding("UTF-8");
  response.getWriter().write("{\"" + SecurityConstants.TOKEN_PREFIX + "\":\"" + token + "\"}");
 }
}



Vamos a resaltar las siguientes partes:
// Sin esta parte la url que atendería sería la de /login
setFilterProcessesUrl(SecurityConstants.SIGNIN_URL);

Por otro lado nos fijamos que en el proceso de authenticate le pasamos como queremos hacer la autenticación:
new UsernamePasswordAuthenticationToken()

Y en caso de tener éxito generamos el token. Este se devolverá en la cabecera y además en el body (no es necesario pero es más cómodo).

4.2 Autorización

Ahora vamos a responder ¿Para qué tengo permiso?.
public class JWTAuthorizationFilter extends BasicAuthenticationFilter {

 public JWTAuthorizationFilter(AuthenticationManager authManager) {
  super(authManager);
 }

 @Override
 protected void doFilterInternal(HttpServletRequest req, HttpServletResponse res, FilterChain chain)
   throws IOException, ServletException {
  String header = req.getHeader(SecurityConstants.HEADER_AUTHORIZACION_KEY);
  if (header == null || !header.startsWith(SecurityConstants.TOKEN_BEARER_PREFIX)) {
   chain.doFilter(req, res);
   return;
  }
  UsernamePasswordAuthenticationToken authentication = getAuthentication(req);
  SecurityContextHolder.getContext().setAuthentication(authentication);
  chain.doFilter(req, res);
 }

 private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request) {
  String token = request.getHeader(SecurityConstants.HEADER_AUTHORIZACION_KEY);
  if (token != null) {
   // Se procesa el token y se recupera el usuario.
   String user = Jwts.parser().setSigningKey(SecurityConstants.SUPER_SECRET_KEY)
     .parseClaimsJws(token.replace(SecurityConstants.TOKEN_BEARER_PREFIX, "")).getBody().getSubject();

   if (user != null) {
    return new UsernamePasswordAuthenticationToken(user, null, new ArrayList<>());
   }
   return null;
  }
  return null;
 }
}


*Este filtro podría cambiarse por OnePerRequest, o por un Generic, etc ...

Como vemos no es muy complejo de entender, simplemente buscamos en la cabecera el token. Si lo encontramos lo validamos, y si todo es correcto devolvemos UsernamePasswordAuthenticationToken(), pero en este caso sin la PASS porque ya no nos hace falta.

4.3 La "magia" de Spring

Una de las bondades de Spring es que muchas cosas ya las da intrínsecamente el propio framework. Es algo que está muy bien, aunque en mi caso me suele poner de los nervios y prefiero tener el control, pero que en la mayoría de los caso nos ahorra mucho trabajo.

@Service
public class UsuarioDetailsServiceImpl implements UserDetailsService {

 private UsuarioRespository usuarioRepository;

 public UsuarioDetailsServiceImpl(UsuarioRespository usuarioRepository) {
  this.usuarioRepository = usuarioRepository;
 }

 @Override
 public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
  Usuario usuario = usuarioRepository.findByUsername(username);
  if (usuario == null) {
   throw new UsernameNotFoundException(username);
  }
  return new User(usuario.getUsername(), usuario.getPassword(), emptyList());
 }
}


El mero hecho de implementar esta Interfaz, le da a Security la capacidad de saber cómo debe hacer para recuperar un usuario de la Base de Datos.

4.4 Configuración


El último paso es la parte de configuración de los filtros y de security. La que aquí pongo es muy elemental y se debe se bastante más exquisito en las cuestiones a añadir.
@Configuration
// @EnableGlobalMethodSecurity(securedEnabled = true) // Este es el método 1 de
// securización a usar: @Secured
@EnableGlobalMethodSecurity(prePostEnabled = true) // Este es el método 2 de securización a usar: @PreAuthorize
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    protected UserDetailsService userDetailsService;

    @Bean
    public BCryptPasswordEncoder bCryptPasswordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {

        http.cors().and().csrf().disable().authorizeRequests().antMatchers("/h2-console/**").permitAll().anyRequest()
                .authenticated().and().addFilter(new JWTAuthenticationFilter(authenticationManager()))
                .addFilter(new JWTAuthorizationFilter(authenticationManager())).sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS);

        http.headers().frameOptions().disable();

    }

    @Override
    public void configure(AuthenticationManagerBuilder auth) throws Exception {
        // Se define la clase que recupera los usuarios y el algoritmo para procesar las
        // passwords
        // Se podría usar un método custom en vez de bCryptPasswordEncoder()

        auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder());
    }

    @Bean
    CorsConfigurationSource corsConfigurationSource() {
        final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", new CorsConfiguration().applyPermitDefaultValues());
        return source;
    }

}



Como podemos ver, se ha usado un  método para desencriptar la contraseña (cada implementación debería sobreescribir este método y adaptarlo a sus necesidades.

Y por ultimo enlazamos "la magia":
        auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder());


5. Resumiendo


No he pretendido ser muy exquisito con todo el código aquí explicado, pero sí dejar "subrayado" aquellas partes que en otros lenguajes o frameworks (como Node) es más fácil de ver y quizá en Spring está algo más oculto.



Read More

9 jun 2019

Microservicios y Swagger (I)

Microservicios hoy


Todos tenemos claro que las aplicaciones monolíticas, como las habíamos conocido hasta ahora, digamos que ... "han cambiado".
Este tipo de aplicaciones son (fueron) buenas soluciones en determinados momentos, pero cuando estas han querido evolucionar, escalar, se han encontrado ante lo que los que llevamos ya tiempo en el sector, hemos ido sufriendo: codigo spaguetti, falta de documentación, mala organización del codigo, falta de aplicar patrones, refactorización difícil, alto acoplamiento, rendimiento que se degrada en cada versión, etc.
No voy a detallar las bondades que seguramente todos conocemos de las arquitecturas orientadas a microservicios, y tampoco creo ser la persona más entendida para hacerlo, pero sí que todos entendemos que este tipo de arquitecturas nacieron para mejorar precisamente el tener esas aplicaciones monolíticas infumables, que parece no querer morir nunca y que cada nuevo desarrollo se intenta pegar como un chicle pegajoso para seguir engordando la bola.
Pero amigo, los microservicios no son la panacea, si no ponemos un poco de orden, no será muy diferente la situación.

SWAGGER

Resultado de imagen de swagger
Swagger nos ayuda un poco al respecto, como dicen en su web: "Diseñe, describa y documente su API en el primer editor de código abierto completamente dedicado a las API basadas en OpenAPI".
Y eso es lo importante, tener un contrato con el que la otra parte, la que consume, sepa que esperar. Esto es muy cool, porque ambas partes (quien genera el servicio y quien lo consume - Front y Back por ejemplo) pueden desarrollar en paralelo siempre que  se respete ese contrato.

Swagger se puede integrar con las API REST de las siguientes maneras:
  1. Un enfoque de arriba hacia abajo: primero la especificación de API y luego generación de código 
  2. Un enfoque sencillo: primero el código de la API y luego la integración Swagger. Esto es bastante familiar y sobre todo útil cuando ya hay una API REST existente incorporada y la documentación de Swagger debe integrarse.

Ejemplo practico (caso 2)


Tienes el código en mi github


Configuracion del pom.xml



    <properties>
        <java.version>1.8</java.version>
        <springfox.version>2.4.0</springfox.version>
    </properties>
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
            <version>${springfox.version}</version>
        </dependency>
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger-ui</artifactId>
            <version>${springfox.version}</version>
        </dependency>



Configuración de Swagger

Con la anotacion @EnableSwagger2 en nuestra aplicacion SpringBoot ya deberíamos tener todo lo necesario.

@SpringBootApplication
@EnableSwagger2
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

}

Si navegamos a la url de nuestro microservicio deberíamos poder verlo:
http://localhost:9000/swagger-ui.html


Configuracion ad-hoc de Swagger

Podemos crear una configuracion particular, porque no queramos tener todo lo que nos añade swagger de la manera más genérica.
Para ello debemos añadir cada "feature" a la configuracion.


package com.mrknight.jpaexample.topic.config;

import static springfox.documentation.builders.PathSelectors.regex;

import com.google.common.base.Predicate;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

@EnableSwagger2
@Configuration
public class SwaggerConfiguration {

/**
* Publish a bean to generate swagger2 endpoints
*
* @return a swagger configuration bean
*/
@Bean
public Docket topicsApi() {
return new Docket(DocumentationType.SWAGGER_2).apiInfo(topicsApiInfo()).select().paths(topicPaths())
.apis(RequestHandlerSelectors.any()).build().useDefaultResponseMessages(false);
}

/**
* Api info
*
* @return ApiInfo
*/
private ApiInfo topicsApiInfo() {
return new ApiInfoBuilder().title("Service Topic").version("2.0").license("Apache License Version 2.0").build();
}

/**
* Config paths.
*
* @return the predicate
*/
private Predicate<String> topicPaths() {
return regex("/topic.*");
}
}


Configuracion del modelo


Modificamos el modelo para añadir la configuracion.
Añadimos las anotaciones  @ApiModel y @ApiModelProperty.

package com.mrknight.jpaexample.topic;

import javax.persistence.Entity;
import javax.persistence.Id;

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;

@Entity
@ApiModel("Model Topic")
public class Topic {

@Id
@ApiModelProperty(value = "the topic's id", required = true)
private String id;
@ApiModelProperty(value = "the topic's name", required = false)
private String name;
@ApiModelProperty(value = "the topic's descr", required = false)
private String descr;

public String getId() {
return id;
}

public void setId(String id) {
this.id = id;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public String getDescr() {
return descr;
}

public void setDescr(String descr) {
this.descr = descr;
}

@Override
public String toString() {
return "Topic [descr=" + descr + ", id=" + id + ", name=" + name + "]";
}

public Topic(String id, String name, String descr) {
this.id = id;
this.name = name;
this.descr = descr;
}

public Topic() {

}

}


Configuracion del Controller


Por ultimo añadimos la config del controller.
Tenemos las anotaciones @Api y @ApiOperation


package com.mrknight.jpaexample.topic;

import java.util.List;
import java.util.Optional;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;

@RestController
@Api(value = "Topics microservice", description = "This API has a CRUD for topics")
public class TopicController {

@Autowired
private TopicService topicsvc;

@GetMapping("topics")
@ApiOperation(value = "Find all topics", notes = "Return a list")
public List<Topic> getAllTopics() {
return topicsvc.getAllTopics();
}

@PostMapping("topics")
@ApiOperation(value = "Save a topic", notes = "Topic included in body")
public void addTopics(@RequestBody Topic topic) {
topicsvc.addTopic(topic);

}

@GetMapping("topics/{id}")
@ApiOperation(value = "Find a topic by Id", notes = "Return topic")
public Optional<Topic> getTopic(@PathVariable("id") String id) {
return topicsvc.getTopic(id);

}

@PutMapping("topics/{id}")
@ApiOperation(value = "Update topic by Id", notes = "Update topic in body")
public void updateTopic(@PathVariable("id") String id, @RequestBody Topic t) {
topicsvc.updateTopic(t, id);
}

@DeleteMapping("topics/{id}")
@ApiOperation(value = "Delete topic by Id", notes = "Del topic by id")
public void delTopic(@PathVariable("id") String id) {
topicsvc.deleteTopic(id);
}
}



Visualizando


navegamos a la url anterior:
http://localhost:9000/swagger-ui.html

Y ahora vemos como ha cambiado ;) El primer controller sería el que nops genera por defecto y el segundo el que hemos generado nosotros.



Hasta aquí la primera parte de como hacer funcionar vuestra API con swagger.
¿Qué sería lo siguiente? Usar las bondades de swagger para autogenerar ese contrato y que ese código podamos importarlo en nuestro proyecto.


Read More