Ruoyi若依前后端一体项目整合cas单点登录

一、内容提要

本文档只是针对Ruoyi前后端不分离项目与cas的集成,默认cas服务器已经安装部署好。好了,话不多说,直接开干

二、在ruoyi-framework子工程中引入cas与shiro集成的pom依赖文件

** 小提示:
Shiro使用Pac4j集成CAS认证,原来shiro自带的shiro-cas已不推荐使用,在shiro-1.3.x以后的版本中,shiro-cas包里面的所有类都被标识为deprecated
1.3.x版本后。shiro接入pac4j,pac4j是一个支持多种支持多种协议的身份认证的Java客户端

<dependency>
<groupId>org.pac4j</groupId>
<artifactId>pac4j-cas</artifactId>
<version>3.0.2</version>
</dependency>
<dependency>
<groupId>io.buji</groupId>
<artifactId>buji-pac4j</artifactId>
<version>4.0.0</version>
<exclusions>
<exclusion>
<artifactId>shiro-web</artifactId>
<groupId>org.apache.shiro</groupId>
</exclusion>
</exclusions>
</dependency>

三、修改ShiroConfig类,可以和原来项目的进行对比

1
package com.ruoyi.framework.config; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.Map; import javax.servlet.Filter; import com.ruoyi.framework.shiro.realm.CasRealm; import io.buji.pac4j.filter.CallbackFilter; import io.buji.pac4j.filter.SecurityFilter; import io.buji.pac4j.subject.Pac4jSubjectFactory; import org.apache.commons.io.IOUtils; import org.apache.shiro.cache.ehcache.EhCacheManager; import org.apache.shiro.codec.Base64; import org.apache.shiro.config.ConfigurationException; import org.apache.shiro.io.ResourceUtils; import org.apache.shiro.mgt.SecurityManager; import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor; import org.apache.shiro.spring.web.ShiroFilterFactoryBean; import org.apache.shiro.web.mgt.CookieRememberMeManager; import org.apache.shiro.web.mgt.DefaultWebSecurityManager; import org.apache.shiro.web.servlet.SimpleCookie; import org.pac4j.core.config.Config; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import com.ruoyi.common.utils.StringUtils; import com.ruoyi.common.utils.spring.SpringUtils; import com.ruoyi.framework.shiro.realm.UserRealm; import com.ruoyi.framework.shiro.session.OnlineSessionDAO; import com.ruoyi.framework.shiro.session.OnlineSessionFactory; import io.buji.pac4j.filter.LogoutFilter; //import com.ruoyi.framework.shiro.web.filter.LogoutFilter; import com.ruoyi.framework.shiro.web.filter.captcha.CaptchaValidateFilter; import com.ruoyi.framework.shiro.web.filter.kickout.KickoutSessionFilter; import com.ruoyi.framework.shiro.web.filter.online.OnlineSessionFilter; import com.ruoyi.framework.shiro.web.filter.sync.SyncOnlineSessionFilter; import com.ruoyi.framework.shiro.web.session.OnlineWebSessionManager; import com.ruoyi.framework.shiro.web.session.SpringSessionValidationScheduler; import at.pollux.thymeleaf.shiro.dialect.ShiroDialect; /** * 权限配置加载 * * @author ruoyi */ @Configuration public class ShiroConfig { public static final String PREMISSION_STRING = "perms[\"{0}\"]"; /** * Session超时时间,单位为毫秒(默认30分钟) */ @Value("${shiro.session.expireTime}") private int expireTime; /** * 相隔多久检查一次session的有效性,单位毫秒,默认就是10分钟 */ @Value("${shiro.session.validationInterval}") private int validationInterval; /** * 同一个用户最大会话数 */ @Value("${shiro.session.maxSession}") private int maxSession; /** * 踢出之前登录的/之后登录的用户,默认踢出之前登录的用户 */ @Value("${shiro.session.kickoutAfter}") private boolean kickoutAfter; /** * 验证码开关 */ @Value("${shiro.user.captchaEnabled}") private boolean captchaEnabled; /** * 验证码类型 */ @Value("${shiro.user.captchaType}") private String captchaType; /** * 设置Cookie的域名 */ @Value("${shiro.cookie.domain}") private String domain; /** * 设置cookie的有效访问路径 */ @Value("${shiro.cookie.path}") private String path; /** * 设置HttpOnly属性 */ @Value("${shiro.cookie.httpOnly}") private boolean httpOnly; /** * 设置Cookie的过期时间,秒为单位 */ @Value("${shiro.cookie.maxAge}") private int maxAge; /** * 登录地址 */ @Value("${shiro.user.loginUrl}") private String loginUrl; /** * 权限认证失败地址 */ @Value("${shiro.user.unauthorizedUrl}") private String unauthorizedUrl; /* liupsh cas begin*/ /** 项目工程路径 */ @Value("${cas.project.url}") private String projectUrl; /** 项目cas服务路径 */ @Value("${cas.server.url}") private String casServerUrl; /** 客户端名称 */ @Value("${cas.client-name}") private String clientName; /* liupsh cas end*/ /** * 缓存管理器 使用Ehcache实现 */ @Bean public EhCacheManager getEhCacheManager() { net.sf.ehcache.CacheManager cacheManager = net.sf.ehcache.CacheManager.getCacheManager("ruoyi"); EhCacheManager em = new EhCacheManager(); if (StringUtils.isNull(cacheManager)) { em.setCacheManager(new net.sf.ehcache.CacheManager(getCacheManagerConfigFileInputStream())); return em; } else { em.setCacheManager(cacheManager); return em; } } /** * 返回配置文件流 避免ehcache配置文件一直被占用,无法完全销毁项目重新部署 */ protected InputStream getCacheManagerConfigFileInputStream() { String configFile = "classpath:ehcache/ehcache-shiro.xml"; InputStream inputStream = null; try { inputStream = ResourceUtils.getInputStreamForPath(configFile); byte[] b = IOUtils.toByteArray(inputStream); InputStream in = new ByteArrayInputStream(b); return in; } catch (IOException e) { throw new ConfigurationException( "Unable to obtain input stream for cacheManagerConfigFile [" + configFile + "]", e); } finally { IOUtils.closeQuietly(inputStream); } } /** * 自定义Realm */ @Bean public UserRealm userRealm(EhCacheManager cacheManager) { UserRealm userRealm = new UserRealm(); userRealm.setCacheManager(cacheManager); return userRealm; } /** * 自定义sessionDAO会话 */ @Bean public OnlineSessionDAO sessionDAO() { OnlineSessionDAO sessionDAO = new OnlineSessionDAO(); return sessionDAO; } /** * 自定义sessionFactory会话 */ @Bean public OnlineSessionFactory sessionFactory() { OnlineSessionFactory sessionFactory = new OnlineSessionFactory(); return sessionFactory; } /** * 会话管理器 */ @Bean public OnlineWebSessionManager sessionManager() { OnlineWebSessionManager manager = new OnlineWebSessionManager(); // 加入缓存管理器 manager.setCacheManager(getEhCacheManager()); // 删除过期的session manager.setDeleteInvalidSessions(true); // 设置全局session超时时间 manager.setGlobalSessionTimeout(expireTime * 60 * 1000); // 去掉 JSESSIONID manager.setSessionIdUrlRewritingEnabled(false); // 定义要使用的无效的Session定时调度器 manager.setSessionValidationScheduler(SpringUtils.getBean(SpringSessionValidationScheduler.class)); // 是否定时检查session manager.setSessionValidationSchedulerEnabled(true); // 自定义SessionDao manager.setSessionDAO(sessionDAO()); // 自定义sessionFactory manager.setSessionFactory(sessionFactory()); return manager; } /** * 安全管理器 */ @Bean public SecurityManager securityManager(CasRealm casRealm) { DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); // 设置realm. securityManager.setRealm(casRealm); // 记住我 securityManager.setRememberMeManager(rememberMeManager()); // 注入缓存管理器; securityManager.setCacheManager(getEhCacheManager()); // session管理器 securityManager.setSessionManager(sessionManager()); securityManager.setSubjectFactory(subjectFactory()); return securityManager; } /** * 使用 pac4j 的 subjectFactory * @return */ @Bean public Pac4jSubjectFactory subjectFactory(){ return new Pac4jSubjectFactory(); } @Bean public CasRealm casRealm(){ CasRealm realm = new CasRealm(); // 使用自定义的realm realm.setClientName(clientName); realm.setCachingEnabled(false); //暂时不使用缓存 realm.setAuthenticationCachingEnabled(false); realm.setAuthorizationCachingEnabled(false); //realm.setAuthenticationCacheName("authenticationCache"); //realm.setAuthorizationCacheName("authorizationCache"); return realm; } /** * 加载shiroFilter权限控制规则(从数据库读取然后配置) * @param shiroFilterFactoryBean */ private void loadShiroFilterChain(ShiroFilterFactoryBean shiroFilterFactoryBean){ /*下面这些规则配置最好配置到配置文件中 */ Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>(); // 对静态资源设置匿名访问 filterChainDefinitionMap.put("/favicon.ico**", "anon"); filterChainDefinitionMap.put("/ruoyi.png**", "anon"); filterChainDefinitionMap.put("/css/**", "anon"); filterChainDefinitionMap.put("/docs/**", "anon"); filterChainDefinitionMap.put("/fonts/**", "anon"); filterChainDefinitionMap.put("/img/**", "anon"); filterChainDefinitionMap.put("/ajax/**", "anon"); filterChainDefinitionMap.put("/js/**", "anon"); filterChainDefinitionMap.put("/ruoyi/**", "anon"); filterChainDefinitionMap.put("/druid/**", "anon"); filterChainDefinitionMap.put("/captcha/captchaImage**", "anon"); //内部接口调用 filterChainDefinitionMap.put("/hiapi/**", "anon"); filterChainDefinitionMap.put("/", "securityFilter"); filterChainDefinitionMap.put("/application/**", "securityFilter"); filterChainDefinitionMap.put("/index", "securityFilter"); filterChainDefinitionMap.put("/callback", "callbackFilter"); filterChainDefinitionMap.put("/logout", "logout"); // filterChainDefinitionMap.put("/**","anon"); shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap); } /** * Shiro过滤器配置 */ @Bean public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager , Config config) { ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); // Shiro的核心安全接口,这个属性是必须的 shiroFilterFactoryBean.setSecurityManager(securityManager); // 添加casFilter到shiroFilter中 loadShiroFilterChain(shiroFilterFactoryBean); Map<String, Filter> filters = new HashMap<>(3); //cas 资源认证拦截器 SecurityFilter securityFilter = new SecurityFilter(); securityFilter.setConfig(config); securityFilter.setClients(clientName); filters.put("securityFilter", securityFilter); //cas 认证后回调拦截器 CallbackFilter callbackFilter = new CallbackFilter(); callbackFilter.setConfig(config); callbackFilter.setDefaultUrl(projectUrl); filters.put("callbackFilter", callbackFilter); // 注销 拦截器 LogoutFilter logoutFilter = new LogoutFilter(); logoutFilter.setConfig(config); logoutFilter.setCentralLogout(true); logoutFilter.setLocalLogout(true); logoutFilter.setDefaultUrl(projectUrl + "/callback?client_name=" + clientName); filters.put("logout",logoutFilter); shiroFilterFactoryBean.setFilters(filters); return shiroFilterFactoryBean; } /** * 自定义在线用户处理过滤器 */ @Bean public OnlineSessionFilter onlineSessionFilter() { OnlineSessionFilter onlineSessionFilter = new OnlineSessionFilter(); onlineSessionFilter.setLoginUrl(loginUrl); return onlineSessionFilter; } /** * 自定义在线用户同步过滤器 */ @Bean public SyncOnlineSessionFilter syncOnlineSessionFilter() { SyncOnlineSessionFilter syncOnlineSessionFilter = new SyncOnlineSessionFilter(); return syncOnlineSessionFilter; } /** * 自定义验证码过滤器 */ @Bean public CaptchaValidateFilter captchaValidateFilter() { CaptchaValidateFilter captchaValidateFilter = new CaptchaValidateFilter(); captchaValidateFilter.setCaptchaEnabled(captchaEnabled); captchaValidateFilter.setCaptchaType(captchaType); return captchaValidateFilter; } /** * cookie 属性设置 */ public SimpleCookie rememberMeCookie() { SimpleCookie cookie = new SimpleCookie("rememberMe"); cookie.setDomain(domain); cookie.setPath(path); cookie.setHttpOnly(httpOnly); cookie.setMaxAge(maxAge * 24 * 60 * 60); return cookie; } /** * 记住我 */ public CookieRememberMeManager rememberMeManager() { CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager(); cookieRememberMeManager.setCookie(rememberMeCookie()); cookieRememberMeManager.setCipherKey(Base64.decode("fCq+/xW488hMTCD+cmJ3aQ==")); return cookieRememberMeManager; } /** * 同一个用户多设备登录限制 */ public KickoutSessionFilter kickoutSessionFilter() { KickoutSessionFilter kickoutSessionFilter = new KickoutSessionFilter(); kickoutSessionFilter.setCacheManager(getEhCacheManager()); kickoutSessionFilter.setSessionManager(sessionManager()); // 同一个用户最大的会话数,默认-1无限制;比如2的意思是同一个用户允许最多同时两个人登录 kickoutSessionFilter.setMaxSession(maxSession); // 是否踢出后来登录的,默认是false;即后者登录的用户踢出前者登录的用户;踢出顺序 kickoutSessionFilter.setKickoutAfter(kickoutAfter); // 被踢出后重定向到的地址; kickoutSessionFilter.setKickoutUrl("/login?kickout=1"); return kickoutSessionFilter; } /** * thymeleaf模板引擎和shiro框架的整合 */ @Bean public ShiroDialect shiroDialect() { return new ShiroDialect(); } /** * 开启Shiro注解通知器 */ @Bean public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor( @Qualifier("securityManager") SecurityManager securityManager) { AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor(); authorizationAttributeSourceAdvisor.setSecurityManager(securityManager); return authorizationAttributeSourceAdvisor; } }

