001: package ru.emdev.EmForge.security;
002:
003: import java.util.Date;
004:
005: import javax.servlet.http.Cookie;
006: import javax.servlet.http.HttpServletRequest;
007: import javax.servlet.http.HttpServletResponse;
008:
009: import org.acegisecurity.Authentication;
010: import org.acegisecurity.providers.rememberme.RememberMeAuthenticationToken;
011: import org.acegisecurity.ui.AuthenticationDetailsSource;
012: import org.acegisecurity.ui.AuthenticationDetailsSourceImpl;
013: import org.acegisecurity.ui.rememberme.TokenBasedRememberMeServices;
014: import org.acegisecurity.userdetails.UserDetails;
015: import org.acegisecurity.userdetails.UserDetailsService;
016: import org.acegisecurity.userdetails.UsernameNotFoundException;
017: import org.apache.commons.codec.binary.Base64;
018: import org.apache.commons.codec.digest.DigestUtils;
019: import org.springframework.util.StringUtils;
020:
021: /** Own implementation for RememberMeServices
022: *
023: * Basic TokenBasedRememberMeServices allows us to use only one user details service
024: * But in our project we are using 2 of them (second for admin user)
025: * So, we need to support it
026: * @author akakunin
027: */
028: public class RememberMeServiceImpl extends TokenBasedRememberMeServices {
029: /** Since basic class has these fields private and has no get-accessor we should dublicate it here */
030: private AuthenticationDetailsSource inheritedAuthenticationDetailsSource = new AuthenticationDetailsSourceImpl();
031: private String inheritedKey;
032:
033: private UserDetailsService secondaryUserService;
034:
035: public UserDetailsService getSecondaryUserService() {
036: return secondaryUserService;
037: }
038:
039: public void setSecondaryUserService(
040: UserDetailsService secondaryUserService) {
041: this .secondaryUserService = secondaryUserService;
042: }
043:
044: @Override
045: public Authentication autoLogin(HttpServletRequest request,
046: HttpServletResponse response) {
047: Cookie[] cookies = request.getCookies();
048:
049: if ((cookies == null) || (cookies.length == 0)) {
050: return null;
051: }
052:
053: for (int i = 0; i < cookies.length; i++) {
054: if (ACEGI_SECURITY_HASHED_REMEMBER_ME_COOKIE_KEY
055: .equals(cookies[i].getName())) {
056: String cookieValue = cookies[i].getValue();
057:
058: if (Base64.isArrayByteBase64(cookieValue.getBytes())) {
059: if (logger.isDebugEnabled()) {
060: logger.debug("Remember-me cookie detected");
061: }
062:
063: // Decode token from Base64
064: // format of token is:
065: // username + ":" + expiryTime + ":" + Md5Hex(username + ":" + expiryTime + ":" + password + ":" + key)
066: String cookieAsPlainText = new String(Base64
067: .decodeBase64(cookieValue.getBytes()));
068: String[] cookieTokens = StringUtils
069: .delimitedListToStringArray(
070: cookieAsPlainText, ":");
071:
072: if (cookieTokens.length == 3) {
073: long tokenExpiryTime;
074:
075: try {
076: tokenExpiryTime = new Long(cookieTokens[1])
077: .longValue();
078: } catch (NumberFormatException nfe) {
079: cancelCookie(request, response,
080: "Cookie token[1] did not contain a valid number (contained '"
081: + cookieTokens[1] + "')");
082:
083: return null;
084: }
085:
086: // Check it has not expired
087: if (tokenExpiryTime < System
088: .currentTimeMillis()) {
089: cancelCookie(request, response,
090: "Cookie token[1] has expired (expired on '"
091: + new Date(tokenExpiryTime)
092: + "'; current time is '"
093: + new Date() + "')");
094:
095: return null;
096: }
097:
098: // Check the user exists
099: // Defer lookup until after expiry time checked, to possibly avoid expensive lookup
100: UserDetails userDetails = null;
101:
102: try {
103: userDetails = getUserDetailsService()
104: .loadUserByUsername(cookieTokens[0]);
105: } catch (UsernameNotFoundException notFound) {
106: // just ignote it here
107: }
108:
109: if (userDetails == null) {
110: //try to find in secondary user details provider
111: try {
112: userDetails = secondaryUserService
113: .loadUserByUsername(cookieTokens[0]);
114: } catch (UsernameNotFoundException notFound) {
115: // just ignote it here
116: }
117: }
118:
119: if (userDetails == null) {
120: cancelCookie(request, response,
121: "Cookie token[0] contained username '"
122: + cookieTokens[0]
123: + "' but was not found");
124: return null;
125: }
126:
127: // Immediately reject if the user is not allowed to login
128: if (!userDetails.isAccountNonExpired()
129: || !userDetails
130: .isCredentialsNonExpired()
131: || !userDetails.isEnabled()) {
132: cancelCookie(
133: request,
134: response,
135: "Cookie token[0] contained username '"
136: + cookieTokens[0]
137: + "' but account has expired, credentials have expired, or user is disabled");
138:
139: return null;
140: }
141:
142: // Check signature of token matches remaining details
143: // Must do this after user lookup, as we need the DAO-derived password
144: // If efficiency was a major issue, just add in a UserCache implementation,
145: // but recall this method is usually only called one per HttpSession
146: // (as if the token is valid, it will cause SecurityContextHolder population, whilst
147: // if invalid, will cause the cookie to be cancelled)
148: String expectedTokenSignature = DigestUtils
149: .md5Hex(userDetails.getUsername() + ":"
150: + tokenExpiryTime + ":"
151: + userDetails.getPassword()
152: + ":" + getKey());
153:
154: if (!expectedTokenSignature
155: .equals(cookieTokens[2])) {
156: cancelCookie(request, response,
157: "Cookie token[2] contained signature '"
158: + cookieTokens[2]
159: + "' but expected '"
160: + expectedTokenSignature
161: + "'");
162:
163: return null;
164: }
165:
166: // By this stage we have a valid token
167: if (logger.isDebugEnabled()) {
168: logger.debug("Remember-me cookie accepted");
169: }
170:
171: RememberMeAuthenticationToken auth = new RememberMeAuthenticationToken(
172: getKey(), userDetails, userDetails
173: .getAuthorities());
174: auth
175: .setDetails(getAuthenticationDetailsSource()
176: .buildDetails(
177: (HttpServletRequest) request));
178:
179: return auth;
180: } else {
181: cancelCookie(request, response,
182: "Cookie token did not contain 3 tokens; decoded value was '"
183: + cookieAsPlainText + "'");
184:
185: return null;
186: }
187: } else {
188: cancelCookie(request, response,
189: "Cookie token was not Base64 encoded; value was '"
190: + cookieValue + "'");
191:
192: return null;
193: }
194: }
195: }
196:
197: return null;
198: }
199:
200: @Override
201: public void setAuthenticationDetailsSource(
202: AuthenticationDetailsSource authenticationDetailsSource) {
203: inheritedAuthenticationDetailsSource = authenticationDetailsSource;
204: super
205: .setAuthenticationDetailsSource(authenticationDetailsSource);
206: }
207:
208: public AuthenticationDetailsSource getAuthenticationDetailsSource() {
209: return inheritedAuthenticationDetailsSource;
210: }
211:
212: @Override
213: public void setKey(String key) {
214: inheritedKey = key;
215: super .setKey(key);
216: }
217:
218: public String getKey() {
219: return inheritedKey;
220: }
221:
222: }
|