-
Notifications
You must be signed in to change notification settings - Fork 260
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add Spring Boot Admin to give users a simple monitoring dashboard exa…
…mple - Introduces HTTP Basic Authentication: Some endpoints (such as a subset of Spring Actuator endpoints) are now protected and require a username and password. For example, `/actuator/mappings` is now protected. The default usernames and passwords are configured in `application.properties`. - Add landing page at `/` with info on endpoints and default user credentials. - Update info on Docker image size, which increased with addition of Spring Boot Admin.
- Loading branch information
Showing
8 changed files
with
408 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
30 changes: 30 additions & 0 deletions
30
src/main/java/com/miguno/javadockerbuild/admin/AdminRedirector.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
package com.miguno.javadockerbuild.admin; | ||
|
||
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; | ||
import org.springframework.beans.factory.annotation.Value; | ||
import org.springframework.stereotype.Controller; | ||
import org.springframework.ui.ModelMap; | ||
import org.springframework.web.bind.annotation.GetMapping; | ||
import org.springframework.web.bind.annotation.RequestMapping; | ||
import org.springframework.web.servlet.ModelAndView; | ||
|
||
/** | ||
* Redirects <code>/admin/</code> requests to <code>/admin</code>. | ||
* | ||
* <p>Spring treats paths like <code>foo/</code> vs. `<code>foo</code>` differently. This controller | ||
* ensures that a user is correctly redirected after a login. | ||
*/ | ||
@SuppressFBWarnings("SPRING_ENDPOINT") | ||
@Controller | ||
@RequestMapping("/${spring.boot.admin.context-path}") | ||
public class AdminRedirector { | ||
|
||
@Value("${spring.boot.admin.context-path}") | ||
private String adminPath; | ||
|
||
@GetMapping("/${spring.boot.admin.context-path}/") | ||
@SuppressFBWarnings("SPRING_FILE_DISCLOSURE") | ||
public ModelAndView redirectWithUsingRedirectPrefix(ModelMap model) { | ||
return new ModelAndView(String.format("redirect:/%s", adminPath), model); | ||
} | ||
} |
36 changes: 36 additions & 0 deletions
36
src/main/java/com/miguno/javadockerbuild/admin/CustomCsrfFilter.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
package com.miguno.javadockerbuild.admin; | ||
|
||
import java.io.IOException; | ||
|
||
import jakarta.servlet.FilterChain; | ||
import jakarta.servlet.ServletException; | ||
import jakarta.servlet.http.Cookie; | ||
import jakarta.servlet.http.HttpServletRequest; | ||
import jakarta.servlet.http.HttpServletResponse; | ||
import org.springframework.security.web.csrf.CsrfToken; | ||
import org.springframework.web.filter.OncePerRequestFilter; | ||
import org.springframework.web.util.WebUtils; | ||
|
||
/** A custom CSRF Filter, derived from the Spring Boot Admin documentation. */ | ||
public class CustomCsrfFilter extends OncePerRequestFilter { | ||
|
||
public static final String CSRF_COOKIE_NAME = "XSRF-TOKEN"; | ||
|
||
@Override | ||
protected void doFilterInternal( | ||
HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) | ||
throws ServletException, IOException { | ||
CsrfToken csrf = (CsrfToken) request.getAttribute(CsrfToken.class.getName()); | ||
if (csrf != null) { | ||
Cookie cookie = WebUtils.getCookie(request, CSRF_COOKIE_NAME); | ||
String token = csrf.getToken(); | ||
|
||
if (cookie == null || token != null && !token.equals(cookie.getValue())) { | ||
cookie = new Cookie(CSRF_COOKIE_NAME, token); | ||
cookie.setPath("/"); | ||
response.addCookie(cookie); | ||
} | ||
} | ||
filterChain.doFilter(request, response); | ||
} | ||
} |
174 changes: 174 additions & 0 deletions
174
src/main/java/com/miguno/javadockerbuild/admin/SecuritySecureConfig.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,174 @@ | ||
package com.miguno.javadockerbuild.admin; | ||
|
||
import java.util.UUID; | ||
|
||
import de.codecentric.boot.admin.server.config.AdminServerProperties; | ||
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; | ||
import jakarta.servlet.DispatcherType; | ||
import org.springframework.beans.factory.annotation.Value; | ||
import org.springframework.boot.autoconfigure.security.SecurityProperties; | ||
import org.springframework.context.annotation.Bean; | ||
import org.springframework.context.annotation.Configuration; | ||
import org.springframework.security.config.Customizer; | ||
import org.springframework.security.config.annotation.web.builders.HttpSecurity; | ||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; | ||
import org.springframework.security.core.userdetails.User; | ||
import org.springframework.security.core.userdetails.UserDetails; | ||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; | ||
import org.springframework.security.crypto.password.PasswordEncoder; | ||
import org.springframework.security.provisioning.InMemoryUserDetailsManager; | ||
import org.springframework.security.web.SecurityFilterChain; | ||
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler; | ||
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter; | ||
import org.springframework.security.web.csrf.CookieCsrfTokenRepository; | ||
import org.springframework.security.web.csrf.CsrfTokenRequestAttributeHandler; | ||
import org.springframework.security.web.util.matcher.AntPathRequestMatcher; | ||
|
||
import static org.springframework.http.HttpMethod.DELETE; | ||
import static org.springframework.http.HttpMethod.POST; | ||
|
||
/** Secures the endpoints of this application. */ | ||
@Configuration(proxyBeanMethods = false) | ||
@EnableWebSecurity | ||
public class SecuritySecureConfig { | ||
|
||
private final AdminServerProperties adminServer; | ||
|
||
@Value("${app.spring-boot-admin.role.user.name}") | ||
private String roleUserName; | ||
|
||
@Value("${app.spring-boot-admin.role.user.password}") | ||
private String roleUserPassword; | ||
|
||
@Value("${app.spring-boot-admin.role.admin.name}") | ||
private String roleAdminName; | ||
|
||
@Value("${app.spring-boot-admin.role.admin.password}") | ||
private String roleAdminPassword; | ||
|
||
@SuppressFBWarnings("EI_EXPOSE_REP2") | ||
public SecuritySecureConfig(AdminServerProperties adminServer, SecurityProperties security) { | ||
this.adminServer = adminServer; | ||
} | ||
|
||
/** | ||
* Applies security policies such as authentication requirements to endpoints. | ||
* | ||
* @param http Supplied by Spring. | ||
* @return The applications' security filter chain. | ||
* @throws Exception Unclear when that happens. | ||
*/ | ||
@Bean | ||
protected SecurityFilterChain filterChain(HttpSecurity http) throws Exception { | ||
SavedRequestAwareAuthenticationSuccessHandler successHandler = | ||
new SavedRequestAwareAuthenticationSuccessHandler(); | ||
successHandler.setTargetUrlParameter("redirectTo"); | ||
successHandler.setDefaultTargetUrl(this.adminServer.path("/")); | ||
|
||
// NOTE: In this project, the Spring Boot Admin server and client are colocated in the same | ||
// application for demonstration purposes. In production, you would typically not do that | ||
// and instead separate the code and functionality. See the recommendations of Spring Boot | ||
// Admin at https://docs.spring-boot-admin.com/current/faq.html. | ||
// The effect of this colocation is that this application contains endpoints for both | ||
// server and client, and the authorization settings below also apply to both: if you | ||
// permit access to a URL in the "for the server" section you also permit access for the | ||
// client, and vice versa. Again, this would be different in production where the server | ||
// and the clients would be separate applications and processes. | ||
http.authorizeHttpRequests( | ||
(authorizeRequests) -> | ||
authorizeRequests | ||
//// For the Spring Boot Admin server. | ||
.requestMatchers( | ||
// Permit public access to all static assets. | ||
new AntPathRequestMatcher(this.adminServer.path("/assets/**")), | ||
// Permit public access to the login page. | ||
new AntPathRequestMatcher(this.adminServer.path("/login"))) | ||
.permitAll() | ||
// Permit asynchronous processing of a request without requiring authentication. | ||
// FIXME: Permitting any async requests as a workaround appears dangerous. | ||
// https://github.com/spring-projects/spring-security/issues/11027 (from 2022) | ||
.dispatcherTypeMatchers(DispatcherType.ASYNC) | ||
.permitAll() | ||
|
||
//// For the Spring Boot Admin client (the "real" app being developed). | ||
.requestMatchers( | ||
new AntPathRequestMatcher("/"), | ||
// Permit public access to this app's example endpoint at `/status`. | ||
new AntPathRequestMatcher("/status"), | ||
// Permit public access to Swagger. | ||
new AntPathRequestMatcher("/swagger-ui.html"), | ||
new AntPathRequestMatcher("/v3/api-docs"), | ||
// Permit public access to a subset of actuator endpoints. | ||
new AntPathRequestMatcher("/actuator/health"), | ||
new AntPathRequestMatcher("/actuator/info"), | ||
new AntPathRequestMatcher("/actuator/prometheus")) | ||
.permitAll() | ||
|
||
//// Applies to both SBA server and clients. | ||
// All other requests must be authenticated. | ||
.anyRequest() | ||
.authenticated()) | ||
// For Spring Boot Admin server: enables form-based login and logout. | ||
.formLogin( | ||
(formLogin) -> | ||
formLogin.loginPage(this.adminServer.path("/login")).successHandler(successHandler)) | ||
.logout((logout) -> logout.logoutUrl(this.adminServer.path("/logout"))) | ||
// Enables HTTP Basic Authentication support. | ||
.httpBasic(Customizer.withDefaults()); | ||
|
||
// Enables CSRF-Protection using cookies. | ||
http.addFilterAfter(new CustomCsrfFilter(), BasicAuthenticationFilter.class) | ||
.csrf( | ||
(csrf) -> | ||
csrf.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()) | ||
.csrfTokenRequestHandler(new CsrfTokenRequestAttributeHandler()) | ||
.ignoringRequestMatchers( | ||
//// For the Spring Boot Admin server. | ||
// Disables CSRF-Protection for the SBA server's endpoints that the SBA | ||
// client uses to (de-)register. | ||
new AntPathRequestMatcher( | ||
this.adminServer.path("/instances"), POST.toString()), | ||
new AntPathRequestMatcher( | ||
this.adminServer.path("/instances/*"), DELETE.toString()), | ||
|
||
//// For the Spring Boot Admin client. | ||
// Disables CSRF-Protection for the SBA client's actuator endpoints that | ||
// the SBA server uses to collect metrics. | ||
new AntPathRequestMatcher("/actuator/**"))); | ||
|
||
http.rememberMe( | ||
(rememberMe) -> rememberMe.key(UUID.randomUUID().toString()).tokenValiditySeconds(1209600)); | ||
|
||
return http.build(); | ||
} | ||
|
||
/** Required to provide UserDetailsService for "remember functionality". */ | ||
@Bean | ||
public InMemoryUserDetailsManager userDetailsService(PasswordEncoder passwordEncoder) { | ||
// NOTE: Because this example project runs the Spring Boot Admin server and client in the same | ||
// application, both the server's secured (with HTTP Basic Authentication) SBA API | ||
// endpoint and the client's Spring actuator endpoints coincidentally require exactly the | ||
// same username/password combination. | ||
// In production, this is not recommended. See the recommendations of Spring Boot Admin at | ||
// https://docs.spring-boot-admin.com/current/faq.html. | ||
// Instead, in production you would separate clients from the server, and thus different | ||
// username/password combinations can be used. | ||
// NOTE: HTTP Basic Authentication itself is not recommended for production. | ||
UserDetails user = | ||
User.withUsername(roleUserName) | ||
.password(passwordEncoder.encode(roleUserPassword)) | ||
.roles("USER") | ||
.build(); | ||
UserDetails admin = | ||
User.withUsername(roleAdminName) | ||
.password(passwordEncoder.encode(roleAdminPassword)) | ||
.roles("ADMIN", "USER") | ||
.build(); | ||
return new InMemoryUserDetailsManager(user, admin); | ||
} | ||
|
||
@Bean | ||
public PasswordEncoder passwordEncoder() { | ||
return new BCryptPasswordEncoder(); | ||
} | ||
} |
59 changes: 59 additions & 0 deletions
59
src/main/java/com/miguno/javadockerbuild/controllers/RootController.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
package com.miguno.javadockerbuild.controllers; | ||
|
||
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; | ||
import org.springframework.beans.factory.annotation.Value; | ||
import org.springframework.web.bind.annotation.GetMapping; | ||
import org.springframework.web.bind.annotation.RestController; | ||
|
||
/** Implements a basic landing page at endpoint `/`. */ | ||
@SuppressFBWarnings("SPRING_ENDPOINT") | ||
@RestController | ||
public class RootController { | ||
|
||
@Value("${app.spring-boot-admin.role.user.name}") | ||
private String roleUserName; | ||
|
||
@Value("${spring.application.name}") | ||
private String appName; | ||
|
||
@Value("${app.spring-boot-admin.role.user.password}") | ||
private String roleUserPassword; | ||
|
||
@Value("${app.spring-boot-admin.role.admin.name}") | ||
private String roleAdminName; | ||
|
||
@Value("${app.spring-boot-admin.role.admin.password}") | ||
private String roleAdminPassword; | ||
|
||
/** | ||
* Returns a basic landing page for this application. | ||
* | ||
* @return Basic landing page in HTML format. | ||
*/ | ||
@GetMapping("/") | ||
@SuppressFBWarnings("VA_FORMAT_STRING_USES_NEWLINE") | ||
public String root() { | ||
return String.format( | ||
""" | ||
<h1>Welcome to %s</h1> | ||
<p>Enjoy playing around with this application!</p> | ||
<h2>Example Endpoints</h2> | ||
<ul> | ||
<li><a href="/status"><code>/status</code></a> — this app's example endpoint</li> | ||
<li><a href="/actuator/health"><code>/actuator/health</code></a> — Spring built-in feature</li> | ||
<li><a href="/actuator/prometheus"><code>/actuator/prometheus</code></a> — Spring built-in feature</li> | ||
<li><a href="/admin">Spring Boot Admin dashboard</a> <strong>(requires login, see below)</strong></li> | ||
<li><a href="/swagger-ui.html">Swagger UI</a></li> | ||
</ul> | ||
<h2>User Accounts</h2> | ||
<p>For endpoints that require login.</p> | ||
<ul> | ||
<li>Admin user: <strong><code>%s</code></strong> with password <strong><code>%s</code></strong></li> | ||
<li>Regular user: <strong><code>%s</code></strong> with password <strong><code>%s</code></strong></li> | ||
</p> | ||
<ul> | ||
</ul> | ||
""", | ||
appName, roleAdminName, roleAdminPassword, roleUserName, roleUserPassword); | ||
} | ||
} |
Oops, something went wrong.