Skip to content

Commit f424668

Browse files
committed
Better error messages for realm exceptions
1 parent dd75228 commit f424668

File tree

10 files changed

+95
-20
lines changed

10 files changed

+95
-20
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
* [chg] Moved `web.server.sessions` configuration options to `web.sessions`.
77
* [chg] Default session timeout with embedded servers is now defined by the `web.server.` configuration options to `web.sessions`.
88
* [chg] Renamed `web.staticResources` configuration options to `web.static`.
9+
* [chg] Better error messages for security realm exceptions.
910
* [fix] Ensure that JVM-wide base configuration is refreshed between tests.
1011
* [fix] With Jersey 2, allow JAX-RS components to be instantiated without Guice as a fallback.
1112
* [fix] Default session timeout for Undertow was incorrect. It is now 20 minutes.

security/core/src/main/java/org/seedstack/seed/security/internal/SecurityConfigurer.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
* License, v. 2.0. If a copy of the MPL was not distributed with this
66
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
77
*/
8+
89
package org.seedstack.seed.security.internal;
910

1011
import java.util.ArrayList;
@@ -22,8 +23,11 @@
2223
import org.seedstack.seed.security.internal.authorization.EmptyRolePermissionResolver;
2324
import org.seedstack.seed.security.internal.authorization.SameRoleMapping;
2425
import org.seedstack.seed.security.internal.realms.ConfigurationRealm;
26+
import org.slf4j.Logger;
27+
import org.slf4j.LoggerFactory;
2528

