天天看點

apache shiro自定義shiro

Shiro是Apache下的一個安全架構,其相比Spring Security來說,更為輕量級,而功能卻不簡單。相關對比目前有很多文章都提到。但尚未有真正技術性的文章——僅有的幾篇也隻有介紹介紹如何配置成功一個應用而已,其實這個還是看官方文檔更清晰。

但官方文檔并沒有很明确地指出如何實作Shiro的單點登入,是以我覺得有必要在此處記錄一下,友善使用shiro的朋友們。

shiro支援幾乎所有的登入方式——因為它的靈活性和可定制性,是以我所提供的方案也隻是其中的一種,大家如果有别的想法,自由定制之。

首先,要實作單點登入,必須有sso服務,假設該服務已部署完成,這裡我用的是jasig cas進行單點登入驗證,其機理大緻就是在應用中增加一層filter進行攔截請求,如果發現request無認證資訊(用戶端驗證憑據)則由filter直接發送302重定向至cas認證伺服器,使用者認證成功後會帶着成功的唯一憑據再次進入應用,此時該filter将根據用戶端提供的驗證憑據連接配接到cas伺服器擷取使用者資訊,通過s2s擷取到使用者資訊後放入應用中完成使用者對本應用的授權。

那麼在shiro中如何去配合cas進行sso呢?接下來我們就來對shiro進行sso配置

1、建一個自定義的token

[java]  view plain copy

  1. package com.jajacode.sample.authc  
  2. import org.apache.shiro.authc.*  
  3. public class TrustedSsoAuthenticationToken implements AuthenticationToken{  
  4.     private String username;  
  5.     public TrustedSsoAuthenticationToken(){}  
  6.     public TrustedSsoAuthenticationToken(String username){  
  7.         this.username = username;  
  8.     }  
  9.     public Object getPrincipal(){  
  10.         return this.username;  
  11.     }  
  12.     public Object getCredentials() {  
  13.   return null;  
  14.     }  
  15.     public void setUsername(String username){  
  16.         this.username = username;  
  17.     }  
  18.     public String getUsername(){  
  19.         return this.username;  
  20.     }  
  21.     public void clear(){  
  22.         this.username = null;  
  23.     }  
  24.     public String toString(){  
  25.         return "username="+this.username;  
  26.     }  
  27. }  

2、建立一個filter,這個filter就要根據實際需要進行extends了,因為我用了jasig cas,是以代碼會是如下:

[java]  view plain copy

  1. package com.jajacode.sample.filter.authc  
  2. import java.io.IOException;  
  3. import java.security.Principal;  
  4. import javax.servlet.Filter;  
  5. import javax.servlet.FilterChain;  
  6. import javax.servlet.FilterConfig;  
  7. import javax.servlet.ServletException;  
  8. import javax.servlet.ServletRequest;  
  9. import javax.servlet.ServletResponse;  
  10. import javax.servlet.http.HttpServletRequest;  
  11. import javax.servlet.http.HttpServletResponse;  
  12. import javax.servlet.http.HttpSession;  
  13. import org.apache.shiro.SecurityUtils;  
  14. import com.jajacode.sample.authc.TrustedSsoAuthenticationToken;  
  15. import com.jajacode.sample.datasource.DatasourceContextHolder;  
  16. public class ShiroSsoFilter implements Filter {  
  17.   public static final String CONST_CAS_ASSERTION = "_const_cas_assertion_";  
  18.   @Override  
  19.   public void destroy() {  
  20.     // TODO Auto-generated method stub  
  21.   }  
  22.   @Override  
  23.   public void doFilter(final ServletRequest servletRequest,  
  24.       final ServletResponse servletResponse, final FilterChain filterChain)  
  25.       throws IOException, ServletException {  
  26.     final HttpServletRequest request = (HttpServletRequest) servletRequest;  
  27.     final HttpServletResponse response = (HttpServletResponse) servletResponse;  
  28.     final HttpSession session = request.getSession();  
  29.     Principal principal = request.getUserPrincipal();  
  30.     if (principal != null) {  
  31.                         // 這裡是多源資料庫的選擇,系統根據使用者組的不同會選擇不同的資料庫操作  
  32.       DatasourceContextHolder.setGroupType(GroupType.CUSTOMER);  
  33.       TrustedSsoAuthenticationToken token = new TrustedSsoAuthenticationToken(principal.getName());  
  34.       SecurityUtils.getSubject().login(token);  
  35.       filterChain.doFilter(request, response);  
  36.     }  
  37.   }  
  38.   @Override  
  39.   public void init(FilterConfig arg0) throws ServletException {  
  40.     // TODO Auto-generated method stub  
  41.   }  
  42. }  

