TokenBasedRememberMeServices 加密解密

2 篇文章 0 订阅


最近看cas发现加密解密的一个好的工具类,先做个笔记,有时间再仔细研究下


代码来自  http://java2s.com/Open-Source/Java/Security/acegi-security/org/acegisecurity/ui/rememberme/TokenBasedRememberMeServices.java.htm


TokenBasedRememberMeServices.java :  » Security » acegi-security » org » acegisecurity » ui » rememberme » Java Open Source
Java Open Source » Security » acegi security	 	
acegi security » org » acegisecurity » ui » rememberme » TokenBasedRememberMeServices.java

/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.acegisecurity.ui.rememberme;

import java.util.Date;
import java.util.Map;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.acegisecurity.Authentication;
import org.acegisecurity.providers.rememberme.RememberMeAuthenticationToken;
import org.acegisecurity.ui.AccessDeniedHandler;
import org.acegisecurity.ui.AuthenticationDetailsSource;
import org.acegisecurity.ui.AuthenticationDetailsSourceImpl;
import org.acegisecurity.ui.logout.LogoutHandler;
import org.acegisecurity.userdetails.UserDetails;
import org.acegisecurity.userdetails.UserDetailsService;
import org.acegisecurity.userdetails.UsernameNotFoundException;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ApplicationContext;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.RequestUtils;

/**
 * Identifies previously remembered users by a Base-64 encoded cookie.
 * 
 * <p>
 * This implementation does not rely on an external database, so is attractive
 * for simple applications. The cookie will be valid for a specific period from
 * the date of the last
 * {@link #loginSuccess(HttpServletRequest, HttpServletResponse, Authentication)}.
 * As per the interface contract, this method will only be called when the
 * principal completes a successful interactive authentication. As such the time
 * period commences from the last authentication attempt where they furnished
 * credentials - not the time period they last logged in via remember-me. The
 * implementation will only send a remember-me token if the parameter defined by
 * {@link #setParameter(String)} is present.
 * </p>
 * 
 * <p>
 * An {@link org.acegisecurity.userdetails.UserDetailsService} is required by
 * this implementation, so that it can construct a valid
 * <code>Authentication</code> from the returned {@link
 * org.acegisecurity.userdetails.UserDetails}. This is also necessary so that
 * the user's password is available and can be checked as part of the encoded
 * cookie.
 * </p>
 * 
 * <p>
 * The cookie encoded by this implementation adopts the following form:
 * 
 * <pre>
 * username + ":" + expiryTime + ":" + Md5Hex(username + ":" + expiryTime + ":" + password + ":" + key)
 * </pre>
 * 
 * </p>
 * <p>
 * As such, if the user changes their password any remember-me token will be
 * invalidated. Equally, the system administrator may invalidate every
 * remember-me token on issue by changing the key. This provides some reasonable
 * approaches to recovering from a remember-me token being left on a public
 * machine (eg kiosk system, Internet cafe etc). Most importantly, at no time is
 * the user's password ever sent to the user agent, providing an important
 * security safeguard. Unfortunately the username is necessary in this
 * implementation (as we do not want to rely on a database for remember-me
 * services) and as such high security applications should be aware of this
 * occasionally undesired disclosure of a valid username.
 * </p>
 * <p>
 * This is a basic remember-me implementation which is suitable for many
 * applications. However, we recommend a database-based implementation if you
 * require a more secure remember-me approach.
 * </p>
 * <p>
 * By default the tokens will be valid for 14 days from the last successful
 * authentication attempt. This can be changed using
 * {@link #setTokenValiditySeconds(long)}.
 * </p>
 * 
 * @author Ben Alex
 * @version $Id: TokenBasedRememberMeServices.java 1871 2007-05-25 03:12:49Z
 * benalex $
 */
public class TokenBasedRememberMeServices implements RememberMeServices, InitializingBean, LogoutHandler {
  // ~ Static fields/initializers
  // =====================================================================================

  public static final String ACEGI_SECURITY_HASHED_REMEMBER_ME_COOKIE_KEY = "ACEGI_SECURITY_HASHED_REMEMBER_ME_COOKIE";

  public static final String DEFAULT_PARAMETER = "_acegi_security_remember_me";