2629
class SecurityConfigurer {
30+
private static final Logger LOGGER = LoggerFactory.getLogger(SecurityConfigurer.class);
2731
private static final Class<? extends Realm> DEFAULT_REALM = ConfigurationRealm.class;
2832
private static final Class<? extends RoleMapping> DEFAULT_ROLE_MAPPING = SameRoleMapping.class;
2933
private static final Class<? extends RoleMapping> CONFIGURATION_ROLE_MAPPING = ConfigurationRoleMapping.class;
@@ -69,6 +73,7 @@ private void buildRealms() {
6973
confRealm.setRolePermissionResolverClass(findRolePermissionResolver(null, confRealm));
7074
confRealm.setRoleMappingClass(findRoleMapping(null, confRealm));
7175
configurationRealms.add(confRealm);
76+
LOGGER.info("No security realm configured, using ConfigurationRealm by default");
7277
} else {
7378
for (SecurityConfig.RealmConfig realmConfig : securityConfig.getRealms()) {
7479
Class<? extends Realm> realmClass = (Class<? extends Realm>) findClass(realmConfig.getName(),
@@ -81,6 +86,7 @@ private void buildRealms() {
8186
confRealm.setRoleMappingClass(findRoleMapping(realmConfig, confRealm));
8287
configurationRealms.add(confRealm);
8388
}
89+
LOGGER.info("Configured security realms: {}", configurationRealms);
8490
}
8591
}
8692

security/core/src/main/java/org/seedstack/seed/security/internal/SecurityInternalModule.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ public void configure() {
5252
private void bindPrincipalCustomizers() {
5353
Multibinder<PrincipalCustomizer> principalCustomizers = Multibinder.newSetBinder(binder(),
5454
PrincipalCustomizer.class);
55-
for (Class<? extends PrincipalCustomizer> customizerClass : securityConfigurer.getPrincipalCustomizers()) {
55+
for (Class<? extends PrincipalCustomizer<?>> customizerClass : securityConfigurer.getPrincipalCustomizers()) {
5656
principalCustomizers.addBinding().to(customizerClass);
5757
}
5858
expose(new PrincipalCustomizersTypeLiteral());

security/core/src/main/java/org/seedstack/seed/security/internal/ShiroRealmAdapter.java

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
* License, v. 2.0. If a copy of the MPL was not distributed with this
66
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
77
*/
8+
89
package org.seedstack.seed.security.internal;
910

1011
import java.util.ArrayList;
@@ -113,7 +114,7 @@ public boolean supports(AuthenticationToken token) {
113114
protected Object getAuthenticationCacheKey(AuthenticationToken token) {
114115
Object authenticationCacheKey = super.getAuthenticationCacheKey(token);
115116
if (authenticationCacheKey instanceof PrincipalProvider) {
116-
return ((PrincipalProvider) authenticationCacheKey).get();
117+
return ((PrincipalProvider<?>) authenticationCacheKey).get();
117118
} else {
118119
return authenticationCacheKey;
119120
}
@@ -123,7 +124,7 @@ protected Object getAuthenticationCacheKey(AuthenticationToken token) {
123124
protected Object getAuthenticationCacheKey(PrincipalCollection principals) {
124125
Object authenticationCacheKey = super.getAuthenticationCacheKey(principals);
125126
if (authenticationCacheKey instanceof PrincipalProvider) {
126-
return ((PrincipalProvider) authenticationCacheKey).get();
127+
return ((PrincipalProvider<?>) authenticationCacheKey).get();
127128
} else {
128129
return authenticationCacheKey;
129130
}
@@ -133,7 +134,7 @@ protected Object getAuthenticationCacheKey(PrincipalCollection principals) {
133134
protected Object getAuthorizationCacheKey(PrincipalCollection principals) {
134135
Object primaryPrincipal = principals.getPrimaryPrincipal();
135136
if (primaryPrincipal instanceof PrincipalProvider) {
136-
return ((PrincipalProvider) primaryPrincipal).get();
137+
return ((PrincipalProvider<?>) primaryPrincipal).get();
137138
} else {
138139
return primaryPrincipal;
139140
}
@@ -157,6 +158,11 @@ void setRealm(Realm realm) {
157158
this.realm = realm;
158159
}
159160

161+
@Override
162+
public String toString() {
163+
return realm.name();
164+
}
165+
160166
private org.seedstack.seed.security.AuthenticationToken convertToken(AuthenticationToken token) {
161167
if (token instanceof org.seedstack.seed.security.AuthenticationToken) {
162168
return (org.seedstack.seed.security.AuthenticationToken) token;

security/core/src/main/java/org/seedstack/seed/security/internal/realms/AuthenticationTokenWrapper.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
* License, v. 2.0. If a copy of the MPL was not distributed with this
66
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
77
*/
8+
89
package org.seedstack.seed.security.internal.realms;
910

1011
import org.apache.shiro.authc.AuthenticationToken;
@@ -35,6 +36,11 @@ public Object getCredentials() {
3536
return seedToken.getCredentials();
3637
}
3738

39+
@Override
40+
public String toString() {
41+
return seedToken.name();
42+
}
43+
3844
/**
3945
* Gives the seed authentication token.
4046
*

security/specs/src/main/java/org/seedstack/seed/security/AuthenticationToken.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
* License, v. 2.0. If a copy of the MPL was not distributed with this
66
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
77
*/
8+
89
package org.seedstack.seed.security;
910

1011
import java.io.Serializable;
@@ -26,6 +27,12 @@
2627
* sufficient for your needs.
2728
*/
2829
public interface AuthenticationToken extends Serializable {
30+
/**
31+
* @return the name of the token type
32+
*/
33+
default String name() {
34+
return getClass().getSimpleName();
35+
}
2936

3037
/**
3138
* Returns the account identity submitted during the authentication process.

security/specs/src/main/java/org/seedstack/seed/security/Realm.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
* License, v. 2.0. If a copy of the MPL was not distributed with this
66
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
77
*/
8+
89
package org.seedstack.seed.security;
910

1011
import java.util.Collection;
@@ -15,6 +16,12 @@
1516
* A realm is used to authenticate and retrieve authorization for a user.
1617
*/
1718
public interface Realm {
19+
/**
20+
* @return the name of the realm
21+
*/
22+
default String name() {
23+
return getClass().getSimpleName();
24+
}
1825

1926
/**
2027
* Get the roles

web/security/src/main/java/org/seedstack/seed/web/security/internal/AntiXsrfFilter.java

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
* License, v. 2.0. If a copy of the MPL was not distributed with this
66
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
77
*/
8+
89
package org.seedstack.seed.web.security.internal;
910

1011
import java.security.SecureRandom;
@@ -18,8 +19,11 @@
1819
import org.apache.shiro.web.filter.PathMatchingFilter;
1920
import org.seedstack.seed.Configuration;
2021
import org.seedstack.seed.web.security.WebSecurityConfig;
22+
import org.slf4j.Logger;
23+
import org.slf4j.LoggerFactory;
2124

2225
public class AntiXsrfFilter extends PathMatchingFilter {
26+
private static final Logger LOGGER = LoggerFactory.getLogger(AntiXsrfFilter.class);
2327
private final static char[] CHARSET = new char[]{'a', 'b', 'c', 'd', 'e',
2428
'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r',
2529
's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4',
@@ -31,7 +35,7 @@ public class AntiXsrfFilter extends PathMatchingFilter {
3135

3236
@Override
3337
protected boolean onPreHandle(ServletRequest request, ServletResponse response,
34-
Object mappedValue) throws Exception {
38+
Object mappedValue) {
3539
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
3640
HttpServletResponse httpServletResponse = (HttpServletResponse) response;
3741
final HttpSession session = httpServletRequest.getSession(false);
@@ -56,8 +60,11 @@ protected boolean onPreHandle(ServletRequest request, ServletResponse response,
5660

5761
// If no cookie is available, send an error
5862
if (cookieToken == null) {
59-
((HttpServletResponse) response).sendError(HttpServletResponse.SC_FORBIDDEN,
60-
"Missing CSRF protection token cookie");
63+
WebSecurityPlugin.sendErrorToClient((HttpServletResponse) response,
64+
LOGGER,
65+
HttpServletResponse.SC_FORBIDDEN,
66+
"Missing CSRF protection token cookie",
67+
null);
6168
return false;
6269
}
6370

@@ -71,15 +78,21 @@ protected boolean onPreHandle(ServletRequest request, ServletResponse response,
7178

7279
// If no request token available, send an error
7380
if (requestToken == null) {
74-
((HttpServletResponse) response).sendError(HttpServletResponse.SC_FORBIDDEN,
75-
"Missing CSRF protection token in the request headers");
81+
WebSecurityPlugin.sendErrorToClient((HttpServletResponse) response,
82+
LOGGER,
83+
HttpServletResponse.SC_FORBIDDEN,
84+
"Missing CSRF protection token in the request headers",
85+
null);
7686
return false;
7787
}
7888

7989
// If tokens don't match, send an error
8090
if (!cookieToken.equals(requestToken)) {
81-
((HttpServletResponse) response).sendError(HttpServletResponse.SC_FORBIDDEN,
82-
"Request token does not match session token");
91+
WebSecurityPlugin.sendErrorToClient((HttpServletResponse) response,
92+
LOGGER,
93+
HttpServletResponse.SC_FORBIDDEN,
94+
"Request token does not match session token",
95+
null);
8396
return false;
8497
}
8598

web/security/src/main/java/org/seedstack/seed/web/security/internal/WebSecurityPlugin.java

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
* License, v. 2.0. If a copy of the MPL was not distributed with this
66
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
77
*/
8+
89
package org.seedstack.seed.web.security.internal;
910

1011
import com.google.common.collect.Lists;
@@ -13,11 +14,14 @@
1314
import io.nuun.kernel.api.plugin.PluginException;
1415
import io.nuun.kernel.api.plugin.context.InitContext;
1516
import io.nuun.kernel.api.plugin.request.ClasspathScanRequest;
17+
import java.io.IOException;
1618
import java.util.ArrayList;
1719
import java.util.Collection;
1820
import java.util.List;
21+
import java.util.UUID;
1922
import javax.servlet.Filter;
2023
import javax.servlet.ServletContext;
24+
import javax.servlet.http.HttpServletResponse;
2125
import org.apache.shiro.guice.web.GuiceShiroFilter;
2226
import org.seedstack.seed.core.SeedRuntime;
2327
import org.seedstack.seed.core.internal.AbstractSeedPlugin;
@@ -31,6 +35,7 @@
3135
import org.seedstack.seed.web.spi.SeedFilterPriority;
3236
import org.seedstack.seed.web.spi.ServletDefinition;
3337
import org.seedstack.seed.web.spi.WebProvider;
38+
import org.slf4j.Logger;
3439

3540
/**
3641
* This plugins adds web security.
@@ -116,4 +121,25 @@ public List<FilterDefinition> filters() {
116121
public List<ListenerDefinition> listeners() {
117122
return null;
118123
}
119-
}
124+
125+
static void sendErrorToClient(HttpServletResponse resp, Logger logger, int code, String message, Exception e) {
126+
String uuid = UUID.randomUUID().toString();
127+
try {
128+
resp.sendError(code, message + " [" + uuid + "]");
129+
if (e != null) {
130+
logger.warn("Sent {} HTTP error to client [{}]: {}. Server-side exception message: {}",
131+
code,
132+
uuid,
133+
message,
134+
e.getMessage());
135+
} else {
136+
logger.warn("Sent {} HTTP error to client [{}]: {}. No server-side exception occurred.",
137+
code,
138+
uuid,
139+
message);
140+
}
141+
} catch (IOException e1) {
142+
logger.warn("Unable to send {} error to client [{}]: {}", code, uuid, message, e1);
143+
}
144+
}
145+
}

web/security/src/main/java/org/seedstack/seed/web/security/internal/X509CertificateFilter.java

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@
55
* License, v. 2.0. If a copy of the MPL was not distributed with this
66
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
77
*/
8+
89
package org.seedstack.seed.web.security.internal;
910

10-
import java.io.IOException;
1111
import java.security.cert.X509Certificate;
1212
import javax.servlet.ServletRequest;
1313
import javax.servlet.ServletResponse;
@@ -18,11 +18,14 @@
1818
import org.apache.shiro.web.filter.authc.AuthenticatingFilter;
1919
import org.seedstack.seed.security.X509CertificateToken;
2020
import org.seedstack.seed.security.internal.realms.AuthenticationTokenWrapper;
21+
import org.slf4j.Logger;
22+
import org.slf4j.LoggerFactory;
2123

2224
/**
2325
* A security filter that extracts the certificate from the request for later use
2426
*/
2527
public class X509CertificateFilter extends AuthenticatingFilter {
28+
private static final Logger LOGGER = LoggerFactory.getLogger(X509CertificateFilter.class);
2629
private static final String OPTIONAL = "optional";
2730
private boolean optional;
2831

@@ -45,14 +48,14 @@ protected boolean onLoginFailure(AuthenticationToken token, AuthenticationExcept
4548
ServletResponse response) {
4649
if (optional) {
4750
return true;
51+
} else {
52+
WebSecurityPlugin.sendErrorToClient((HttpServletResponse) response,
53+
LOGGER,
54+
HttpServletResponse.SC_UNAUTHORIZED,
55+
"A valid certificate is required to gain access",
56+
e);
57+
return false;
4858
}
49-
try {
50-
((HttpServletResponse) response).sendError(HttpServletResponse.SC_UNAUTHORIZED,
51-
"A valid certificate is required to gain access");
52-
} catch (IOException e1) {
53-
throw new IllegalStateException(e1);
54-
}
55-
return false;
5659
}
5760

5861
@Override

0 commit comments

Comments
 (0)