使用Spring Boot Actuator监视Java应用程序

朋友不允许朋友写用户身份验证。 厌倦了管理自己的用户? 立即尝试Okta的API和Java SDK。 在几分钟之内即可对任何应用程序中的用户进行身份验证,管理和保护。

您是否曾与Spring Boot Actuator合作? 这是一个非常有用的库,可帮助您监视应用程序的运行状况以及与应用程序的交互-非常适合投入生产! Spring Boot Actuator包含一个内置端点,该端点用于跟踪对您的应用程序的HTTP调用-对于监视OpenID Connect(OIDC)请求非常有用-但不幸的是,默认实现不跟踪主体内容。 在这篇文章中,我将向您展示如何扩展httptrace端点以捕获内容并跟踪OIDC流。

让我们开始吧!

使用Spring Initializr和Okta创建一个OpenID Connect应用程序

您可以使用出色的Spring Initializr网站或API通过Okta集成创建示例OIDC应用程序:

curl https://start.spring.io/starter.zip \dependencies==web,okta \packageName==com.okta.developer.demo -d

但是,在运行OIDC应用程序之前,您将需要一个Okta帐户。 Okta是一项开发人员服务,可为您处理存储用户帐户和实施用户管理(包括OIDC)。 继续并注册一个免费的开发者帐户以继续。

登录到Okta帐户后,转到仪表板,然后转到“ 应用程序”部分。 添加一个新的Web应用程序,然后在“常规”部分中获取客户端凭据: 客户端ID客户端密钥

您还将需要颁发者 ,它也是组织URL,您可以在仪表板主页的右上角找到它。 注意 :默认情况下,内置的Everyone Okta组已分配给该应用程序,因此Okta组织中的任何用户都可以对其进行身份验证。

使用您的客户ID,客户密码。 然后在适当的位置发行人,通过在命令行中传递凭据来启动您的应用程序:

OKTA_OAUTH2_REDIRECTURI=/authorization-code/callback \
OKTA_OAUTH2_ISSUER=<issuer>/oauth2 \
OKTA_OAUTH2_CLIENT_ID=<client id> \
OKTA_OAUTH2_CLIENT_SECRET=<client secret> \
./mvnw spring-boot:run

将测试控制器添加到Spring Boot App

最好添加一个简单的控制器来测试身份验证流程。 默认情况下,仅允许经过身份验证的用户访问。

@Controller
@RequestMapping(value = "/hello")
public class HelloController {@GetMapping(value = "/greeting")@ResponseBodypublic String getGreeting(Principal user) {return "Good morning " + user.getName();}
}

您可以通过重新启动应用程序并浏览到/ hello / greeting来进行测试 。

添加Spring Boot Actuator依赖关系

通过将启动器Maven依赖项添加到pom.xml file来启用Spring Boot Actuator:

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

要启用httptrace端点,请编辑src/main/resources/application.properties并添加以下行:

management.endpoints.web.exposure.include=info,health,httptrace

您可以运行应用程序并浏览到/ hello / greeting并登录,以测试现成的执行器功能。

在自动配置下,Spring Security过滤器的优先级高于httptrace执行器添加的过滤器。

这意味着默认情况下仅跟踪经过身份验证的呼叫。 我们将在此处对此进行更改,但是现在,您可以在/ actuator / httptrace中看到跟踪的内容 。 响应应类似于以下JSON有效负载:

{"traces":[{"timestamp":"2019-05-19T05:38:42.726Z","principal":{"name":"***"},"session":{"id":"***"},"request":{"method":"GET","uri":"http://localhost:8080/","headers":{},"remoteAddress":"0:0:0:0:0:0:0:1"},"response":{"status":200,"headers":{}},"timeTaken":145}]
}

将自定义HTTP跟踪添加到您的Spring Boot应用程序

HTTP跟踪不是很灵活。 httptrace执行器的作者Andy Wilkinson建议,如果需要进行身体跟踪,请实现自己的端点 。

另外,通过一些自定义过滤器,我们无需进行大量工作即可增强基本实现。 在以下各节中,我将向您展示如何:

  • 创建一个过滤器以捕获请求和响应正文
  • 配置过滤器优先级以跟踪OIDC调用
  • 使用自定义跟踪存储库创建httptrace端点扩展以存储其他数据