  protected static final Log logger = LogFactory.getLog(TokenBasedRememberMeServices.class);

  // ~ Instance fields
  // ================================================================================================

  protected AuthenticationDetailsSource authenticationDetailsSource = new AuthenticationDetailsSourceImpl();

  private String key;

  private String parameter = DEFAULT_PARAMETER;

  private UserDetailsService userDetailsService;

  protected long tokenValiditySeconds = 1209600; // 14 days

  private boolean alwaysRemember = false;

  private static final int DEFAULT_ORDER = Integer.MAX_VALUE; // ~ default

  private String cookieName = ACEGI_SECURITY_HASHED_REMEMBER_ME_COOKIE_KEY;

  // ~ Methods
  // ========================================================================================================

  public void afterPropertiesSet() throws Exception {
    Assert.hasLength(key);
    Assert.hasLength(parameter);
    Assert.hasLength(cookieName);
    Assert.notNull(userDetailsService);
  }

  /**
   * Introspects the <code>Applicationcontext</code> for the single instance
   * of {@link AccessDeniedHandler}. If found invoke
   * setAccessDeniedHandler(AccessDeniedHandler accessDeniedHandler) method by
   * providing the found instance of accessDeniedHandler as a method
   * parameter. If more than one instance of <code>AccessDeniedHandler</code>
   * is found, the method throws <code>IllegalStateException</code>.
   * 
   * @param applicationContext to locate the instance
   */
  private void autoDetectAndUseAnyUserDetailsService(ApplicationContext applicationContext) {
    Map map = applicationContext.getBeansOfType(UserDetailsService.class);
    if (map.size() > 1) {
      throw new IllegalArgumentException(
          "More than one UserDetailsService beans detected please refer to the one using "
              + " [ principalRepositoryBeanRef  ] " + "attribute");
    }
    else if (map.size() == 1) {
      setUserDetailsService((UserDetailsService) map.values().iterator().next());
    }
  }

  public Authentication autoLogin(HttpServletRequest request, HttpServletResponse response) {
    Cookie[] cookies = request.getCookies();

    if ((cookies == null) || (cookies.length == 0)) {
      return null;
    }

    for (int i = 0; i < cookies.length; i++) {
      if (cookieName.equals(cookies[i].getName())) {
        String cookieValue = cookies[i].getValue();

        for (int j = 0; j < cookieValue.length() % 4; j++) {
          cookieValue = cookieValue + "=";
        }

        if (Base64.isArrayByteBase64(cookieValue.getBytes())) {
          if (logger.isDebugEnabled()) {
            logger.debug("Remember-me cookie detected");
          }

          // Decode token from Base64
          // format of token is:
          // username + ":" + expiryTime + ":" +
          // Md5Hex(username + ":" + expiryTime + ":" + password + ":"
          // + key)
          String cookieAsPlainText = new String(Base64.decodeBase64(cookieValue.getBytes()));
          String[] cookieTokens = StringUtils.delimitedListToStringArray(cookieAsPlainText, ":");

          if (cookieTokens.length == 3) {

            long tokenExpiryTime;

            try {
              tokenExpiryTime = new Long(cookieTokens[1]).longValue();
            }
            catch (NumberFormatException nfe) {
              cancelCookie(request, response,
                  "Cookie token[1] did not contain a valid number (contained '" + cookieTokens[1]
                      + "')");

              return null;
            }

            if (isTokenExpired(tokenExpiryTime)) {
              cancelCookie(request, response, "Cookie token[1] has expired (expired on '"
                  + new Date(tokenExpiryTime) + "'; current time is '" + new Date() + "')");

              return null;
            }

            // Check the user exists
            // Defer lookup until after expiry time checked, to
            // possibly avoid expensive lookup
            UserDetails userDetails = loadUserDetails(request, response, cookieTokens);

            if (userDetails == null) {
              cancelCookie(request, response, "Cookie token[0] contained username '" + cookieTokens[0]
                  + "' but was not found");
              return null;
            }

            if (!isValidUserDetails(request, response, userDetails, cookieTokens)) {
              return null;
            }

            // Check signature of token matches remaining details
            // Must do this after user lookup, as we need the
            // DAO-derived password
            // If efficiency was a major issue, just add in a
            // UserCache implementation,
            // but recall this method is usually only called one per
            // HttpSession
            // (as if the token is valid, it will cause
            // SecurityContextHolder population, whilst
            // if invalid, will cause the cookie to be cancelled)
            String expectedTokenSignature = makeTokenSignature(tokenExpiryTime, userDetails);

            if (!expectedTokenSignature.equals(cookieTokens[2])) {
              cancelCookie(request, response, "Cookie token[2] contained signature '" + cookieTokens[2]
                  + "' but expected '" + expectedTokenSignature + "'");

              return null;
            }

            // By this stage we have a valid token
            if (logger.isDebugEnabled()) {
              logger.debug("Remember-me cookie accepted");
            }

            RememberMeAuthenticationToken auth = new RememberMeAuthenticationToken(this.key, userDetails,
                userDetails.getAuthorities());
            auth.setDetails(authenticationDetailsSource.buildDetails((HttpServletRequest) request));

            return auth;
          }
          else {
            cancelCookie(request, response, "Cookie token did not contain 3 tokens; decoded value was '"
                + cookieAsPlainText + "'");

            return null;
          }
        }
        else {
          cancelCookie(request, response, "Cookie token was not Base64 encoded; value was '" + cookieValue
              + "'");

          return null;
        }
      }
    }

    return null;
  }

