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