1. 程式人生 > 程式設計 >Shiro+Cas微服務化及前後端完全分離

Shiro+Cas微服務化及前後端完全分離

本文例項為大家分享了Shiro Cas微服務化及前後端完全分離,供大家參考,具體內容如下

shiro+cas微服務化筆記

1.Spring Boot 配置

有如下兩個配置檔案: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許可權管理模組conf
 *
 * @author Dean.Hwang
 * @date 17/5/18
 */
@Configuration
@Log4j
public class ShiroBaseConfiguration {
  @Value("${cas.server.url.prefix}")
  private String casPrefix;
  @Value("${cas.service}")
  private String casService;
  
  /**
   * 會話Cookie模板
   *
   * @return
   */
  @Bean
  public SimpleCookie sessionIdCookie() {
    SimpleCookie simpleCookie = new SimpleCookie("sid");
    simpleCookie.setHttpOnly(true);
    simpleCookie.setMaxAge(1800000);
    return simpleCookie;
  }

  /**
   * 會話Cookie模板
   *
   * @return
   */
  @Bean
  public SimpleCookie rememberCookie() {
    SimpleCookie simpleCookie = new SimpleCookie("rememberMe");
    simpleCookie.setHttpOnly(true);
    simpleCookie.setMaxAge(2592000);//30天
    return simpleCookie;
  }

  /**
   * rememberMe 管理器
   *
   * @return
   */
  @Bean
  public CookieRememberMeManager rememberMeManager(SimpleCookie rememberCookie) {
    CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager();
    cookieRememberMeManager.setCipherKey(Base64.decode(""));// rememberMe cookie加密的金鑰 建議每個專案都不一樣 預設AES演算法 金鑰長度(128 256 512 位)
    cookieRememberMeManager.setCookie(rememberCookie);
    return cookieRememberMeManager;
  }

  /**
   * 會話DAO
   *
   * @return
   */
  @Bean
  public MemorySessionDAO sessionDAO() {
    return new MemorySessionDAO();
  }


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

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

  @Bean
  public CasFilter casFilter() {
    CasFilter casFilter = new CasFilter();
    casFilter.setEnabled(true);
    casFilter.setName("casFilter");
    casFilter.setFailureUrl("/authority/casFailure");
    return casFilter;
  }


}

下面ShiroManagerConfiguration.java 檔案

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>
 * <p>
 * Copyright: Copyright (c) 2015
 * </p>
 * <p>
 * </p>
 *
 * @author Dean.Hwang
 * @date 17/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() {
    DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
    securityManager.setRealm(kryCasRealm);
    securityManager.setSessionManager(new ServletContainerSessionManager());
    securityManager.setCacheManager(shiroCacheManager);
    securityManager.setRememberMeManager(rememberMeManager);
    securityManager.setSubjectFactory(new CasSubjectFactory());
    return securityManager;
  }

  /**
   * 相當於呼叫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);
    //只能在這裡初始化LogoutFilter,不然會被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);
    return factoryBean;  }
}  

2.UserFilter的改造

2.1改造的原因:

因為,我們現在新的伺服器架構是前後端完全分離的。但是,shiro是不支援完全的前後端分離。所以導致了單點登入完成以後會跳轉至介面,而不是目標頁面。同時,由於歷史原因,我們的cas驗證伺服器與業務伺服器不是同一個域。如果,需要在伺服器端進行重定向就必須要通過跨域,考慮到跨域會有風險。所以,我也將sso伺服器登入重定向進行了重構。做成了返回json,前端在接收到json自己進行登入頁跳轉。

具體的實現程式碼如下:

protected void saveRequestAndRedirectToLogin(ServletRequest request,ServletResponse response) throws IOException {
    Session session = SecurityUtils.getSubject().getSession();
    if (session != null) {
      SavedRequest savedRequest = new PortalSavedRequest(WebUtils.toHttp(request));//重寫的SavedRequest,具體處理由不同的業務需求自定
      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();
      }
    }
  }

此方法是將Cas中的Userfilter進行了重寫,並且在配置時使用重寫的類對原有的UserFilter進行了覆蓋。

#3.登入成功後的重定向:

由於在sso驗證伺服器登入成功以後會重定向到本地業務伺服器上。本地業務伺服器驗證登入成功以後會預設重定向至配置的SuccessUrl。這樣並不能將頁面跳轉回使用者的原來請求的頁面。所以我重寫了CasFilter中的issueSuccessRedirect達到這個目的

/**
 * <p>
 * Copyright: Copyright (c) 2015
 * </p>
 *
 * @author Dean.Hwang
 * @date 17/7/17
 */
public class PortalCasFilter extends CasFilter {

  @Override
  protected void issueSuccessRedirect(ServletRequest request,ServletResponse response) throws Exception {
    String successUrl = ((ShiroHttpServletRequest) request).getHeader("page-url");//前端頁面在請求的時候在header中帶上請求這個介面的url。這樣便將登入成功後需要跳轉的地址繫結到了對應的Subject物件中。以便於在登入以後跳轉到這個頁面
    if (StringUtil.isBlank(successUrl)) {
      WebUtils.redirectToSavedRequest(request,response,getSuccessUrl());
    } else {
      WebUtils.redirectToSavedRequest(request,successUrl);
    }
  }
}

#4.使用者安全的退出

後期發現直接依靠原有的logout會發生session未登出的情況。所以重寫了LogoutFilter。登出的時候直接呼叫配置的URL即可

/**

 * </p>
 * <p>
 * Copyright: Copyright (c) 2015
 * </p>
 *
 * @author Dean.Hwang
 * @date 17/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,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,redirectUrl);
    return false;
  }
}

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支援我們。