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