#55 migrated the API doc from swagger2 to OpenAPI 3

This commit is contained in:
Fabio Formosa
2021-12-04 17:14:06 +01:00
parent f3506304d9
commit cbd3066f57
6 changed files with 132 additions and 128 deletions

View File

@@ -17,7 +17,7 @@
<main.basedir>${basedir}/../..</main.basedir>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<springfox.version>2.9.2</springfox.version>
<openapi.version>1.5.12</openapi.version>
<java.version>1.8</java.version>
</properties>
@@ -141,7 +141,12 @@
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-ui</artifactId>
<version>1.5.12</version>
<version>${openapi.version}</version>
</dependency>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-security</artifactId>
<version>${openapi.version}</version>
</dependency>
<dependency>
<groupId>io.swagger.core.v3</groupId>

View File

@@ -5,6 +5,7 @@ import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import it.fabioformosa.quartzmanager.dto.SchedulerConfigParam;
import it.fabioformosa.quartzmanager.dto.SchedulerDTO;
import it.fabioformosa.quartzmanager.dto.TriggerStatus;
@@ -33,6 +34,7 @@ import java.util.Map;
* @author Fabio.Formosa
*/
@RestController
@SecurityRequirement(name = "basic-auth")
@RequestMapping("/quartz-manager/scheduler")
public class SchedulerController {

View File

@@ -5,6 +5,7 @@ import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import it.fabioformosa.quartzmanager.dto.SchedulerConfigParam;
import it.fabioformosa.quartzmanager.dto.TriggerDTO;
import it.fabioformosa.quartzmanager.services.SchedulerService;
@@ -18,6 +19,7 @@ import javax.validation.Valid;
@Slf4j
@RequestMapping(TriggerController.TRIGGER_CONTROLLER_BASE_URL)
@SecurityRequirement(name = "basic-auth")
@RestController
public class TriggerController {

View File

@@ -104,7 +104,7 @@ public class WebSecurityConfigJWT extends WebSecurityConfigurerAdapter {
}
@Override
public void configure(WebSecurity web) throws Exception {
public void configure(WebSecurity web) {
web.ignoring()//
.antMatchers(HttpMethod.GET, PATTERNS_SWAGGER_UI) //
.antMatchers(HttpMethod.GET, WEBJAR_PATH + "/css/**", WEBJAR_PATH + "/js/**", WEBJAR_PATH + "/img/**", WEBJAR_PATH + "/lib/**", WEBJAR_PATH + "/assets/**");
@@ -146,7 +146,7 @@ public class WebSecurityConfigJWT extends WebSecurityConfigurerAdapter {
}
@Bean
public JwtTokenAuthenticationFilter jwtAuthenticationTokenFilter() throws Exception {
public JwtTokenAuthenticationFilter jwtAuthenticationTokenFilter() {
return new JwtTokenAuthenticationFilter(jwtTokenHelper(), userDetailsService);
}

View File

@@ -1,105 +1,103 @@
package it.fabioformosa.quartzmanager.security.helpers.impl;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.OrRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.web.filter.OncePerRequestFilter;
/**
* It finds the jwtToken into the request, it validates it and sets an @Authentication into the @SecurityContextHolder.
* If the request has a path included into the paths that must be skipped, it sets an anonymous authentication
*
* It delegates the jwtToken retrieve to the @JwtTokenHelper that applies several strategies.
*
*/
public class JwtTokenAuthenticationFilter extends OncePerRequestFilter {
private static final Logger log = LoggerFactory.getLogger(JwtTokenAuthenticationFilter.class);
private static final String ROOT_MATCHER = "/";
private static final String FAVICON_MATCHER = "/favicon.ico";
private static final String HTML_MATCHER = "/**/*.html";
private static final String CSS_MATCHER = "/**/*.css";
private static final String JS_MATCHER = "/**/*.js";
private static final String IMG_MATCHER = "/images/*";
private static final String LOGIN_MATCHER = "/api/login";
private static final String LOGOUT_MATCHER = "/api/logout";
private static List<String> PATH_TO_SKIP = Arrays.asList(
ROOT_MATCHER,
HTML_MATCHER,
FAVICON_MATCHER,
CSS_MATCHER,
JS_MATCHER,
IMG_MATCHER,
LOGIN_MATCHER,
LOGOUT_MATCHER
);
private final JwtTokenHelper jwtTokenHelper;
private final UserDetailsService userDetailsService;
public JwtTokenAuthenticationFilter(JwtTokenHelper jwtTokenHelper, UserDetailsService userDetailsService) {
super();
this.jwtTokenHelper = jwtTokenHelper;
this.userDetailsService = userDetailsService;
}
@Override
public void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
String jwtToken = jwtTokenHelper.retrieveToken(request);
if (jwtToken != null) {
log.debug("Found a jwtToken into the request {}", request.getPathInfo());
try {
String username = jwtTokenHelper.getUsernameFromToken(jwtToken);
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
JwtTokenBasedAuthentication authentication = new JwtTokenBasedAuthentication(userDetails);
authentication.setToken(jwtToken);
SecurityContextHolder.getContext().setAuthentication(authentication);
} catch (Exception e) {
log.error("Authentication failed! an expected error occurred authenticating the request {}", request.getRequestURL());
// SecurityContextHolder.getContext().setAuthentication(new AnonAuthentication());
// log.error("Switched to Anonymous Authentication, "
// + "because an error occurred setting authentication in security context holder due to " + e.getMessage(), e);
}
}
else if(skipPathRequest(request, PATH_TO_SKIP)) {
log.debug("Detected a path to be skipped from authentication, so activated anonymous auth for {}", request.getRequestURL());
SecurityContextHolder.getContext().setAuthentication(new AnonAuthentication());
}
else
log.debug("Not found any jwtToken and the request hasn't a path to be skipped from auth. Path: {}", request.getRequestURL());
chain.doFilter(request, response);
}
private boolean skipPathRequest(HttpServletRequest request, List<String> pathsToSkip ) {
if(pathsToSkip == null)
pathsToSkip = new ArrayList<String>();
List<RequestMatcher> matchers = pathsToSkip.stream().map(path -> new AntPathRequestMatcher(path)).collect(Collectors.toList());
OrRequestMatcher compositeMatchers = new OrRequestMatcher(matchers);
return compositeMatchers.matches(request);
}
}
package it.fabioformosa.quartzmanager.security.helpers.impl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.OrRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
/**
* It finds the jwtToken into the request, it validates it and sets an @Authentication into the @SecurityContextHolder.
* If the request has a path included into the paths that must be skipped, it sets an anonymous authentication
*
* It delegates the jwtToken retrieve to the @JwtTokenHelper that applies several strategies.
*
*/
public class JwtTokenAuthenticationFilter extends OncePerRequestFilter {
private static final Logger log = LoggerFactory.getLogger(JwtTokenAuthenticationFilter.class);
private static final String ROOT_MATCHER = "/";
private static final String FAVICON_MATCHER = "/favicon.ico";
private static final String HTML_MATCHER = "/**/*.html";
private static final String CSS_MATCHER = "/**/*.css";
private static final String JS_MATCHER = "/**/*.js";
private static final String IMG_MATCHER = "/images/*";
private static final String LOGIN_MATCHER = "/api/login";
private static final String LOGOUT_MATCHER = "/api/logout";
private static List<String> PATH_TO_SKIP = Arrays.asList(
ROOT_MATCHER,
HTML_MATCHER,
FAVICON_MATCHER,
CSS_MATCHER,
JS_MATCHER,
IMG_MATCHER,
LOGIN_MATCHER,
LOGOUT_MATCHER
);
private final JwtTokenHelper jwtTokenHelper;
private final UserDetailsService userDetailsService;
public JwtTokenAuthenticationFilter(JwtTokenHelper jwtTokenHelper, UserDetailsService userDetailsService) {
super();
this.jwtTokenHelper = jwtTokenHelper;
this.userDetailsService = userDetailsService;
}
@Override
public void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
String jwtToken = jwtTokenHelper.retrieveToken(request);
if (jwtToken != null) {
log.debug("Found a jwtToken into the request {}", request.getPathInfo());
try {
String username = jwtTokenHelper.verifyTokenAndExtractUsername(jwtToken);
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
JwtTokenBasedAuthentication authentication = new JwtTokenBasedAuthentication(userDetails);
authentication.setToken(jwtToken);
SecurityContextHolder.getContext().setAuthentication(authentication);
} catch (Exception e) {
log.error("Authentication failed! an expected error occurred authenticating the request {} due to {}", request.getRequestURL(), e.getMessage(), e);
// SecurityContextHolder.getContext().setAuthentication(new AnonAuthentication());
// log.error("Switched to Anonymous Authentication, "
// + "because an error occurred setting authentication in security context holder due to " + e.getMessage(), e);
}
}
else if(skipPathRequest(request, PATH_TO_SKIP)) {
log.debug("Detected a path to be skipped from authentication, so activated anonymous auth for {}", request.getRequestURL());
SecurityContextHolder.getContext().setAuthentication(new AnonAuthentication());
}
else
log.debug("Not found any jwtToken and the request hasn't a path to be skipped from auth. Path: {}", request.getRequestURL());
chain.doFilter(request, response);
}
private boolean skipPathRequest(HttpServletRequest request, List<String> pathsToSkip ) {
if(pathsToSkip == null)
pathsToSkip = new ArrayList<String>();
List<RequestMatcher> matchers = pathsToSkip.stream().map(path -> new AntPathRequestMatcher(path)).collect(Collectors.toList());
OrRequestMatcher compositeMatchers = new OrRequestMatcher(matchers);
return compositeMatchers.matches(request);
}
}

View File

@@ -1,5 +1,15 @@
package it.fabioformosa.quartzmanager.security.helpers.impl;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import it.fabioformosa.quartzmanager.security.configuration.properties.JwtSecurityProperties;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.time.ZoneId;
@@ -7,18 +17,6 @@ import java.util.Base64;
import java.util.Date;
import java.util.Map;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import it.fabioformosa.quartzmanager.security.configuration.properties.JwtSecurityProperties;
/**
*
* @author Fabio.Formosa
@@ -47,7 +45,7 @@ public class JwtTokenHelper {
public Boolean canTokenBeRefreshed(String token) {
try {
final Date expirationDate = getClaimsFromToken(token).getExpiration();
final Date expirationDate = verifyAndGetClaimsFromToken(token).getExpiration();
// String username = getUsernameFromToken(token);
// UserDetails userDetails = userDetailsService.loadUserByUsername(username);
return expirationDate.compareTo(generateCurrentDate()) > 0;
@@ -75,7 +73,7 @@ public class JwtTokenHelper {
.signWith(SIGNATURE_ALGORITHM, base64EncodeSecretKey(jwtSecurityProps.getSecret())).compact();
}
private Claims getClaimsFromToken(String token) {
private Claims verifyAndGetClaimsFromToken(String token) {
Claims claims;
try {
claims = Jwts.parser().setSigningKey(base64EncodeSecretKey(jwtSecurityProps.getSecret()))
@@ -109,13 +107,12 @@ public class JwtTokenHelper {
return LocalDateTime.now().atZone(ZoneId.of("Europe/Rome")).toInstant().toEpochMilli();
}
public String getUsernameFromToken(String token) {
public String verifyTokenAndExtractUsername(String token) {
String username;
try {
final Claims claims = getClaimsFromToken(token);
final Claims claims = verifyAndGetClaimsFromToken(token);
username = claims.getSubject();
} catch (Exception e) {
username = null;
log.error("Error getting claims from jwt token due to " + e.getMessage(), e);
throw e;
}
@@ -125,7 +122,7 @@ public class JwtTokenHelper {
public String refreshToken(String token) {
String refreshedToken;
try {
final Claims claims = getClaimsFromToken(token);
final Claims claims = verifyAndGetClaimsFromToken(token);
claims.setIssuedAt(generateCurrentDate());
refreshedToken = generateToken(claims);
} catch (Exception e) {
@@ -136,7 +133,7 @@ public class JwtTokenHelper {
}
public String retrieveToken(HttpServletRequest request) {
if (jwtSecurityProps.getCookieStrategy().isEnabled() == true) {
if (jwtSecurityProps.getCookieStrategy().isEnabled()) {
Cookie authCookie = getCookieValueByName(request, jwtSecurityProps.getCookieStrategy().getCookie());
if (authCookie != null)
return authCookie.getValue();