init: 클린 기반 auth 서버 설계

This commit is contained in:
DongHyeonka
2026-07-24 14:30:18 +09:00
parent 471db0203d
commit 8a1ac1e769
3642 changed files with 275893 additions and 1 deletions
@@ -0,0 +1,55 @@
{
"groups": [
{
"name": "app.docs",
"type": "com.project.auth.config.openapi.AppDocsProperties",
"sourceType": "com.project.auth.config.openapi.AppDocsProperties"
},
{
"name": "app.logging.access",
"type": "com.project.auth.config.web.AccessLogProperties",
"sourceType": "com.project.auth.config.web.AccessLogProperties"
},
{
"name": "app.persistence.migration",
"type": "com.project.auth.config.persistence.MigrationProperties",
"sourceType": "com.project.auth.config.persistence.MigrationProperties"
}
],
"properties": [
{
"name": "app.docs.description",
"type": "java.lang.String",
"sourceType": "com.project.auth.config.openapi.AppDocsProperties"
},
{
"name": "app.docs.title",
"type": "java.lang.String",
"sourceType": "com.project.auth.config.openapi.AppDocsProperties"
},
{
"name": "app.docs.version",
"type": "java.lang.String",
"sourceType": "com.project.auth.config.openapi.AppDocsProperties"
},
{
"name": "app.logging.access.excluded-path-prefixes",
"type": "java.util.List<java.lang.String>",
"sourceType": "com.project.auth.config.web.AccessLogProperties"
},
{
"name": "app.persistence.migration.location",
"type": "java.lang.String",
"sourceType": "com.project.auth.config.persistence.MigrationProperties"
},
{
"name": "app.persistence.migration.schema",
"type": "java.lang.String",
"sourceType": "com.project.auth.config.persistence.MigrationProperties"
}
],
"hints": [],
"ignored": {
"properties": []
}
}
Binary file not shown.
@@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="en"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>JwtTypValidator</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">bootstrap</a> &gt; <a href="index.html" class="el_package">com.project.auth.config.auth.security</a> &gt; <span class="el_class">JwtTypValidator</span></div><h1>JwtTypValidator</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">29 of 38</td><td class="ctr2">23%</td><td class="bar">2 of 2</td><td class="ctr2">0%</td><td class="ctr1">2</td><td class="ctr2">3</td><td class="ctr1">5</td><td class="ctr2">8</td><td class="ctr1">1</td><td class="ctr2">2</td></tr></tfoot><tbody><tr><td id="a1"><a href="JwtTypValidator.java.html#L23" class="el_method">validate(Jwt)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="29" alt="29"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="2" alt="2"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">2</td><td class="ctr2" id="g0">2</td><td class="ctr1" id="h0">5</td><td class="ctr2" id="i0">5</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a0"><a href="JwtTypValidator.java.html#L17" class="el_method">JwtTypValidator(String)</a></td><td class="bar" id="b1"><img src="../jacoco-resources/greenbar.gif" width="37" height="10" title="9" alt="9"/></td><td class="ctr2" id="c0">100%</td><td class="bar" id="d1"/><td class="ctr2" id="e1">n/a</td><td class="ctr1" id="f1">0</td><td class="ctr2" id="g1">1</td><td class="ctr1" id="h1">0</td><td class="ctr2" id="i1">3</td><td class="ctr1" id="j1">0</td><td class="ctr2" id="k1">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.13.202504020838</span></div></body></html>
@@ -0,0 +1,35 @@
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="en"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>JwtTypValidator.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">bootstrap</a> &gt; <a href="index.source.html" class="el_package">com.project.auth.config.auth.security</a> &gt; <span class="el_source">JwtTypValidator.java</span></div><h1>JwtTypValidator.java</h1><pre class="source lang-java linenums">package com.project.auth.config.auth.security;
import org.springframework.security.oauth2.core.OAuth2Error;
import org.springframework.security.oauth2.core.OAuth2TokenValidator;
import org.springframework.security.oauth2.core.OAuth2TokenValidatorResult;
import org.springframework.security.oauth2.jwt.Jwt;
import java.util.Objects;
public class JwtTypValidator implements OAuth2TokenValidator&lt;Jwt&gt; {
private static final String TYP_CLAIM = &quot;typ&quot;;
private static final String ERROR_CODE = &quot;invalid_token&quot;;
private final String expectedTyp;
<span class="fc" id="L17"> public JwtTypValidator(String expectedTyp) {</span>
<span class="fc" id="L18"> this.expectedTyp = Objects.requireNonNull(expectedTyp, &quot;expectedTyp must not be null&quot;);</span>
<span class="fc" id="L19"> }</span>
@Override
public OAuth2TokenValidatorResult validate(Jwt jwt) {
<span class="nc" id="L23"> String actual = jwt.getClaimAsString(TYP_CLAIM);</span>
<span class="nc bnc" id="L24" title="All 2 branches missed."> if (expectedTyp.equals(actual)) {</span>
<span class="nc" id="L25"> return OAuth2TokenValidatorResult.success();</span>
}
<span class="nc" id="L27"> OAuth2Error error = new OAuth2Error(</span>
ERROR_CODE,
&quot;Expected JWT typ=&quot; + expectedTyp + &quot; but got &quot; + actual,
null
);
<span class="nc" id="L32"> return OAuth2TokenValidatorResult.failure(error);</span>
}
}
</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.13.202504020838</span></div></body></html>
@@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="en"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>KeycloakAuthenticatedUserFactory</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">bootstrap</a> &gt; <a href="index.html" class="el_package">com.project.auth.config.auth.security</a> &gt; <span class="el_class">KeycloakAuthenticatedUserFactory</span></div><h1>KeycloakAuthenticatedUserFactory</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">4 of 39</td><td class="ctr2">89%</td><td class="bar">2 of 4</td><td class="ctr2">50%</td><td class="ctr1">2</td><td class="ctr2">6</td><td class="ctr1">1</td><td class="ctr2">13</td><td class="ctr1">0</td><td class="ctr2">4</td></tr></tfoot><tbody><tr><td id="a2"><a href="KeycloakAuthenticatedUserFactory.java.html#L27" class="el_method">displayName(Jwt)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="32" height="10" title="4" alt="4"/><img src="../jacoco-resources/greenbar.gif" width="88" height="10" title="11" alt="11"/></td><td class="ctr2" id="c3">73%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="60" height="10" title="2" alt="2"/><img src="../jacoco-resources/greenbar.gif" width="60" height="10" title="2" alt="2"/></td><td class="ctr2" id="e0">50%</td><td class="ctr1" id="f0">2</td><td class="ctr2" id="g0">3</td><td class="ctr1" id="h0">1</td><td class="ctr2" id="i1">4</td><td class="ctr1" id="j0">0</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a1"><a href="KeycloakAuthenticatedUserFactory.java.html#L18" class="el_method">create(Jwt, Collection)</a></td><td class="bar" id="b1"><img src="../jacoco-resources/greenbar.gif" width="104" height="10" title="13" alt="13"/></td><td class="ctr2" id="c0">100%</td><td class="bar" id="d1"/><td class="ctr2" id="e1">n/a</td><td class="ctr1" id="f1">0</td><td class="ctr2" id="g1">1</td><td class="ctr1" id="h1">0</td><td class="ctr2" id="i0">5</td><td class="ctr1" id="j1">0</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a0"><a href="KeycloakAuthenticatedUserFactory.java.html#L35" class="el_method">authorityNames(Collection)</a></td><td class="bar" id="b2"><img src="../jacoco-resources/greenbar.gif" width="64" height="10" title="8" alt="8"/></td><td class="ctr2" id="c1">100%</td><td class="bar" id="d2"/><td class="ctr2" id="e2">n/a</td><td class="ctr1" id="f2">0</td><td class="ctr2" id="g2">1</td><td class="ctr1" id="h2">0</td><td class="ctr2" id="i2">3</td><td class="ctr1" id="j2">0</td><td class="ctr2" id="k2">1</td></tr><tr><td id="a3"><a href="KeycloakAuthenticatedUserFactory.java.html#L11" class="el_method">KeycloakAuthenticatedUserFactory()</a></td><td class="bar" id="b3"><img src="../jacoco-resources/greenbar.gif" width="24" height="10" title="3" alt="3"/></td><td class="ctr2" id="c2">100%</td><td class="bar" id="d3"/><td class="ctr2" id="e3">n/a</td><td class="ctr1" id="f3">0</td><td class="ctr2" id="g3">1</td><td class="ctr1" id="h3">0</td><td class="ctr2" id="i3">1</td><td class="ctr1" id="j3">0</td><td class="ctr2" id="k3">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.13.202504020838</span></div></body></html>
@@ -0,0 +1,40 @@
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="en"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>KeycloakAuthenticatedUserFactory.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">bootstrap</a> &gt; <a href="index.source.html" class="el_package">com.project.auth.config.auth.security</a> &gt; <span class="el_source">KeycloakAuthenticatedUserFactory.java</span></div><h1>KeycloakAuthenticatedUserFactory.java</h1><pre class="source lang-java linenums">package com.project.auth.config.auth.security;
import com.project.auth.presentation.auth.current.AuthenticatedUser;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.oauth2.jwt.Jwt;
import java.util.Collection;
import java.util.Set;
import java.util.stream.Collectors;
<span class="fc" id="L11">public class KeycloakAuthenticatedUserFactory {</span>
private static final String CLAIM_EMAIL = &quot;email&quot;;
private static final String CLAIM_NAME = &quot;name&quot;;
private static final String CLAIM_PREFERRED_USERNAME = &quot;preferred_username&quot;;
public AuthenticatedUser create(Jwt jwt, Collection&lt;? extends GrantedAuthority&gt; authorities) {
<span class="fc" id="L18"> return new AuthenticatedUser(</span>
<span class="fc" id="L19"> jwt.getSubject(),</span>
<span class="fc" id="L20"> jwt.getClaimAsString(CLAIM_EMAIL),</span>
<span class="fc" id="L21"> displayName(jwt),</span>
<span class="fc" id="L22"> authorityNames(authorities)</span>
);
}
private static String displayName(Jwt jwt) {
<span class="fc" id="L27"> String name = jwt.getClaimAsString(CLAIM_NAME);</span>
<span class="pc bpc" id="L28" title="2 of 4 branches missed."> if (name != null &amp;&amp; !name.isBlank()) {</span>
<span class="fc" id="L29"> return name;</span>
}
<span class="nc" id="L31"> return jwt.getClaimAsString(CLAIM_PREFERRED_USERNAME);</span>
}
private static Set&lt;String&gt; authorityNames(Collection&lt;? extends GrantedAuthority&gt; authorities) {
<span class="fc" id="L35"> return authorities.stream()</span>
<span class="fc" id="L36"> .map(GrantedAuthority::getAuthority)</span>
<span class="fc" id="L37"> .collect(Collectors.toUnmodifiableSet());</span>
}
}
</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.13.202504020838</span></div></body></html>
@@ -0,0 +1,50 @@
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="en"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>KeycloakGrantedAuthoritiesConverter.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">bootstrap</a> &gt; <a href="index.source.html" class="el_package">com.project.auth.config.auth.security</a> &gt; <span class="el_source">KeycloakGrantedAuthoritiesConverter.java</span></div><h1>KeycloakGrantedAuthoritiesConverter.java</h1><pre class="source lang-java linenums">package com.project.auth.config.auth.security;
import org.springframework.core.convert.converter.Converter;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.server.resource.authentication.JwtGrantedAuthoritiesConverter;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
<span class="fc" id="L15">public class KeycloakGrantedAuthoritiesConverter implements Converter&lt;Jwt, Collection&lt;GrantedAuthority&gt;&gt; {</span>
private static final String CLAIM_REALM_ACCESS = &quot;realm_access&quot;;
private static final String CLAIM_ROLES = &quot;roles&quot;;
private static final String ROLE_PREFIX = &quot;ROLE_&quot;;
<span class="fc" id="L21"> private final JwtGrantedAuthoritiesConverter scopeConverter = new JwtGrantedAuthoritiesConverter();</span>
@Override
public Collection&lt;GrantedAuthority&gt; convert(Jwt jwt) {
<span class="fc" id="L25"> Set&lt;GrantedAuthority&gt; authorities = new LinkedHashSet&lt;&gt;(scopeConverter.convert(jwt));</span>
<span class="fc bfc" id="L26" title="All 2 branches covered."> for (String role : realmRoles(jwt)) {</span>
<span class="fc" id="L27"> authorities.add(new SimpleGrantedAuthority(ROLE_PREFIX + role));</span>
<span class="fc" id="L28"> }</span>
<span class="fc" id="L29"> return Set.copyOf(authorities);</span>
}
private static List&lt;String&gt; realmRoles(Jwt jwt) {
<span class="fc" id="L33"> Map&lt;String, Object&gt; realmAccess = jwt.getClaim(CLAIM_REALM_ACCESS);</span>
<span class="pc bpc" id="L34" title="1 of 2 branches missed."> if (realmAccess == null) {</span>
<span class="nc" id="L35"> return List.of();</span>
}
<span class="fc" id="L38"> Object roles = realmAccess.get(CLAIM_ROLES);</span>
<span class="pc bpc" id="L39" title="1 of 2 branches missed."> if (!(roles instanceof Collection&lt;?&gt; roleValues)) {</span>
<span class="nc" id="L40"> return List.of();</span>
}
<span class="fc" id="L43"> return roleValues.stream()</span>
<span class="fc" id="L44"> .filter(String.class::isInstance)</span>
<span class="fc" id="L45"> .map(String.class::cast)</span>
<span class="pc bpc" id="L46" title="1 of 2 branches missed."> .filter(role -&gt; !role.isBlank())</span>
<span class="fc" id="L47"> .toList();</span>
}
}
</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.13.202504020838</span></div></body></html>
@@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="en"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>KeycloakJwtAuthenticationConverter</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">bootstrap</a> &gt; <a href="index.html" class="el_package">com.project.auth.config.auth.security</a> &gt; <span class="el_class">KeycloakJwtAuthenticationConverter</span></div><h1>KeycloakJwtAuthenticationConverter</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">0 of 36</td><td class="ctr2">100%</td><td class="bar">0 of 0</td><td class="ctr2">n/a</td><td class="ctr1">0</td><td class="ctr2">2</td><td class="ctr1">0</td><td class="ctr2">7</td><td class="ctr1">0</td><td class="ctr2">2</td></tr></tfoot><tbody><tr><td id="a0"><a href="KeycloakJwtAuthenticationConverter.java.html#L30" class="el_method">convert(Jwt)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/greenbar.gif" width="120" height="10" title="21" alt="21"/></td><td class="ctr2" id="c0">100%</td><td class="bar" id="d0"/><td class="ctr2" id="e0">n/a</td><td class="ctr1" id="f0">0</td><td class="ctr2" id="g0">1</td><td class="ctr1" id="h0">0</td><td class="ctr2" id="i1">3</td><td class="ctr1" id="j0">0</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a1"><a href="KeycloakJwtAuthenticationConverter.java.html#L20" class="el_method">KeycloakJwtAuthenticationConverter(Converter, KeycloakAuthenticatedUserFactory)</a></td><td class="bar" id="b1"><img src="../jacoco-resources/greenbar.gif" width="85" height="10" title="15" alt="15"/></td><td class="ctr2" id="c1">100%</td><td class="bar" id="d1"/><td class="ctr2" id="e1">n/a</td><td class="ctr1" id="f1">0</td><td class="ctr2" id="g1">1</td><td class="ctr1" id="h1">0</td><td class="ctr2" id="i0">4</td><td class="ctr1" id="j1">0</td><td class="ctr2" id="k1">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.13.202504020838</span></div></body></html>
@@ -0,0 +1,35 @@
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="en"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>KeycloakJwtAuthenticationConverter.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">bootstrap</a> &gt; <a href="index.source.html" class="el_package">com.project.auth.config.auth.security</a> &gt; <span class="el_source">KeycloakJwtAuthenticationConverter.java</span></div><h1>KeycloakJwtAuthenticationConverter.java</h1><pre class="source lang-java linenums">package com.project.auth.config.auth.security;
import com.project.auth.presentation.auth.current.AuthenticatedUser;
import org.springframework.core.convert.converter.Converter;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.oauth2.jwt.Jwt;
import java.util.Collection;
import java.util.Objects;
public class KeycloakJwtAuthenticationConverter implements Converter&lt;Jwt, AbstractAuthenticationToken&gt; {
private final Converter&lt;Jwt, Collection&lt;GrantedAuthority&gt;&gt; authoritiesConverter;
private final KeycloakAuthenticatedUserFactory authenticatedUserFactory;
public KeycloakJwtAuthenticationConverter(
Converter&lt;Jwt, Collection&lt;GrantedAuthority&gt;&gt; authoritiesConverter,
KeycloakAuthenticatedUserFactory authenticatedUserFactory
<span class="fc" id="L20"> ) {</span>
<span class="fc" id="L21"> this.authoritiesConverter = Objects.requireNonNull(authoritiesConverter, &quot;authoritiesConverter must not be null&quot;);</span>
<span class="fc" id="L22"> this.authenticatedUserFactory = Objects.requireNonNull(</span>
authenticatedUserFactory,
&quot;authenticatedUserFactory must not be null&quot;
);
<span class="fc" id="L26"> }</span>
@Override
public AbstractAuthenticationToken convert(Jwt jwt) {
<span class="fc" id="L30"> Collection&lt;GrantedAuthority&gt; authorities = authoritiesConverter.convert(jwt);</span>
<span class="fc" id="L31"> AuthenticatedUser principal = authenticatedUserFactory.create(jwt, authorities);</span>
<span class="fc" id="L32"> return new KeycloakJwtAuthenticationToken(jwt, authorities, principal, jwt.getSubject());</span>
}
}
</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.13.202504020838</span></div></body></html>
@@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="en"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>KeycloakJwtAuthenticationToken</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">bootstrap</a> &gt; <a href="index.html" class="el_package">com.project.auth.config.auth.security</a> &gt; <span class="el_class">KeycloakJwtAuthenticationToken</span></div><h1>KeycloakJwtAuthenticationToken</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">0 of 12</td><td class="ctr2">100%</td><td class="bar">0 of 0</td><td class="ctr2">n/a</td><td class="ctr1">0</td><td class="ctr2">2</td><td class="ctr1">0</td><td class="ctr2">4</td><td class="ctr1">0</td><td class="ctr2">2</td></tr></tfoot><tbody><tr><td id="a1"><a href="KeycloakJwtAuthenticationToken.java.html#L20" class="el_method">KeycloakJwtAuthenticationToken(Jwt, Collection, AuthenticatedUser, String)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/greenbar.gif" width="120" height="10" title="9" alt="9"/></td><td class="ctr2" id="c0">100%</td><td class="bar" id="d0"/><td class="ctr2" id="e0">n/a</td><td class="ctr1" id="f0">0</td><td class="ctr2" id="g0">1</td><td class="ctr1" id="h0">0</td><td class="ctr2" id="i0">3</td><td class="ctr1" id="j0">0</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a0"><a href="KeycloakJwtAuthenticationToken.java.html#L26" class="el_method">getPrincipal()</a></td><td class="bar" id="b1"><img src="../jacoco-resources/greenbar.gif" width="40" height="10" title="3" alt="3"/></td><td class="ctr2" id="c1">100%</td><td class="bar" id="d1"/><td class="ctr2" id="e1">n/a</td><td class="ctr1" id="f1">0</td><td class="ctr2" id="g1">1</td><td class="ctr1" id="h1">0</td><td class="ctr2" id="i1">1</td><td class="ctr1" id="j1">0</td><td class="ctr2" id="k1">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.13.202504020838</span></div></body></html>
@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="en"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>KeycloakJwtAuthenticationToken.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">bootstrap</a> &gt; <a href="index.source.html" class="el_package">com.project.auth.config.auth.security</a> &gt; <span class="el_source">KeycloakJwtAuthenticationToken.java</span></div><h1>KeycloakJwtAuthenticationToken.java</h1><pre class="source lang-java linenums">package com.project.auth.config.auth.security;
import com.project.auth.presentation.auth.current.AuthenticatedUser;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;
import java.util.Collection;
public class KeycloakJwtAuthenticationToken extends JwtAuthenticationToken {
private final AuthenticatedUser principal;
public KeycloakJwtAuthenticationToken(
Jwt jwt,
Collection&lt;? extends GrantedAuthority&gt; authorities,
AuthenticatedUser principal,
String name
) {
<span class="fc" id="L20"> super(jwt, authorities, name);</span>
<span class="fc" id="L21"> this.principal = principal;</span>
<span class="fc" id="L22"> }</span>
@Override
public AuthenticatedUser getPrincipal() {
<span class="fc" id="L26"> return principal;</span>
}
}
</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.13.202504020838</span></div></body></html>
@@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="en"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>SecurityActorIdResolver</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">bootstrap</a> &gt; <a href="index.html" class="el_package">com.project.auth.config.auth.security</a> &gt; <span class="el_class">SecurityActorIdResolver</span></div><h1>SecurityActorIdResolver</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">10 of 30</td><td class="ctr2">66%</td><td class="bar">5 of 8</td><td class="ctr2">37%</td><td class="ctr1">3</td><td class="ctr2">6</td><td class="ctr1">2</td><td class="ctr2">9</td><td class="ctr1">0</td><td class="ctr2">2</td></tr></tfoot><tbody><tr><td id="a0"><a href="SecurityActorIdResolver.java.html#L16" class="el_method">resolve(HttpServletRequest)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="44" height="10" title="10" alt="10"/><img src="../jacoco-resources/greenbar.gif" width="75" height="10" title="17" alt="17"/></td><td class="ctr2" id="c1">62%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="75" height="10" title="5" alt="5"/><img src="../jacoco-resources/greenbar.gif" width="45" height="10" title="3" alt="3"/></td><td class="ctr2" id="e0">37%</td><td class="ctr1" id="f0">3</td><td class="ctr2" id="g0">5</td><td class="ctr1" id="h0">2</td><td class="ctr2" id="i0">8</td><td class="ctr1" id="j0">0</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a1"><a href="SecurityActorIdResolver.java.html#L11" class="el_method">SecurityActorIdResolver()</a></td><td class="bar" id="b1"><img src="../jacoco-resources/greenbar.gif" width="13" height="10" title="3" alt="3"/></td><td class="ctr2" id="c0">100%</td><td class="bar" id="d1"/><td class="ctr2" id="e1">n/a</td><td class="ctr1" id="f1">0</td><td class="ctr2" id="g1">1</td><td class="ctr1" id="h1">0</td><td class="ctr2" id="i1">1</td><td class="ctr1" id="j1">0</td><td class="ctr2" id="k1">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.13.202504020838</span></div></body></html>
@@ -0,0 +1,31 @@
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="en"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>SecurityActorIdResolver.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">bootstrap</a> &gt; <a href="index.source.html" class="el_package">com.project.auth.config.auth.security</a> &gt; <span class="el_source">SecurityActorIdResolver.java</span></div><h1>SecurityActorIdResolver.java</h1><pre class="source lang-java linenums">package com.project.auth.config.auth.security;
import com.project.auth.application.support.logging.LogSanitizer;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.security.authentication.AnonymousAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import java.security.Principal;
<span class="fc" id="L11">public class SecurityActorIdResolver {</span>
private static final String ANONYMOUS_PRINCIPAL = &quot;anonymous&quot;;
public String resolve(HttpServletRequest request) {
<span class="fc" id="L16"> Principal principal = request.getUserPrincipal();</span>
<span class="fc bfc" id="L17" title="All 2 branches covered."> if (principal != null) {</span>
<span class="fc" id="L18"> return LogSanitizer.actorId(principal.getName());</span>
}
<span class="fc" id="L21"> Authentication authentication = SecurityContextHolder.getContext().getAuthentication();</span>
<span class="pc bpc" id="L22" title="1 of 2 branches missed."> if (authentication != null</span>
<span class="nc bnc" id="L23" title="All 4 branches missed."> &amp;&amp; authentication.isAuthenticated()</span>
&amp;&amp; !(authentication instanceof AnonymousAuthenticationToken)) {
<span class="nc" id="L25"> return LogSanitizer.actorId(authentication.getName());</span>
}
<span class="fc" id="L28"> return LogSanitizer.actorId(ANONYMOUS_PRINCIPAL);</span>
}
}
</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.13.202504020838</span></div></body></html>
@@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="en"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>SecurityAuditTrailWriter</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">bootstrap</a> &gt; <a href="index.html" class="el_package">com.project.auth.config.auth.security</a> &gt; <span class="el_class">SecurityAuditTrailWriter</span></div><h1>SecurityAuditTrailWriter</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">0 of 64</td><td class="ctr2">100%</td><td class="bar">0 of 0</td><td class="ctr2">n/a</td><td class="ctr1">0</td><td class="ctr2">3</td><td class="ctr1">0</td><td class="ctr2">16</td><td class="ctr1">0</td><td class="ctr2">3</td></tr></tfoot><tbody><tr><td id="a0"><a href="SecurityAuditTrailWriter.java.html#L22" class="el_method">record(HttpServletRequest, AuthAuditEventType, String)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/greenbar.gif" width="120" height="10" title="51" alt="51"/></td><td class="ctr2" id="c0">100%</td><td class="bar" id="d0"/><td class="ctr2" id="e0">n/a</td><td class="ctr1" id="f0">0</td><td class="ctr2" id="g0">1</td><td class="ctr1" id="h0">0</td><td class="ctr2" id="i0">11</td><td class="ctr1" id="j0">0</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a2"><a href="SecurityAuditTrailWriter.java.html#L12" class="el_method">static {...}</a></td><td class="bar" id="b1"><img src="../jacoco-resources/greenbar.gif" width="16" height="10" title="7" alt="7"/></td><td class="ctr2" id="c1">100%</td><td class="bar" id="d1"/><td class="ctr2" id="e1">n/a</td><td class="ctr1" id="f1">0</td><td class="ctr2" id="g1">1</td><td class="ctr1" id="h1">0</td><td class="ctr2" id="i2">2</td><td class="ctr1" id="j1">0</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a1"><a href="SecurityAuditTrailWriter.java.html#L17" class="el_method">SecurityAuditTrailWriter(SecurityActorIdResolver)</a></td><td class="bar" id="b2"><img src="../jacoco-resources/greenbar.gif" width="14" height="10" title="6" alt="6"/></td><td class="ctr2" id="c2">100%</td><td class="bar" id="d2"/><td class="ctr2" id="e2">n/a</td><td class="ctr1" id="f2">0</td><td class="ctr2" id="g2">1</td><td class="ctr1" id="h2">0</td><td class="ctr2" id="i1">3</td><td class="ctr1" id="j2">0</td><td class="ctr2" id="k2">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.13.202504020838</span></div></body></html>
@@ -0,0 +1,39 @@
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="en"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>SecurityAuditTrailWriter.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">bootstrap</a> &gt; <a href="index.source.html" class="el_package">com.project.auth.config.auth.security</a> &gt; <span class="el_source">SecurityAuditTrailWriter.java</span></div><h1>SecurityAuditTrailWriter.java</h1><pre class="source lang-java linenums">package com.project.auth.config.auth.security;
import com.project.auth.application.support.audit.AuthAuditEventType;
import com.project.auth.application.support.audit.AuthAuditFields;
import com.project.auth.application.support.logging.LogSanitizer;
import jakarta.servlet.http.HttpServletRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class SecurityAuditTrailWriter {
<span class="fc" id="L12"> private static final Logger log = LoggerFactory.getLogger(SecurityAuditTrailWriter.class);</span>
<span class="fc" id="L13"> private static final Logger audit = LoggerFactory.getLogger(&quot;audit.auth&quot;);</span>
private final SecurityActorIdResolver actorIdResolver;
<span class="fc" id="L17"> public SecurityAuditTrailWriter(SecurityActorIdResolver actorIdResolver) {</span>
<span class="fc" id="L18"> this.actorIdResolver = actorIdResolver;</span>
<span class="fc" id="L19"> }</span>
public void record(HttpServletRequest request, AuthAuditEventType eventType, String message) {
<span class="fc" id="L22"> String actorId = actorIdResolver.resolve(request);</span>
<span class="fc" id="L23"> String requestPath = LogSanitizer.requestPath(request.getRequestURI());</span>
<span class="fc" id="L24"> log.warn(</span>
&quot;{} actorId={} method={} requestPath={}&quot;,
message,
actorId,
<span class="fc" id="L28"> request.getMethod(),</span>
requestPath
);
<span class="fc" id="L31"> audit.atWarn()</span>
<span class="fc" id="L32"> .addKeyValue(AuthAuditFields.EVENT_TYPE, eventType.code())</span>
<span class="fc" id="L33"> .addKeyValue(AuthAuditFields.ACTOR_ID, actorId)</span>
<span class="fc" id="L34"> .addKeyValue(AuthAuditFields.METHOD, request.getMethod())</span>
<span class="fc" id="L35"> .addKeyValue(AuthAuditFields.REQUEST_PATH, requestPath)</span>
<span class="fc" id="L36"> .log(eventType.code());</span>
<span class="fc" id="L37"> }</span>
}
</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.13.202504020838</span></div></body></html>
@@ -0,0 +1,77 @@
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="en"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>SecurityExceptionHandler.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">bootstrap</a> &gt; <a href="index.source.html" class="el_package">com.project.auth.config.auth.security</a> &gt; <span class="el_source">SecurityExceptionHandler.java</span></div><h1>SecurityExceptionHandler.java</h1><pre class="source lang-java linenums">package com.project.auth.config.auth.security;
import com.project.auth.application.support.audit.AuthAuditEventType;
import com.project.auth.application.support.logging.LogSanitizer;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.web.servlet.HandlerExceptionResolver;
import java.util.Objects;
public class SecurityExceptionHandler implements AuthenticationEntryPoint, AccessDeniedHandler {
<span class="fc" id="L19"> private static final Logger log = LoggerFactory.getLogger(SecurityExceptionHandler.class);</span>
private final HandlerExceptionResolver handlerExceptionResolver;
private final SecurityAuditTrailWriter securityAuditTrailWriter;
public SecurityExceptionHandler(
HandlerExceptionResolver handlerExceptionResolver,
SecurityAuditTrailWriter securityAuditTrailWriter
<span class="fc" id="L27"> ) {</span>
<span class="fc" id="L28"> this.handlerExceptionResolver = Objects.requireNonNull(</span>
handlerExceptionResolver,
&quot;handlerExceptionResolver must not be null&quot;
);
<span class="fc" id="L32"> this.securityAuditTrailWriter = Objects.requireNonNull(</span>
securityAuditTrailWriter,
&quot;securityAuditTrailWriter must not be null&quot;
);
<span class="fc" id="L36"> }</span>
@Override
public void commence(
HttpServletRequest request,
HttpServletResponse response,
AuthenticationException authException
) {
<span class="fc" id="L44"> recordAudit(request, AuthAuditEventType.AUTHENTICATION_REQUIRED, &quot;Authentication required.&quot;);</span>
<span class="pc bpc" id="L45" title="1 of 2 branches missed."> if (response.isCommitted()) {</span>
<span class="nc" id="L46"> log.warn(&quot;Response already committed; cannot render authentication error body. requestPath={}&quot;,</span>
<span class="nc" id="L47"> LogSanitizer.requestPath(request.getRequestURI()));</span>
<span class="nc" id="L48"> return;</span>
}
<span class="fc" id="L50"> handlerExceptionResolver.resolveException(request, response, null, authException);</span>
<span class="fc" id="L51"> }</span>
@Override
public void handle(
HttpServletRequest request,
HttpServletResponse response,
AccessDeniedException accessDeniedException
) {
<span class="fc" id="L59"> recordAudit(request, AuthAuditEventType.ACCESS_DENIED, &quot;Access denied.&quot;);</span>
<span class="pc bpc" id="L60" title="1 of 2 branches missed."> if (response.isCommitted()) {</span>
<span class="nc" id="L61"> log.warn(&quot;Response already committed; cannot render access-denied error body. requestPath={}&quot;,</span>
<span class="nc" id="L62"> LogSanitizer.requestPath(request.getRequestURI()));</span>
<span class="nc" id="L63"> return;</span>
}
<span class="fc" id="L65"> handlerExceptionResolver.resolveException(request, response, null, accessDeniedException);</span>
<span class="fc" id="L66"> }</span>
private void recordAudit(HttpServletRequest request, AuthAuditEventType type, String description) {
try {
<span class="fc" id="L70"> securityAuditTrailWriter.record(request, type, description);</span>
<span class="nc" id="L71"> } catch (RuntimeException auditFailure) {</span>
<span class="nc" id="L72"> log.warn(&quot;Security audit write failed; continuing with response rendering. type={}&quot;,</span>
type, auditFailure);
<span class="fc" id="L74"> }</span>
<span class="fc" id="L75"> }</span>
}
</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.13.202504020838</span></div></body></html>
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="en"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>AuthAuditLoggingConfiguration.AuthAuditEventLogListener</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">bootstrap</a> &gt; <a href="index.html" class="el_package">com.project.auth.config.logging</a> &gt; <span class="el_class">AuthAuditLoggingConfiguration.AuthAuditEventLogListener</span></div><h1>AuthAuditLoggingConfiguration.AuthAuditEventLogListener</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">0 of 36</td><td class="ctr2">100%</td><td class="bar">0 of 2</td><td class="ctr2">100%</td><td class="ctr1">0</td><td class="ctr2">4</td><td class="ctr1">0</td><td class="ctr2">6</td><td class="ctr1">0</td><td class="ctr2">3</td></tr></tfoot><tbody><tr><td id="a2"><a href="AuthAuditLoggingConfiguration.java.html#L35" class="el_method">onAuthAuditEvent(AuthAuditEvent)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/greenbar.gif" width="120" height="10" title="26" alt="26"/></td><td class="ctr2" id="c0">100%</td><td class="bar" id="d0"><img src="../jacoco-resources/greenbar.gif" width="120" height="10" title="2" alt="2"/></td><td class="ctr2" id="e0">100%</td><td class="ctr1" id="f0">0</td><td class="ctr2" id="g0">2</td><td class="ctr1" id="h0">0</td><td class="ctr2" id="i0">5</td><td class="ctr1" id="j0">0</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a1"><a href="AuthAuditLoggingConfiguration.java.html#L37" class="el_method">lambda$onAuthAuditEvent$0(LoggingEventBuilder, String, String)</a></td><td class="bar" id="b1"><img src="../jacoco-resources/greenbar.gif" width="32" height="10" title="7" alt="7"/></td><td class="ctr2" id="c1">100%</td><td class="bar" id="d1"/><td class="ctr2" id="e1">n/a</td><td class="ctr1" id="f1">0</td><td class="ctr2" id="g1">1</td><td class="ctr1" id="h1">0</td><td class="ctr2" id="i1">1</td><td class="ctr1" id="j1">0</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a0"><a href="AuthAuditLoggingConfiguration.java.html#L31" class="el_method">AuthAuditLoggingConfiguration.AuthAuditEventLogListener()</a></td><td class="bar" id="b2"><img src="../jacoco-resources/greenbar.gif" width="13" height="10" title="3" alt="3"/></td><td class="ctr2" id="c2">100%</td><td class="bar" id="d2"/><td class="ctr2" id="e2">n/a</td><td class="ctr1" id="f2">0</td><td class="ctr2" id="g2">1</td><td class="ctr1" id="h2">0</td><td class="ctr2" id="i2">1</td><td class="ctr1" id="j2">0</td><td class="ctr2" id="k2">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.13.202504020838</span></div></body></html>
@@ -0,0 +1,42 @@
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="en"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>AuthAuditLoggingConfiguration.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">bootstrap</a> &gt; <a href="index.source.html" class="el_package">com.project.auth.config.logging</a> &gt; <span class="el_source">AuthAuditLoggingConfiguration.java</span></div><h1>AuthAuditLoggingConfiguration.java</h1><pre class="source lang-java linenums">package com.project.auth.config.logging;
import com.project.auth.application.support.audit.AuditLevel;
import com.project.auth.application.support.audit.AuthAuditEvent;
import com.project.auth.application.support.audit.AuthAuditEventPublisher;
import com.project.auth.application.support.audit.AuthAuditFields;
import com.project.auth.application.support.logging.LogSanitizer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.spi.LoggingEventBuilder;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.event.EventListener;
@Configuration
public class AuthAuditLoggingConfiguration {
private static final Logger audit = LoggerFactory.getLogger(&quot;audit.auth&quot;);
@Bean
public AuthAuditEventPublisher authAuditEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
return applicationEventPublisher::publishEvent;
}
@Bean
public AuthAuditEventLogListener authAuditEventListener() {
return new AuthAuditEventLogListener();
}
<span class="fc" id="L31"> static final class AuthAuditEventLogListener {</span>
@EventListener
public void onAuthAuditEvent(AuthAuditEvent event) {
<span class="fc bfc" id="L35" title="All 2 branches covered."> LoggingEventBuilder builder = event.level() == AuditLevel.WARN ? audit.atWarn() : audit.atInfo();</span>
<span class="fc" id="L36"> builder.addKeyValue(AuthAuditFields.EVENT_TYPE, event.eventType());</span>
<span class="fc" id="L37"> event.fields().forEach((key, value) -&gt; builder.addKeyValue(key, LogSanitizer.normalize(value)));</span>
<span class="fc" id="L38"> builder.log(event.eventType());</span>
<span class="fc" id="L39"> }</span>
}
}
</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.13.202504020838</span></div></body></html>
@@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="en"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>com.project.auth.config.logging</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb', 'coveragetable'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="index.source.html" class="el_source">Source Files</a><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">bootstrap</a> &gt; <span class="el_package">com.project.auth.config.logging</span></div><h1>com.project.auth.config.logging</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td><td class="sortable ctr1" id="l" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="m" onclick="toggleSort(this)">Classes</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">0 of 36</td><td class="ctr2">100%</td><td class="bar">0 of 2</td><td class="ctr2">100%</td><td class="ctr1">0</td><td class="ctr2">4</td><td class="ctr1">0</td><td class="ctr2">6</td><td class="ctr1">0</td><td class="ctr2">3</td><td class="ctr1">0</td><td class="ctr2">1</td></tr></tfoot><tbody><tr><td id="a0"><a href="AuthAuditLoggingConfiguration$AuthAuditEventLogListener.html" class="el_class">AuthAuditLoggingConfiguration.AuthAuditEventLogListener</a></td><td class="bar" id="b0"><img src="../jacoco-resources/greenbar.gif" width="120" height="10" title="36" alt="36"/></td><td class="ctr2" id="c0">100%</td><td class="bar" id="d0"><img src="../jacoco-resources/greenbar.gif" width="120" height="10" title="2" alt="2"/></td><td class="ctr2" id="e0">100%</td><td class="ctr1" id="f0">0</td><td class="ctr2" id="g0">4</td><td class="ctr1" id="h0">0</td><td class="ctr2" id="i0">6</td><td class="ctr1" id="j0">0</td><td class="ctr2" id="k0">3</td><td class="ctr1" id="l0">0</td><td class="ctr2" id="m0">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.13.202504020838</span></div></body></html>
@@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="en"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>com.project.auth.config.logging</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb', 'coveragetable'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="index.html" class="el_class">Classes</a><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">bootstrap</a> &gt; <span class="el_package">com.project.auth.config.logging</span></div><h1>com.project.auth.config.logging</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td><td class="sortable ctr1" id="l" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="m" onclick="toggleSort(this)">Classes</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">0 of 36</td><td class="ctr2">100%</td><td class="bar">0 of 2</td><td class="ctr2">100%</td><td class="ctr1">0</td><td class="ctr2">4</td><td class="ctr1">0</td><td class="ctr2">6</td><td class="ctr1">0</td><td class="ctr2">3</td><td class="ctr1">0</td><td class="ctr2">1</td></tr></tfoot><tbody><tr><td id="a0"><a href="AuthAuditLoggingConfiguration.java.html" class="el_source">AuthAuditLoggingConfiguration.java</a></td><td class="bar" id="b0"><img src="../jacoco-resources/greenbar.gif" width="120" height="10" title="36" alt="36"/></td><td class="ctr2" id="c0">100%</td><td class="bar" id="d0"><img src="../jacoco-resources/greenbar.gif" width="120" height="10" title="2" alt="2"/></td><td class="ctr2" id="e0">100%</td><td class="ctr1" id="f0">0</td><td class="ctr2" id="g0">4</td><td class="ctr1" id="h0">0</td><td class="ctr2" id="i0">6</td><td class="ctr1" id="j0">0</td><td class="ctr2" id="k0">3</td><td class="ctr1" id="l0">0</td><td class="ctr2" id="m0">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.13.202504020838</span></div></body></html>
File diff suppressed because one or more lines are too long
@@ -0,0 +1,115 @@
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="en"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>ApiErrorController.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">bootstrap</a> &gt; <a href="index.source.html" class="el_package">com.project.auth.config.web</a> &gt; <span class="el_source">ApiErrorController.java</span></div><h1>ApiErrorController.java</h1><pre class="source lang-java linenums">package com.project.auth.config.web;
import com.project.auth.application.support.exception.ClientFacingErrorCode;
import com.project.auth.application.support.exception.CommonErrorCode;
import com.project.auth.application.support.logging.LogSanitizer;
import com.project.auth.presentation.support.exception.PresentationErrorCode;
import com.project.auth.presentation.support.response.ApiResult;
import com.project.auth.presentation.support.response.ApiResultFactory;
import jakarta.servlet.RequestDispatcher;
import jakarta.servlet.http.HttpServletRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.webmvc.error.ErrorController;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Objects;
/**
* {@code @RestControllerAdvice}를 우회하는 요청(필터에서 던져진 예외,
* {@code response.sendError(...)} 호출, 컨테이너 레벨 라우팅 실패, 다른 핸들러 내부의
* 이중 폴트 등)에 대한 최후의 에러 렌더러.
*
* 컨테이너가 결정한 상태 코드는 그대로 보존하고, 본문만 {@link ApiResult} 형태로 정규화한다.
* 5xx는 내부 분류 노출을 막기 위해 {@code COMMON-999}로 정규화하고, 4xx는
* {@link PresentationErrorCode} 기반 코드로 매핑하여 클라이언트 SDK가
* &quot;요청이 잘못됐다&quot;&quot;서버가 깨졌다&quot;를 구분할 수 있게 한다.
*
* 의존성 정책: 이 컨트롤러는 Spring Boot의 ErrorAttributes를 사용하지 않는다.
* 필요한 정보(상태 코드, 원인 예외, 원래 요청 경로)는 모두 서블릿 표준
* RequestDispatcher.ERROR_* attribute로부터 직접 추출한다. 이렇게 두면
* ErrorAttributeOptions의 변경(예: 스택트레이스 포함)이 이 컨트롤러가 노출하는 정보를
* 본의 아니게 확장하지 못하며, 빈 의존 그래프도 작아진다.
*/
@RestController
class ApiErrorController implements ErrorController {
<span class="fc" id="L40"> private static final Logger log = LoggerFactory.getLogger(ApiErrorController.class);</span>
private final ApiResultFactory apiResultFactory;
<span class="fc" id="L44"> ApiErrorController(ApiResultFactory apiResultFactory) {</span>
<span class="fc" id="L45"> this.apiResultFactory = Objects.requireNonNull(apiResultFactory, &quot;apiResultFactory must not be null&quot;);</span>
<span class="fc" id="L46"> }</span>
@RequestMapping(&quot;/error&quot;)
ResponseEntity&lt;ApiResult&lt;Void&gt;&gt; error(HttpServletRequest request) {
<span class="fc" id="L50"> int rawStatus = resolveStatusCode(request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE));</span>
<span class="fc" id="L51"> HttpStatus httpStatus = safeStatus(rawStatus);</span>
<span class="fc" id="L52"> Throwable error = resolveError(request);</span>
<span class="fc" id="L53"> String requestPath = LogSanitizer.requestPath(originalRequestPath(request));</span>
<span class="fc" id="L54"> String method = request.getMethod();</span>
<span class="pc bpc" id="L56" title="1 of 4 branches missed."> if (httpStatus.is5xxServerError() &amp;&amp; error != null) {</span>
<span class="nc" id="L57"> log.error(&quot;Unhandled exception. method={} requestPath={} status={}&quot;,</span>
<span class="nc" id="L58"> method, requestPath, httpStatus.value(), error);</span>
<span class="fc bfc" id="L59" title="All 2 branches covered."> } else if (httpStatus.is5xxServerError()) {</span>
<span class="fc" id="L60"> log.error(&quot;Unhandled error response. method={} requestPath={} status={}&quot;,</span>
<span class="fc" id="L61"> method, requestPath, httpStatus.value());</span>
} else {
<span class="fc" id="L63"> log.warn(&quot;Unhandled error response. method={} requestPath={} status={}&quot;,</span>
<span class="fc" id="L64"> method, requestPath, httpStatus.value());</span>
}
<span class="fc" id="L67"> ClientFacingErrorCode errorCode = classify(httpStatus);</span>
<span class="fc" id="L69"> return ResponseEntity.status(httpStatus)</span>
<span class="fc" id="L70"> .body(apiResultFactory.failure(errorCode.code(), errorCode.message()));</span>
}
private static ClientFacingErrorCode classify(HttpStatus status) {
<span class="fc bfc" id="L74" title="All 2 branches covered."> if (status.is5xxServerError()) {</span>
<span class="fc" id="L75"> return CommonErrorCode.INTERNAL_SERVER_ERROR;</span>
}
<span class="pc bpc" id="L77" title="3 of 6 branches missed."> return switch (status) {</span>
<span class="fc" id="L78"> case NOT_FOUND -&gt; PresentationErrorCode.RESOURCE_NOT_FOUND;</span>
<span class="fc" id="L79"> case METHOD_NOT_ALLOWED -&gt; PresentationErrorCode.METHOD_NOT_ALLOWED;</span>
<span class="nc" id="L80"> case UNSUPPORTED_MEDIA_TYPE -&gt; PresentationErrorCode.UNSUPPORTED_MEDIA_TYPE;</span>
<span class="nc" id="L81"> case NOT_ACCEPTABLE -&gt; PresentationErrorCode.NOT_ACCEPTABLE;</span>
<span class="nc" id="L82"> case CONTENT_TOO_LARGE -&gt; PresentationErrorCode.PAYLOAD_TOO_LARGE;</span>
<span class="fc" id="L83"> default -&gt; PresentationErrorCode.UNHANDLED_CLIENT_ERROR;</span>
};
}
private static HttpStatus safeStatus(int rawStatus) {
try {
<span class="fc" id="L89"> return HttpStatus.valueOf(rawStatus);</span>
<span class="fc" id="L90"> } catch (IllegalArgumentException ignored) {</span>
<span class="fc" id="L91"> return HttpStatus.INTERNAL_SERVER_ERROR;</span>
}
}
private static int resolveStatusCode(Object status) {
<span class="fc bfc" id="L96" title="All 2 branches covered."> if (status instanceof Integer value) {</span>
<span class="fc" id="L97"> return value;</span>
}
<span class="fc" id="L99"> return HttpStatus.INTERNAL_SERVER_ERROR.value();</span>
}
private static String originalRequestPath(HttpServletRequest request) {
<span class="fc" id="L103"> Object path = request.getAttribute(RequestDispatcher.ERROR_REQUEST_URI);</span>
<span class="pc bpc" id="L104" title="1 of 4 branches missed."> if (path instanceof String requestUri &amp;&amp; !requestUri.isBlank()) {</span>
<span class="fc" id="L105"> return requestUri;</span>
}
<span class="fc" id="L107"> return request.getRequestURI();</span>
}
private static Throwable resolveError(HttpServletRequest request) {
<span class="fc" id="L111"> Object exception = request.getAttribute(RequestDispatcher.ERROR_EXCEPTION);</span>
<span class="pc bpc" id="L112" title="1 of 2 branches missed."> return exception instanceof Throwable throwable ? throwable : null;</span>
}
}
</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.13.202504020838</span></div></body></html>
@@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="en"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>InfrastructureExceptionHandler</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">bootstrap</a> &gt; <a href="index.html" class="el_package">com.project.auth.config.web</a> &gt; <span class="el_class">InfrastructureExceptionHandler</span></div><h1>InfrastructureExceptionHandler</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">0 of 80</td><td class="ctr2">100%</td><td class="bar">0 of 0</td><td class="ctr2">n/a</td><td class="ctr1">0</td><td class="ctr2">4</td><td class="ctr1">0</td><td class="ctr2">19</td><td class="ctr1">0</td><td class="ctr2">4</td></tr></tfoot><tbody><tr><td id="a0"><a href="InfrastructureExceptionHandler.java.html#L42" class="el_method">handleInfrastructureException(InfrastructureException, HttpServletRequest)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/greenbar.gif" width="120" height="10" title="38" alt="38"/></td><td class="ctr2" id="c0">100%</td><td class="bar" id="d0"/><td class="ctr2" id="e0">n/a</td><td class="ctr1" id="f0">0</td><td class="ctr2" id="g0">1</td><td class="ctr1" id="h0">0</td><td class="ctr2" id="i0">8</td><td class="ctr1" id="j0">0</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a1"><a href="InfrastructureExceptionHandler.java.html#L62" class="el_method">handleLeakedDomainException(DomainException, HttpServletRequest)</a></td><td class="bar" id="b1"><img src="../jacoco-resources/greenbar.gif" width="101" height="10" title="32" alt="32"/></td><td class="ctr2" id="c1">100%</td><td class="bar" id="d1"/><td class="ctr2" id="e1">n/a</td><td class="ctr1" id="f1">0</td><td class="ctr2" id="g1">1</td><td class="ctr1" id="h1">0</td><td class="ctr2" id="i1">7</td><td class="ctr1" id="j1">0</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a2"><a href="InfrastructureExceptionHandler.java.html#L33" class="el_method">InfrastructureExceptionHandler(ApiResultFactory)</a></td><td class="bar" id="b2"><img src="../jacoco-resources/greenbar.gif" width="18" height="10" title="6" alt="6"/></td><td class="ctr2" id="c2">100%</td><td class="bar" id="d2"/><td class="ctr2" id="e2">n/a</td><td class="ctr1" id="f2">0</td><td class="ctr2" id="g2">1</td><td class="ctr1" id="h2">0</td><td class="ctr2" id="i2">3</td><td class="ctr1" id="j2">0</td><td class="ctr2" id="k2">1</td></tr><tr><td id="a3"><a href="InfrastructureExceptionHandler.java.html#L29" class="el_method">static {...}</a></td><td class="bar" id="b3"><img src="../jacoco-resources/greenbar.gif" width="12" height="10" title="4" alt="4"/></td><td class="ctr2" id="c3">100%</td><td class="bar" id="d3"/><td class="ctr2" id="e3">n/a</td><td class="ctr1" id="f3">0</td><td class="ctr2" id="g3">1</td><td class="ctr1" id="h3">0</td><td class="ctr2" id="i3">1</td><td class="ctr1" id="j3">0</td><td class="ctr2" id="k3">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.13.202504020838</span></div></body></html>
@@ -0,0 +1,76 @@
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="en"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>InfrastructureExceptionHandler.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">bootstrap</a> &gt; <a href="index.source.html" class="el_package">com.project.auth.config.web</a> &gt; <span class="el_source">InfrastructureExceptionHandler.java</span></div><h1>InfrastructureExceptionHandler.java</h1><pre class="source lang-java linenums">package com.project.auth.config.web;
import com.project.auth.application.support.exception.CommonErrorCode;
import com.project.auth.application.support.logging.LogSanitizer;
import com.project.auth.domain.user.exception.DomainException;
import com.project.auth.infrastructure.support.exception.InfrastructureException;
import com.project.auth.presentation.support.exception.ApiErrorHttpStatusMapper;
import com.project.auth.presentation.support.response.ApiResult;
import com.project.auth.presentation.support.response.ApiResultFactory;
import jakarta.servlet.http.HttpServletRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
/**
* InfrastructureErrorCode is kept as an internal classification for logs and alerting.
* Client responses are intentionally normalized to COMMON-999 to avoid exposing internal dependency details.
* This advice stays in bootstrap because moving it to presentation would create a
* presentation -&gt; infrastructure dependency and break the layer rule.
*/
@RestControllerAdvice
@Order(Ordered.HIGHEST_PRECEDENCE)
public class InfrastructureExceptionHandler {
<span class="fc" id="L29"> private static final Logger log = LoggerFactory.getLogger(InfrastructureExceptionHandler.class);</span>
private final ApiResultFactory apiResultFactory;
<span class="fc" id="L33"> public InfrastructureExceptionHandler(ApiResultFactory apiResultFactory) {</span>
<span class="fc" id="L34"> this.apiResultFactory = apiResultFactory;</span>
<span class="fc" id="L35"> }</span>
@ExceptionHandler(InfrastructureException.class)
public ResponseEntity&lt;ApiResult&lt;Void&gt;&gt; handleInfrastructureException(
InfrastructureException exception,
HttpServletRequest request
) {
<span class="fc" id="L42"> log.error(</span>
&quot;Infrastructure failure. errorCode={} method={} requestPath={}&quot;,
<span class="fc" id="L44"> exception.getErrorCode().code(),</span>
<span class="fc" id="L45"> request.getMethod(),</span>
<span class="fc" id="L46"> LogSanitizer.requestPath(request.getRequestURI()),</span>
exception
);
<span class="fc" id="L50"> return ResponseEntity.status(ApiErrorHttpStatusMapper.map(CommonErrorCode.INTERNAL_SERVER_ERROR))</span>
<span class="fc" id="L51"> .body(apiResultFactory.failure(</span>
<span class="fc" id="L52"> CommonErrorCode.INTERNAL_SERVER_ERROR.code(),</span>
<span class="fc" id="L53"> CommonErrorCode.INTERNAL_SERVER_ERROR.message()</span>
));
}
@ExceptionHandler(DomainException.class)
public ResponseEntity&lt;ApiResult&lt;Void&gt;&gt; handleLeakedDomainException(
DomainException exception,
HttpServletRequest request
) {
<span class="fc" id="L62"> log.error(</span>
&quot;Leaked domain exception. method={} requestPath={}&quot;,
<span class="fc" id="L64"> request.getMethod(),</span>
<span class="fc" id="L65"> LogSanitizer.requestPath(request.getRequestURI()),</span>
exception
);
<span class="fc" id="L69"> return ResponseEntity.status(ApiErrorHttpStatusMapper.map(CommonErrorCode.INTERNAL_SERVER_ERROR))</span>
<span class="fc" id="L70"> .body(apiResultFactory.failure(</span>
<span class="fc" id="L71"> CommonErrorCode.INTERNAL_SERVER_ERROR.code(),</span>
<span class="fc" id="L72"> CommonErrorCode.INTERNAL_SERVER_ERROR.message()</span>
));
}
}
</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.13.202504020838</span></div></body></html>
File diff suppressed because one or more lines are too long
@@ -0,0 +1,90 @@
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="en"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>RequestAccessLogFilter.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">bootstrap</a> &gt; <a href="index.source.html" class="el_package">com.project.auth.config.web</a> &gt; <span class="el_source">RequestAccessLogFilter.java</span></div><h1>RequestAccessLogFilter.java</h1><pre class="source lang-java linenums">package com.project.auth.config.web;
import com.project.auth.application.support.logging.LogSanitizer;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.filter.OncePerRequestFilter;
import java.io.IOException;
import java.util.List;
public class RequestAccessLogFilter extends OncePerRequestFilter {
<span class="fc" id="L19"> private static final Logger log = LoggerFactory.getLogger(&quot;http.access&quot;);</span>
private static final String ACCESS_EVENT_TYPE = &quot;HTTP_ACCESS&quot;;
private final List&lt;String&gt; excludedPathPrefixes;
<span class="fc" id="L24"> public RequestAccessLogFilter(AccessLogProperties accessLogProperties) {</span>
<span class="fc bfc" id="L25" title="All 2 branches covered."> this.excludedPathPrefixes = accessLogProperties.excludedPathPrefixes() == null</span>
<span class="fc" id="L26"> ? List.of()</span>
<span class="fc" id="L27"> : List.copyOf(accessLogProperties.excludedPathPrefixes());</span>
<span class="fc" id="L28"> }</span>
@Override
protected boolean shouldNotFilter(HttpServletRequest request) {
<span class="fc" id="L32"> String path = request.getRequestURI();</span>
<span class="fc" id="L33"> return excludedPathPrefixes.stream().anyMatch(path::startsWith);</span>
}
@Override
protected boolean shouldNotFilterAsyncDispatch() {
<span class="nc" id="L38"> return true;</span>
}
@Override
protected boolean shouldNotFilterErrorDispatch() {
<span class="nc" id="L43"> return true;</span>
}
@Override
protected void doFilterInternal(
HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
<span class="fc" id="L51"> long startTime = System.nanoTime();</span>
try {
<span class="fc" id="L54"> filterChain.doFilter(request, response);</span>
} finally {
<span class="fc" id="L56"> long durationMs = (System.nanoTime() - startTime) / 1_000_000;</span>
<span class="fc" id="L57"> logRequestSummary(request, response, durationMs);</span>
}
<span class="fc" id="L59"> }</span>
private void logRequestSummary(HttpServletRequest request, HttpServletResponse response, long durationMs) {
<span class="fc" id="L62"> String method = request.getMethod();</span>
<span class="fc" id="L63"> String path = LogSanitizer.requestPath(request.getRequestURI());</span>
<span class="fc" id="L64"> int status = response.getStatus();</span>
<span class="fc" id="L65"> String remoteIp = LogSanitizer.clientIp(request.getRemoteAddr());</span>
<span class="fc" id="L66"> String actorId = resolveActorId();</span>
<span class="pc bpc" id="L67" title="1 of 2 branches missed."> String result = status &lt; 400 ? &quot;success&quot; : &quot;failure&quot;;</span>
<span class="fc" id="L69"> log.atInfo()</span>
<span class="fc" id="L70"> .addKeyValue(&quot;eventType&quot;, ACCESS_EVENT_TYPE)</span>
<span class="fc" id="L71"> .addKeyValue(&quot;method&quot;, method)</span>
<span class="fc" id="L72"> .addKeyValue(&quot;requestPath&quot;, path)</span>
<span class="fc" id="L73"> .addKeyValue(&quot;status&quot;, status)</span>
<span class="fc" id="L74"> .addKeyValue(&quot;durationMs&quot;, durationMs)</span>
<span class="fc" id="L75"> .addKeyValue(&quot;remoteIp&quot;, remoteIp)</span>
<span class="fc" id="L76"> .addKeyValue(&quot;actorId&quot;, actorId)</span>
<span class="fc" id="L77"> .addKeyValue(&quot;result&quot;, result)</span>
<span class="fc" id="L78"> .log(&quot;ACCESS&quot;);</span>
<span class="fc" id="L79"> }</span>
private String resolveActorId() {
<span class="fc" id="L82"> Authentication authentication = SecurityContextHolder.getContext().getAuthentication();</span>
<span class="pc bpc" id="L83" title="1 of 4 branches missed."> if (authentication != null &amp;&amp; authentication.isAuthenticated()</span>
<span class="pc bpc" id="L84" title="1 of 2 branches missed."> &amp;&amp; !&quot;anonymousUser&quot;.equals(authentication.getPrincipal())) {</span>
<span class="fc" id="L85"> return LogSanitizer.actorId(authentication.getName());</span>
}
<span class="fc" id="L87"> return LogSanitizer.actorId(&quot;anonymous&quot;);</span>
}
}
</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.13.202504020838</span></div></body></html>
File diff suppressed because one or more lines are too long
@@ -0,0 +1,69 @@
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="en"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>RequestBoundApiResultFactory.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">bootstrap</a> &gt; <a href="index.source.html" class="el_package">com.project.auth.config.web</a> &gt; <span class="el_source">RequestBoundApiResultFactory.java</span></div><h1>RequestBoundApiResultFactory.java</h1><pre class="source lang-java linenums">package com.project.auth.config.web;
import com.project.auth.presentation.support.response.ApiResult;
import com.project.auth.presentation.support.response.ApiResultFactory;
import org.slf4j.MDC;
import java.time.Clock;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.Map;
import java.util.Objects;
public class RequestBoundApiResultFactory implements ApiResultFactory {
private static final String TRACE_ID_KEY = &quot;traceId&quot;;
/**
* MDC에 traceId가 없을 때 사용하는 sentinel. JSON {@code null}로 그대로 두면
* TraceIdFilter가 누락/오설정된 사실이 가려지므로, 명시적인 placeholder 문자열로
* 로그·대시보드에서 즉시 식별 가능하게 한다.
*/
static final String MISSING_TRACE_ID = &quot;-&quot;;
<span class="fc" id="L22"> private static final DateTimeFormatter TIMESTAMP_FORMATTER = DateTimeFormatter.ISO_INSTANT;</span>
private final Clock clock;
<span class="fc" id="L26"> public RequestBoundApiResultFactory(Clock clock) {</span>
<span class="fc" id="L27"> this.clock = Objects.requireNonNull(clock, &quot;clock must not be null&quot;);</span>
<span class="fc" id="L28"> }</span>
@Override
public &lt;T&gt; ApiResult&lt;T&gt; success(String code, String message, T data) {
<span class="fc" id="L32"> return result(true, code, message, data, null);</span>
}
@Override
public ApiResult&lt;Void&gt; success(String code, String message) {
<span class="nc" id="L37"> return result(true, code, message, null, null);</span>
}
@Override
public ApiResult&lt;Void&gt; failure(String code, String message) {
<span class="fc" id="L42"> return result(false, code, message, null, null);</span>
}
@Override
public ApiResult&lt;Void&gt; failure(String code, String message, Map&lt;String, List&lt;String&gt;&gt; errors) {
<span class="fc" id="L47"> return result(false, code, message, null, errors);</span>
}
private &lt;T&gt; ApiResult&lt;T&gt; result(
boolean success,
String code,
String message,
T data,
Map&lt;String, List&lt;String&gt;&gt; errors
) {
<span class="fc" id="L57"> String traceId = MDC.get(TRACE_ID_KEY);</span>
<span class="fc" id="L58"> return new ApiResult&lt;&gt;(</span>
success,
code,
message,
data,
errors,
<span class="fc bfc" id="L64" title="All 4 branches covered."> traceId == null || traceId.isBlank() ? MISSING_TRACE_ID : traceId,</span>
<span class="fc" id="L65"> TIMESTAMP_FORMATTER.format(clock.instant())</span>
);
}
}
</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.13.202504020838</span></div></body></html>
File diff suppressed because one or more lines are too long
@@ -0,0 +1,144 @@
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="en"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>SecurityResponseExceptionHandler.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">bootstrap</a> &gt; <a href="index.source.html" class="el_package">com.project.auth.config.web</a> &gt; <span class="el_source">SecurityResponseExceptionHandler.java</span></div><h1>SecurityResponseExceptionHandler.java</h1><pre class="source lang-java linenums">package com.project.auth.config.web;
import com.project.auth.application.support.audit.AuthAuditEventType;
import com.project.auth.application.support.exception.AuthErrorCode;
import com.project.auth.application.support.logging.LogSanitizer;
import com.project.auth.config.auth.security.SecurityAuditTrailWriter;
import com.project.auth.presentation.support.exception.ApiErrorHttpStatusMapper;
import com.project.auth.presentation.support.response.ApiResult;
import com.project.auth.presentation.support.response.ApiResultFactory;
import jakarta.servlet.http.HttpServletRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.authentication.AnonymousAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import java.util.Objects;
/**
* Spring Security 예외를 처리하는 bootstrap 계층 advice.
*
* presentation 모듈은 ArchUnit 규칙에 의해 org.springframework.security.core / context에
* 직접 의존할 수 없으므로(LayerDependencyArchitectureTest 참고), 이 advice는 bootstrap에 둔다.
*
* AccessDeniedException은 현재 인증 상태에 따라 401/403으로 분기한다. 익명 principal이
* 보호된 리소스에 접근한 경우는 인증이 필요하다는 의미로 401을 반환하고, 인증된 principal이
* 권한이 부족한 경우에만 403을 반환한다. Spring Security의 ExceptionTranslationFilter가
* 필터 단계에서 던지는 예외에 대해 적용하는 분기 로직을 컨트롤러 단(@PreAuthorize 등 메서드
* 보안)에서 던져진 동일 예외에도 일관되게 적용한 것이다.
*
* Audit 정책: 이 advice의 모든 분기(인증 필요, 익명 401, 인증된 사용자 403)는
* SecurityAuditTrailWriter로 audit를 기록한다. 필터 단의 SecurityExceptionHandler가
* 기록하는 audit 채널과 동일 채널을 사용하므로, &quot;필터 단/컨트롤러 단&quot; 어느 경로로 들어와도
* 보안 이벤트가 누락 없이 동일 형태로 적재된다.
*
* 스레드/익명 판정: SecurityContextHolder의 ThreadLocal 컨텍스트를 우선 보지만,
* 비동기 컨트롤러로 컨텍스트가 워커 스레드로 전파되지 않은 케이스를 대비해
* HttpServletRequest.getUserPrincipal()을 보조 신호로 사용한다. principal이 명시적으로
* 존재하면 익명으로 분류하지 않는다 — 컨텍스트가 비어 있더라도 인증된 사용자가 잘못
* 401로 분류되지 않도록 보호하는 방어적 폴백이다.
*/
@RestControllerAdvice
@Order(Ordered.HIGHEST_PRECEDENCE + 5)
public class SecurityResponseExceptionHandler {
<span class="fc" id="L53"> private static final Logger log = LoggerFactory.getLogger(SecurityResponseExceptionHandler.class);</span>
private final ApiResultFactory apiResultFactory;
private final SecurityAuditTrailWriter securityAuditTrailWriter;
public SecurityResponseExceptionHandler(
ApiResultFactory apiResultFactory,
SecurityAuditTrailWriter securityAuditTrailWriter
<span class="fc" id="L61"> ) {</span>
<span class="fc" id="L62"> this.apiResultFactory = Objects.requireNonNull(apiResultFactory, &quot;apiResultFactory must not be null&quot;);</span>
<span class="fc" id="L63"> this.securityAuditTrailWriter = Objects.requireNonNull(</span>
securityAuditTrailWriter, &quot;securityAuditTrailWriter must not be null&quot;);
<span class="fc" id="L65"> }</span>
@ExceptionHandler(AuthenticationException.class)
public ResponseEntity&lt;ApiResult&lt;Void&gt;&gt; handleAuthenticationException(
AuthenticationException exception,
HttpServletRequest request
) {
<span class="fc" id="L72"> log.warn(&quot;Authentication required. exceptionType={} method={} requestPath={} errorCode={}&quot;,</span>
<span class="fc" id="L73"> exception.getClass().getSimpleName(),</span>
<span class="fc" id="L74"> request.getMethod(),</span>
<span class="fc" id="L75"> LogSanitizer.requestPath(request.getRequestURI()),</span>
<span class="fc" id="L76"> AuthErrorCode.AUTHENTICATION_REQUIRED.code());</span>
<span class="fc" id="L77"> recordAudit(request, AuthAuditEventType.AUTHENTICATION_REQUIRED, &quot;Authentication required.&quot;);</span>
<span class="fc" id="L79"> return ResponseEntity.status(ApiErrorHttpStatusMapper.map(AuthErrorCode.AUTHENTICATION_REQUIRED))</span>
<span class="fc" id="L80"> .body(apiResultFactory.failure(</span>
<span class="fc" id="L81"> AuthErrorCode.AUTHENTICATION_REQUIRED.code(),</span>
<span class="fc" id="L82"> AuthErrorCode.AUTHENTICATION_REQUIRED.message()</span>
));
}
@ExceptionHandler(AccessDeniedException.class)
public ResponseEntity&lt;ApiResult&lt;Void&gt;&gt; handleAccessDeniedException(
AccessDeniedException exception,
HttpServletRequest request
) {
<span class="pc bpc" id="L91" title="1 of 2 branches missed."> if (isAnonymous(SecurityContextHolder.getContext().getAuthentication(), request)) {</span>
<span class="nc" id="L92"> log.warn(&quot;AccessDenied for anonymous principal -&gt; 401. exceptionType={} method={} requestPath={} errorCode={}&quot;,</span>
<span class="nc" id="L93"> exception.getClass().getSimpleName(),</span>
<span class="nc" id="L94"> request.getMethod(),</span>
<span class="nc" id="L95"> LogSanitizer.requestPath(request.getRequestURI()),</span>
<span class="nc" id="L96"> AuthErrorCode.AUTHENTICATION_REQUIRED.code());</span>
<span class="nc" id="L97"> recordAudit(request, AuthAuditEventType.AUTHENTICATION_REQUIRED,</span>
&quot;Authentication required (AccessDenied for anonymous).&quot;);
<span class="nc" id="L100"> return ResponseEntity.status(ApiErrorHttpStatusMapper.map(AuthErrorCode.AUTHENTICATION_REQUIRED))</span>
<span class="nc" id="L101"> .body(apiResultFactory.failure(</span>
<span class="nc" id="L102"> AuthErrorCode.AUTHENTICATION_REQUIRED.code(),</span>
<span class="nc" id="L103"> AuthErrorCode.AUTHENTICATION_REQUIRED.message()</span>
));
}
<span class="fc" id="L107"> log.warn(&quot;Access denied for authenticated principal. exceptionType={} method={} requestPath={} errorCode={}&quot;,</span>
<span class="fc" id="L108"> exception.getClass().getSimpleName(),</span>
<span class="fc" id="L109"> request.getMethod(),</span>
<span class="fc" id="L110"> LogSanitizer.requestPath(request.getRequestURI()),</span>
<span class="fc" id="L111"> AuthErrorCode.ACCESS_DENIED.code());</span>
<span class="fc" id="L112"> recordAudit(request, AuthAuditEventType.ACCESS_DENIED, &quot;Access denied.&quot;);</span>
<span class="fc" id="L114"> return ResponseEntity.status(ApiErrorHttpStatusMapper.map(AuthErrorCode.ACCESS_DENIED))</span>
<span class="fc" id="L115"> .body(apiResultFactory.failure(</span>
<span class="fc" id="L116"> AuthErrorCode.ACCESS_DENIED.code(),</span>
<span class="fc" id="L117"> AuthErrorCode.ACCESS_DENIED.message()</span>
));
}
/**
* SecurityContext가 비어 있어도 서블릿 principal이 명시적으로 존재하면 익명이 아니다.
* ThreadLocal 컨텍스트가 비동기 워커 스레드로 전파되지 않은 케이스에서 인증된 사용자가
* 잘못 401로 분류되는 사고를 막기 위한 방어적 폴백.
*/
private static boolean isAnonymous(Authentication authentication, HttpServletRequest request) {
<span class="pc bpc" id="L127" title="1 of 2 branches missed."> if (request.getUserPrincipal() != null) {</span>
<span class="fc" id="L128"> return false;</span>
}
<span class="nc bnc" id="L130" title="All 2 branches missed."> return authentication == null</span>
<span class="nc bnc" id="L131" title="All 4 branches missed."> || !authentication.isAuthenticated()</span>
|| authentication instanceof AnonymousAuthenticationToken;
}
private void recordAudit(HttpServletRequest request, AuthAuditEventType type, String description) {
try {
<span class="fc" id="L137"> securityAuditTrailWriter.record(request, type, description);</span>
<span class="nc" id="L138"> } catch (RuntimeException auditFailure) {</span>
<span class="nc" id="L139"> log.warn(&quot;Security audit write failed; continuing with response rendering. type={}&quot;,</span>
type, auditFailure);
<span class="fc" id="L141"> }</span>
<span class="fc" id="L142"> }</span>
}
</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.13.202504020838</span></div></body></html>
@@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="en"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>TraceIdFilter</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">bootstrap</a> &gt; <a href="index.html" class="el_package">com.project.auth.config.web</a> &gt; <span class="el_class">TraceIdFilter</span></div><h1>TraceIdFilter</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">4 of 61</td><td class="ctr2">93%</td><td class="bar">3 of 4</td><td class="ctr2">25%</td><td class="ctr1">2</td><td class="ctr2">6</td><td class="ctr1">0</td><td class="ctr2">15</td><td class="ctr1">0</td><td class="ctr2">4</td></tr></tfoot><tbody><tr><td id="a1"><a href="TraceIdFilter.java.html#L47" class="el_method">nextTraceId()</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="14" height="10" title="4" alt="4"/><img src="../jacoco-resources/greenbar.gif" width="65" height="10" title="18" alt="18"/></td><td class="ctr2" id="c3">81%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="90" height="10" title="3" alt="3"/><img src="../jacoco-resources/greenbar.gif" width="30" height="10" title="1" alt="1"/></td><td class="ctr2" id="e0">25%</td><td class="ctr1" id="f0">2</td><td class="ctr2" id="g0">3</td><td class="ctr1" id="h0">0</td><td class="ctr2" id="i1">4</td><td class="ctr1" id="j0">0</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a0"><a href="TraceIdFilter.java.html#L30" class="el_method">doFilterInternal(HttpServletRequest, HttpServletResponse, FilterChain)</a></td><td class="bar" id="b1"><img src="../jacoco-resources/greenbar.gif" width="120" height="10" title="33" alt="33"/></td><td class="ctr2" id="c0">100%</td><td class="bar" id="d1"/><td class="ctr2" id="e1">n/a</td><td class="ctr1" id="f1">0</td><td class="ctr2" id="g1">1</td><td class="ctr1" id="h1">0</td><td class="ctr2" id="i0">9</td><td class="ctr1" id="j1">0</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a3"><a href="TraceIdFilter.java.html#L16" class="el_method">TraceIdFilter()</a></td><td class="bar" id="b2"><img src="../jacoco-resources/greenbar.gif" width="10" height="10" title="3" alt="3"/></td><td class="ctr2" id="c1">100%</td><td class="bar" id="d2"/><td class="ctr2" id="e2">n/a</td><td class="ctr1" id="f2">0</td><td class="ctr2" id="g2">1</td><td class="ctr1" id="h2">0</td><td class="ctr2" id="i2">1</td><td class="ctr1" id="j2">0</td><td class="ctr2" id="k2">1</td></tr><tr><td id="a2"><a href="TraceIdFilter.java.html#L23" class="el_method">static {...}</a></td><td class="bar" id="b3"><img src="../jacoco-resources/greenbar.gif" width="10" height="10" title="3" alt="3"/></td><td class="ctr2" id="c2">100%</td><td class="bar" id="d3"/><td class="ctr2" id="e3">n/a</td><td class="ctr1" id="f3">0</td><td class="ctr2" id="g3">1</td><td class="ctr1" id="h3">0</td><td class="ctr2" id="i3">1</td><td class="ctr1" id="j3">0</td><td class="ctr2" id="k3">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.13.202504020838</span></div></body></html>
@@ -0,0 +1,55 @@
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="en"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>TraceIdFilter.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">bootstrap</a> &gt; <a href="index.source.html" class="el_package">com.project.auth.config.web</a> &gt; <span class="el_source">TraceIdFilter.java</span></div><h1>TraceIdFilter.java</h1><pre class="source lang-java linenums">package com.project.auth.config.web;
import com.project.auth.application.support.logging.LogSanitizer;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.slf4j.MDC;
import org.slf4j.MDC.MDCCloseable;
import org.springframework.web.filter.OncePerRequestFilter;
import java.io.IOException;
import java.util.HexFormat;
import java.util.concurrent.ThreadLocalRandom;
<span class="fc" id="L16">public class TraceIdFilter extends OncePerRequestFilter {</span>
private static final String TRACE_ID_KEY = &quot;traceId&quot;;
private static final String CLIENT_IP_KEY = &quot;clientIp&quot;;
private static final String USER_AGENT_KEY = &quot;userAgent&quot;;
private static final String TRACE_ID_HEADER = &quot;X-Trace-Id&quot;;
private static final String USER_AGENT_HEADER = &quot;User-Agent&quot;;
<span class="fc" id="L23"> private static final HexFormat HEX_FORMAT = HexFormat.of();</span>
@Override
protected void doFilterInternal(
HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
<span class="fc" id="L30"> String traceId = nextTraceId();</span>
<span class="fc" id="L31"> String clientIp = LogSanitizer.clientIp(request.getRemoteAddr());</span>
<span class="fc" id="L32"> String userAgent = LogSanitizer.userAgent(request.getHeader(USER_AGENT_HEADER));</span>
<span class="fc" id="L33"> response.setHeader(TRACE_ID_HEADER, traceId);</span>
try (
<span class="fc" id="L36"> MDCCloseable ignoredTraceId = MDC.putCloseable(TRACE_ID_KEY, traceId);</span>
<span class="fc" id="L37"> MDCCloseable ignoredClientIp = MDC.putCloseable(CLIENT_IP_KEY, clientIp);</span>
<span class="fc" id="L38"> MDCCloseable ignoredUserAgent = MDC.putCloseable(USER_AGENT_KEY, userAgent)) {</span>
<span class="fc" id="L39"> filterChain.doFilter(request, response);</span>
}
<span class="fc" id="L41"> }</span>
private String nextTraceId() {
long highBits;
long lowBits;
do {
<span class="fc" id="L47"> highBits = ThreadLocalRandom.current().nextLong();</span>
<span class="fc" id="L48"> lowBits = ThreadLocalRandom.current().nextLong();</span>
<span class="pc bpc" id="L49" title="3 of 4 branches missed."> } while (highBits == 0L &amp;&amp; lowBits == 0L);</span>
<span class="fc" id="L51"> return HEX_FORMAT.toHexDigits(highBits) + HEX_FORMAT.toHexDigits(lowBits);</span>
}
}
</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.13.202504020838</span></div></body></html>
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="en"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="jacoco-resources/report.gif" type="image/gif"/><title>bootstrap</title><script type="text/javascript" src="jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb', 'coveragetable'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="jacoco-sessions.html" class="el_session">Sessions</a></span><span class="el_report">bootstrap</span></div><h1>bootstrap</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td><td class="sortable ctr1" id="l" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="m" onclick="toggleSort(this)">Classes</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">153 of 1,096</td><td class="ctr2">86%</td><td class="bar">32 of 76</td><td class="ctr2">57%</td><td class="ctr1">29</td><td class="ctr2">102</td><td class="ctr1">35</td><td class="ctr2">266</td><td class="ctr1">3</td><td class="ctr2">62</td><td class="ctr1">0</td><td class="ctr2">14</td></tr></tfoot><tbody><tr><td id="a2"><a href="com.project.auth.config.web/index.html" class="el_package">com.project.auth.config.web</a></td><td class="bar" id="b0"><img src="jacoco-resources/redbar.gif" width="19" height="10" title="114" alt="114"/><img src="jacoco-resources/greenbar.gif" width="100" height="10" title="598" alt="598"/></td><td class="ctr2" id="c2">83%</td><td class="bar" id="d0"><img src="jacoco-resources/redbar.gif" width="48" height="10" title="20" alt="20"/><img src="jacoco-resources/greenbar.gif" width="72" height="10" title="30" alt="30"/></td><td class="ctr2" id="e1">60%</td><td class="ctr1" id="f0">19</td><td class="ctr2" id="g0">64</td><td class="ctr1" id="h0">22</td><td class="ctr2" id="i0">169</td><td class="ctr1" id="j0">3</td><td class="ctr2" id="k0">37</td><td class="ctr1" id="l0">0</td><td class="ctr2" id="m1">6</td></tr><tr><td id="a0"><a href="com.project.auth.config.auth.security/index.html" class="el_package">com.project.auth.config.auth.security</a></td><td class="bar" id="b1"><img src="jacoco-resources/redbar.gif" width="6" height="10" title="39" alt="39"/><img src="jacoco-resources/greenbar.gif" width="52" height="10" title="309" alt="309"/></td><td class="ctr2" id="c1">88%</td><td class="bar" id="d1"><img src="jacoco-resources/redbar.gif" width="28" height="10" title="12" alt="12"/><img src="jacoco-resources/greenbar.gif" width="28" height="10" title="12" alt="12"/></td><td class="ctr2" id="e2">50%</td><td class="ctr1" id="f1">10</td><td class="ctr2" id="g1">34</td><td class="ctr1" id="h1">13</td><td class="ctr2" id="i1">91</td><td class="ctr1" id="j1">0</td><td class="ctr2" id="k1">22</td><td class="ctr1" id="l1">0</td><td class="ctr2" id="m0">7</td></tr><tr><td id="a1"><a href="com.project.auth.config.logging/index.html" class="el_package">com.project.auth.config.logging</a></td><td class="bar" id="b2"><img src="jacoco-resources/greenbar.gif" width="6" height="10" title="36" alt="36"/></td><td class="ctr2" id="c0">100%</td><td class="bar" id="d2"><img src="jacoco-resources/greenbar.gif" width="4" height="10" title="2" alt="2"/></td><td class="ctr2" id="e0">100%</td><td class="ctr1" id="f2">0</td><td class="ctr2" id="g2">4</td><td class="ctr1" id="h2">0</td><td class="ctr2" id="i2">6</td><td class="ctr1" id="j2">0</td><td class="ctr2" id="k2">3</td><td class="ctr1" id="l2">0</td><td class="ctr2" id="m2">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.13.202504020838</span></div></body></html>
Binary file not shown.

After

Width:  |  Height:  |  Size: 91 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 91 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 91 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 709 B

Some files were not shown because too many files have changed in this diff Show More