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