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

简介: 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类,可以和原来项目的进行对比

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类

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

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

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)方法

/**
     * 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;
    }

七、启动应用

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 注解即可。

@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;


相关文章
|
SQL 缓存 NoSQL
【前后端】低代码平台Jeecg-Boot 3.2云服务器部署流程
【前后端】低代码平台Jeecg-Boot 3.2云服务器部署流程
1150 0
【前后端】低代码平台Jeecg-Boot 3.2云服务器部署流程
|
5月前
|
监控 Java Maven
微服务技术系列教程(10) - SpringBoot - 监控管理 - Admin-UI
微服务技术系列教程(10) - SpringBoot - 监控管理 - Admin-UI
54 0
|
1月前
|
传感器 人工智能 监控
智慧工地云信息平台源码(微服务+java+springcloud+uniapp+mysql)
智慧工地云信息平台源码(微服务+java+springcloud+uniapp+mysql)
31 0
|
3月前
datax-web修改web端口号
datax-web修改web端口号
140 4
|
3月前
|
安全 JavaScript 前端开发
基于ruoyi框架实现的短链接生成管理系统(前后端分离)
基于ruoyi框架实现的短链接生成管理系统(前后端分离)
|
4月前
|
JavaScript Java 关系型数据库
基于微服务、Java、Springcloud、Vue、MySQL开发的智慧工地管理系统源码
基于微服务、Java、Springcloud、Vue、MySQL开发的智慧工地管理系统源码
40 0
|
10月前
|
前端开发 Java 应用服务中间件
【超详细】RuoYi 前后端分离版部署流程
部署前端(前端访问域名,跳转域名修改)、后端配置(配置文件),Nginx 配置文件详解,上传后部署需要的脚本。
1318 1
|
10月前
|
存储 前端开发 JavaScript
SpringBoot+Vue 前后端分离 微服务项目 打包部署全流程(原始部署/宝塔部署)
SpringBoot+Vue 前后端分离 微服务项目 打包部署全流程(原始部署/宝塔部署)
1702 2
SpringBoot+Vue 前后端分离 微服务项目 打包部署全流程(原始部署/宝塔部署)
|
存储 缓存 监控
Sentry Web 前端监控 - 最佳实践(官方教程)
Sentry Web 前端监控 - 最佳实践(官方教程)
857 0
Sentry Web 前端监控 - 最佳实践(官方教程)
|
存储 Kubernetes 中间件
【中间件-keycloak】第一次改开源中间件keycloak总个结
【中间件-keycloak】第一次改开源中间件keycloak总个结
511 1
【中间件-keycloak】第一次改开源中间件keycloak总个结