-
Notifications
You must be signed in to change notification settings - Fork 2.3k
/
ActiveDirectoryRealm.java
234 lines (188 loc) · 9.3 KB
/
ActiveDirectoryRealm.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.shiro.realm.activedirectory;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.realm.ldap.AbstractLdapRealm;
import org.apache.shiro.realm.ldap.LdapContextFactory;
import org.apache.shiro.realm.ldap.LdapUtils;
import org.apache.shiro.subject.PrincipalCollection;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.directory.Attribute;
import javax.naming.directory.Attributes;
import javax.naming.directory.SearchControls;
import javax.naming.directory.SearchResult;
import javax.naming.ldap.LdapContext;
import java.util.*;
/**
* A {@link Realm} that authenticates with an active directory LDAP
* server to determine the roles for a particular user. This implementation
* queries for the user's groups and then maps the group names to roles using the
* {@link #groupRolesMap}.
*
* @since 0.1
*/
public class ActiveDirectoryRealm extends AbstractLdapRealm {
//TODO - complete JavaDoc
/*--------------------------------------------
| C O N S T A N T S |
============================================*/
private static final Logger log = LoggerFactory.getLogger(ActiveDirectoryRealm.class);
private static final String ROLE_NAMES_DELIMETER = ",";
/*--------------------------------------------
| I N S T A N C E V A R I A B L E S |
============================================*/
/**
* Mapping from fully qualified active directory
* group names (e.g. CN=Group,OU=Company,DC=MyDomain,DC=local)
* as returned by the active directory LDAP server to role names.
*/
private Map<String, String> groupRolesMap;
/*--------------------------------------------
| C O N S T R U C T O R S |
============================================*/
public void setGroupRolesMap(Map<String, String> groupRolesMap) {
this.groupRolesMap = groupRolesMap;
}
/*--------------------------------------------
| M E T H O D S |
============================================*/
/**
* Builds an {@link AuthenticationInfo} object by querying the active directory LDAP context for the
* specified username. This method binds to the LDAP server using the provided username and password -
* which if successful, indicates that the password is correct.
* <p/>
* This method can be overridden by subclasses to query the LDAP server in a more complex way.
*
* @param token the authentication token provided by the user.
* @param ldapContextFactory the factory used to build connections to the LDAP server.
* @return an {@link AuthenticationInfo} instance containing information retrieved from LDAP.
* @throws NamingException if any LDAP errors occur during the search.
*/
protected AuthenticationInfo queryForAuthenticationInfo(AuthenticationToken token, LdapContextFactory ldapContextFactory) throws NamingException {
UsernamePasswordToken upToken = (UsernamePasswordToken) token;
// Binds using the username and password provided by the user.
LdapContext ctx = null;
try {
ctx = ldapContextFactory.getLdapContext(upToken.getUsername(), String.valueOf(upToken.getPassword()));
} finally {
LdapUtils.closeContext(ctx);
}
return buildAuthenticationInfo(upToken.getUsername(), upToken.getPassword());
}
protected AuthenticationInfo buildAuthenticationInfo(String username, char[] password) {
return new SimpleAuthenticationInfo(username, password, getName());
}
/**
* Builds an {@link org.apache.shiro.authz.AuthorizationInfo} object by querying the active directory LDAP context for the
* groups that a user is a member of. The groups are then translated to role names by using the
* configured {@link #groupRolesMap}.
* <p/>
* This implementation expects the <tt>principal</tt> argument to be a String username.
* <p/>
* Subclasses can override this method to determine authorization data (roles, permissions, etc) in a more
* complex way. Note that this default implementation does not support permissions, only roles.
*
* @param principals the principal of the Subject whose account is being retrieved.
* @param ldapContextFactory the factory used to create LDAP connections.
* @return the AuthorizationInfo for the given Subject principal.
* @throws NamingException if an error occurs when searching the LDAP server.
*/
protected AuthorizationInfo queryForAuthorizationInfo(PrincipalCollection principals, LdapContextFactory ldapContextFactory) throws NamingException {
String username = (String) getAvailablePrincipal(principals);
// Perform context search
LdapContext ldapContext = ldapContextFactory.getSystemLdapContext();
Set<String> roleNames;
try {
roleNames = getRoleNamesForUser(username, ldapContext);
} finally {
LdapUtils.closeContext(ldapContext);
}
return buildAuthorizationInfo(roleNames);
}
protected AuthorizationInfo buildAuthorizationInfo(Set<String> roleNames) {
return new SimpleAuthorizationInfo(roleNames);
}
protected Set<String> getRoleNamesForUser(String username, LdapContext ldapContext) throws NamingException {
Set<String> roleNames;
roleNames = new LinkedHashSet<String>();
SearchControls searchCtls = new SearchControls();
searchCtls.setSearchScope(SearchControls.SUBTREE_SCOPE);
String userPrincipalName = username;
if (principalSuffix != null && !userPrincipalName.toLowerCase(Locale.ROOT).endsWith(principalSuffix.toLowerCase(Locale.ROOT))) {
userPrincipalName += principalSuffix;
}
Object[] searchArguments = new Object[]{userPrincipalName};
NamingEnumeration answer = ldapContext.search(searchBase, searchFilter, searchArguments, searchCtls);
while (answer.hasMoreElements()) {
SearchResult sr = (SearchResult) answer.next();
if (log.isDebugEnabled()) {
log.debug("Retrieving group names for user [" + sr.getName() + "]");
}
Attributes attrs = sr.getAttributes();
if (attrs != null) {
NamingEnumeration ae = attrs.getAll();
while (ae.hasMore()) {
Attribute attr = (Attribute) ae.next();
if (attr.getID().equals("memberOf")) {
Collection<String> groupNames = LdapUtils.getAllAttributeValues(attr);
if (log.isDebugEnabled()) {
log.debug("Groups found for user [" + username + "]: " + groupNames);
}
Collection<String> rolesForGroups = getRoleNamesForGroups(groupNames);
roleNames.addAll(rolesForGroups);
}
}
}
}
return roleNames;
}
/**
* This method is called by the default implementation to translate Active Directory group names
* to role names. This implementation uses the {@link #groupRolesMap} to map group names to role names.
*
* @param groupNames the group names that apply to the current user.
* @return a collection of roles that are implied by the given role names.
*/
protected Collection<String> getRoleNamesForGroups(Collection<String> groupNames) {
Set<String> roleNames = new HashSet<String>(groupNames.size());
if (groupRolesMap != null) {
for (String groupName : groupNames) {
String strRoleNames = groupRolesMap.get(groupName);
if (strRoleNames != null) {
for (String roleName : strRoleNames.split(ROLE_NAMES_DELIMETER)) {
if (log.isDebugEnabled()) {
log.debug("User is member of group [" + groupName + "] so adding role [" + roleName + "]");
}
roleNames.add(roleName);
}
}
}
}
return roleNames;
}
}