3、建立sso的realm,此realm繼承shiro的AuthorizingRealm,并重寫doGetAuthenticationInfo方法

[java]  view plain copy

  1. package com.jajacode.sample.realm;  
  2. import java.util.Collection;  
  3. import java.util.HashSet;  
  4. import org.apache.shiro.authc.AccountException;  
  5. import org.apache.shiro.authc.AuthenticationException;  
  6. import org.apache.shiro.authc.AuthenticationInfo;  
  7. import org.apache.shiro.authc.AuthenticationToken;  
  8. import org.apache.shiro.authc.SimpleAuthenticationInfo;  
  9. import org.apache.shiro.authc.credential.AllowAllCredentialsMatcher;  
  10. import org.apache.shiro.authz.AuthorizationInfo;  
  11. import org.apache.shiro.authz.SimpleAuthorizationInfo;  
  12. import org.apache.shiro.cache.Cache;  
  13. import org.apache.shiro.realm.AuthorizingRealm;  
  14. import org.apache.shiro.subject.PrincipalCollection;  
  15. import org.apache.shiro.subject.SimplePrincipalCollection;  
  16. import org.springframework.beans.factory.annotation.Autowired;  
  17. import com.jajacode.sample.authc.TrustedSsoAuthenticationToken;  
  18. import com.jajacode.sample.domain.account.Permission;  
  19. import com.jajacode.sample.domain.account.Role;  
  20. import com.jajacode.sample.domain.account.User;  
  21. public class ShiroSsoRealm extends AuthorizingRealm {  
  22.   @Autowired  
  23.   private AccountManager accountManager;  
  24.   public ShiroDbRealm(){  
  25.                 // 設定無需憑證,因為從sso認證後才會有使用者名  
  26.     setCredentialsMatcher(new AllowAllCredentialsMatcher());  
  27.                 // 設定token為我們自定義的  
  28.     setAuthenticationTokenClass(TrustedSsoAuthenticationToken.class);  
  29.   }  
  30.   @Override  
  31.   protected AuthenticationInfo doGetAuthenticationInfo(  
  32.       AuthenticationToken authcToken) throws AuthenticationException {  
  33.     TrustedSsoAuthenticationToken token = (TrustedSsoAuthenticationToken)authcToken;  
  34.     //UsernamePasswordToken token = (UsernamePasswordToken) authcToken;  
  35.     Object username = token.getPrincipal();  
  36. //    String username = token.getUsername();  
  37.     //不允許無username  
  38.     if(username==null){  
  39.                         // 自定義異常,于前端捕獲  
  40.       throw new AccountException("使用者名不允許為空!");  
  41.     }  
  42.     return new SimpleAuthenticationInfo(token.getPrincipal(), token.getCredentials(), getName());  
  43.   }  
  44.   @Override  
  45.   protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {  
  46.     String loginName = (String) principals.fromRealm(getName()).iterator().next();  
  47.     User user = accountManager.findUserByLoginName(loginName);  
  48.     if(user != null){  
  49.       SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();  
  50.       // 将使用者權限放入其中,代碼略  
  51.       return info;  
  52.     }  
  53.     return null;  
  54.   }  
  55.   public void clearCachedAuthorizationInfo(String principal){  
  56.     SimplePrincipalCollection principals = new SimplePrincipalCollection(principal, getName());  
  57.     clearCachedAuthorizationInfo(principals);  
  58.   }  
  59.   public void clearAllCachedAuthorizationInfo(){  
  60.     Cache<Object, AuthorizationInfo> cache = getAuthorizationCache();  
  61.     if (cache != null) {  
  62.       for (Object key : cache.keys()) {  
  63.         cache.remove(key);  
  64.       }  
  65.     }  
  66.   }  
  67. }  

4、其他配置參考官方正常配置即可。将filter寫入web.xml中,同時配置sso的一些filter注意mapping順序即可。

歡迎拍磚

其實按照shiro标準,後面的Filter應該繼承自org.apache.shiro.web.filter.authc.AuthenticatingFilter會好一些,并且重寫方法:createToken(request,response),傳回TrustedSsoAuthenticationToken執行個體;重寫onAccessDenied(request,response)方法,來調用executeLogin(request,response),最終還是調用了SecurityUtils.getSubject().login(token),是以我就簡化到直接使用filter來實作,單例的Subject非常友善!

繼續閱讀