001/**
002 * Copyright (c) 2015-2022, Michael Yang 杨福海 (fuhai999@gmail.com).
003 * <p>
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 * <p>
008 * http://www.apache.org/licenses/LICENSE-2.0
009 * <p>
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package io.jboot.utils;
017
018import com.jfinal.core.Controller;
019import com.jfinal.kit.Base64Kit;
020import com.jfinal.kit.HashKit;
021import com.jfinal.kit.LogKit;
022import com.jfinal.kit.StrKit;
023import com.jfinal.log.Log;
024import io.jboot.Jboot;
025import io.jboot.exception.JbootIllegalConfigException;
026import io.jboot.web.JbootWebConfig;
027
028import javax.servlet.http.Cookie;
029import javax.servlet.http.HttpServletRequest;
030import javax.servlet.http.HttpServletResponse;
031import java.math.BigInteger;
032
033/**
034 * 参考:spring-security
035 * https://github.com/spring-projects/spring-security/
036 * blob/master/web/src/main/java/org/springframework/security/
037 * web/authentication/rememberme/TokenBasedRememberMeServices.java
038 * ....AbstractRememberMeServices.java
039 * <p>
040 * 加密的cookie工具类
041 */
042public class CookieUtil {
043    private static final Log LOG = Log.getLog(CookieUtil.class);
044
045    private final static String COOKIE_SEPARATOR = "#";
046
047    // cookie 加密秘钥
048    private static String COOKIE_ENCRYPT_KEY = JbootWebConfig.getInstance().getCookieEncryptKey();
049
050    // 2 days(单位:秒)
051    private static int COOKIE_MAX_AGE = JbootWebConfig.getInstance().getCookieMaxAge();
052
053    // 默认的路径, null 默认路径 "/"
054    private static String defaultPath = null;
055
056    // 默认的域名, null 为当前域名
057    private static String defaultDomain = null;
058
059
060    /**
061     * 在使用之前,小调用此方法进行加密key的设置
062     *
063     * @param key
064     */
065    public static void initEncryptKey(String key) {
066        COOKIE_ENCRYPT_KEY = key;
067    }
068
069
070    public static String getEncryptKey() {
071        return COOKIE_ENCRYPT_KEY;
072    }
073
074    /**
075     * 设置 默认的 Cookie 有效时间,单位:秒
076     *
077     * @param seconds
078     */
079    public static void initDefaultCookieMaxAge(int seconds) {
080        COOKIE_MAX_AGE = seconds;
081    }
082
083    public static int getDefaultCookieMaxAge() {
084        return COOKIE_MAX_AGE;
085    }
086
087
088    public static String getDefaultPath() {
089        return defaultPath;
090    }
091
092    public static void setDefaultPath(String defaultPath) {
093        CookieUtil.defaultPath = defaultPath;
094    }
095
096    public static String getDefaultDomain() {
097        return defaultDomain;
098    }
099
100    public static void setDefaultDomain(String defaultDomain) {
101        CookieUtil.defaultDomain = defaultDomain;
102    }
103
104    public static void put(Controller ctr, String key, Object value) {
105        put(ctr, key, value, COOKIE_MAX_AGE, defaultPath, defaultDomain, COOKIE_ENCRYPT_KEY);
106    }
107
108    public static void put(HttpServletResponse response, String key, Object value) {
109        put(response, key, value, COOKIE_MAX_AGE, defaultPath, defaultDomain, COOKIE_ENCRYPT_KEY);
110    }
111
112    public static void put(HttpServletResponse response, String key, Object value, int maxAgeInSeconds) {
113        put(response, key, value, maxAgeInSeconds, defaultPath, defaultDomain, COOKIE_ENCRYPT_KEY);
114    }
115
116
117    public static void put(Controller ctr, String key, Object value, String secretKey) {
118        put(ctr, key, value, COOKIE_MAX_AGE, defaultPath, defaultDomain, secretKey);
119    }
120
121
122    public static void put(Controller ctr, String key, String value, int maxAgeInSeconds) {
123        put(ctr, key, value, maxAgeInSeconds, defaultPath, defaultDomain, COOKIE_ENCRYPT_KEY);
124    }
125
126    public static void put(Controller ctr, String key, String value, int maxAgeInSeconds, String secretKey) {
127        put(ctr, key, value, maxAgeInSeconds, defaultPath, defaultDomain, secretKey);
128    }
129
130
131    public static void put(Controller ctr, String key, Object value, int maxAgeInSeconds, String path, String domain, String secretKey) {
132        put(ctr.getResponse(), key, value, maxAgeInSeconds, path, domain, secretKey);
133    }
134
135    public static void put(HttpServletResponse response, String key, Object value, int maxAgeInSeconds, String path, String domain, String secretKey) {
136        String cookie = buildCookieValue(value.toString(), maxAgeInSeconds, secretKey);
137        doSetCookie(response, key, cookie, maxAgeInSeconds, path, domain, true);
138    }
139
140    public static void remove(Controller ctr, String key) {
141        doSetCookie(ctr.getResponse(), key, null, 0, null, null, null);
142    }
143
144    public static void remove(HttpServletResponse response, String key) {
145        doSetCookie(response, key, null, 0, null, null, null);
146    }
147
148    public static void remove(Controller ctr, String key, String path, String domain) {
149        doSetCookie(ctr.getResponse(), key, null, 0, path, domain, null);
150    }
151
152    public static String get(Controller ctr, String key) {
153        return get(ctr, key, COOKIE_ENCRYPT_KEY);
154    }
155
156    public static String get(HttpServletRequest request, String key) {
157        return get(request, key, COOKIE_ENCRYPT_KEY);
158    }
159
160    public static String get(Controller ctr, String key, String secretKey) {
161        return get(ctr.getRequest(), key, secretKey);
162    }
163
164    public static String get(HttpServletRequest request, String key, String secretKey) {
165        String cookieValue = getCookie(request, key, null);
166
167        if (cookieValue == null) {
168            return null;
169        }
170
171        try {
172            String value = new String(Base64Kit.decode(cookieValue));
173            return getFromCookieInfo(secretKey, value);
174        }
175
176        //倘若 cookie 被人为修改的情况下能会出现异常情况
177        catch (Exception ex) {
178            LogKit.error(ex.toString(), ex);
179        }
180
181        return null;
182    }
183
184
185    public static String buildCookieValue(String value, int maxAgeInSeconds, String secretKey) {
186        long saveTime = System.currentTimeMillis();
187        String encryptValue = encrypt(secretKey, saveTime, maxAgeInSeconds, value);
188
189        StringBuilder stringBuilder = new StringBuilder();
190        stringBuilder.append(encryptValue);
191        stringBuilder.append(COOKIE_SEPARATOR);
192        stringBuilder.append(saveTime);
193        stringBuilder.append(COOKIE_SEPARATOR);
194        stringBuilder.append(maxAgeInSeconds);
195        stringBuilder.append(COOKIE_SEPARATOR);
196        stringBuilder.append(Base64Kit.encode(value));
197
198        return Base64Kit.encode(stringBuilder.toString());
199    }
200
201    private static String encrypt(String secretKey, Object saveTime, Object maxAgeInSeconds, String value) {
202
203        // 若使用了默认的加密秘钥
204        if (JbootWebConfig.DEFAULT_COOKIE_ENCRYPT_KEY.equals(secretKey)) {
205
206            if (Jboot.isDevMode()) {
207                LOG.warn("Warn!!! encrypt key is defalut value. please config \"jboot.web.cookieEncryptKey = xxx\" in jboot.properties");
208            }
209            // 生产环境下,直接抛出错误!
210            else {
211                throw new JbootIllegalConfigException("Error!!! Cookie encrypt key is not configured. please config \"jboot.web.cookieEncryptKey = xxx\" in jboot.properties");
212            }
213        }
214        return HashKit.md5(secretKey + saveTime.toString() + maxAgeInSeconds.toString() + value);
215    }
216
217
218    public static String getFromCookieInfo(String secretKey, String cookieValue) {
219        if (StrUtil.isBlank(cookieValue)) {
220            return null;
221        }
222
223        String[] cookieStrings = cookieValue.split(COOKIE_SEPARATOR);
224        if (cookieStrings.length != 4) {
225            return null;
226        }
227
228        String encryptValue = cookieStrings[0];
229        String saveTime = cookieStrings[1];
230        String maxAgeInSeconds = cookieStrings[2];
231        String value = Base64Kit.decodeToStr(cookieStrings[3]);
232
233        String encrypt = encrypt(secretKey, Long.valueOf(saveTime), maxAgeInSeconds, value);
234
235        // 非常重要,确保 cookie 不被人为修改
236        if (!encrypt.equals(encryptValue)) {
237            return null;
238        }
239
240        long maxTimeMillis = Long.parseLong(maxAgeInSeconds) * 1000;
241
242        //可能设置的时间为 0 或者 -1
243        if (maxTimeMillis <= 0) {
244            return value;
245        }
246
247        long saveTimeMillis = Long.parseLong(saveTime);
248        // 查看是否过时
249        if ((saveTimeMillis + maxTimeMillis) - System.currentTimeMillis() > 0) {
250            return value;
251        }
252        //已经超时了
253        else {
254            return null;
255        }
256    }
257
258    public static Long getLong(Controller ctr, String key) {
259        String value = get(ctr, key);
260        return null == value ? null : Long.parseLong(value);
261    }
262
263    public static long getLong(Controller ctr, String key, long defalut) {
264        String value = get(ctr, key);
265        return null == value ? defalut : Long.parseLong(value);
266    }
267
268    public static Integer getInt(Controller ctr, String key) {
269        String value = get(ctr, key);
270        return null == value ? null : Integer.parseInt(value);
271    }
272
273    public static int getInt(Controller ctr, String key, int defalut) {
274        String value = get(ctr, key);
275        return null == value ? defalut : Integer.parseInt(value);
276    }
277
278    public static BigInteger getBigInteger(Controller ctr, String key) {
279        String value = get(ctr, key);
280        return null == value ? null : new BigInteger(value);
281    }
282
283    public static BigInteger getBigInteger(Controller ctr, String key, BigInteger defalut) {
284        String value = get(ctr, key);
285        return null == value ? defalut : new BigInteger(value);
286    }
287
288
289    private static String getCookie(HttpServletRequest request, String name, String defaultValue) {
290        Cookie cookie = getCookieObject(request, name);
291        return cookie != null ? cookie.getValue() : defaultValue;
292    }
293
294    /**
295     * Get cookie object by cookie name.
296     */
297    private static Cookie getCookieObject(HttpServletRequest request, String name) {
298        Cookie[] cookies = request.getCookies();
299        if (cookies != null) {
300            for (Cookie cookie : cookies) {
301                if (cookie.getName().equals(name)) {
302                    return cookie;
303                }
304            }
305        }
306        return null;
307    }
308
309
310    private static void doSetCookie(HttpServletResponse response, String name, String value, int maxAgeInSeconds, String path, String domain, Boolean isHttpOnly) {
311        Cookie cookie = new Cookie(name, value);
312        cookie.setMaxAge(maxAgeInSeconds);
313        // set the default path value to "/"
314        if (StrKit.isBlank(path)) {
315            path = "/";
316        }
317        cookie.setPath(path);
318
319        if (domain != null) {
320            cookie.setDomain(domain);
321        }
322        if (isHttpOnly != null) {
323            cookie.setHttpOnly(isHttpOnly);
324        }
325        response.addCookie(cookie);
326    }
327
328
329}