Skip to content

Commit 98888d8

Browse files
authored
Merge pull request #270 from adrienlauer/sec-login-method
Add a login() method to security support
2 parents 35cbeb4 + 6e3fccf commit 98888d8

File tree

13 files changed

+133
-71
lines changed

13 files changed

+133
-71
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
# Version 3.9.1 (2019-11-30)
2+
3+
* [new] Support for programmatic login through `SecuritySupport` interface (no need for Shiro-specific code anymore).
4+
* [chg] Obtaining principals by type now honors inheritance (instead of returning principals of the exact specified type).
5+
* [chg] Principals are no longer required to be serializable.
6+
17
# Version 3.9.0 (2019-08-12)
28

39
* [new] Introduce the `diag` tool to manually write a diagnostic report to standard output or in a file.

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ public boolean supports(AuthenticationToken token) {
113113
protected Object getAuthenticationCacheKey(AuthenticationToken token) {
114114
Object authenticationCacheKey = super.getAuthenticationCacheKey(token);
115115
if (authenticationCacheKey instanceof PrincipalProvider) {
116-
return ((PrincipalProvider) authenticationCacheKey).getPrincipal();
116+
return ((PrincipalProvider) authenticationCacheKey).get();
117117
} else {
118118
return authenticationCacheKey;
119119
}
@@ -123,7 +123,7 @@ protected Object getAuthenticationCacheKey(AuthenticationToken token) {
123123
protected Object getAuthenticationCacheKey(PrincipalCollection principals) {
124124
Object authenticationCacheKey = super.getAuthenticationCacheKey(principals);
125125
if (authenticationCacheKey instanceof PrincipalProvider) {
126-
return ((PrincipalProvider) authenticationCacheKey).getPrincipal();
126+
return ((PrincipalProvider) authenticationCacheKey).get();
127127
} else {
128128
return authenticationCacheKey;
129129
}
@@ -133,7 +133,7 @@ protected Object getAuthenticationCacheKey(PrincipalCollection principals) {
133133
protected Object getAuthorizationCacheKey(PrincipalCollection principals) {
134134
Object primaryPrincipal = principals.getPrimaryPrincipal();
135135
if (primaryPrincipal instanceof PrincipalProvider) {
136-
return ((PrincipalProvider) primaryPrincipal).getPrincipal();
136+
return ((PrincipalProvider) primaryPrincipal).get();
137137
} else {
138138
return primaryPrincipal;
139139
}

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

Lines changed: 33 additions & 3 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.security.internal;
910

10-
import java.io.Serializable;
1111
import java.util.ArrayList;
1212
import java.util.Arrays;
1313
import java.util.Collection;
@@ -18,25 +18,31 @@
1818
import org.apache.commons.lang.ArrayUtils;
1919
import org.apache.shiro.SecurityUtils;
2020
import org.apache.shiro.authz.AuthorizationInfo;
21+
import org.apache.shiro.mgt.SecurityManager;
2122
import org.apache.shiro.realm.Realm;
2223
import org.apache.shiro.session.Session;
2324
import org.apache.shiro.subject.PrincipalCollection;
2425
import org.apache.shiro.subject.Subject;
26+
import org.apache.shiro.util.ThreadContext;
27+
import org.seedstack.seed.security.AuthenticationException;
28+
import org.seedstack.seed.security.AuthenticationToken;
2529
import org.seedstack.seed.security.AuthorizationException;
2630
import org.seedstack.seed.security.Role;
2731
import org.seedstack.seed.security.Scope;
2832
import org.seedstack.seed.security.SecuritySupport;
2933
import org.seedstack.seed.security.SimpleScope;
3034
import org.seedstack.seed.security.internal.authorization.ScopePermission;
3135
import org.seedstack.seed.security.internal.authorization.SeedAuthorizationInfo;
36+
import org.seedstack.seed.security.internal.realms.AuthenticationTokenWrapper;
3237
import org.seedstack.seed.security.principals.PrincipalProvider;
3338
import org.seedstack.seed.security.principals.Principals;
3439
import org.seedstack.seed.security.principals.SimplePrincipalProvider;
3540

3641
class ShiroSecuritySupport implements SecuritySupport {
37-
3842
@Inject
3943
private Set<Realm> realms;
44+
@Inject
45+
private SecurityManager securityManager;
4046

4147
@Override
4248
public PrincipalProvider<?> getIdentityPrincipal() {
@@ -63,7 +69,12 @@ public Collection<PrincipalProvider<?>> getOtherPrincipals() {
6369
}
6470

6571
@Override
66-
public <T extends Serializable> Collection<PrincipalProvider<T>> getPrincipalsByType(Class<T> principalClass) {
72+
public <T> PrincipalProvider<T> getPrincipalByType(Class<T> principalClass) {
73+
return Principals.getOnePrincipalByType(getOtherPrincipals(), principalClass);
74+
}
75+
76+
@Override
77+
public <T> Collection<PrincipalProvider<T>> getPrincipalsByType(Class<T> principalClass) {
6778
return Principals.getPrincipalsByType(getOtherPrincipals(), principalClass);
6879
}
6980

@@ -206,6 +217,25 @@ public void checkRoles(String... roleIdentifiers) {
206217
}
207218
}
208219

220+
@Override
221+
public void login(AuthenticationToken authenticationToken) {
222+
SecurityManager alreadyBoundSecurityManager = ThreadContext.getSecurityManager();
223+
try {
224+
if (alreadyBoundSecurityManager == null) {
225+
ThreadContext.bind(securityManager);
226+
}
227+
Subject currentSubject = SecurityUtils.getSubject();
228+
currentSubject.login(new AuthenticationTokenWrapper(authenticationToken));
229+
} catch (org.apache.shiro.authc.AuthenticationException e) {
230+
throw new AuthenticationException("Unable to login subject with provided credentials " + authenticationToken
231+
.getPrincipal(), e);
232+
} finally {
233+
if (alreadyBoundSecurityManager == null) {
234+
ThreadContext.unbindSecurityManager();
235+
}
236+
}
237+
}
238+
209239
@Override
210240
public void logout() {
211241
SecurityUtils.getSubject().logout();

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ protected ConfigurationRealm(@Named("ConfigurationRealm-role-mapping") RoleMappi
5757
@Override
5858
public Set<String> getRealmRoles(PrincipalProvider<?> identityPrincipal,
5959
Collection<PrincipalProvider<?>> otherPrincipals) {
60-
ConfigurationUser user = findUser(identityPrincipal.getPrincipal().toString());
60+
ConfigurationUser user = findUser(identityPrincipal.get().toString());
6161
if (user != null) {
6262
return user.roles;
6363
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ public Set<String> getRealmRoles(PrincipalProvider<?> identityPrincipal,
122122
if (certificatePrincipals.isEmpty()) {
123123
return Collections.emptySet();
124124
}
125-
X509Certificate[] certificates = certificatePrincipals.iterator().next().getPrincipal();
125+
X509Certificate[] certificates = certificatePrincipals.iterator().next().get();
126126
for (X509Certificate certificate : certificates) {
127127
String dn = certificate.getIssuerX500Principal().getName(X500Principal.RFC2253);
128128
LdapName ln;

security/core/src/test/java/org/seedstack/seed/security/internal/realms/ConfigurationRealmUnitTest.java

Lines changed: 2 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.security.internal.realms;
910

1011
import static org.assertj.core.api.Assertions.assertThat;
@@ -61,7 +62,7 @@ public void getRealmRoles_returns_empty_if_user_unknown() {
6162
public void getAuthenticationInfo_nominal() {
6263
UsernamePasswordToken token = new UsernamePasswordToken(USERNAME, PASSWORD);
6364
AuthenticationInfo authInfo = underTest.getAuthenticationInfo(token);
64-
assertThat(authInfo.getIdentityPrincipal().getPrincipal()).isEqualTo(USERNAME);
65+
assertThat(authInfo.getIdentityPrincipal().get()).isEqualTo(USERNAME);
6566
}
6667

6768
@Test(expected = IncorrectCredentialsException.class)

security/core/src/test/java/org/seedstack/seed/security/internal/realms/X509CertificateRealmTest.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,10 +56,10 @@ public void getAuthenticationInfoShouldReturnAuthenticationInfo() {
5656
when(x509Certificate.getSubjectX500Principal()).thenReturn(x500Principal);
5757
AuthenticationInfo authInfo = underTest.getAuthenticationInfo(token);
5858

59-
assertThat(authInfo.getIdentityPrincipal().getPrincipal()).isEqualTo(id);
59+
assertThat(authInfo.getIdentityPrincipal().get()).isEqualTo(id);
6060
PrincipalProvider<X509Certificate[]> x509pp = Principals.getOnePrincipalByType(authInfo.getOtherPrincipals(),
6161
X509Certificate[].class);
62-
assertThat(x509pp.getPrincipal()[0]).isEqualTo(x509Certificate);
62+
assertThat(x509pp.get()[0]).isEqualTo(x509Certificate);
6363
}
6464

6565
@Test(expected = UnsupportedTokenException.class)
@@ -78,7 +78,7 @@ public void getAuthenticationInfoNoUid() {
7878
X500Principal x500Principal = new X500Principal("CN=John Doe, OU=SI, O=PSA");
7979
when(x509Certificate.getSubjectX500Principal()).thenReturn(x500Principal);
8080
AuthenticationInfo authInfo = underTest.getAuthenticationInfo(token);
81-
assertThat(authInfo.getIdentityPrincipal().getPrincipal()).isEqualTo(x500Principal);
81+
assertThat(authInfo.getIdentityPrincipal().get()).isEqualTo(x500Principal);
8282
}
8383

8484
@Test

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

Lines changed: 30 additions & 23 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.security;
910

10-
import java.io.Serializable;
1111
import java.util.Collection;
1212
import java.util.Set;
1313
import org.seedstack.seed.security.principals.PrincipalProvider;
@@ -34,21 +34,25 @@ public interface SecuritySupport {
3434
Collection<PrincipalProvider<?>> getOtherPrincipals();
3535

3636
/**
37-
* Gets all the PrincipalProviders corresponding to a type of PrincipalProvider.<br>
38-
* <br>
39-
* For example, you can use this method to get the LDAPUser by calling :<br>
40-
* {@code getPrincipalsByType(LDAPUser.class)}.<br>
41-
* <br>
42-
* Then on the first element of the collection : <br>
43-
* {@code LDAPUser user =
44-
* ldapUserPrincipalProvider.getPrincipal()}.
37+
* Gets the first {@link PrincipalProvider} that provide a principal assignable to the specified type.
38+
*
39+
* @param <T> type of the principal
40+
* @param principalClass the Principal type, not null
41+
* @return The first first {@link PrincipalProvider} that provide a principal assignable to the specified type.
42+
* Null if none found.
43+
* @see org.seedstack.seed.security.principals.Principals#getOnePrincipalByType(Collection, Class)
44+
*/
45+
<T> PrincipalProvider<T> getPrincipalByType(Class<T> principalClass);
46+
47+
/**
48+
* Gets all {@link PrincipalProvider}s that provide a principal assignable to the specified type.
4549
*
4650
* @param <T> type of the principal
4751
* @param principalClass the Principal type, not null
4852
* @return A collection of the user's PrincipalProviders of type principalProviderClass. Not null.
4953
* @see org.seedstack.seed.security.principals.Principals#getPrincipalsByType(Collection, Class)
5054
*/
51-
<T extends Serializable> Collection<PrincipalProvider<T>> getPrincipalsByType(Class<T> principalClass);
55+
<T> Collection<PrincipalProvider<T>> getPrincipalsByType(Class<T> principalClass);
5256

5357
/**
5458
* Gets the user's SimplePrincipalProviders.<br>
@@ -231,26 +235,29 @@ public interface SecuritySupport {
231235
*/
232236
Set<SimpleScope> getSimpleScopes();
233237

238+
/**
239+
* Explicitly authenticates a subject with its authentication token.
240+
* <h3>Web Environment Warning</h3> In a Web application, this is typically handled by a security Web filter, so
241+
* calling this method should be avoided in such environments.
242+
*/
243+
void login(AuthenticationToken authenticationToken);
244+
234245
/**
235246
* Logs out the connected user and invalidates and/or removes any associated entities, such as a Session and
236-
* authorization data. After this method
237-
* is called, the user is considered 'anonymous' and may continue to be used for another log-in if desired.
238-
* <h3>Web Environment Warning</h3>
239-
* Calling this method in web environments will usually remove any associated session cookie as part of session
240-
* invalidation. Because cookies are
241-
* part of the HTTP header, and headers can only be set before the response body (html, image, etc) is sent, this
242-
* method in web environments must
243-
* be called before <em>any</em> content has been rendered.
244-
* <p>
245-
* The typical approach most applications use in this scenario is to redirect the user to a different location (e
246-
* .g. home page) immediately after
247-
* calling this method. This is an effect of the HTTP protocol itself and not a reflection of the implementation.
247+
* authorization data. After this method is called, the user is considered 'anonymous' and may continue to be
248+
* used for another log-in if desired. <h3>Web Environment Warning</h3> Calling this method in web environments
249+
* will usually remove any associated session cookie as part of session invalidation. Because cookies are part of
250+
* the HTTP header, and headers can only be set before the response body (html, image, etc) is sent, this method
251+
* in web environments must be called before <em>any</em> content has been rendered. <p> The typical approach
252+
* most applications use in this scenario is to redirect the user to a different location (e .g. home page)
253+
* immediately after calling this method. This is an effect of the HTTP protocol itself and not a reflection of
254+
* the implementation.
248255
*/
249256
void logout();
250257

251258
/**
252259
* Check if the current user is authenticated.
253-
*
260+
* <p>
254261
* Authenticated on Shiro means that subject has successfully logged in on the current session
255262
*
256263
* @return true if authenticated, false otherwise.

security/specs/src/main/java/org/seedstack/seed/security/principals/PrincipalProvider.java

Lines changed: 15 additions & 2 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.principals;
910

1011
import java.io.Serializable;
@@ -14,12 +15,24 @@
1415
*
1516
* @param <T> the type of the object provided by the principal
1617
*/
17-
public interface PrincipalProvider<T extends Serializable> {
18+
public interface PrincipalProvider<T> {
19+
/**
20+
* For compatibility purposes.
21+
*
22+
* @param <X> a serializable type
23+
* @return the object cast as X.
24+
* @deprecated
25+
*/
26+
@Deprecated
27+
@SuppressWarnings("unchecked")
28+
default <X extends Serializable> X getPrincipal() {
29+
return (X) get();
30+
}
1831

1932
/**
2033
* Gives the enclosed principal.
2134
*
2235
* @return the object enclosed in the principal
2336
*/
24-
T getPrincipal();
37+
T get();
2538
}

0 commit comments

Comments
 (0)