使用Spring Boot Actuator捕获请求和响应正文内容

接下来,创建一个用于跟踪请求和响应正文内容的过滤器。 此过滤器将优先于httptrace过滤器,因此当执行器保存跟踪时,缓存的正文内容可用。

@Component
@ConditionalOnProperty(prefix = "management.trace.http", name = "enabled", matchIfMissing = true)
public class ContentTraceFilter extends OncePerRequestFilter {private ContentTraceManager traceManager;@Value("${management.trace.http.tracebody:false}")private boolean traceBody;public ContentTraceFilter(ContentTraceManager traceManager) {super();this.traceManager = traceManager;}@Overrideprotected void doFilterInternal(HttpServletRequest request,HttpServletResponse response, FilterChain filterChain)throws ServletException, IOException {if (!isRequestValid(request) || !traceBody) {filterChain.doFilter(request, response);return;}ContentCachingRequestWrapper wrappedRequest = new ContentCachingRequestWrapper(request, 1000);ContentCachingResponseWrapper wrappedResponse = new ContentCachingResponseWrapper(response);try {filterChain.doFilter(wrappedRequest, wrappedResponse);traceManager.updateBody(wrappedRequest, wrappedResponse);} finally {wrappedResponse.copyBodyToResponse();}}private boolean isRequestValid(HttpServletRequest request) {try {new URI(request.getRequestURL().toString());return true;} catch (URISyntaxException ex) {return false;}}}

注意对ContentTraceManager的调用,它是一个简单的@RequestScope bean,它将存储其他数据:

@Component
@RequestScope
@ConditionalOnProperty(prefix = "management.trace.http", name = "enabled", matchIfMissing = true)
public class ContentTraceManager {private ContentTrace trace;public ContentTraceManager(ContentTrace trace) {this.trace=trace;}protected static Logger logger = LoggerFactory.getLogger(ContentTraceManager.class);public void updateBody(ContentCachingRequestWrapper wrappedRequest,ContentCachingResponseWrapper wrappedResponse) {String requestBody = getRequestBody(wrappedRequest);getTrace().setRequestBody(requestBody);String responseBody = getResponseBody(wrappedResponse);getTrace().setResponseBody(responseBody);}protected String getRequestBody(ContentCachingRequestWrapper wrappedRequest) {try {if (wrappedRequest.getContentLength() <= 0) {return null;}return new String(wrappedRequest.getContentAsByteArray(), 0,wrappedRequest.getContentLength(),wrappedRequest.getCharacterEncoding());} catch (UnsupportedEncodingException e) {logger.error("Could not read cached request body: " + e.getMessage());return null;}}protected String getResponseBody(ContentCachingResponseWrapper wrappedResponse) {try {if (wrappedResponse.getContentSize() <= 0) {return null;}return new String(wrappedResponse.getContentAsByteArray(), 0,wrappedResponse.getContentSize(),wrappedResponse.getCharacterEncoding());} catch (UnsupportedEncodingException e) {logger.error("Could not read cached response body: " + e.getMessage());return null;}}public ContentTrace getTrace() {if (trace == null) {trace = new ContentTrace();}return trace;}
}

为了使用附加数据对跟踪建模,请使用内置的HttpTrace信息组成一个自定义ContentTrace类,并添加用于存储正文内容的属性。

public class ContentTrace {protected HttpTrace httpTrace;protected String requestBody;protected String responseBody;protected Authentication principal;public ContentTrace() {}public void setHttpTrace(HttpTrace httpTrace) {this.httpTrace = httpTrace;}
}

httpTraceprincipalrequestBodyresponseBody添加setter和getter。

配置过滤器优先级

为了捕获对应用程序中OIDC端点的请求,跟踪过滤器必须位于Spring Security过滤器之前。 只要ContentTraceFilter优先级高于HttpTraceFilter ,那么两者都可以放在SecurityContextPersistenceFilter之前或之后,后者是Spring Security过滤器链中的第一个。

