In an e-commerce project, when a user logs in to a single server, the user information is set to the session. The user information is obtained from the session and deleted from the session when the user logs out.
However, after setting up the Tomcat cluster, Session sharing needs to be considered, which can be realized through single sign-on solutions. There are two main methods: one is to realize it by Redis + Cookie, and the other is to solve it by using the Spring Session framework.
Redis + cookies
Single sign-on idea
User login:
- First verify the user password is correct, and return the user information;
- use
uuid
或session.getId
Generate a uniqueid(token)
And set it tocookie
, write it to the client; - Add user information (
user
Object) tojson
Format; - In order to
key=token
.Value =(JSON format of user)
Write,redis
And set the expiration time.
Log out:
- It is carried when the user requests it
cookie
, fromcookie
Get to thetoken
; - Obtained from the request
cookie
, and set the expiration time to0
, is written to the response, that is, deletedtoken
; - Again from
redis
Remove thetoken
;
Get user information:
- Carried from the request
cookie
Get to thetoken
; - According to the
token
在redis
To query the correspondinguser
The object’sjson
String; - will
json
String into auser
Object;
Redis connection pool and utility classes
Since both token and User objects are stored in Redis, there is a redis connection pool and utility class encapsulated here.
First, encapsulate a Redis connection pool and fetch jedis instances directly from the pool each time.
public class RedisPool {
private static JedisPool jedisPool;
private static String redisIP = PropertiesUtil.getProperty("redis.ip"."192.168.23.130");
private static Integer redisPort = Integer.parseInt(PropertiesUtil.getProperty("redis.port"."6379"));
// Maximum number of connections
private static Integer maxTotal = Integer.parseInt(PropertiesUtil.getProperty("redis.max.total"."20"));
// Maximum number of jedis instances in idle state
private static Integer maxIdle = Integer.parseInt(PropertiesUtil.getProperty("redis.max.idle"."10"));
// Minimum number of idle jedis instances
private static Integer minIdle = Integer.parseInt(PropertiesUtil.getProperty("redis.min.idle"."2"));
// Whether to validate when borrow a Jedis instance
private static Boolean testOnBorrow = Boolean.parseBoolean(PropertiesUtil.getProperty("redis.test.borrow"."true"));
// Whether to validate when returning a Jedis instance
private static Boolean testOnReturn = Boolean.parseBoolean(PropertiesUtil.getProperty("redis.test.return"."true"));
static {
JedisPoolConfig config = new JedisPoolConfig();
config.setMaxTotal(maxTotal);
config.setMaxIdle(maxIdle);
config.setMinIdle(minIdle);
config.setTestOnBorrow(testOnBorrow);
config.setTestOnReturn(testOnReturn);
jedisPool = new JedisPool(config, redisIP, redisPort, 1000*2);
}
public static Jedis getJedis(a) {
return jedisPool.getResource();
}
public static void returnJedis(Jedis jedis) { jedis.close(); }}Copy the code
It is then packaged into a utility class. The basic operation is to fetch the JEDis instance from the Redis connection pool, perform set/ GET /expire operations, and then put it back into the Redis connection pool.
@Slf4j
public class RedisPoolUtil {
// exTime is in seconds
public static Long expire(String key, int exTime) {
Jedis jedis = null;
Long result = null;
try {
jedis = RedisPool.getJedis();
result = jedis.expire(key, exTime);
} catch (Exception e) {
log.error("expire key:{}, error", key, e);
}
RedisPool.returnJedis(jedis);
return result;
}
public static Long del(String key) {
Jedis jedis = null;
Long result = null;
try {
jedis = RedisPool.getJedis();
result = jedis.del(key);
} catch (Exception e) {
log.error("del key:{}, error", key, e);
}
RedisPool.returnJedis(jedis);
return result;
}
public static String get(String key) {
Jedis jedis = null;
String result = null;
try {
jedis = RedisPool.getJedis();
result = jedis.get(key);
} catch (Exception e) {
log.error("get key:{}, error", key, e);
}
RedisPool.returnJedis(jedis);
return result;
}
public static String set(String key, String value) {
Jedis jedis = null;
String result = null;
try {
jedis = RedisPool.getJedis();
result = jedis.set(key, value);
} catch (Exception e) {
log.error("set key:{}, value:{}, error", key, value, e);
}
RedisPool.returnJedis(jedis);
return result;
}
// exTime is in seconds
public static String setEx(String key, String value, int exTime) {
Jedis jedis = null;
String result = null;
try {
jedis = RedisPool.getJedis();
result = jedis.setex(key, exTime, value);
} catch (Exception e) {
log.error("setex key:{}, value:{}, error", key, value, e);
}
RedisPool.returnJedis(jedis);
returnresult; }}Copy the code
JsonUtil tools
To store the User object in Redis, you need to convert it to JSON format. To retrieve the user object from Redis, you need to convert it to user object. A JSON utility class is encapsulated here.
The JsonUtil utility classes mainly use ObjectMapper classes.
bean
Class is converted toString
Type, usingwriterValueAsString
Methods.String
Type conversion tobean
Class, the use ofreadValue
Methods.
@Slf4j
public class JsonUtil {
private static ObjectMapper objectMapper = new ObjectMapper();
static {
// All fields are included when serializing
objectMapper.setSerializationInclusion(JsonSerialize.Inclusion.ALWAYS);
// Cancel the default from DATES to TIMESTAMPS
objectMapper.configure(SerializationConfig.Feature.WRITE_DATES_AS_TIMESTAMPS, false);
// Ignore the empty bean to JSON error
objectMapper.configure(SerializationConfig.Feature.FAIL_ON_EMPTY_BEANS, false);
// All dates have the same style
objectMapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
// Ignore the presence in json strings, there is no corresponding property in Java objects
objectMapper.configure(DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES, false);
}
public static <T> String obj2Str(T obj) {
if (obj == null) { return null; }
try {
return obj instanceof String ? (String) obj : objectMapper.writeValueAsString(obj);
} catch (Exception e) {
log.warn("Parse Object to String error", e);
return null; }}public static <T> String obj2StrPretty(T obj) {
if (obj == null) { return null; }
try {
return obj instanceof String ? (String) obj :
objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(obj);
} catch (Exception e) {
log.warn("Parse Object to String error", e);
return null; }}public static <T> T str2Obj(String str, Class<T> clazz) {
if (StringUtils.isEmpty(str) || clazz == null) {
return null;
}
try {
return clazz.equals(String.class) ? (T)str : objectMapper.readValue(str, clazz);
} catch (Exception e) {
log.warn("Parse String to Object error", e);
return null; }}public static <T> T str2Obj(String str, TypeReference<T> typeReference) {
if (StringUtils.isEmpty(str) || typeReference == null) {
return null;
}
try {
return typeReference.getType().equals(String.class) ? (T)str : objectMapper.readValue(str, typeReference);
} catch (Exception e) {
log.warn("Parse String to Object error", e);
return null; }}public static <T> T str2Obj(String str, Class
collectionClass, Class
elementClass) {
JavaType javaType = objectMapper.getTypeFactory().constructParametricType(collectionClass, elementClass);
try {
return objectMapper.readValue(str, javaType);
} catch (Exception e) {
log.warn("Parse String to Object error", e);
return null; }}}Copy the code
CookieUtil tools
Upon login, you need to set the token in the cookie and return it to the client. Upon exit, you need to read the token from the cookie carried in the request. After setting the expiration time, you need to set the token in the cookie and return it to the client. You need to read the token from the cookie carried in the request, and query in Redis to obtain the User object. Here, I’m also encapsulating a cookie utility class.
In CookieUtil:
readLoginToken
The methods are mainly fromrequest
readCookie
;writeLoginToken
Method Main SettingsCookie
Object to theresponse
;delLoginToken
The methods are mainly fromrequest
Reads theCookie
themaxAge
Set to0
And then add toresponse
;
@Slf4j
public class CookieUtil {
private static final String COOKIE_DOMAIN = ".happymmall.com";
private static final String COOKIE_NAME = "mmall_login_token";
public static String readLoginToken(HttpServletRequest request) {
Cookie[] cookies = request.getCookies();
if(cookies ! =null) {
for (Cookie cookie : cookies) {
log.info("read cookieName:{}, cookieValue:{}", cookie.getName(), cookie.getValue());
if (StringUtils.equals(COOKIE_NAME, cookie.getName())) {
log.info("return cookieName:{}, cookieValue:{}", cookie.getName(), cookie.getValue());
returncookie.getValue(); }}}return null;
}
public static void writeLoginToken(HttpServletResponse response, String token) {
Cookie cookie = new Cookie(COOKIE_NAME, token);
cookie.setDomain(COOKIE_DOMAIN);
cookie.setPath("/");
// Prevent scripting attacks
cookie.setHttpOnly(true);
// The unit is seconds. If the value is -1, it is permanent.
// If MaxAge is not set, cookies are not written to hard disk, but in memory, and are only valid for the current page
cookie.setMaxAge(60 * 60 * 24 * 365);
log.info("write cookieName:{}, cookieValue:{}", cookie.getName(), cookie.getValue());
response.addCookie(cookie);
}
public static void delLoginToken(HttpServletRequest request, HttpServletResponse response) {
Cookie[] cookies = request.getCookies();
if(cookies ! =null) {
for (Cookie cookie : cookies) {
if (StringUtils.equals(COOKIE_NAME, cookie.getName())) {
cookie.setDomain(COOKIE_DOMAIN);
cookie.setPath("/");
// If maxAge is set to 0, it will be deleted
cookie.setMaxAge(0);
log.info("del cookieName:{}, cookieValue:{}", cookie.getName(), cookie.getValue());
response.addCookie(cookie);
return;
}
}
}
}
}
Copy the code
Specific business
After verifying the password during login:
CookieUtil.writeLoginToken(response, session.getId());
RedisShardedPoolUtil.setEx(session.getId(), JsonUtil.obj2Str(serverResponse.getData()), Const.RedisCacheExtime.REDIS_SESSION_EXTIME);
Copy the code
When logging out:
String loginToken = CookieUtil.readLoginToken(request);
CookieUtil.delLoginToken(request, response);
RedisShardedPoolUtil.del(loginToken);
Copy the code
When obtaining user information:
String loginToken = CookieUtil.readLoginToken(request);
if (StringUtils.isEmpty(loginToken)) {
return ServerResponse.createByErrorMessage("User is not logged in, cannot obtain current user information");
}
String userJsonStr = RedisShardedPoolUtil.get(loginToken);
User user = JsonUtil.str2Obj(userJsonStr, User.class);
Copy the code
SessionExpireFilter filter
In addition, after a user logs in, the Session validity period needs to be reset after each operation. You can do this using filters.
public class SessionExpireFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {}@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
String loginToken = CookieUtil.readLoginToken(httpServletRequest);
if (StringUtils.isNotEmpty(loginToken)) {
String userJsonStr = RedisShardedPoolUtil.get(loginToken);
User user = JsonUtil.str2Obj(userJsonStr, User.class);
if(user ! =null) {
RedisShardedPoolUtil.expire(loginToken, Const.RedisCacheExtime.REDIS_SESSION_EXTIME);
}
}
filterChain.doFilter(servletRequest, servletResponse);
}
@Override
public void destroy(a) {}}Copy the code
You also need to configure it in the web.xml file:
<filter>
<filter-name>sessionExpireFilter</filter-name>
<filter-class>com.mmall.controller.common.SessionExpireFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>sessionExpireFilter</filter-name>
<url-pattern>*.do</url-pattern>
</filter-mapping>
Copy the code
The pitfalls of this approach
redis + cookie
The single sign-on method is more intrusive to the code.- The client must be enabled
cookie
Some browsers don’t support itcookie
; Cookie
Set up thedomain
The domain name mode of the server must be unified.
The Spring Session implementation
Spring Session is one of the Spring projects that provides a solution for creating and managing Server HTTPSessions. By default, external Redis is used to store Session data, so as to solve the problem of Session sharing.
Spring Sessions can solve the Session sharing problem noninvasively, but they cannot be sharded.
Spring Session project integration
1. Introduce Spring Session POM
<dependency> <groupId>org.springframework.session</groupId> <artifactId>spring-session-data-redis</artifactId> < version > 1.2.2. RELEASE < / version > < / dependency >Copy the code
2. Configure DelegatingFilterProxy
<filter>
<filter-name>springSessionRepositoryFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>springSessionRepositoryFilter</filter-name>
<url-pattern>*.do</url-pattern>
</filter-mapping>
Copy the code
3, configuration RedisHttpSessionConfiguration
<bean id="redisHttpSessionConfiguration" class="org.springframework.session.data.redis.config.annotation.web.http.RedisHttpSessionConfiguration">
<property name="maxInactiveIntervalInSeconds" value="1800" />
</bean>
Copy the code
4. Configure JedisPoolConfig
<bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig">
<property name="maxTotal" value="20" />
</bean>
Copy the code
5. Configure JedisSessionFactory
<bean id="jedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory" >
<property name="hostName" value="192.168.23.130" />
<property name="port" value="6379" />
<property name="database" value="0" />
<property name="poolConfig" ref="jedisPoolConfig" />
</bean>
Copy the code
6. Configure DefaultCookieSerializer
<bean id="defaultCookieSerializer" class="org.springframework.session.web.http.DefaultCookieSerializer">
<property name="cookieName" value="SESSION_NAME" />
<property name="domainName" value=".happymmall.com" />
<property name="useHttpOnlyCookie" value="true" />
<property name="cookiePath" value="/" />
<property name="cookieMaxAge" value="31536000" />
</bean>
Copy the code
Business code
User login:
session.setAttribute(Const.CURRENT_USER, response.getData());
Copy the code
When logging out:
session.removeAttribute(Const.CURRENT_USER);
Copy the code
When obtaining user information:
User user = (User) session.getAttribute(Const.CURRENT_USER);
Copy the code