Title | Spring, Redis Session 연동하기 - 중복 세션 관리 Spring, Redis Session - Duplicate Session Management | ||||||
Writer | 이지섭 | Write Date | Jul 12 2020 | Modify Date | Jan 24 2025 | View Count | 12154 |
The idea is to use Redis Session in Spring to manage redundant sessions.
Below is the Maven pom.xml dependency settings. <dependency> <groupId>org.springframework.session</groupId> <artifactId>spring-session-data-redis</artifactId> <version>2.3.3.RELEASE</version> </dependency> <dependency> <groupId>io.lettuce</groupId> <artifactId>lettuce-core</artifactId> <version>5.3.1.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.data</groupId> <artifactId>spring-data-redis</artifactId> <version>2.3.9.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.session</groupId> <artifactId>spring-session-core</artifactId> <version>2.3.3.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.data</groupId> <artifactId>spring-data-commons</artifactId> <version>2.3.9.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.data</groupId> <artifactId>spring-data-keyvalue</artifactId> <version>2.3.9.RELEASE</version> </dependency>
Below is also a necessary library in some cases, so I wrote it together. <dependency> <groupId>io.netty</groupId> <artifactId>netty-transport</artifactId> <version>4.1.51.Final</version> </dependency> <dependency> <groupId>io.netty</groupId> <artifactId>netty-handler</artifactId> <version>4.1.51.Final</version> </dependency> <dependency> <groupId>io.reactivex</groupId> <artifactId>rxjava</artifactId> <version>1.0.17</version> </dependency> <dependency> <groupId>io.projectreactor</groupId> <artifactId>reactor-core</artifactId> <version>3.4.14</version> </dependency> <dependency> <groupId>io.netty</groupId> <artifactId>netty-codec</artifactId> <version>4.0.33.Final</version> </dependency> <dependency> <groupId>io.reactivex.rxjava3</groupId> <artifactId>rxjava</artifactId> <version>3.0.9</version> </dependency>
Below is the setting in web.xml.
Then, <distributable /> is set. <!-- redis --> <!--
Below is the jboss-web.xml file used in JBoss. Located in the WEB-INF folder. <?xml version="1.0" encoding="UTF-8"?> <jboss-web> <context-root>/</context-root> <replication-config> <replication-trigger>SET</replication-trigger> <replication-granularity>SESSION</replication-granularity> </replication-config> </jboss-web>
Below is a Java class implementation of the Redis setting of Spring. @Configuration
//@EnableRedisHttpSession(maxInactiveIntervalInSeconds=32400)
@PropertySource("classpath:application.properties")
public class RedisConfigureAction {
@Value("${redis.host}")
private String redisHostName;
@Value("${redis.port}")
private int redisPort;
@Value("${redis.password}")
private String redisPwd;
// lettuce
@Bean
public RedisConnectionFactory redisConnectionFactory() {
RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration(redisHostName, redisPort);
redisStandaloneConfiguration.setPassword(redisPwd);
LettuceConnectionFactory lettuceConnectionFactory = new LettuceConnectionFactory(redisStandaloneConfiguration);
return lettuceConnectionFactory;
}
@Bean
public StringRedisTemplate redisTemplate() {
StringRedisTemplate redisTemplate = new StringRedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory());
redisTemplate.setDefaultSerializer(new StringRedisSerializer());
return redisTemplate;
}
@Bean
public static ConfigureRedisAction configureRedisAction() {
return ConfigureRedisAction.NO_OP;
}
}
Below is the application.properties file. The content will have to be personalized. In some cases, the Redis password is not used. redis.host=localhost redis.port=6379 redis.password=123456 redis.maxInactiveIntervalInSeconds=50000
Below is the contents of the spring-common-context.xml file for using the contents of applications.properties in the xml setup. Set the application properties. <?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd">
<util:properties id="application" location="classpath:application.properties" />
......
</beans>
Below is the contents of the spring-redis-context.xml file. <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:util="http://www.springframework.org/schema/util" xmlns:cache="http://www.springframework.org/schema/cache" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd">
In the web.xml file, the filter settings related to the redis session were commented out. Also in the spring-redis-context.xml file, RedisHttpSessionConfiguration settings were commented out.
Duplicate sessions could be managed by entering and reading session data directly into the redis. To make sure the session itself is all stored on redis,
1) In your java configuration, add the following
@EnableRedisHttpSession(maxInactiveIntervalInSeconds=32400)
If it's an xml configuration, uncomment the following
RedisHttpSessionConfiguration
2) In your web.xml file, add the following
<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>/*</url-pattern>
</filter-mapping>
If you don't have a web.xml file, just skip it.
Below is a service program that inputs, deletes, and reads data into the redis.
package isry.redis.service; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.TimeUnit; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.Cursor; import org.springframework.data.redis.core.HashOperations; import org.springframework.data.redis.core.ScanOptions; import org.springframework.data.redis.core.SetOperations; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.stereotype.Component; import isry.itgcms.sysmgmt.userauth.vo.UserInstAuthVO; import isry.itgcms.sysmgmt.userlogin.vo.UserDetailsVO; @Component public class RedisService3 { private static final Logger logger = LoggerFactory.getLogger(RedisService3.class); @Autowired StringRedisTemplate redisTemplate; public void insertRedisMap(String redisKey, Map<String, Object> map) { HashOperations<String, Object, Object> stringObjectObjectHashOperations = redisTemplate.opsForHash(); //Map<Integer, Integer> map = new HashMap<Integer, Integer>(); for (Map.Entry<String, Object> entry : map.entrySet()) { //System.out.println("Key = " + entry.getKey() + ", Value = " + entry.getValue()); stringObjectObjectHashOperations.put(redisKey, entry.getKey(), entry.getValue()); } //stringObjectObjectHashOperations.put(redisKey, "Hello", "rg1"); //stringObjectObjectHashOperations.put(redisKey, "Hello2", "rg2"); //stringObjectObjectHashOperations.put(redisKey, "Hello3", "rg3"); } public void processRedisLogout(String redisKey) { Set<String> keys = redisTemplate.keys(redisKey + "*"); for (String key : keys) { redisTemplate.delete(key); } } public int selectRedisLikeSessionCount(String redisKey) { int count = 0; Set<String> keys = redisTemplate.keys(redisKey + "*"); for (String key : keys) { count++; } return count; } public List<UserDetailsVO> selectPrevLoginVOList(String redisKey) { List<UserDetailsVO> list = new ArrayList<>(); if (redisKey == null || redisKey.trim().equals("")) { return null; } Set<String> keys = redisTemplate.keys(redisKey + "*"); for (String key : keys) { UserDetailsVO vo = selectRedisSession(key); list.add(vo); } return list; } public void deleteRedisLikeSession(String redisKey) { Set<String> keys = redisTemplate.keys(redisKey + "*"); for (String key : keys) { redisTemplate.delete(key); } } public void setTimeOutSecond(String key, Integer sessionTime) { redisTemplate.expire(key, sessionTime, TimeUnit.SECONDS); } @SuppressWarnings("unchecked") public UserDetailsVO selectRedisSession(String key) { HashOperations<String, Object, Object> stringObjectObjectHashOperations = redisTemplate.opsForHash(); Map<Object, Object> entries = stringObjectObjectHashOperations.entries(key); UserDetailsVO vo = new UserDetailsVO(); vo.setAge(String.valueOf(entries.get("age"))); vo.setAgencyContacts(String.valueOf(entries.get("agencyContacts"))); vo.setAuthrtSeCd(String.valueOf(entries.get("authrtSeCd"))); vo.setBirthdate(String.valueOf(entries.get("birthdate"))); vo.setCertificate(String.valueOf(entries.get("certificate"))); vo.setCtpvNm(String.valueOf(entries.get("ctpvNm"))); vo.setDeptCd(String.valueOf(entries.get("deptCd"))); vo.setDeptNm(String.valueOf(entries.get("deptNm"))); vo.setEmail(String.valueOf(entries.get("email"))); vo.setEnfsnNo(String.valueOf(entries.get("enfsnNo"))); vo.setEnfsnRoleSeCd(String.valueOf(entries.get("enfsnRoleSeCd"))); vo.setEngCtpvNm(String.valueOf(entries.get("engCtpvNm"))); vo.setGender(String.valueOf(entries.get("gender"))); vo.setGroupAuthrtSeCd(String.valueOf(entries.get("groupAuthrtSeCd"))); vo.setId(String.valueOf(entries.get("id"))); vo.setIndvIdntfcNo(String.valueOf(entries.get("indvIdntfcNo"))); vo.setInstAuthList((List<UserInstAuthVO>)entries.get("instAuthList")); vo.setInstNm(String.valueOf(entries.get("instNm"))); vo.setInstNo(Integer.parseInt(String.valueOf(entries.get("instNo")))); vo.setInstTypeSeCd(String.valueOf(entries.get("instTypeSeCd"))); vo.setIp(String.valueOf(entries.get("ip"))); vo.setLastLoginTime(String.valueOf(entries.get("lastLoginTime"))); vo.setLgnScsYn(String.valueOf(entries.get("lgnScsYn"))); vo.setManagerYn(String.valueOf(entries.get("managerYn"))); vo.setMemberType(String.valueOf(entries.get("memberType"))); vo.setMobile(String.valueOf(entries.get("mobile"))); vo.setOrgCode(Integer.parseInt(String.valueOf(entries.get("orgCode")))); vo.setOrgName(String.valueOf(entries.get("orgName"))); vo.setPass(String.valueOf(entries.get("pass"))); vo.setRgnSeCd(String.valueOf(entries.get("rgnSeCd"))); vo.setSessionId(String.valueOf(entries.get("sessionId"))); vo.setSggCd(String.valueOf(entries.get("sggCd"))); vo.setSggNm(String.valueOf(entries.get("sggNm"))); vo.setSidoNm(String.valueOf(entries.get("sidoNm"))); vo.setSigunguNm(String.valueOf(entries.get("sigunguNm"))); vo.setTopMenuNo(String.valueOf(entries.get("topMenuNo"))); vo.setUntTaskwk(String.valueOf(entries.get("untTaskwk"))); vo.setUntTaskwkSeCd(String.valueOf(entries.get("untTaskwkSeCd"))); vo.setUserInstNo(Integer.parseInt(String.valueOf(entries.get("userInstNo")))); vo.setUserName(String.valueOf(entries.get("userName"))); vo.setWrdTelno(String.valueOf(entries.get("wrdTelno"))); vo.setYngbgsPrtcrNo(String.valueOf(entries.get("yngbgsPrtcrNo"))); return vo; } public Set<String> selectKey(String key) { Set<String> set = new HashSet<>(); SetOperations<String, String> stringStringSetOperations = redisTemplate.opsForSet(); Cursor<String> cursor = stringStringSetOperations.scan(key, ScanOptions.scanOptions().match("*").build()); while (cursor.hasNext()) { //logger.debug("cursor = " + cursor.next()); set.add(cursor.next()); } return set; } }
Using the above program, you can use the redis to save and read the login session as follows.
package com.rg.util.controller; import java.util.HashMap; import java.util.Map; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; import com.rg.login.dto.CustomUserDetails; import com.rg.login.dto.UserDetailsVO; import com.rg.login.service.LoginService; import com.rg.util.RedisService3; import java.util.Optional; @Controller public class EnvironmentController { private final Logger logger = LogManager.getLogger(EnvironmentController.class); @Autowired private LoginService loginService; @Autowired private RedisService3 redisService; @RequestMapping(value = {"/getEnvironment.do", "/{lang}/getEnvironment.do"}) @ResponseBody public Map getEnvironment(@PathVariable("lang") Optional langVal, HttpServletRequest request, HttpServletResponse response) { HttpSession session = request.getSession(); UserDetailsVO vo = null; CustomUserDetails userInfo = null; String loginId = null; String loginUserName = null; try { if (SecurityContextHolder.getContext().getAuthentication() != null && SecurityContextHolder.getContext().getAuthentication().isAuthenticated()) { if (SecurityContextHolder.getContext().getAuthentication().getDetails() instanceof CustomUserDetails) { userInfo = (CustomUserDetails)SecurityContextHolder.getContext().getAuthentication().getDetails(); loginId = userInfo.getUsername(); loginUserName = loginService.getUserName(loginId); } } } catch (Exception e) { e.printStackTrace(); } String redisKey = "LOGIN||SESSION||" + loginId + "||" + session.getId(); vo = redisService.selectRedisSession(redisKey); loginId = vo == null ? "" : vo.getLoginId(); logger.info("#### remoteAddr : " + request.getRemoteAddr()); logger.info("#### loginId : " + loginId); logger.info("#### loginUserName : " + loginUserName); Map map = new HashMap<String, String>(); if (vo != null && loginId != null && !"".equals(loginId) && !"null".equals(loginId)) { map.put("loginId", loginId); map.put("loginUserName", vo.getLoginUserName()); int sessionTime = 60 * 60 * 5; redisService.setTimeOutSecond(redisKey, sessionTime); } return map; } }
@Component public class CustomLoginSuccessHandler implements AuthenticationSuccessHandler { private final Logger logger = LogManager.getLogger(CustomLoginSuccessHandler.class); @Autowired private LoginService loginService; @Autowired private RedisService3 redisService; @Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { String userId = (String)authentication.getPrincipal(); String loginId = authentication.getName(); String loginName = loginService.getUserName(loginId); HttpSession session = request.getSession(); String sId = session.getId(); session.setAttribute("loginId", loginId); session.setAttribute("loginUserName", loginName); UserDetailsVO userDetailsVO = new UserDetailsVO(); int sessionTime = 60 * 60 * 5; userDetailsVO.setLoginId(loginId); userDetailsVO.setLoginUserId1(userId); userDetailsVO.setLoginUserName(loginName); redisService.deleteRedisLikeSession("LOGIN||SESSION||" + loginId + "||*"); // save redis session String redisKey = "LOGIN||SESSION||" + loginId + "||" + sId; redisService.insertRedisMap(redisKey, new HashMap<String, Object>(userDetailsVO.getMap())); redisService.setTimeOutSecond(redisKey, sessionTime); CookieHandle.setCookie(response, "login_id", loginId); CookieHandle.setCookie(response, "session_id", sId); response.sendRedirect("/rg/index.jsp"); } }
public class CustomLogoutSuccessHandler implements LogoutSuccessHandler { @Autowired private RedisService3 redisService; @Override public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { HttpSession session = request.getSession(false); String loginId = CookieHandle.getCookie(request, "login_id"); String sessionId = CookieHandle.getCookie(request, "session_id"); redisService.processRedisLogout("LOGIN||SESSION||" + loginId + "||" + sessionId); if (session != null) { session.invalidate(); } response.setStatus(HttpServletResponse.SC_OK); response.sendRedirect("/"); } }
Below is the viewing of the redis server. ubuntu@ip-172-31-27-22:~$ redis-cli 127.0.0.1:6379> auth 123456 OK 127.0.0.1:6379> flushall OK 127.0.0.1:6379> keys * 1) "spring:session:sessions:expires:6d51ce20-5aba-4648-91bd-2b4d40f96723" 2) "spring:session:sessions:c876fd17-6f68-46ad-9607-41656c9f6911" 3) "spring:session:sessions:ad469d20-91bf-4060-8a7b-d50aeb8922d9" 4) "spring:session:expirations:1599488220000" 5) "spring:session:sessions:6d51ce20-5aba-4648-91bd-2b4d40f96723" 6) "spring:session:index:org.springframework.session.FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME:rg" 7) "spring:session:sessions:expires:c876fd17-6f68-46ad-9607-41656c9f6911" 8) "spring:session:sessions:expires:ad469d20-91bf-4060-8a7b-d50aeb8922d9" 127.0.0.1:6379> type spring:session:sessions:6d51ce20-5aba-4648-91bd-2b4d40f96723 hash 127.0.0.1:6379> hgetall spring:session:sessions:6d51ce20-5aba-4648-91bd-2b4d40f96723 1) "creationTime" 2) "\xac\xed\x00\x05sr\x00\x0ejava.lang.Long;\x8b\xe4\x90\xcc\x8f#\xdf\x02\x00\x01J\x00\x05valuexr\x00\x10java.lang.Number\x86\xac\x95\x1d\x0b\x94\xe0\x8b\x02\x00\x00xp\x00\x00\x01te\xf1\xd9N" 3) "lastAccessedTime" 4) "\xac\xed\x00\x05sr\x00\x0ejava.lang.Long;\x8b\xe4\x90\xcc\x8f#\xdf\x02\x00\x01J\x00\x05valuexr\x00\x10java.lang.Number\x86\xac\x95\x1d\x0b\x94\xe0\x8b\x02\x00\x00xp\x00\x00\x01te\xf1\xd9N" 5) "sessionAttr:org.springframework.security.web.csrf.HttpSessionCsrfTokenRepository.CSRF_TOKEN" 6) "\xac\xed\x00\x05sr\x006org.springframework.security.web.csrf.DefaultCsrfTokenZ\xef\xb7\xc8/\xa2\xfb\xd5\x02\x00\x03L\x00\nheaderNamet\x00\x12Ljava/lang/String;L\x00\rparameterNameq\x00~\x00\x01L\x00\x05tokenq\x00~\x00\x01xpt\x00\x0cX-CSRF-TOKENt\x00\x05_csrft\x00$bb6d297d-c3f0-44be-a61e-00a24c254cdd" 7) "maxInactiveInterval" 8) "\xac\xed\x00\x05sr\x00\x11java.lang.Integer\x12\xe2\xa0\xa4\xf7\x81\x878\x02\x00\x01I\x00\x05valuexr\x00\x10java.lang.Number\x86\xac\x95\x1d\x0b\x94\xe0\x8b\x02\x00\x00xp\x00\x00\xc3P" 127.0.0.1:6379> If the redis server does not have a password, the auth command is not required.
Among the attached files, the UserLoginServiceImpl.java file is a version that does not use spring security.
[ Web Page Referenced ] https://handcoding.tistory.com/137 https://m.blog.naver.com/jooda99/221460700542 https://sabarada.tistory.com/105 | |||||||
Attachment File | StringUtil.java (32,879 byte) RedisService3.java (6,176 byte) context-redis.xml (2,719 byte) context-security.xml (2,123 byte) CookieHandle.java (922 byte) CustomLogoutSuccessHandler.java (1,261 byte) EnvironmentController.java (2,878 byte) CustomLoginSuccessHandler.java (2,415 byte) CustomAuthenticationProvider.java (2,439 byte) SHA512.java (610 byte) UserLoginServiceImpl.java (85,938 byte) UserDetailsVO.java (14,781 byte) UnitSysAuthVO.java (8,712 byte) | ||||||