mirror of
https://github.com/fabioformosa/quartz-manager.git
synced 2025-12-30 22:23:15 +09:00
#55 migrated the API doc from swagger2 to OpenAPI 3
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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 {
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user