Shiro + CAS Microservitization Notes

1. Spring Boot configuration

There are two configuration files: ShiroBaseConfig. Java


import lombok.extern.log4j.Log4j;
import org.apache.shiro.cache.CacheManager;
import org.apache.shiro.cache.MemoryConstrainedCacheManager;
import org.apache.shiro.cas.CasFilter;
import org.apache.shiro.codec.Base64;
import org.apache.shiro.session.mgt.eis.MemorySessionDAO;
import org.apache.shiro.web.mgt.CookieRememberMeManager;
import org.apache.shiro.web.servlet.SimpleCookie;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/** * <p> * Description: Shiro permission management module conf **@author Dean.Hwang
 * @date17/5/18 * /
@Configuration
@Log4j
public class ShiroBaseConfiguration {
    @Value("${cas.server.url.prefix}")
    private String casPrefix;
    @Value("${cas.service}")
    private String casService;
    
    /** * Session Cookie template **@return* /
    @Bean
    public SimpleCookie sessionIdCookie(a) {
        SimpleCookie simpleCookie = new SimpleCookie("sid");
        simpleCookie.setHttpOnly(true);
        simpleCookie.setMaxAge(1800000);
        return simpleCookie;
    }

    /** * Session Cookie template **@return* /
    @Bean
    public SimpleCookie rememberCookie(a) {
        SimpleCookie simpleCookie = new SimpleCookie("rememberMe");
        simpleCookie.setHttpOnly(true);
        simpleCookie.setMaxAge(2592000);/ / for 30 days
        return simpleCookie;
    }

    /** rememberMe manager **@return* /
    @Bean
    public CookieRememberMeManager rememberMeManager(SimpleCookie rememberCookie) {
        CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager();
        cookieRememberMeManager.setCipherKey(Base64.decode(""));Default AES key length (128 256 512 bits)
        cookieRememberMeManager.setCookie(rememberCookie);
        return cookieRememberMeManager;
    }

    /** * session DAO **@return* /
    @Bean
    public MemorySessionDAO sessionDAO(a) {
        return new MemorySessionDAO();
    }


    @Bean
    public CacheManager shiroCacheManager(a) {
        return new MemoryConstrainedCacheManager();
    }

    @Bean
    public KryCasRealm casRealm(CacheManager shiroCacheManager) {
        return new KryCasRealm(casPrefix, casService, shiroCacheManager);
    }

    @Bean
    public CasFilter casFilter(a) {
        CasFilter casFilter = new CasFilter();
        casFilter.setEnabled(true);
        casFilter.setName("casFilter");
        casFilter.setFailureUrl("/authority/casFailure");
        returncasFilter; }}Copy the code

Below ShiroManagerConfiguration. Java file


import com.keruyun.portal.portalbiz.sso.KryCasRealm;
import com.keruyun.portal.portalbiz.sso.filter.PortalUserFilter;
import org.apache.shiro.cache.CacheManager;
import org.apache.shiro.cas.CasFilter;
import org.apache.shiro.cas.CasSubjectFactory;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.filter.authc.LogoutFilter;
import org.apache.shiro.web.mgt.CookieRememberMeManager;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.session.mgt.ServletContainerSessionManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.config.MethodInvokingFactoryBean;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.servlet.Filter;
import java.util.HashMap;
import java.util.Map;

/** * <p> * Description: com.keruyun.portal.portalbiz.conf.shiro * </p> * <p> * Copyright: Copyright (c) 2015 * </p> * <p> * Company: Customer like a cloud * </p> * *@author Dean.Hwang
 * @date17/5/18 * /
@Configuration
@AutoConfigureAfter(
        {ShiroBaseConfiguration.class}
)
public class ShiroManagerConfiguration {
    @Autowired
    private KryCasRealm kryCasRealm;
    @Autowired
    private CacheManager shiroCacheManager;
    @Autowired
    private CookieRememberMeManager rememberMeManager;
    @Value("${cas.server.login.url}")
    private String loginUrl;
    @Value("${cas.client.url.prefix}")
    private String urlPrefix;
    @Autowired
    private CasFilter casFilter;
    @Value("${cas.server.logout.url}")
    private String logoutUrl;
    @Value("${cas.client.index.url}")
    private String indexUrl;

    @Bean
    public DefaultWebSecurityManager securityManager(a) {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(kryCasRealm);
        securityManager.setSessionManager(new ServletContainerSessionManager());
        securityManager.setCacheManager(shiroCacheManager);
        securityManager.setRememberMeManager(rememberMeManager);
        securityManager.setSubjectFactory(new CasSubjectFactory());
        return securityManager;
    }

    / * * * is equivalent to call the SecurityUtils. SetSecurityManager (securityManager) * *@param securityManager
     * @return* /
    @Bean
    public MethodInvokingFactoryBean methodInvokingFactoryBean(DefaultWebSecurityManager securityManager) {
        MethodInvokingFactoryBean bean = new MethodInvokingFactoryBean();
        bean.setStaticMethod("org.apache.shiro.SecurityUtils.setSecurityManager");
        bean.setArguments(new Object[]{securityManager});
        return bean;
    }

    @Bean
    public ShiroFilterFactoryBean shiroFilter(DefaultWebSecurityManager securityManager) {
         ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();
        factoryBean.setSecurityManager(securityManager);
        factoryBean.setLoginUrl(loginUrl + serviceStr + urlPrefix + "/cas");
        factoryBean.setSuccessUrl(".. /mind/index.do");
        factoryBean.setUnauthorizedUrl("/unauthorized.jsp");
        Map<String, Filter> filterMap = new HashMap<>();
        filterMap.put("cas", casFilter);
        filterMap.put("user", portalUserFilter);
        // The LogoutFilter can only be initialized here, otherwise it will be registered with /* by Spring Boot
        PortalLogoutFilter logoutFilter = new PortalLogoutFilter();
        logoutFilter.setRedirectUrl(logoutUrl + serviceStr + indexUrl);
        filterMap.put("logout", logoutFilter);
        factoryBean.setFilters(filterMap);
        Map<String, String> filters = new HashMap<>();
        filters.put("/casFailure.jsp"."anon");
        filters.put("/js/**"."anon");
        filters.put("/themes/**"."anon");
        filters.put("/3rdOauth/**"."anon");
        filters.put("/cas"."cas");
        filters.put("/logout"."logout");
        filters.put("/ * *"."user");
        factoryBean.setFilterChainDefinitionMap(filters);
        returnfactoryBean; }}Copy the code

2. UserFilter modification

2.1 Reasons for the transformation:

Because our new server architecture is now completely separate from the front and back ends. However, Shiro does not support complete front and back end separation. As a result, the single sign-on is redirected to the interface instead of the target page. Also, due to historical reasons, our CAS authentication server is not in the same domain as the business server. If redirection is required on the server side, it must go through cross-domains, which is risky. So, I refactored sso server login redirection as well. Return JSON is made, and the front end jumps to the login page after receiving JSON. The specific implementation code is as follows:

protected void saveRequestAndRedirectToLogin(ServletRequest request, ServletResponse response) throws IOException {
        Session session = SecurityUtils.getSubject().getSession();
        if(session ! =null) {
            SavedRequest savedRequest = new PortalSavedRequest(WebUtils.toHttp(request));// Rewritten SavedRequest, the specific processing is customized by different business requirements
            session.setAttribute(SAVED_REQUEST_KEY, savedRequest);

        }
        PrintWriter out = null;
        try {
            ResultVO<Object> vo = ResultVO.isRedirect();
            RedirectInfo info = new RedirectInfo(loginRedirectUrl);
            vo.setData(info);
            response.setCharacterEncoding("UTF-8");
            response.setContentType("application/json; charset=utf-8");
            out = response.getWriter();
            out.write(JsonMapper.nonDefaultMapper().toJson(vo));
        } catch (IOException e) {
            log.error("Login Redirect Failed", e);
        } finally {
            if(out ! =null) { out.close(); }}}Copy the code

This method overwrites the Userfilter in Cas and overwrites the original Userfilter with the overwritten class during configuration.

#3. Redirection after successful login: Since a successful login on the SSO authentication server redirects to the local business server. After a successful login is verified, the local service server redirects to the SuccessUrl by default. This does not redirect the page back to the user’s original request. So I rewrote the Issue CessRedirect in CasFilter to do just that

/**
 * <p>
 * Description: com.keruyun.portal.portalbiz.sso.filter
 * </p>
 * <p>
 * Copyright: Copyright (c) 2015
 * </p>
 * <p>
 * </p>
 *
 * @author Dean.Hwang
 * @date17/7/17 * /
public class PortalCasFilter extends CasFilter {

    @Override
    protected void issueSuccessRedirect(ServletRequest request, ServletResponse response) throws Exception {
        String successUrl = ((ShiroHttpServletRequest) request).getHeader("page-url");// The front-end page puts the url of the requested interface in the header when requesting it. In this way, the address to jump to after a successful login is bound to the Subject object. So that you can jump to this page after you log in
        if (StringUtil.isBlank(successUrl)) {
            WebUtils.redirectToSavedRequest(request, response, getSuccessUrl());
        } else{ WebUtils.redirectToSavedRequest(request, response, successUrl); }}}Copy the code

#4. If the user logs out safely, the session will not be logged out if the user directly relies on the original logout. So I rewrote the LogoutFilter. Call the configured URL when you log out

/**
 * <p>
 * Description: com.keruyun.portal.portalbiz.sso.filter
 * </p>
 * <p>
 * Copyright: Copyright (c) 2015
 * </p>
 * <p>
 * </p>
 *
 * @author Dean.Hwang
 * @date17/7/17 * /
public class PortalLogoutFilter extends AdviceFilter {

    private static final Logger log = LoggerFactory.getLogger(LogoutFilter.class);

    public static final String DEFAULT_REDIRECT_URL = "/";

    private String redirectUrl = DEFAULT_REDIRECT_URL;

    @Override
    protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
        Subject subject = getSubject(request, response);
        String redirectUrl = getRedirectUrl(request, response, subject);
        //try/catch added for SHIRO-298:
        try {
            subject.logout();
            Session session = subject.getSession();
            session.stop();
        } catch (SessionException ise) {
            log.debug("Encountered session exception during logout. This can generally safely be ignored.", ise);
        }
        issueRedirect(request, response, redirectUrl);
        return false; }}Copy the code