1 package org.springframework.security.ui.rememberme;
2
3 import org.apache.commons.codec.binary.Base64;
4 import org.apache.commons.logging.Log;
5 import org.apache.commons.logging.LogFactory;
6 import org.springframework.beans.factory.InitializingBean;
7 import org.springframework.context.support.MessageSourceAccessor;
8 import org.springframework.security.Authentication;
9 import org.springframework.security.SpringSecurityMessageSource;
10 import org.springframework.security.AccountStatusException;
11 import org.springframework.security.providers.rememberme.RememberMeAuthenticationToken;
12 import org.springframework.security.ui.AuthenticationDetailsSource;
13 import org.springframework.security.ui.WebAuthenticationDetailsSource;
14 import org.springframework.security.ui.logout.LogoutHandler;
15 import org.springframework.security.userdetails.UserDetails;
16 import org.springframework.security.userdetails.UserDetailsService;
17 import org.springframework.security.userdetails.UsernameNotFoundException;
18 import org.springframework.security.userdetails.UserDetailsChecker;
19 import org.springframework.security.userdetails.checker.AccountStatusUserDetailsChecker;
20 import org.springframework.util.Assert;
21 import org.springframework.util.StringUtils;
22
23 import javax.servlet.http.Cookie;
24 import javax.servlet.http.HttpServletRequest;
25 import javax.servlet.http.HttpServletResponse;
26
27
28
29
30
31
32
33
34 public abstract class AbstractRememberMeServices implements RememberMeServices, InitializingBean, LogoutHandler {
35
36
37 public static final String SPRING_SECURITY_REMEMBER_ME_COOKIE_KEY = "SPRING_SECURITY_REMEMBER_ME_COOKIE";
38 public static final String DEFAULT_PARAMETER = "_spring_security_remember_me";
39
40 private static final String DELIMITER = ":";
41
42
43 protected final Log logger = LogFactory.getLog(getClass());
44
45 protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();
46
47 private UserDetailsService userDetailsService;
48 private UserDetailsChecker userDetailsChecker = new AccountStatusUserDetailsChecker();
49 private AuthenticationDetailsSource authenticationDetailsSource = new WebAuthenticationDetailsSource();
50
51 private String cookieName = SPRING_SECURITY_REMEMBER_ME_COOKIE_KEY;
52 private String parameter = DEFAULT_PARAMETER;
53 private boolean alwaysRemember;
54 private String key;
55 private int tokenValiditySeconds = 1209600;
56
57 public void afterPropertiesSet() throws Exception {
58 Assert.hasLength(key);
59 Assert.hasLength(parameter);
60 Assert.hasLength(cookieName);
61 Assert.notNull(userDetailsService);
62 }
63
64
65
66
67
68
69
70
71
72 public final Authentication autoLogin(HttpServletRequest request, HttpServletResponse response) {
73 String rememberMeCookie = extractRememberMeCookie(request);
74
75 if (rememberMeCookie == null) {
76 return null;
77 }
78
79 logger.debug("Remember-me cookie detected");
80
81 UserDetails user = null;
82
83 try {
84 String[] cookieTokens = decodeCookie(rememberMeCookie);
85 user = processAutoLoginCookie(cookieTokens, request, response);
86 userDetailsChecker.check(user);
87 } catch (CookieTheftException cte) {
88 cancelCookie(request, response);
89 throw cte;
90 } catch (UsernameNotFoundException noUser) {
91 cancelCookie(request, response);
92 logger.debug("Remember-me login was valid but corresponding user not found.", noUser);
93 return null;
94 } catch (InvalidCookieException invalidCookie) {
95 cancelCookie(request, response);
96 logger.debug("Invalid remember-me cookie: " + invalidCookie.getMessage());
97 return null;
98 } catch (AccountStatusException statusInvalid) {
99 cancelCookie(request, response);
100 logger.debug("Invalid UserDetails: " + statusInvalid.getMessage());
101 return null;
102 } catch (RememberMeAuthenticationException e) {
103 cancelCookie(request, response);
104 logger.debug(e.getMessage());
105 return null;
106 }
107
108 logger.debug("Remember-me cookie accepted");
109
110 RememberMeAuthenticationToken auth = new RememberMeAuthenticationToken(key, user, user.getAuthorities());
111 auth.setDetails(authenticationDetailsSource.buildDetails(request));
112
113 return auth;
114 }
115
116
117
118
119
120
121
122 private String extractRememberMeCookie(HttpServletRequest request) {
123 Cookie[] cookies = request.getCookies();
124
125 if ((cookies == null) || (cookies.length == 0)) {
126 return null;
127 }
128
129 for (int i = 0; i < cookies.length; i++) {
130 if (cookieName.equals(cookies[i].getName())) {
131 return cookies[i].getValue();
132 }
133 }
134
135 return null;
136 }
137
138
139
140
141
142
143
144
145 protected String[] decodeCookie(String cookieValue) throws InvalidCookieException {
146 for (int j = 0; j < cookieValue.length() % 4; j++) {
147 cookieValue = cookieValue + "=";
148 }
149
150 if (!Base64.isArrayByteBase64(cookieValue.getBytes())) {
151 throw new InvalidCookieException( "Cookie token was not Base64 encoded; value was '" + cookieValue + "'");
152 }
153
154 String cookieAsPlainText = new String(Base64.decodeBase64(cookieValue.getBytes()));
155
156 return StringUtils.delimitedListToStringArray(cookieAsPlainText, DELIMITER);
157 }
158
159
160
161
162
163
164
165 protected String encodeCookie(String[] cookieTokens) {
166 StringBuffer sb = new StringBuffer();
167 for(int i=0; i < cookieTokens.length; i++) {
168 sb.append(cookieTokens[i]);
169
170 if (i < cookieTokens.length - 1) {
171 sb.append(DELIMITER);
172 }
173 }
174
175 String value = sb.toString();
176
177 sb = new StringBuffer(new String(Base64.encodeBase64(value.getBytes())));
178
179 while (sb.charAt(sb.length() - 1) == '=') {
180 sb.deleteCharAt(sb.length() - 1);
181 }
182
183 return sb.toString();
184 }
185
186 public final void loginFail(HttpServletRequest request, HttpServletResponse response) {
187 logger.debug("Interactive login attempt was unsuccessful.");
188 cancelCookie(request, response);
189 onLoginFail(request, response);
190 }
191
192 protected void onLoginFail(HttpServletRequest request, HttpServletResponse response) {}
193
194
195
196
197
198 public final void loginSuccess(HttpServletRequest request, HttpServletResponse response,
199 Authentication successfulAuthentication) {
200
201 if (!rememberMeRequested(request, parameter)) {
202 logger.debug("Remember-me login not requested.");
203 return;
204 }
205
206 onLoginSuccess(request, response, successfulAuthentication);
207 }
208
209
210
211
212
213
214 protected abstract void onLoginSuccess(HttpServletRequest request, HttpServletResponse response,
215 Authentication successfulAuthentication);
216
217
218
219
220
221
222
223
224
225
226
227
228
229 protected boolean rememberMeRequested(HttpServletRequest request, String parameter) {
230 if (alwaysRemember) {
231 return true;
232 }
233
234 String paramValue = request.getParameter(parameter);
235
236 if (paramValue != null) {
237 if (paramValue.equalsIgnoreCase("true") || paramValue.equalsIgnoreCase("on") ||
238 paramValue.equalsIgnoreCase("yes") || paramValue.equals("1")) {
239 return true;
240 }
241 }
242
243 if (logger.isDebugEnabled()) {
244 logger.debug("Did not send remember-me cookie (principal did not set parameter '" + parameter + "')");
245 }
246
247 return false;
248 }
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263 protected abstract UserDetails processAutoLoginCookie(String[] cookieTokens, HttpServletRequest request,
264 HttpServletResponse response) throws RememberMeAuthenticationException, UsernameNotFoundException;
265
266
267
268
269
270
271
272 protected void cancelCookie(HttpServletRequest request, HttpServletResponse response) {
273 logger.debug("Cancelling cookie");
274 Cookie cookie = new Cookie(cookieName, null);
275 cookie.setMaxAge(0);
276 cookie.setPath(StringUtils.hasLength(request.getContextPath()) ? request.getContextPath() : "/");
277
278 response.addCookie(cookie);
279 }
280
281
282
283
284
285
286
287
288
289 protected void setCookie(String[] tokens, int maxAge, HttpServletRequest request, HttpServletResponse response) {
290 String cookieValue = encodeCookie(tokens);
291 Cookie cookie = new Cookie(cookieName, cookieValue);
292 cookie.setMaxAge(maxAge);
293 cookie.setPath(StringUtils.hasLength(request.getContextPath()) ? request.getContextPath() : "/");
294 response.addCookie(cookie);
295 }
296
297
298
299
300 public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
301 if (logger.isDebugEnabled()) {
302 logger.debug( "Logout of user "
303 + (authentication == null ? "Unknown" : authentication.getName()));
304 }
305 cancelCookie(request, response);
306 }
307
308 public void setCookieName(String cookieName) {
309 this.cookieName = cookieName;
310 }
311
312 protected String getCookieName() {
313 return cookieName;
314 }
315
316 public void setAlwaysRemember(boolean alwaysRemember) {
317 this.alwaysRemember = alwaysRemember;
318 }
319
320
321
322
323
324
325
326 public void setParameter(String parameter) {
327 Assert.hasText(parameter, "Parameter name cannot be null");
328 this.parameter = parameter;
329 }
330
331 public String getParameter() {
332 return parameter;
333 }
334
335 protected UserDetailsService getUserDetailsService() {
336 return userDetailsService;
337 }
338
339 public void setUserDetailsService(UserDetailsService userDetailsService) {
340 Assert.notNull(userDetailsService, "UserDetailsService canot be null");
341 this.userDetailsService = userDetailsService;
342 }
343
344 public void setKey(String key) {
345 this.key = key;
346 }
347
348 public String getKey() {
349 return key;
350 }
351
352 public void setTokenValiditySeconds(int tokenValiditySeconds) {
353 this.tokenValiditySeconds = tokenValiditySeconds;
354 }
355
356 protected int getTokenValiditySeconds() {
357 return tokenValiditySeconds;
358 }
359
360 protected AuthenticationDetailsSource getAuthenticationDetailsSource() {
361 return authenticationDetailsSource;
362 }
363
364 public void setAuthenticationDetailsSource(AuthenticationDetailsSource authenticationDetailsSource) {
365 Assert.notNull(authenticationDetailsSource, "AuthenticationDetailsSource cannot be null");
366 this.authenticationDetailsSource = authenticationDetailsSource;
367 }
368 }