@Configuration
@ConditionalOnProperty(prefix = "management.trace.http", name = "enabled", matchIfMissing = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {private HttpTraceFilter httpTraceFilter;private ContentTraceFilter contentTraceFilter;public WebSecurityConfig(HttpTraceFilter httpTraceFilter, ContentTraceFilter contentTraceFilter) {this.httpTraceFilter = httpTraceFilter;this.contentTraceFilter = contentTraceFilter;}@Overrideprotected void configure(HttpSecurity http) throws Exception {http.addFilterBefore(contentTraceFilter,SecurityContextPersistenceFilter.class).addFilterAfter(httpTraceFilter,SecurityContextPersistenceFilter.class).authorizeRequests().anyRequest().authenticated().and().oauth2Client().and().oauth2Login();}
}

跟踪经过身份验证的用户

我们将在Spring Security过滤器链之前安装跟踪过滤器。 这意味着当HttpTraceFilter保存跟踪时,主体不再可用。 我们可以使用新的过滤器和ContentTraceManager还原此跟踪数据。

@Component
@ConditionalOnProperty(prefix = "management.trace.http", name = "enabled", matchIfMissing = true)
public class PrincipalTraceFilter extends OncePerRequestFilter {private ContentTraceManager traceManager;private HttpTraceProperties traceProperties;public PrincipalTraceFilter(ContentTraceManager traceManager,HttpTraceProperties traceProperties) {super();this.traceManager = traceManager;this.traceProperties = traceProperties;}@Overrideprotected void doFilterInternal(HttpServletRequest request,HttpServletResponse response,FilterChain filterChain)throws ServletException, IOException {if (!isRequestValid(request)) {filterChain.doFilter(request, response);return;}try {filterChain.doFilter(request, response);} finally {if (traceProperties.getInclude().contains(Include.PRINCIPAL)) {traceManager.updatePrincipal();}}}private boolean isRequestValid(HttpServletRequest request) {try {new URI(request.getRequestURL().toString());return true;} catch (URISyntaxException ex) {return false;}}}

添加缺少的ContentTraceManager类以更新主体:

public class ContentTraceManager {public void updatePrincipal() {Authentication authentication = SecurityContextHolder.getContext().getAuthentication();if (authentication != null) {getTrace().setPrincipal(authentication);}}
}

PrincipalTraceFilter优先级必须低于Spring Security过滤器链的优先级,因此从安全上下文请求身份验证的主体时可用。 修改WebSecurityConfig以将过滤器插入到WebSecurityConfig的最后一个过滤器FilterSecurityInterceptor之后。

@Configuration
@ConditionalOnProperty(prefix = "management.trace.http", name = "enabled", matchIfMissing = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {private HttpTraceFilter httpTraceFilter;private ContentTraceFilter contentTraceFilter;private PrincipalTraceFilter principalTraceFilter;public WebSecurityConfig(HttpTraceFilter httpTraceFilter,ContentTraceFilter contentTraceFilter,PrincipalTraceFilter principalTraceFilter) {super();this.httpTraceFilter = httpTraceFilter;this.contentTraceFilter = contentTraceFilter;this.principalTraceFilter = principalTraceFilter;}@Overrideprotected void configure(HttpSecurity http) throws Exception {http.addFilterBefore(contentTraceFilter,SecurityContextPersistenceFilter.class).addFilterAfter(httpTraceFilter,SecurityContextPersistenceFilter.class).addFilterAfter(principalTraceFilter,FilterSecurityInterceptor.class).authorizeRequests().anyRequest().authenticated().and().oauth2Client().and().oauth2Login();}
}

HTTPTrace端点扩展

最后,使用@EndpointWebExtension批注定义端点增强。 实现CustomHttpTraceRepository以存储和检索带有其他数据的ContentTrace

@Component
@EndpointWebExtension(endpoint = HttpTraceEndpoint.class)
@ConditionalOnProperty(prefix = "management.trace.http", name = "enabled", matchIfMissing = true)
public class HttpTraceEndpointExtension {private CustomHttpTraceRepository repository;public HttpTraceEndpointExtension(CustomHttpTraceRepository repository) {super();this.repository = repository;}@ReadOperationpublic ContentTraceDescriptor contents() {List<ContentTrace> traces = repository.findAllWithContent();return new ContentTraceDescriptor(traces);}
}

重新定义端点返回类型的描述符:

public class ContentTraceDescriptor {protected List<ContentTrace> traces;public ContentTraceDescriptor(List<ContentTrace> traces) {super();this.traces = traces;}public List<ContentTrace> getTraces() {return traces;}public void setTraces(List<ContentTrace> traces) {this.traces = traces;}}

创建CustomHttpTraceRepository实现HttpTraceRepository接口:

@Component
@ConditionalOnProperty(prefix = "management.trace.http", name = "enabled", matchIfMissing = true)
public class CustomHttpTraceRepository implements HttpTraceRepository {private final List<ContentTrace> contents = new LinkedList<>();private ContentTraceManager traceManager;public CustomHttpTraceRepository(ContentTraceManager traceManager) {super();this.traceManager = traceManager;}@Overridepublic void add(HttpTrace trace) {synchronized (this.contents) {ContentTrace contentTrace = traceManager.getTrace();contentTrace.setHttpTrace(trace);this.contents.add(0, contentTrace);}}@Overridepublic List<HttpTrace> findAll() {synchronized (this.contents) {return contents.stream().map(ContentTrace::getHttpTrace).collect(Collectors.toList());}}public List<ContentTrace> findAllWithContent() {synchronized (this.contents) {return Collections.unmodifiableList(new ArrayList<>(this.contents));}}}

检查OpenID Connect HTTP跟踪

通过添加以下行来修改application.properties文件以跟踪所有可用数据:

management.trace.http.include=request-headers,response-headers,cookie-headers,principal,time-taken,authorization-header,remote-address,session-id

再次运行该应用程序,然后调用安全控制器/ hello / greeting 。 针对Okta进行身份验证,然后检查/ actuator / httptrace中的跟踪 。

现在,您应该在跟踪中看到OIDC调用以及请求和响应内容。 例如,在下面的跟踪中,对应用程序授权端点的请求将重定向到Okta授权服务器,从而启动OIDC授权代码流。

{"httpTrace": {"timestamp": "2019-05-22T00:52:22.383Z","principal": null,"session": {"id": "C2174F5E5F85B313B2284639EE4016E7"},"request": {"method": "GET","uri": "http://localhost:8080/oauth2/authorization/okta","headers": {"cookie": ["JSESSIONID=C2174F5E5F85B313B2284639EE4016E7"],"accept-language": ["en-US,en;q=0.9"],"upgrade-insecure-requests": ["1"],"host": ["localhost:8080"],"connection": ["keep-alive"],"accept-encoding": ["gzip, deflate, br"],"accept": ["text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8"],"user-agent": ["Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36"]},"remoteAddress": "0:0:0:0:0:0:0:1"},"response": {"status": 302,"headers": {"X-Frame-Options": ["DENY"],"Cache-Control": ["no-cache, no-store, max-age=0, must-revalidate"],"X-Content-Type-Options": ["nosniff"],"Expires": ["0"],"Pragma": ["no-cache"],"X-XSS-Protection": ["1; mode=block"],"Location": ["https://dev-239352.okta.com/oauth2/default/v1/authorize?response_type=code&client_id=0oalrp4qx3Do43VyI356&scope=openid%20profile%20email&state=1uzHRyaHVmyKcpb7eAvJVrdJTZ6wTgkPv3fsC14qdOk%3D&redirect_uri=http://localhost:8080/authorization-code/callback"]}},"timeTaken": 9},"requestBody": null,"responseBody": null
}

这篇文章中的所有代码都可以在okta-spring-boot-custom-actuator-example存储库的GitHub上找到。

学到更多

这里的所有都是它的! 您刚刚了解了如何配置和扩展httptrace执行器端点以监视OIDC应用程序。 有关Spring Boot Actuator,常规Spring Boot或用户身份验证的更多信息,请查看以下链接:

  • 带有Spring Boot和Spring Cloud的Java微服务
  • 弹簧启动执行器端点
  • 实施自定义端点
  • Okta身份验证快速入门指南Java Spring

与往常一样,如果您对此信息有任何意见或疑问,请在下面发表评论。 将来不要错过Twitter和YouTube上的任何精彩内容。

“使用Spring Boot Actuator监视Java应用程序”最初于2019年7月17日发布在Okta Developer博客上。

朋友不允许朋友写用户身份验证。 厌倦了管理自己的用户? 立即尝试Okta的API和Java SDK。 在几分钟之内即可对任何应用程序中的用户进行身份验证,管理和保护。

翻译自: https://www.javacodegeeks.com/2019/09/monitor-your-java-apps-spring-boot-actuator.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/341232.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【sublime】 按 ctrl+shift+f没有反应(系统:win10+搜狗输入法)

系统&#xff1a; win10 输入法&#xff1a; 搜狗 问题&#xff1a; sublime 按 ctrlshiftf没有反应 原因&#xff1a; sumlime 中 ctrlshiftf 与搜狗繁简切换冲突 解决&#xff1a; A. 右击搜狗输入法状态栏&#xff08;就是那个小条条&#xff09;&#xff0c;选择属性设…

SublimeText3 ctrl+f打开搜索框后怎么关闭

按esc就可以退出了&#xff0c;点那个 find all也会退出

javaone_JavaOne 2014 –有关提交的一些初步分析

javaone这些天时间不多了。 并行发生的事情如此之多&#xff0c;当然&#xff0c;最重要的Java会议就是一切。 JavaOne 2014已经关闭了CfP门&#xff0c;投票正在进行中。 程序委员会几乎没有什么可以谈论的&#xff0c;但是去年跳过了这种分析之后&#xff0c;现在是我寻求许…

Sublime打开与关闭右侧缩略图

启动sublime text 3软件&#xff0c;如图所示&#xff1b; 发现右侧缩略图占据了太大的位置&#xff0c;瞬间感觉整个世界都不好了&#xff1b; 要想关闭这个缩略图&#xff0c;需要到菜单视图里面设置&#xff0c;首先点击菜单栏“view”——“hideMiniMap”&#xff1b;如图所…

Sublime Text 如何查看当前文件的编码格式?

打开preference->setting->输入下面代码 "show_encoding": true,"show_line_endings": true 此时保存该配置文件&#xff0c;就能够看到 sublime 最底下一行会显示文件编码格式了。以上的配置内容在 Perference → Setting─Default 都是 false 的。…

jaxb-xjc.jar_使用xjc在一秒钟内生成您的JAXB类

jaxb-xjc.jar由于JAXB是JDK的一部分&#xff0c;因此它是处理XML文档最常用的框架之一。 它提供了一种从XML文档检索数据并将其存储到Java类的简便方法。 因为几乎每个Java开发人员都已经使用过JAXB&#xff0c;所以我不会解释不同的JAXB批注。 相反&#xff0c;我将重点介绍一…

修改Typora默认的字体为好看秀美的中文字体“华康手札体“

一 下载并安装"华康手札体W5P"字体 &#xff08;1&#xff09;下载方式&#xff1a; 1. 方式一 http://www.downcc.com/font/341067.html2.百度云链接&#xff1a; 链接&#xff1a;https://pan.baidu.com/s/125Hh-tqWOy1Ht-GvD_P2MQ 提取码&#xff1a;z6ka 复制…

测试Spring Boot有条件的合理方式

如果您或多或少有经验的Spring Boot用户&#xff0c;那么很幸运&#xff0c;在某些时候您可能需要遇到必须有条件地注入特定bean或配置的情况 。 它的机制是很好理解的 &#xff0c;但有时这样的测试条件下&#xff08;以及它们的组合&#xff09;可能会导致混乱。 在这篇文章中…

金山打字通答案

背景 金山打字通练习键盘键位&#xff0c;需要对第一关熟悉键盘知识进行答题。一下整理答案 答案 ABADCBCA

Onetab快速删除所有历史网页

1. 打开网页的控制台&#xff08;F12&#xff09; 2. 控制台粘贴该命令 document.querySelectorAll(div.deleteAllButton).forEach(function(ele, index, list){ele.click();});3. 然后按住回车&#xff08;有 conform 弹框需要确认&#xff09;&#xff0c;等待全部清空

golang的jwt学习笔记

文章目录 初始化项目加密一步一步编写程序另一个参数--加密方式关于StandardClaims 解密解析出来的怎么用关于`MapClaims`上面使用结构体的全代码实战项目关于验证这个项目的前端初始化项目 自然第一步是暗转jwt-go的依赖啦 #go get github.com/golang-jwt/jwt/v5 go get githu…

Ubuntu根目录下各文件夹的功能详细介绍

Ubuntu的根目录下存在着很多的文件夹&#xff0c;但你知道他们都存放着哪些文件呢&#xff1f;这些是深入了解Ubuntu系统必不缺少的知识&#xff0c;本文就关于此做一下介绍吧。 /bin/ 用以存储二进制可执行命令文件。 /sbin/ 许多系统命令的存储位置&#xff0c;/usr/sb…

mock测试使用断言_使用自定义断言丰富测试代码

mock测试使用断言受GeeCON会议期间tkaczanowski演讲的启发&#xff0c;我决定仔细研究AssertJ库的自定义断言。 在我的“骰子”游戏中&#xff0c;我创建了一个“机会”&#xff0c;它是骰子的任何组合&#xff0c;其分数是所有骰子的总和。 这是相对简单的对象&#xff1a; …

如何实现有序列表端内换行

背景 有序列表换行后自动开启下一个标号&#xff0c;让人苦恼。 操作 操作系统换行操作 1. 【Enter】键是硬回车&#xff0c;即段落标记。回车后文字属于下一段落 2. 【shiftEnter】是软回车快捷键。即人工换行符。回车后文字仍属于前一段落&#xff0c;只不过重新换行。 …

Java性能:For-eaching与Streaming

在for循环中向上或向下计数是最有效的迭代方式吗&#xff1f; 有时答案既不是。 阅读这篇文章&#xff0c;了解不同迭代品种的影响。 迭代性能 关于如何以高性能进行迭代有很多观点。 Java中的传统迭代方式是一个for循环&#xff0c;该循环从零开始&#xff0c;然后计数到一些…

好用的截图工具

目录 截图软件介绍 神器2——Snipaste PicPick——自带画图 LightShot——自带图床 功能总结 截图软件介绍 参考推荐5个截图工具&#xff08;超好用&#xff09; - 知乎&#xff0c;列出几个好用的截图工具 神器2——Snipaste 超级好用&#xff0c;QQ的功能Snipaste全都有…

Markdown语法与文本内容冲突的解决方案(软件Typora)

背景 使用Typora输入文字后&#xff0c;其中特殊字符自动识别为Markdown语法 解决方式 在使用markdown书写博客时&#xff0c;有可能会出现文本中的字符是markdown语法&#xff0c;那么markdown翻译器就会误将这些符号也翻译成某种功能&#xff0c;就会出现显示错误的情况。…

如何在Flutter(REST API)中进行API调用

在本文中&#xff0c;我们将探讨如何在波动中进行API调用并使用简单的REST API。 在这里查看我在Flutter上的其他一些帖子&#xff1a; Flutter vs React Native 了解Flutter中的BLoC架构 &#xff08;强烈建议&#xff09; 在Flutter中构建ListView&#xff08;RecyclerVi…

sublime关闭左边文件路径快捷键

目录 背景 解决方法 方案一&#xff1a; 方案二&#xff1a; 背景 sublime查看某一文件具体内容&#xff0c;左边文件路径占用一部分空间&#xff0c;影响观看 解决方法 方案一&#xff1a; 使用快捷键&#xff1a;关闭和打开相同&#xff0c;先按 CtrlK&#xff0c;再按…

Typora全局搜素

目录 背景 解决方式 全局文件夹下搜索 方法一 方法二 单一文件下搜索 查找功能 1 查找整个单词 ​2 区分大小写 背景 有时需要在打开的文件夹中所有文件搜索某一单词&#xff0c;有时需要在一个文件下搜索 解决方式 全局文件夹下搜索 方法一 方法二 快捷键&#x…