朋友不允许朋友写用户身份验证。 厌倦了管理自己的用户? 立即尝试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;}
}
为
httpTrace
,principal
,requestBody
和responseBody
添加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