  /**
   * @param tokenExpiryTime
   * @param userDetails
   * @return
   */
  protected String makeTokenSignature(long tokenExpiryTime, UserDetails userDetails) {
    String expectedTokenSignature = DigestUtils.md5Hex(userDetails.getUsername() + ":" + tokenExpiryTime + ":"
        + userDetails.getPassword() + ":" + this.key);
    return expectedTokenSignature;
  }

  protected boolean isValidUserDetails(HttpServletRequest request, HttpServletResponse response,
      UserDetails userDetails, String[] cookieTokens) {
    // Immediately reject if the user is not allowed to
    // login
    if (!userDetails.isAccountNonExpired() || !userDetails.isCredentialsNonExpired() || !userDetails.isEnabled()) {
      cancelCookie(request, response, "Cookie token[0] contained username '" + cookieTokens[0]
          + "' but account has expired, credentials have expired, or user is disabled");

      return false;
    }
    return true;
  }

  protected UserDetails loadUserDetails(HttpServletRequest request, HttpServletResponse response,
      String[] cookieTokens) {
    UserDetails userDetails = null;

    try {
      userDetails = this.userDetailsService.loadUserByUsername(cookieTokens[0]);
    }
    catch (UsernameNotFoundException notFound) {
      cancelCookie(request, response, "Cookie token[0] contained username '" + cookieTokens[0]
          + "' but was not found");

      return null;
    }
    return userDetails;
  }

  protected boolean isTokenExpired(long tokenExpiryTime) {
    // Check it has not expired
    if (tokenExpiryTime < System.currentTimeMillis()) {
      return true;
    }
    return false;
  }

  protected void cancelCookie(HttpServletRequest request, HttpServletResponse response, String reasonForLog) {
    if ((reasonForLog != null) && logger.isDebugEnabled()) {
      logger.debug("Cancelling cookie for reason: " + reasonForLog);
    }

    response.addCookie(makeCancelCookie(request));
  }

  public String getKey() {
    return key;
  }

  public String getParameter() {
    return parameter;
  }

  public long getTokenValiditySeconds() {
    return tokenValiditySeconds;
  }

  public UserDetailsService getUserDetailsService() {
    return userDetailsService;
  }

  public void loginFail(HttpServletRequest request, HttpServletResponse response) {
    cancelCookie(request, response, "Interactive authentication attempt was unsuccessful");
  }

  protected boolean rememberMeRequested(HttpServletRequest request, String parameter) {
    if (alwaysRemember) {
      return true;
    }

    return RequestUtils.getBooleanParameter(request, parameter, false);
  }

