001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.wicket.authentication.strategy; 018 019import java.util.UUID; 020 021import org.apache.wicket.authentication.IAuthenticationStrategy; 022import org.apache.wicket.util.cookies.CookieDefaults; 023import org.apache.wicket.util.cookies.CookieUtils; 024import org.apache.wicket.util.crypt.ICrypt; 025import org.apache.wicket.util.crypt.SunJceCrypt; 026import org.apache.wicket.util.lang.Args; 027import org.apache.wicket.util.string.Strings; 028import org.slf4j.Logger; 029import org.slf4j.LoggerFactory; 030 031/** 032 * Wicket's default implementation of an authentication strategy. It'll concatenate username and 033 * password, encrypt it and put it into one Cookie. 034 * <p> 035 * Note: To support automatic authentication across application restarts you have to use 036 * the constructor {@link DefaultAuthenticationStrategy#DefaultAuthenticationStrategy(String, ICrypt)}. 037 * 038 * @author Juergen Donnerstag 039 */ 040public class DefaultAuthenticationStrategy implements IAuthenticationStrategy 041{ 042 private static final Logger logger = LoggerFactory.getLogger(DefaultAuthenticationStrategy.class); 043 044 /** The cookie name to store the username and password */ 045 protected final String cookieKey; 046 047 /** 048 * @deprecated no longer used TODO remove in Wicket 10 049 */ 050 @Deprecated(forRemoval = true) 051 protected final String encryptionKey = null; 052 053 /** The separator used to concatenate the username and password */ 054 protected final String VALUE_SEPARATOR = "-sep-"; 055 056 /** Cookie utils with default settings */ 057 private CookieUtils cookieUtils; 058 059 /** Use to encrypt cookie values for username and password. */ 060 private ICrypt crypt; 061 062 /** 063 * Constructor 064 * 065 * @param cookieKey 066 * The name of the cookie 067 * 068 * @deprecated supply a crypt instead TODO remove in Wicket 10 069 */ 070 @Deprecated(forRemoval = true) 071 public DefaultAuthenticationStrategy(final String cookieKey) 072 { 073 this(cookieKey, defaultEncryptionKey()); 074 } 075 076 private static String defaultEncryptionKey() 077 { 078 return UUID.randomUUID().toString(); 079 } 080 081 /** 082 * @deprecated supply a crypt instead TODO remove in Wicket 10 083 */ 084 @Deprecated(forRemoval = true) 085 public DefaultAuthenticationStrategy(final String cookieKey, final String encryptionKey) 086 { 087 this(cookieKey, defaultCrypt(encryptionKey)); 088 } 089 090 private static ICrypt defaultCrypt(String encryptionKey) 091 { 092 byte[] salt = SunJceCrypt.randomSalt(); 093 094 SunJceCrypt crypt = new SunJceCrypt(salt, 1000); 095 crypt.setKey(encryptionKey); 096 return crypt; 097 } 098 099 /** 100 * This is the recommended constructor to be used, which allows automatic authentication across 101 * application restarts. 102 * 103 * @param cookieKey 104 * The name of the cookie 105 * @param crypt 106 * the crypt 107 */ 108 public DefaultAuthenticationStrategy(final String cookieKey, ICrypt crypt) 109 { 110 this.cookieKey = Args.notEmpty(cookieKey, "cookieKey"); 111 this.crypt = Args.notNull(crypt, "crypt"); 112 } 113 114 /** 115 * Make sure you always return a valid CookieUtils 116 * 117 * @return CookieUtils 118 */ 119 protected CookieUtils getCookieUtils() 120 { 121 if (cookieUtils == null) 122 { 123 CookieDefaults settings = new CookieDefaults(); 124 settings.setHttpOnly(true); 125 cookieUtils = new CookieUtils(settings); 126 } 127 return cookieUtils; 128 } 129 130 /** 131 * @return The crypt engine to be used 132 */ 133 protected ICrypt getCrypt() 134 { 135 return crypt; 136 } 137 138 @Override 139 public String[] load() 140 { 141 String value = getCookieUtils().load(cookieKey); 142 if (Strings.isEmpty(value) == false) 143 { 144 try 145 { 146 value = getCrypt().decryptUrlSafe(value); 147 } 148 catch (RuntimeException e) 149 { 150 logger.info( 151 "Error decrypting login cookie: {}. The cookie will be deleted. Possible cause is that a session-relative encryption key was used to encrypt this cookie while this decryption attempt is happening in a different session, eg user coming back to the application after session expiration", 152 cookieKey); 153 getCookieUtils().remove(cookieKey); 154 value = null; 155 } 156 return decode(value); 157 } 158 159 return null; 160 } 161 162 /** 163 * This method will decode decrypted cookie value based on application needs 164 * 165 * @param value decrypted cookie value 166 * @return decomposed values array, or null in case cookie value was empty. 167 */ 168 protected String[] decode(String value) { 169 if (Strings.isEmpty(value) == false) 170 { 171 String username = null; 172 String password = null; 173 174 String[] values = value.split(VALUE_SEPARATOR); 175 if ((values.length > 0) && (Strings.isEmpty(values[0]) == false)) 176 { 177 username = values[0]; 178 } 179 if ((values.length > 1) && (Strings.isEmpty(values[1]) == false)) 180 { 181 password = values[1]; 182 } 183 184 return new String[] { username, password }; 185 } 186 return null; 187 } 188 189 @Override 190 public void save(final String credential, final String... extraCredentials) 191 { 192 String encryptedValue = getCrypt().encryptUrlSafe(encode(credential, extraCredentials)); 193 194 getCookieUtils().save(cookieKey, encryptedValue); 195 } 196 197 /** 198 * This method can be overridden to provide different encoding mechanism 199 * 200 * @param credential 201 * @param extraCredentials 202 * @return String representation of the parameters given 203 */ 204 protected String encode(final String credential, final String... extraCredentials) 205 { 206 StringBuilder value = new StringBuilder(credential); 207 if (extraCredentials != null) 208 { 209 for (String extraCredential : extraCredentials) 210 { 211 value.append(VALUE_SEPARATOR).append(extraCredential); 212 } 213 } 214 return value.toString(); 215 } 216 217 @Override 218 public void remove() 219 { 220 getCookieUtils().remove(cookieKey); 221 } 222}