1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package net.sf.michaelo.tomcat.authenticator;
17
18 import java.io.IOException;
19 import java.security.Principal;
20 import java.security.PrivilegedActionException;
21 import java.security.PrivilegedExceptionAction;
22
23 import javax.security.auth.Subject;
24 import javax.security.auth.login.LoginContext;
25 import javax.security.auth.login.LoginException;
26 import javax.servlet.http.HttpServletResponse;
27
28 import org.apache.catalina.Realm;
29 import org.apache.catalina.connector.Request;
30 import org.apache.commons.lang3.StringUtils;
31 import org.apache.tomcat.util.codec.binary.Base64;
32 import org.ietf.jgss.GSSContext;
33 import org.ietf.jgss.GSSCredential;
34 import org.ietf.jgss.GSSException;
35 import org.ietf.jgss.GSSManager;
36 import org.ietf.jgss.GSSName;
37
38
39
40
41
42
43 public class SpnegoAuthenticator extends GSSAuthenticatorBase {
44
45 protected static final String SPNEGO_METHOD = "SPNEGO";
46 protected static final String SPNEGO_AUTH_SCHEME = "Negotiate";
47
48 private static final byte[] NTLM_TYPE1_MESSAGE_START = { (byte) 'N', (byte) 'T', (byte) 'L',
49 (byte) 'M', (byte) 'S', (byte) 'S', (byte) 'P', (byte) '\0', (byte) 0x01, (byte) 0x00,
50 (byte) 0x00, (byte) 0x00 };
51
52 @Override
53 protected boolean doAuthenticate(Request request, HttpServletResponse response)
54 throws IOException {
55
56 if (checkForCachedAuthentication(request, response, true)) {
57 return true;
58 }
59
60 String authorization = request.getHeader("Authorization");
61
62 if (!StringUtils.startsWithIgnoreCase(authorization, SPNEGO_AUTH_SCHEME)) {
63 sendUnauthorized(request, response, SPNEGO_AUTH_SCHEME);
64 return false;
65 }
66
67 String authorizationValue = StringUtils.substring(authorization,
68 SPNEGO_AUTH_SCHEME.length() + 1);
69
70 if (StringUtils.isEmpty(authorizationValue)) {
71 sendUnauthorized(request, response, SPNEGO_AUTH_SCHEME);
72 return false;
73 }
74
75 byte[] outToken = null;
76 byte[] inToken = null;
77
78 if (logger.isDebugEnabled())
79 logger.debug(sm.getString("spnegoAuthenticator.processingToken", authorizationValue));
80
81 try {
82 inToken = Base64.decodeBase64(authorizationValue);
83 } catch (Exception e) {
84 logger.warn(sm.getString("spnegoAuthenticator.incorrectlyEncodedToken",
85 authorizationValue), e);
86
87 sendUnauthorized(request, response, SPNEGO_AUTH_SCHEME,
88 "spnegoAuthenticator.incorrectlyEncodedToken.responseMessage");
89 return false;
90 }
91
92 if (inToken.length >= NTLM_TYPE1_MESSAGE_START.length) {
93 boolean ntlmDetected = false;
94 for (int i = 0; i < NTLM_TYPE1_MESSAGE_START.length; i++) {
95 ntlmDetected = inToken[i] == NTLM_TYPE1_MESSAGE_START[i];
96
97 if (!ntlmDetected)
98 break;
99 }
100
101 if (ntlmDetected) {
102 logger.warn(sm.getString("spnegoAuthenticator.ntlmNotSupported"));
103
104 sendUnauthorized(request, response, SPNEGO_AUTH_SCHEME,
105 "spnegoAuthenticator.ntlmNotSupported.responseMessage");
106 return false;
107 }
108 }
109
110 LoginContext lc = null;
111 GSSContext gssContext = null;
112 Principal principal = null;
113
114 try {
115 try {
116 lc = new LoginContext(getLoginEntryName());
117 lc.login();
118 } catch (LoginException e) {
119 logger.error(sm.getString("spnegoAuthenticator.obtainFailed"), e);
120
121 sendInternalServerError(request, response, "spnegoAuthenticator.obtainFailed");
122 return false;
123 }
124
125 final GSSManager manager = GSSManager.getInstance();
126 final PrivilegedExceptionAction<GSSCredential> action = new PrivilegedExceptionAction<GSSCredential>() {
127 @Override
128 public GSSCredential run() throws GSSException {
129 return manager.createCredential(null, GSSCredential.INDEFINITE_LIFETIME,
130 SPNEGO_MECHANISM, GSSCredential.ACCEPT_ONLY);
131 }
132 };
133
134 try {
135 gssContext = manager.createContext(Subject.doAs(lc.getSubject(), action));
136 } catch (PrivilegedActionException e) {
137 logger.error(sm.getString("spnegoAuthenticator.obtainFailed"), e.getException());
138
139 sendInternalServerError(request, response, "spnegoAuthenticator.obtainFailed");
140 return false;
141 } catch (GSSException e) {
142 logger.error(sm.getString("spnegoAuthenticator.createContextFailed"), e);
143
144 sendInternalServerError(request, response,
145 "spnegoAuthenticator.createContextFailed");
146 return false;
147 }
148
149 try {
150 outToken = gssContext.acceptSecContext(inToken, 0, inToken.length);
151 } catch (GSSException e) {
152 logger.warn(sm.getString("spnegoAuthenticator.invalidToken", authorizationValue), e);
153
154 sendUnauthorized(request, response, SPNEGO_AUTH_SCHEME,
155 "spnegoAuthenticator.invalidToken.responseMessage");
156 return false;
157 }
158
159 try {
160 if (gssContext.isEstablished()) {
161 if (logger.isDebugEnabled())
162 logger.debug(sm.getString("spnegoAuthenticator.contextSuccessfullyEstablished"));
163
164 Realm realm = context.getRealm();
165 principal = realm.authenticate(gssContext, isStoreDelegatedCredential());
166
167 if (principal == null) {
168 GSSName srcName = gssContext.getSrcName();
169 sendUnauthorized(request, response, SPNEGO_AUTH_SCHEME,
170 "gssAuthenticatorBase.userNotFound", srcName);
171 return false;
172 }
173 } else {
174 logger.error(sm.getString("spnegoAuthenticator.continueContextNotSupported"));
175
176 sendInternalServerError(request, response,
177 "spnegoAuthenticator.continueContextNotSupported.responseMessage");
178 return false;
179 }
180
181 } catch (GSSException e) {
182 logger.error(sm.getString("gssAuthenticatorBase.inquireNameFailed"), e);
183
184 sendInternalServerError(request, response, "gssAuthenticatorBase.inquireNameFailed");
185 return false;
186 }
187
188 } finally {
189 if (gssContext != null) {
190 try {
191 gssContext.dispose();
192 } catch (GSSException e) {
193 ;
194 }
195 }
196 if (lc != null) {
197 try {
198 lc.logout();
199 } catch (LoginException e) {
200 ;
201 }
202 }
203 }
204
205 register(request, response, principal, SPNEGO_METHOD, principal.getName(), null);
206
207 if (outToken != null) {
208 String authenticationValue = Base64.encodeBase64String(outToken);
209 if (logger.isDebugEnabled())
210 logger.debug(sm.getString("spnegoAuthenticator.respondingWithToken", authenticationValue));
211
212 response.setHeader(AUTH_HEADER_NAME, SPNEGO_AUTH_SCHEME + " " + authenticationValue);
213 }
214
215 return true;
216 }
217
218 @Override
219 protected String getAuthMethod() {
220 return SPNEGO_METHOD;
221 }
222
223 }