  public void loginSuccess(HttpServletRequest request, HttpServletResponse response,
      Authentication successfulAuthentication) {
    // Exit if the principal hasn't asked to be remembered
    if (!rememberMeRequested(request, parameter)) {
      if (logger.isDebugEnabled()) {
        logger.debug("Did not send remember-me cookie (principal did not set parameter '" + this.parameter
            + "')");
      }

      return;
    }

    // Determine username and password, ensuring empty strings
    Assert.notNull(successfulAuthentication.getPrincipal());
    Assert.notNull(successfulAuthentication.getCredentials());

    String username = retrieveUserName(successfulAuthentication);
    String password = retrievePassword(successfulAuthentication);

    // If unable to find a username and password, just abort as
    // TokenBasedRememberMeServices unable to construct a valid token in
    // this case
    if (!StringUtils.hasLength(username) || !StringUtils.hasLength(password)) {
      return;
    }

    long expiryTime = System.currentTimeMillis() + (tokenValiditySeconds * 1000);

    // construct token to put in cookie; format is:
    // username + ":" + expiryTime + ":" + Md5Hex(username + ":" +
    // expiryTime + ":" + password + ":" + key)
    String signatureValue = DigestUtils.md5Hex(username + ":" + expiryTime + ":" + password + ":" + key);
    String tokenValue = username + ":" + expiryTime + ":" + signatureValue;
    String tokenValueBase64 = new String(Base64.encodeBase64(tokenValue.getBytes()));
    response.addCookie(makeValidCookie(tokenValueBase64, request, tokenValiditySeconds));

    if (logger.isDebugEnabled()) {
      logger
          .debug("Added remember-me cookie for user '" + username + "', expiry: '" + new Date(expiryTime)
              + "'");
    }
  }

  public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
    cancelCookie(request, response, "Logout of user "
        + (authentication == null ? "Unknown" : authentication.getName()));
  }

  protected String retrieveUserName(Authentication successfulAuthentication) {
    if (isInstanceOfUserDetails(successfulAuthentication)) {
      return ((UserDetails) successfulAuthentication.getPrincipal()).getUsername();
    }
    else {
      return successfulAuthentication.getPrincipal().toString();
    }
  }

  protected String retrievePassword(Authentication successfulAuthentication) {
    if (isInstanceOfUserDetails(successfulAuthentication)) {
      return ((UserDetails) successfulAuthentication.getPrincipal()).getPassword();
    }
    else {
      return successfulAuthentication.getCredentials().toString();
    }
  }

  private boolean isInstanceOfUserDetails(Authentication authentication) {
    return authentication.getPrincipal() instanceof UserDetails;
  }

  protected Cookie makeCancelCookie(HttpServletRequest request) {
    Cookie cookie = new Cookie(cookieName, null);
    cookie.setMaxAge(0);
    cookie.setPath(StringUtils.hasLength(request.getContextPath()) ? request.getContextPath() : "/");

    return cookie;
  }

  protected Cookie makeValidCookie(String tokenValueBase64, HttpServletRequest request, long maxAge) {
    Cookie cookie = new Cookie(cookieName, tokenValueBase64);
    cookie.setMaxAge(new Long(maxAge).intValue());
    cookie.setPath(StringUtils.hasLength(request.getContextPath()) ? request.getContextPath() : "/");

    return cookie;
  }

  public void setAuthenticationDetailsSource(AuthenticationDetailsSource authenticationDetailsSource) {
    Assert.notNull(authenticationDetailsSource, "AuthenticationDetailsSource required");
    this.authenticationDetailsSource = authenticationDetailsSource;
  }

  public void setKey(String key) {
    this.key = key;
  }

  public void setParameter(String parameter) {
    this.parameter = parameter;
  }

  public void setCookieName(String cookieName) {
    this.cookieName = cookieName;
  }

  public void setTokenValiditySeconds(long tokenValiditySeconds) {
    this.tokenValiditySeconds = tokenValiditySeconds;
  }

  public void setUserDetailsService(UserDetailsService userDetailsService) {
    this.userDetailsService = userDetailsService;
  }

  public boolean isAlwaysRemember() {
    return alwaysRemember;
  }

  public void setAlwaysRemember(boolean alwaysRemember) {
    this.alwaysRemember = alwaysRemember;
  }

  public String getCookieName() {
    return cookieName;
  }

}

	


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值