四、自定义CasRealm类,实现自己的认证授权过程,不在走原来的UserRealm类

1
package com.ruoyi.framework.shiro.realm; import com.ruoyi.common.exception.user.*; import com.ruoyi.framework.shiro.service.SysLoginService; import com.ruoyi.framework.util.ShiroUtils; import com.ruoyi.system.domain.SysUser; import com.ruoyi.system.service.ISysMenuService; import com.ruoyi.system.service.ISysRoleService; import io.buji.pac4j.realm.Pac4jRealm; import io.buji.pac4j.subject.Pac4jPrincipal; import io.buji.pac4j.token.Pac4jToken; import org.apache.shiro.authc.*; import org.apache.shiro.authz.AuthorizationInfo; import org.apache.shiro.authz.SimpleAuthorizationInfo; import org.apache.shiro.crypto.hash.Md5Hash; import org.apache.shiro.subject.PrincipalCollection; import org.apache.shiro.subject.SimplePrincipalCollection; import org.pac4j.core.profile.CommonProfile; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import java.util.HashSet; import java.util.List; import java.util.Set; /** * 认证与授权 **/ public class CasRealm extends Pac4jRealm { private final static Logger log = LoggerFactory.getLogger(CasRealm.class); private String clientName; @Autowired private ISysMenuService menuService; @Autowired private ISysRoleService roleService; @Autowired private SysLoginService loginService; public String getClientName() { return clientName; } public void setClientName(String clientName) { this.clientName = clientName; } public ISysMenuService getMenuService() { return menuService; } public void setMenuService(ISysMenuService menuService) { this.menuService = menuService; } public ISysRoleService getRoleService() { return roleService; } public void setRoleService(ISysRoleService roleService) { this.roleService = roleService; } public SysLoginService getLoginService() { return loginService; } public void setLoginService(SysLoginService loginService) { this.loginService = loginService; } /** * 认证 * @param authenticationToken * @return * @throws AuthenticationException */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { final Pac4jToken pac4jToken = (Pac4jToken) authenticationToken; final List<CommonProfile> commonProfileList = pac4jToken.getProfiles(); final CommonProfile commonProfile = commonProfileList.get(0); //todo final Pac4jPrincipal principal = new Pac4jPrincipal(commonProfileList, getPrincipalNameAttribute()); final PrincipalCollection principalCollection = new SimplePrincipalCollection(principal, getName()); String username = commonProfile.getId(); SysUser user = null; try { user = loginService.casLogin(username); } catch (CaptchaException e) { throw new AuthenticationException(e.getMessage(), e); } catch (UserNotExistsException e) { throw new UnknownAccountException(e.getMessage(), e); } catch (UserPasswordNotMatchException e) { throw new IncorrectCredentialsException(e.getMessage(), e); } catch (UserPasswordRetryLimitExceedException e) { throw new ExcessiveAttemptsException(e.getMessage(), e); } catch (UserBlockedException e) { throw new LockedAccountException(e.getMessage(), e); } catch (RoleBlockedException e) { throw new LockedAccountException(e.getMessage(), e); } catch (Exception e) { log.info("对用户[" + username + "]进行登录验证..验证未通过{}", e.getMessage()); throw new AuthenticationException(e.getMessage(), e); } return new SimpleAuthenticationInfo(user, commonProfileList.hashCode(), getName()); } /** * 授权/验权(todo 后续有权限在此增加) * @param principals * @return */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); SysUser user = ShiroUtils.getSysUser(); // 角色列表 Set<String> roles = new HashSet<String>(); // 功能列表 Set<String> menus = new HashSet<String>(); // 管理员拥有所有权限 if (user.isAdmin()) { info.addRole("admin"); info.addStringPermission("*:*:*"); } else { roles = roleService.selectRoleKeys(user.getUserId()); menus = menuService.selectPermsByUserId(user.getUserId()); // 角色加入AuthorizationInfo认证对象 info.setRoles(roles); // 权限加入AuthorizationInfo认证对象 info.setStringPermissions(menus); } return info; } public static void main(String[] args) { encryptPassword(); } public static void encryptPassword(){ System.out.println(new Md5Hash("admin"+"admin123"+"111111").toHex()); } }

四、自定义初始化配置类Pac4jConfig

1
package com.ruoyi.framework.config; import com.ruoyi.framework.shiro.client.Pac4jCasClient; import io.buji.pac4j.context.ShiroSessionStore; import org.pac4j.cas.config.CasConfiguration; import org.pac4j.cas.config.CasProtocol; import org.pac4j.core.config.Config; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class Pac4jConfig { /** 地址为:cas地址 */ @Value("${cas.server.url}") private String casServerUrl; /** 地址为:验证返回后的项目地址:http://localhost:8081 */ @Value("${cas.project.url}") private String projectUrl; /** 相当于一个标志,可以随意 */ @Value("${cas.client-name}") private String clientName; /** * pac4j配置 * @param casClient * @param shiroSessionStore * @return */ @Bean("authcConfig") public Config config(Pac4jCasClient casClient, ShiroSessionStore shiroSessionStore) { Config config = new Config(casClient); config.setSessionStore(shiroSessionStore); return config; } /** * 自定义存储 * @return */ @Bean public ShiroSessionStore shiroSessionStore(){ return new ShiroSessionStore(); } /** * cas 客户端配置 * @param casConfig * @return */ @Bean public Pac4jCasClient casClient(CasConfiguration casConfig){ Pac4jCasClient casClient = new Pac4jCasClient(casConfig); //客户端回调地址 casClient.setCallbackUrl(projectUrl + "/callback?client_name=" + clientName); casClient.setName(clientName); return casClient; } /** * 请求cas服务端配置 */ @Bean public CasConfiguration casConfig(){ final CasConfiguration configuration = new CasConfiguration(); //CAS server登录地址 configuration.setLoginUrl(casServerUrl + "/login"); //CAS 版本,默认为 CAS30,我们使用的是 CAS20 configuration.setProtocol(CasProtocol.CAS20); configuration.setAcceptAnyProxy(true); configuration.setPrefixUrl(casServerUrl + "/"); return configuration; } }

五、自定义类Pac4jCasClient

1
package com.ruoyi.framework.shiro.client; import org.apache.shiro.SecurityUtils; import org.apache.shiro.session.Session; import org.pac4j.cas.config.CasConfiguration; import org.pac4j.core.context.Pac4jConstants; import org.pac4j.core.context.WebContext; import org.pac4j.core.context.session.SessionStore; import org.pac4j.core.redirect.RedirectAction; import org.pac4j.core.util.CommonHelper; import org.pac4j.cas.client.CasClient; public class Pac4jCasClient extends CasClient { public Pac4jCasClient() { super(); } public Pac4jCasClient(CasConfiguration configuration) { super(configuration); } /* * (non-Javadoc) * @see org.pac4j.core.client.IndirectClient#getRedirectAction(org.pac4j.core.context.WebContext) */ @Override public RedirectAction getRedirectAction(WebContext context) { this.init(); if (getAjaxRequestResolver().isAjax(context)) { this.logger.info("AJAX request detected -> returning the appropriate action"); RedirectAction action = getRedirectActionBuilder().redirect(context); this.cleanRequestedUrl(context); return getAjaxRequestResolver().buildAjaxResponse(action.getLocation(), context); } else { final String attemptedAuth = (String)context.getSessionStore().get(context, this.getName() + ATTEMPTED_AUTHENTICATION_SUFFIX); if (CommonHelper.isNotBlank(attemptedAuth)) { this.cleanAttemptedAuthentication(context); this.cleanRequestedUrl(context); //这里按自己需求处理,默认是返回了401,我在这边改为跳转到cas登录页面 return this.getRedirectActionBuilder().redirect(context); } else { return this.getRedirectActionBuilder().redirect(context); } } } private void cleanRequestedUrl(WebContext context) { SessionStore<WebContext> sessionStore = context.getSessionStore(); if (sessionStore.get(context, Pac4jConstants.REQUESTED_URL) != null) { sessionStore.set(context, Pac4jConstants.REQUESTED_URL, ""); } } private void cleanAttemptedAuthentication(WebContext context) { SessionStore<WebContext> sessionStore = context.getSessionStore(); if (sessionStore.get(context, this.getName() + ATTEMPTED_AUTHENTICATION_SUFFIX) != null) { sessionStore.set(context, this.getName() + ATTEMPTED_AUTHENTICATION_SUFFIX, ""); } } }

六、修改SysLoginService类,增加casLogin(String username)方法

1
/** * cas登录 */ public SysUser casLogin(String username) { // 查询用户信息 SysUser user = userService.selectUserByLoginName(username); // AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success"))); recordLoginInfo(user); return user; }

七、启动应用

1
10:10:11.536 [restartedMain] INFO c.a.d.p.DruidDataSource - [close,2024] - {dataSource-0} closing ... 10:10:11.556 [restartedMain] ERROR o.s.b.d.LoggingFailureAnalysisReporter - [report,42] - *************************** APPLICATION FAILED TO START *************************** Description: The dependencies of some of the beans in the application context form a cycle: objectMapperConfigurer defined in class path resource [springfox/documentation/spring/web/SpringfoxWebMvcConfiguration.class]  authorizationAttributeSourceAdvisor defined in class path resource [com/ruoyi/framework/config/ShiroConfig.class]  shiroFilterFactoryBean defined in class path resource [com/ruoyi/framework/config/ShiroConfig.class]  securityManager defined in class path resource [com/ruoyi/framework/config/ShiroConfig.class] ┌─────┐ | sessionManager defined in class path resource [com/ruoyi/framework/config/ShiroConfig.class]   | springSessionValidationScheduler (field private org.apache.shiro.session.mgt.ValidatingSessionManager com.ruoyi.framework.shiro.web.session.SpringSessionValidationScheduler.sessionManager) └─────┘ Disconnected from the target VM, address: '127.0.0.1:56974', transport: 'socket' Process finished with exit code 0

发现直接BBQ了,翻车了,看一下报错,发现是循环依赖问题。
解决办法如下:
找到SpringSessionValidationScheduler类,找到成员属性private ValidatingSessionManager sessionManager;在他上面加上 @Lazy 注解即可。

1
@Component public class SpringSessionValidationScheduler implements SessionValidationScheduler { private static final Logger log = LoggerFactory.getLogger(SpringSessionValidationScheduler.class); public static final long DEFAULT_SESSION_VALIDATION_INTERVAL = DefaultSessionManager.DEFAULT_SESSION_VALIDATION_INTERVAL; /** * 定时器,用于处理超时的挂起请求,也用于连接断开时的重连。 */ @Autowired @Qualifier("scheduledExecutorService") private ScheduledExecutorService executorService; private volatile boolean enabled = false; /** * 会话验证管理器 */ @Autowired @Qualifier("sessionManager") @Lazy private ValidatingSessionManager sessionManager;