├── .gitignore ├── README.md ├── pom.xml └── src └── main └── java └── com └── ctlok └── web └── session ├── StatelessSession.java ├── StatelessSessionConfig.java ├── StatelessSessionFilter.java └── crypto ├── AesEncryptor.java ├── CryptoUtils.java └── Encryptor.java /.gitignore: -------------------------------------------------------------------------------- 1 | #* 2 | *$ 3 | *.BAK 4 | *.Z 5 | *.bak 6 | *.class 7 | *.elc 8 | *.ln 9 | *.log 10 | *.o 11 | *.obj 12 | *.olb 13 | *.old 14 | *.orig 15 | *.pyc 16 | *.pyo 17 | *.rej 18 | *~ 19 | ,* 20 | .#* 21 | .DS_Store 22 | .del-* 23 | .deployables 24 | .make.state 25 | .nse_depinfo 26 | .svn 27 | CVS.adm 28 | RCS 29 | RCSLOG 30 | SCCS 31 | _$* 32 | _svn 33 | target 34 | .settings 35 | .classpath 36 | .project 37 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## HTTP Stateless / Cookie-based Session for Java 2 | 3 | HTTP Stateless Session help you to build stateless web application base on Java. 4 | Stateless Session compliable with `HttpSession`. 5 | 6 | What are the benefits of a stateless web application? 7 | 8 | 1. Reduces memory usage. 9 | 2. Easier to support server farms. 10 | 3. Reduce session expiration problems. 11 | 12 | Reference: [http://stackoverflow.com/questions/5539823/what-are-the-benefits-of-a-stateless-web-application] (http://stackoverflow.com/questions/5539823/what-are-the-benefits-of-a-stateless-web-application) 13 | 14 | ### Limitation 15 | 16 | 1. Data total size cannot over 4KB, because all session data is storded in cookie. 17 | 2. Data type must be String. 18 | 19 | ## Basic Usage 20 | 21 | ### Dependency: 22 | 23 | * commons-codec 1.7 or above 24 | * gson 2.2.2 or above 25 | 26 | ### Maven 27 | 28 | ``` 29 | 30 | com.ctlok 31 | stateless-http-session 32 | 1.2.4 33 | 34 | ``` 35 | 36 | ### Basic Web.xml Config 37 | 38 | ``` 39 | 40 | statelessSessionFilter 41 | com.ctlok.web.session.StatelessSessionFilter 42 | 43 | HMAC_SHA1_KEY 44 | aDg3uE6t8X57bnFwcqRql8tvd 45 | 46 | 47 | 48 | 49 | statelessSessionFilter 50 | /* 51 | 52 | ``` 53 | 54 | `HMAC_SHA1_KEY` is a mandatory field for check session data is it modified. 55 | If session data was modified by client, all session data will destroy and create a new session. 56 | 57 | ### Other Config 58 | 59 | 1. `ENCRYPTION_SECRET_KEY` is a secret key to encrypt session data. By default, session data is not encrypted. 60 | 2. `ENCRYPTION_IMPL_CLASS` is a class name implemented `com.ctlok.web.session.crypto.Encryptor`. Default: `com.ctlok.web.session.crypto.AesEncryptor`. 61 | 3. `SESSION_NAME` is a session cookie name. Default: `SESSION`. 62 | 4. `SESSION_MAX_AGE` is a session cookie max age. Default: `-1` expire when browser closed. 63 | 5. `SESSION_PATH` is a session cookie path on current domain. Default: `/`. 64 | 6. `SESSION_DOMAIN` is a session cookie domain. Default is null. 65 | 66 | ### Java Code Example 67 | 68 | ``` 69 | HttpSession session = request.getSession(true); 70 | session.setAttribute("user", "lawrence"); 71 | session.getAttribute("user"); 72 | ``` 73 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4.0.0 3 | com.ctlok 4 | stateless-http-session 5 | 1.2.5-SNAPSHOT 6 | jar 7 | Stateless HTTP Session 8 | HTTP Stateless Session for Java 9 | https://github.com/lawrence0819/spring-webmvc-rythm 10 | 11 | 12 | 13 | The Apache Software License, Version 2.0 14 | http://www.apache.org/licenses/LICENSE-2.0.txt 15 | repo 16 | A business-friendly OSS license 17 | 18 | 19 | 20 | 21 | https://github.com/lawrence0819/java-stateless-http-session 22 | scm:git:git://github.com/lawrence0819/java-stateless-http-session.git 23 | scm:git:git@github.com:lawrence0819/java-stateless-http-session.git 24 | HEAD 25 | 26 | 27 | 28 | 29 | lawrence 30 | Lawrence Cheung 31 | lawrence0819@gmail.com 32 | +8 33 | 34 | 35 | 36 | 37 | 38 | sonatype-nexus-snapshots 39 | Sonatype Nexus snapshot repository 40 | https://oss.sonatype.org/content/repositories/snapshots 41 | 42 | 43 | sonatype-nexus-staging 44 | Sonatype Nexus release repository 45 | https://oss.sonatype.org/service/local/staging/deploy/maven2/ 46 | 47 | 48 | 49 | 50 | UTF-8 51 | 1.6 52 | 53 | 3.0.1 54 | 1.7 55 | 2.2.2 56 | 57 | 2.3.2 58 | 2.4.1 59 | 1.4 60 | 61 | 62 | 63 | 64 | release-sign-artifacts 65 | 66 | 67 | performRelease 68 | true 69 | 70 | 71 | 72 | 73 | 74 | org.apache.maven.plugins 75 | maven-gpg-plugin 76 | ${plugin.gpg.version} 77 | 78 | ${gpg.passphrase} 79 | 80 | 81 | 82 | sign-artifacts 83 | verify 84 | 85 | sign 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | javax.servlet 98 | javax.servlet-api 99 | ${dependency.servlet-api.version} 100 | provided 101 | 102 | 103 | 104 | commons-codec 105 | commons-codec 106 | ${dependency.commons-codec.version} 107 | 108 | 109 | 110 | com.google.code.gson 111 | gson 112 | ${dependency.gson.version} 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | org.apache.maven.plugins 121 | maven-compiler-plugin 122 | ${plugin.maven-compiler.version} 123 | 124 | ${java.version} 125 | ${java.version} 126 | ${project.build.sourceEncoding} 127 | 128 | 129 | 130 | 131 | org.apache.maven.plugins 132 | maven-release-plugin 133 | ${plugin.release.version} 134 | 135 | -Dgpg.passphrase=${gpg.passphrase} 136 | 137 | 138 | 139 | 140 | 141 | 142 | -------------------------------------------------------------------------------- /src/main/java/com/ctlok/web/session/StatelessSession.java: -------------------------------------------------------------------------------- 1 | package com.ctlok.web.session; 2 | 3 | import java.math.BigInteger; 4 | import java.security.InvalidKeyException; 5 | import java.util.Collections; 6 | import java.util.Enumeration; 7 | import java.util.Map; 8 | import java.util.TreeMap; 9 | import java.util.UUID; 10 | 11 | import javax.servlet.ServletContext; 12 | import javax.servlet.http.Cookie; 13 | import javax.servlet.http.HttpSession; 14 | import javax.servlet.http.HttpSessionContext; 15 | 16 | import com.ctlok.web.session.crypto.CryptoUtils; 17 | import com.google.gson.Gson; 18 | 19 | public class StatelessSession implements HttpSession { 20 | 21 | private static final String CHECKSUM_KEY = "__s"; 22 | private static final String ID_KEY = "__id"; 23 | private static final String CREATION_TIME_KEY = "__ct"; 24 | 25 | private final Map attributes = new TreeMap(); 26 | private final Gson gson = new Gson(); 27 | 28 | private final StatelessSessionConfig config; 29 | private boolean newSession; 30 | 31 | private String sessionId; 32 | private long creationTime; 33 | 34 | public StatelessSession(final StatelessSessionConfig config){ 35 | 36 | this.config = config; 37 | 38 | try{ 39 | 40 | final Cookie sessionCookie = findSessionCookie(); 41 | if (sessionCookie == null){ 42 | this.initNewSession(); 43 | }else{ 44 | String cookieValue = sessionCookie.getValue(); 45 | 46 | if (this.config.getSecretKey() != null){ 47 | 48 | cookieValue = this.config.getEncryptor().decrypt( 49 | this.config.getSecretKey(), cookieValue); 50 | 51 | } 52 | 53 | if (this.isValidSessionCookieValue(cookieValue)){ 54 | this.attributes.putAll(this.jsonToMap(cookieValue)); 55 | this.sessionId = this.attributes.get(ID_KEY); 56 | this.creationTime = Long.valueOf(this.attributes.get(CREATION_TIME_KEY)); 57 | 58 | this.attributes.remove(CHECKSUM_KEY); 59 | this.attributes.remove(ID_KEY); 60 | this.attributes.remove(CREATION_TIME_KEY); 61 | }else{ 62 | this.initNewSession(); 63 | } 64 | } 65 | 66 | } catch (Exception e){ 67 | e.printStackTrace(); 68 | this.initNewSession(); 69 | } 70 | } 71 | 72 | private void initNewSession(){ 73 | this.attributes.clear(); 74 | this.sessionId = this.generateSessionId(); 75 | this.creationTime = System.currentTimeMillis(); 76 | this.newSession = true; 77 | } 78 | 79 | protected Cookie findSessionCookie(){ 80 | Cookie sessionCookie = null; 81 | 82 | if (this.config.getRequest().getCookies() != null){ 83 | 84 | for (final Cookie cookie: this.config.getRequest().getCookies()){ 85 | if (this.config.getSessionName().equals(cookie.getName())){ 86 | sessionCookie = cookie; 87 | } 88 | } 89 | 90 | } 91 | 92 | return sessionCookie; 93 | } 94 | 95 | protected boolean isValidSessionCookieValue(final String cookieValue){ 96 | final Map map = jsonToMap(cookieValue); 97 | 98 | if (map.containsKey(CHECKSUM_KEY) 99 | && map.containsKey(ID_KEY) 100 | && map.containsKey(CREATION_TIME_KEY)){ 101 | 102 | final String checksum = map.get(CHECKSUM_KEY); 103 | map.remove(CHECKSUM_KEY); 104 | 105 | return checksum.equals(this.mapChecksum(map)); 106 | 107 | } 108 | 109 | return false; 110 | } 111 | 112 | protected String mapToJson(final Map map){ 113 | return gson.toJson(map); 114 | } 115 | 116 | @SuppressWarnings("unchecked") 117 | protected Map jsonToMap(final String json){ 118 | return gson.fromJson(json, Map.class); 119 | } 120 | 121 | protected String mapChecksum(Map map){ 122 | try { 123 | return CryptoUtils.hmacSha1(this.config.getHmacSHA1Key(), this.mapToJson(map)); 124 | } catch (InvalidKeyException e) { 125 | throw new IllegalStateException(e); 126 | } 127 | } 128 | 129 | protected String generateSessionId(){ 130 | final String uuid = UUID.randomUUID().toString(); 131 | return new BigInteger(uuid.replaceAll("-", ""), 16).toString(32); 132 | } 133 | 134 | protected Cookie createCookie() { 135 | try{ 136 | final Map map = new TreeMap(); 137 | map.putAll(this.attributes); 138 | map.put(ID_KEY, sessionId); 139 | map.put(CREATION_TIME_KEY, Long.toString(creationTime)); 140 | 141 | final String checksum = mapChecksum(map); 142 | map.put(CHECKSUM_KEY, checksum); 143 | 144 | final String json = mapToJson(map); 145 | final String cookieValue = this.config.getSecretKey() == null ? json : 146 | this.config.getEncryptor().encrypt(this.config.getSecretKey(), json); 147 | 148 | final Cookie cookie = new Cookie(this.config.getSessionName(), cookieValue); 149 | 150 | cookie.setMaxAge(this.config.getSessionMaxAge()); 151 | cookie.setPath(this.config.getPath()); 152 | cookie.setHttpOnly(this.config.isHttpOnly()); 153 | 154 | if (this.config.getDomain() != null){ 155 | cookie.setDomain(this.config.getDomain()); 156 | } 157 | 158 | return cookie; 159 | } catch (final Exception e){ 160 | throw new IllegalStateException(e); 161 | } 162 | } 163 | 164 | public void flush(){ 165 | this.config.getResponse().addCookie(this.createCookie()); 166 | } 167 | 168 | @Override 169 | public long getCreationTime() { 170 | return this.creationTime; 171 | } 172 | 173 | @Override 174 | public String getId() { 175 | return this.sessionId; 176 | } 177 | 178 | @Override 179 | public long getLastAccessedTime() { 180 | return 0; 181 | } 182 | 183 | @Override 184 | public ServletContext getServletContext() { 185 | return this.config.getServletContext(); 186 | } 187 | 188 | @Override 189 | public void setMaxInactiveInterval(int interval) { 190 | 191 | } 192 | 193 | @Override 194 | public int getMaxInactiveInterval() { 195 | return 0; 196 | } 197 | 198 | @Override 199 | public HttpSessionContext getSessionContext() { 200 | return null; 201 | } 202 | 203 | @Override 204 | public Object getAttribute(String name) { 205 | return this.attributes.get(name); 206 | } 207 | 208 | @Override 209 | public Object getValue(String name) { 210 | return this.getAttribute(name); 211 | } 212 | 213 | @Override 214 | public Enumeration getAttributeNames() { 215 | return Collections.enumeration(this.attributes.keySet()); 216 | } 217 | 218 | @Override 219 | public String[] getValueNames() { 220 | return this.attributes.keySet().toArray(new String[0]); 221 | } 222 | 223 | @Override 224 | public void setAttribute(String name, Object value) { 225 | if (value instanceof String){ 226 | this.attributes.put(name, value.toString()); 227 | this.flush(); 228 | }else{ 229 | throw new IllegalArgumentException("Stateless session only accept String value"); 230 | } 231 | } 232 | 233 | @Override 234 | public void putValue(String name, Object value) { 235 | this.setAttribute(name, value); 236 | } 237 | 238 | @Override 239 | public void removeAttribute(String name) { 240 | this.attributes.remove(name); 241 | this.flush(); 242 | } 243 | 244 | @Override 245 | public void removeValue(String name) { 246 | this.removeAttribute(name); 247 | } 248 | 249 | @Override 250 | public void invalidate() { 251 | final Cookie cookie = this.createCookie(); 252 | cookie.setMaxAge(0); 253 | this.config.getResponse().addCookie(cookie); 254 | } 255 | 256 | @Override 257 | public boolean isNew() { 258 | return this.newSession; 259 | } 260 | 261 | } 262 | -------------------------------------------------------------------------------- /src/main/java/com/ctlok/web/session/StatelessSessionConfig.java: -------------------------------------------------------------------------------- 1 | package com.ctlok.web.session; 2 | 3 | import javax.servlet.ServletContext; 4 | import javax.servlet.http.HttpServletRequest; 5 | import javax.servlet.http.HttpServletResponse; 6 | 7 | import com.ctlok.web.session.crypto.Encryptor; 8 | 9 | public class StatelessSessionConfig { 10 | 11 | private final ServletContext servletContext; 12 | private final HttpServletRequest request; 13 | private final HttpServletResponse response; 14 | 15 | private final String hmacSHA1Key; 16 | private final String secretKey; 17 | private final Encryptor encryptor; 18 | private final String sessionName; 19 | 20 | private final int sessionMaxAge; 21 | private final String path; 22 | private final String domain; 23 | private final boolean httpOnly; 24 | 25 | public StatelessSessionConfig(ServletContext servletContext, 26 | HttpServletRequest request, HttpServletResponse response, 27 | String hmacSHA1Key, String secretKey, Encryptor encryptor, 28 | String sessionName, int sessionMaxAge, String path, String domain, 29 | boolean httpOnly) { 30 | super(); 31 | this.servletContext = servletContext; 32 | this.request = request; 33 | this.response = response; 34 | this.hmacSHA1Key = hmacSHA1Key; 35 | this.secretKey = secretKey; 36 | this.encryptor = encryptor; 37 | this.sessionName = sessionName; 38 | this.sessionMaxAge = sessionMaxAge; 39 | this.path = path; 40 | this.domain = domain; 41 | this.httpOnly = httpOnly; 42 | } 43 | 44 | public ServletContext getServletContext() { 45 | return servletContext; 46 | } 47 | 48 | public HttpServletRequest getRequest() { 49 | return request; 50 | } 51 | 52 | public HttpServletResponse getResponse() { 53 | return response; 54 | } 55 | 56 | public String getHmacSHA1Key() { 57 | return hmacSHA1Key; 58 | } 59 | 60 | public String getSecretKey() { 61 | return secretKey; 62 | } 63 | 64 | public Encryptor getEncryptor() { 65 | return encryptor; 66 | } 67 | 68 | public String getSessionName() { 69 | return sessionName; 70 | } 71 | 72 | public int getSessionMaxAge() { 73 | return sessionMaxAge; 74 | } 75 | 76 | public String getPath() { 77 | return path; 78 | } 79 | 80 | public String getDomain() { 81 | return domain; 82 | } 83 | 84 | public boolean isHttpOnly() { 85 | return httpOnly; 86 | } 87 | 88 | } 89 | -------------------------------------------------------------------------------- /src/main/java/com/ctlok/web/session/StatelessSessionFilter.java: -------------------------------------------------------------------------------- 1 | package com.ctlok.web.session; 2 | 3 | import java.io.IOException; 4 | import java.security.InvalidKeyException; 5 | 6 | import javax.servlet.Filter; 7 | import javax.servlet.FilterChain; 8 | import javax.servlet.FilterConfig; 9 | import javax.servlet.ServletException; 10 | import javax.servlet.ServletRequest; 11 | import javax.servlet.ServletResponse; 12 | import javax.servlet.http.Cookie; 13 | import javax.servlet.http.HttpServletRequest; 14 | import javax.servlet.http.HttpServletRequestWrapper; 15 | import javax.servlet.http.HttpServletResponse; 16 | import javax.servlet.http.HttpSession; 17 | 18 | import com.ctlok.web.session.crypto.CryptoUtils; 19 | import com.ctlok.web.session.crypto.Encryptor; 20 | 21 | /** 22 | * @author Lawrence Cheung 23 | * 24 | */ 25 | public class StatelessSessionFilter implements Filter { 26 | 27 | private static final String PARAM_HMAC_SHA1_KEY = "HMAC_SHA1_KEY"; 28 | private static final String PARAM_ENCRYPTION_SECRET_KEY = "ENCRYPTION_SECRET_KEY"; 29 | private static final String PARAM_ENCRYPTION_IMPL_CLASS = "ENCRYPTION_IMPL_CLASS"; 30 | 31 | private static final String PARAM_SESSION_NAME = "SESSION_NAME"; 32 | private static final String PARAM_SESSION_MAX_AGE = "SESSION_MAX_AGE"; 33 | private static final String PARAM_SESSION_PATH = "SESSION_PATH"; 34 | private static final String PARAM_SESSION_DOMAIN = "SESSION_DOMAIN"; 35 | private static final String PARAM_SESSION_HTTP_ONLY = "HTTP_ONLY"; 36 | 37 | private static final String DEFAULT_ENCRYPTION_IMPL_CLASS = "com.ctlok.web.session.crypto.AesEncryptor"; 38 | 39 | private static final String DEFAULT_SESSION_NAME = "SESSION"; 40 | private static final String DEFAULT_SESSION_MAX_AGE = "-1"; 41 | private static final String DEFAULT_SESSION_PATH = "/"; 42 | private static final String DEFAULT_SESSION_DOMAIN = null; 43 | private static final String DEFAULT_SESSION_HTTP_ONLY = "true"; 44 | 45 | private FilterConfig filterConfig; 46 | private String hmacSha1Key; 47 | private String secretkey; 48 | private Encryptor encryptor; 49 | 50 | private String sessionName; 51 | private int sessionMaxAge; 52 | private String sessionPath; 53 | private String sessionDomain; 54 | private boolean httpOnly; 55 | 56 | @Override 57 | public void init(FilterConfig filterConfig) throws ServletException { 58 | this.filterConfig = filterConfig; 59 | 60 | this.hmacSha1Key = filterConfig.getInitParameter(PARAM_HMAC_SHA1_KEY); 61 | if (this.hmacSha1Key == null){ 62 | throw new ServletException("HMAC_SHA1_KEY is mandatory value"); 63 | } 64 | 65 | try { 66 | CryptoUtils.hmacSha1("test", this.hmacSha1Key); 67 | } catch (InvalidKeyException e) { 68 | throw new ServletException("Invalid HMAC_SHA1_KEY", e); 69 | } 70 | 71 | this.secretkey = filterConfig.getInitParameter(PARAM_ENCRYPTION_SECRET_KEY); 72 | 73 | if (this.secretkey != null){ 74 | final String className = this.getConfig(filterConfig, PARAM_ENCRYPTION_IMPL_CLASS, DEFAULT_ENCRYPTION_IMPL_CLASS); 75 | try { 76 | this.encryptor = (Encryptor) Class.forName(className).newInstance(); 77 | 78 | if (!isValidEncryptor()){ 79 | throw new IllegalStateException("Not a valid encryptor"); 80 | } 81 | } catch (final Exception e) { 82 | throw new ServletException("Create encryptor occur problem", e); 83 | } 84 | } 85 | 86 | this.sessionName = this.getConfig(filterConfig, PARAM_SESSION_NAME, DEFAULT_SESSION_NAME); 87 | this.sessionMaxAge = Integer.valueOf(this.getConfig(filterConfig, PARAM_SESSION_MAX_AGE, DEFAULT_SESSION_MAX_AGE)); 88 | this.sessionPath = this.getConfig(filterConfig, PARAM_SESSION_PATH, DEFAULT_SESSION_PATH); 89 | this.sessionDomain = this.getConfig(filterConfig, PARAM_SESSION_DOMAIN, DEFAULT_SESSION_DOMAIN); 90 | this.httpOnly = Boolean.valueOf(this.getConfig(filterConfig, PARAM_SESSION_HTTP_ONLY, DEFAULT_SESSION_HTTP_ONLY)); 91 | } 92 | 93 | @Override 94 | public void doFilter(ServletRequest req, ServletResponse resp, 95 | FilterChain chain) throws IOException, ServletException { 96 | 97 | final HttpServletRequest request = (HttpServletRequest) req; 98 | final HttpServletResponse response = (HttpServletResponse) resp; 99 | 100 | final StatelessSessionConfig sessionConfig = createStatelessSessionConfig(request, response); 101 | final HttpServletRequest requestWrapper = new RequestWrapper(request, sessionConfig); 102 | 103 | chain.doFilter(requestWrapper, response); 104 | 105 | } 106 | 107 | protected StatelessSessionConfig createStatelessSessionConfig( 108 | final HttpServletRequest request, 109 | final HttpServletResponse response){ 110 | 111 | return new StatelessSessionConfig(this.filterConfig.getServletContext(), 112 | request, response, this.hmacSha1Key, 113 | this.secretkey, this.encryptor, 114 | this.sessionName, this.sessionMaxAge, 115 | this.sessionPath, this.sessionDomain, 116 | this.httpOnly); 117 | 118 | } 119 | 120 | @Override 121 | public void destroy() { 122 | 123 | } 124 | 125 | protected String getConfig(final FilterConfig filterConfig, 126 | final String name, final String defaultValue){ 127 | 128 | String value = filterConfig.getInitParameter(name); 129 | 130 | if (value == null){ 131 | value = defaultValue; 132 | } 133 | 134 | return value; 135 | } 136 | 137 | protected boolean isValidEncryptor() throws Exception{ 138 | final String data = "test"; 139 | final String encryptedString = this.encryptor.encrypt(this.secretkey, data); 140 | 141 | if (this.encryptor.decrypt(this.secretkey, encryptedString).equals(data)){ 142 | return true; 143 | }else{ 144 | return false; 145 | } 146 | } 147 | 148 | static class RequestWrapper extends HttpServletRequestWrapper{ 149 | 150 | private final HttpServletRequest request; 151 | private final StatelessSessionConfig sessionConfig; 152 | 153 | private HttpSession session; 154 | 155 | public RequestWrapper(final HttpServletRequest request, 156 | final StatelessSessionConfig sessionConfig) { 157 | super(request); 158 | this.request = request; 159 | this.sessionConfig = sessionConfig; 160 | 161 | if (isSessionCookieExist(sessionConfig.getSessionName())){ 162 | this.session = createStatelessSession(sessionConfig); 163 | } 164 | } 165 | 166 | @Override 167 | public HttpSession getSession(boolean create) { 168 | if (create && this.session == null){ 169 | this.session = new StatelessSession(this.sessionConfig); 170 | } 171 | return session; 172 | } 173 | 174 | @Override 175 | public HttpSession getSession() { 176 | return getSession(true); 177 | } 178 | 179 | protected HttpSession createStatelessSession(final StatelessSessionConfig sessionConfig){ 180 | return new StatelessSession(this.sessionConfig); 181 | } 182 | 183 | private boolean isSessionCookieExist(final String sessionName){ 184 | boolean result = false; 185 | 186 | if (request.getCookies() != null){ 187 | 188 | for (final Cookie cookie: request.getCookies()){ 189 | if (cookie.getName().equals(sessionName)){ 190 | result = true; 191 | break; 192 | } 193 | } 194 | 195 | } 196 | 197 | return result; 198 | } 199 | 200 | } 201 | 202 | } 203 | -------------------------------------------------------------------------------- /src/main/java/com/ctlok/web/session/crypto/AesEncryptor.java: -------------------------------------------------------------------------------- 1 | package com.ctlok.web.session.crypto; 2 | 3 | /** 4 | * @author Lawrence Cheung 5 | * 6 | */ 7 | public class AesEncryptor implements Encryptor { 8 | 9 | private static final String ALGORITHM = "AES"; 10 | 11 | @Override 12 | public String encrypt(String key, String data) throws Exception { 13 | return CryptoUtils.encrypt(ALGORITHM, key, data); 14 | } 15 | 16 | @Override 17 | public String decrypt(String key, String data) throws Exception { 18 | return CryptoUtils.decrypt(ALGORITHM, key, data); 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/com/ctlok/web/session/crypto/CryptoUtils.java: -------------------------------------------------------------------------------- 1 | package com.ctlok.web.session.crypto; 2 | 3 | import java.security.InvalidKeyException; 4 | import java.security.Key; 5 | import java.security.NoSuchAlgorithmException; 6 | 7 | import javax.crypto.BadPaddingException; 8 | import javax.crypto.Cipher; 9 | import javax.crypto.IllegalBlockSizeException; 10 | import javax.crypto.Mac; 11 | import javax.crypto.NoSuchPaddingException; 12 | import javax.crypto.spec.SecretKeySpec; 13 | 14 | import org.apache.commons.codec.DecoderException; 15 | import org.apache.commons.codec.binary.Base64; 16 | import org.apache.commons.codec.binary.Hex; 17 | 18 | /** 19 | * @author Lawrence Cheung 20 | * 21 | */ 22 | public class CryptoUtils { 23 | 24 | private static final String HMAC_SHA1_ALGORITHM = "HmacSHA1"; 25 | 26 | public static String hmacSha1(final String key, final String data) 27 | throws InvalidKeyException { 28 | String result = null; 29 | 30 | try { 31 | final Key secretKey = new SecretKeySpec(key.getBytes(), 32 | HMAC_SHA1_ALGORITHM); 33 | final Mac mac = Mac.getInstance(HMAC_SHA1_ALGORITHM); 34 | mac.init(secretKey); 35 | result = bytesToHex(mac.doFinal(data.getBytes())); 36 | } catch (final NoSuchAlgorithmException e) { 37 | throw new IllegalStateException(e); 38 | } 39 | 40 | return result; 41 | } 42 | 43 | public static String encrypt(final String algorithm, final String key, 44 | final String data) throws IllegalBlockSizeException, 45 | BadPaddingException, InvalidKeyException, NoSuchAlgorithmException, 46 | NoSuchPaddingException, DecoderException { 47 | 48 | return encryptDecrypt(algorithm, true, key, data); 49 | 50 | } 51 | 52 | public static String decrypt(final String algorithm, final String key, 53 | final String data) throws InvalidKeyException, 54 | NoSuchAlgorithmException, NoSuchPaddingException, 55 | IllegalBlockSizeException, BadPaddingException, DecoderException { 56 | 57 | return encryptDecrypt(algorithm, false, key, data); 58 | 59 | } 60 | 61 | public static String bytesToHex(final byte[] bytes) { 62 | return Hex.encodeHexString(bytes); 63 | } 64 | 65 | public static byte[] hexStringToBytes(final String hexString) throws DecoderException { 66 | return Hex.decodeHex(hexString.toCharArray()); 67 | } 68 | 69 | public static String encodeBase64(final byte[] bytes){ 70 | return Base64.encodeBase64String(bytes); 71 | } 72 | 73 | public static byte[] decodeBase64(final String str){ 74 | return Base64.decodeBase64(str); 75 | } 76 | 77 | private static String encryptDecrypt(final String algorithm, 78 | boolean encrypt, final String key, final String data) 79 | throws NoSuchAlgorithmException, NoSuchPaddingException, 80 | IllegalBlockSizeException, BadPaddingException, InvalidKeyException, DecoderException{ 81 | 82 | final int mode = encrypt ? Cipher.ENCRYPT_MODE : Cipher.DECRYPT_MODE; 83 | final Key secretKey = new SecretKeySpec(key.getBytes(), algorithm); 84 | final Cipher cipher = Cipher.getInstance(algorithm); 85 | cipher.init(mode, secretKey); 86 | 87 | return encrypt ? 88 | encodeBase64(cipher.doFinal(data.getBytes())) : 89 | new String(cipher.doFinal(decodeBase64(data))); 90 | 91 | } 92 | 93 | } 94 | -------------------------------------------------------------------------------- /src/main/java/com/ctlok/web/session/crypto/Encryptor.java: -------------------------------------------------------------------------------- 1 | package com.ctlok.web.session.crypto; 2 | 3 | /** 4 | * @author Lawrence Cheung 5 | * 6 | */ 7 | public interface Encryptor { 8 | 9 | public String encrypt(String key, String data) throws Exception; 10 | public String decrypt(String key, String data) throws Exception; 11 | 12 | } 13 | --------------------------------------------------------------------------------