diff --git a/pear-common/pom.xml b/pear-common/pom.xml
index 635dd41b3fcfbca10ac508d60fd30342fe54d6d8..d6b159167cc09bab7f60aa87d5eb606f689bd0bb 100644
--- a/pear-common/pom.xml
+++ b/pear-common/pom.xml
@@ -68,10 +68,21 @@
commons-io
commons-io
+
+
+
+ org.apache.commons
+ commons-pool2
+
+
com.fasterxml.jackson.datatype
jackson-datatype-jsr310
- 2.9.9
+
+
+
+ org.springframework.boot
+ spring-boot-starter-data-redis
diff --git a/pear-common/src/main/java/com/pearadmin/common/config/RedisConfig.java b/pear-common/src/main/java/com/pearadmin/common/config/RedisConfig.java
new file mode 100644
index 0000000000000000000000000000000000000000..ff88a3ca3a0e564003e1b63ed37fa47f19e43c37
--- /dev/null
+++ b/pear-common/src/main/java/com/pearadmin/common/config/RedisConfig.java
@@ -0,0 +1,30 @@
+package com.pearadmin.common.config;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
+import org.springframework.data.redis.serializer.StringRedisSerializer;
+
+import java.io.Serializable;
+
+/**
+ * Describe: redis 序列化配置
+ * Author: John Ming
+ * CreateTime: 2020/11/22
+ * */
+@Configuration
+public class RedisConfig {
+
+ @Bean
+ public RedisTemplate
+ redisTemplate(LettuceConnectionFactory connectionFactory) {
+ RedisTemplate redisTemplate = new RedisTemplate<>();
+ redisTemplate.setKeySerializer(new StringRedisSerializer());
+ redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
+ redisTemplate.setConnectionFactory(connectionFactory);
+ return redisTemplate;
+ }
+
+}
diff --git a/pear-entrance/src/main/java/com/pearadmin/security/SecurityConfig.java b/pear-entrance/src/main/java/com/pearadmin/security/SecurityConfig.java
index e8089ffeb202539d62c36af73ecb588ccb2c1da5..44d4de0e7cc2a9b7c077defd182bbfd2569ed19b 100644
--- a/pear-entrance/src/main/java/com/pearadmin/security/SecurityConfig.java
+++ b/pear-entrance/src/main/java/com/pearadmin/security/SecurityConfig.java
@@ -3,6 +3,7 @@ package com.pearadmin.security;
import com.pearadmin.common.config.proprety.SecurityProperty;
import com.pearadmin.security.domain.SecurityUserDetailsService;
import com.pearadmin.security.process.*;
+import com.pearadmin.security.support.RedisTokenRepositor;
import com.pearadmin.security.support.SecurityPermissionEvaluator;
import com.pearadmin.security.support.SecurityVerifyCodeFilter;
import org.springframework.beans.factory.annotation.Autowired;
@@ -19,8 +20,6 @@ import org.springframework.security.core.session.SessionRegistryImpl;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.access.expression.DefaultWebSecurityExpressionHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
-import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl;
-import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
import javax.annotation.Resource;
import javax.sql.DataSource;
@@ -62,8 +61,12 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private SecurityUserDetailsService securityUserDetailsService; //实现userservice
- @Resource
- private DataSource dataSource; // 数据源
+
+ @Autowired
+ private RedisTokenRepositor redisTokenRepositor;
+
+// @Resource
+// private DataSource dataSource; // 数据源
/**
* Describe: 自定义权限注解实现
@@ -92,18 +95,24 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter {
}
- /**
- * 持久化token
- *
- * Security中,默认是使用PersistentTokenRepository的子类InMemoryTokenRepositoryImpl,将token放在内存中
- * 如果使用JdbcTokenRepositoryImpl,会创建表persistent_logins,将token持久化到数据库
- */
+// /**
+// * 持久化token
+// *
+// * Security中,默认是使用PersistentTokenRepository的子类InMemoryTokenRepositoryImpl,将token放在内存中
+// * 如果使用JdbcTokenRepositoryImpl,会创建表persistent_logins,将token持久化到数据库
+// */
+// @Bean
+// public PersistentTokenRepository persistentTokenRepository() {
+// JdbcTokenRepositoryImpl tokenRepository = new JdbcTokenRepositoryImpl();
+// tokenRepository.setDataSource(dataSource); // 设置数据源
+//// tokenRepository.setCreateTableOnStartup(true); // 启动创建表,创建成功后注释掉
+// return tokenRepository;
+// }
+
+ /** 注册SessionRegistry*/
@Bean
- public PersistentTokenRepository persistentTokenRepository() {
- JdbcTokenRepositoryImpl tokenRepository = new JdbcTokenRepositoryImpl();
- tokenRepository.setDataSource(dataSource); // 设置数据源
-// tokenRepository.setCreateTableOnStartup(true); // 启动创建表,创建成功后注释掉
- return tokenRepository;
+ public SessionRegistry sessionRegistry(){
+ return new SessionRegistryImpl();
}
@@ -135,12 +144,13 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter {
.logoutUrl("/logout")
//配置用户登出自定义处理类
.logoutSuccessHandler(securityAccessLogoutHander)
+ .deleteCookies("remember-me") //退出登录删除 cookie缓存的rememberMe
.and()
//配置没有权限自定义处理类
.exceptionHandling().accessDeniedHandler(securityAccessDeniedHander)
.and()
.rememberMe().rememberMeParameter("remember-me")
- .tokenRepository(persistentTokenRepository())
+ .tokenRepository(redisTokenRepositor)
.key(securityProperty.getRememberKey())
.and()
// 防止iframe 造成跨域
@@ -151,19 +161,12 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter {
// 取消跨站请求伪造防护
.csrf().disable()
.sessionManagement()
- .invalidSessionUrl("/login") //无效session要跳转到login
+ .invalidSessionUrl("/login?kickout=1") //无效session要跳转到login
.maximumSessions(1)//同时登陆多个只保留一个
.expiredUrl("/login")//过期session跳转
- .sessionRegistry(sessionRegistry())
-
-;
+ .sessionRegistry(sessionRegistry());
http.headers().frameOptions().disable();
}
- /** 注册SessionRegistry*/
- @Bean
- public SessionRegistry sessionRegistry(){
- return new SessionRegistryImpl();
- }
}
diff --git a/pear-entrance/src/main/java/com/pearadmin/security/support/RedisTokenRepositor.java b/pear-entrance/src/main/java/com/pearadmin/security/support/RedisTokenRepositor.java
new file mode 100644
index 0000000000000000000000000000000000000000..86b26217428d22b90a5e1d19660d6cce6d23aa7f
--- /dev/null
+++ b/pear-entrance/src/main/java/com/pearadmin/security/support/RedisTokenRepositor.java
@@ -0,0 +1,100 @@
+package com.pearadmin.security.support;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.redis.core.HashOperations;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.data.redis.core.StringRedisTemplate;
+import org.springframework.security.web.authentication.rememberme.PersistentRememberMeToken;
+import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
+import org.springframework.stereotype.Component;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Describe: redis remember me 持久化到redis
+ * Author: John Ming
+ * CreateTime: 2020/11/22
+ * */
+@Component
+public class RedisTokenRepositor implements PersistentTokenRepository {
+
+ private final static String USERNAME_KEY = "spring:security:rememberMe:username_key:";
+ private final static String SERIES_KEY = "spring:security:rememberMe:series_key:";
+
+ @Autowired
+ RedisTemplate redisTemplate;
+
+ @Autowired
+ StringRedisTemplate stringRedisTemplate;
+ @Override
+ public void createNewToken(PersistentRememberMeToken persistentRememberMeToken) {
+ String series = persistentRememberMeToken.getSeries();
+ String key = generateKey(series,SERIES_KEY);
+ String usernameKey = generateKey(persistentRememberMeToken.getUsername(),USERNAME_KEY);
+ //用户只要采用账户密码重新登录,那么为了安全就有必要清除之前的token信息。deleteIfPresent方法通过
+ //username查找到用户对应的series,然后删除旧的token信息。
+ deleteIfPresent(usernameKey);
+ HashMap hashMap = new HashMap<>();
+ hashMap.put("username",persistentRememberMeToken.getUsername());
+ hashMap.put("token",persistentRememberMeToken.getTokenValue());
+ hashMap.put("date",String.valueOf(persistentRememberMeToken.getDate().getTime()));
+ HashOperations hashOperations = redisTemplate.opsForHash();
+ hashOperations.putAll(key,hashMap);
+ redisTemplate.expire(key,1, TimeUnit.DAYS);//设置token保存期限
+ stringRedisTemplate.opsForValue().set(usernameKey,series);
+ redisTemplate.expire(usernameKey,1, TimeUnit.DAYS);
+ }
+
+ @Override
+ public void updateToken(String s, String s1, Date date) {
+ String key = generateKey(s,SERIES_KEY);
+ if(redisTemplate.hasKey(key))
+ redisTemplate.opsForHash().put(key,"token",s1);
+ }
+
+ @Override
+ public PersistentRememberMeToken getTokenForSeries(String s) {
+ String key = generateKey(s,SERIES_KEY);
+ List hashKeys = new ArrayList<>();
+ hashKeys.add("username");
+ hashKeys.add("token");
+ hashKeys.add("date");
+ List hashValues = redisTemplate.opsForHash().multiGet(key, hashKeys);
+ String username = hashValues.get(0);
+ String tokenValue = hashValues.get(1);
+ String date = hashValues.get(2);
+ if (null == username || null == tokenValue || null == date) {
+ return null;
+ }
+ Long timestamp = Long.valueOf(date);
+ Date time = new Date(timestamp);
+ return new PersistentRememberMeToken(username, s, tokenValue, time);
+ }
+
+ @Override
+ public void removeUserTokens(String s) {
+ //rememberMeService实现类中调用这个方法传入的参数是username,因此我们必须通过username查找到
+ //对应的series,然后再通过series查找到对应的token信息再删除。
+ String key = generateKey(s,USERNAME_KEY);
+ deleteIfPresent(key);
+ }
+
+ private void deleteIfPresent(String key){
+ //删除token时应该同时删除token信息,以及保存了对应的username与series对照数据。
+ if(redisTemplate.hasKey(key)){
+ String series = generateKey(stringRedisTemplate.opsForValue().get(key),SERIES_KEY);
+ if(series!=null && redisTemplate.hasKey(series)){
+ redisTemplate.delete(series);
+ redisTemplate.delete(key);
+ }
+ }
+ }
+ private String generateKey(String series,String prefix) {
+ return prefix + series;
+ }
+
+}
diff --git a/pear-entrance/src/main/resources/application-datasource.yml b/pear-entrance/src/main/resources/application-datasource.yml
index 251134b8cf9b1c3bbcdf2d890bbabd9d6c39b205..f57f93696c5a5131781c8050042a2ec88fb5aef9 100644
--- a/pear-entrance/src/main/resources/application-datasource.yml
+++ b/pear-entrance/src/main/resources/application-datasource.yml
@@ -1,5 +1,21 @@
spring:
+ redis:
+ host: 192.168.0.110
+ port: 6379
+ password:
+ database: 0
+ lettuce:
+ pool:
+ # 连接池中的最大空闲连接
+ max-active: 20
+ # 连接池最大阻塞等待时间(使用负值表示没有限制)
+ max-wait: -1
+ # 连接池中的最大空闲连接
+ max-idle: 4
+ # 连接池中的最小空闲连接
+ min-idle: 0
+ timeout: 1000
datasource:
dynamic:
primary: master #设置默认的数据源或者数据源组,默认值即为master
@@ -48,4 +64,4 @@ spring:
merge-sql: true
wall:
config:
- multi-statement-allow: true
\ No newline at end of file
+ multi-statement-allow: true
diff --git a/pear-entrance/src/main/resources/templates/login.html b/pear-entrance/src/main/resources/templates/login.html
index ed93a388bc660aff5fb50c7fd85eb0a721a27e84..ce21dc5ac6bdddf39cc9d3ec88347b86b588dcef 100644
--- a/pear-entrance/src/main/resources/templates/login.html
+++ b/pear-entrance/src/main/resources/templates/login.html
@@ -75,11 +75,37 @@
document.getElementById("captchaImage").src="/system/captcha/generate?"+Math.random();
},30000)
+
+ validateKickout();
+
})
+
+