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.   
  3. import org.apache.shiro.authc.*  
  4.   
  5. public class TrustedSsoAuthenticationToken implements AuthenticationToken{  
  6.   
  7.     private String username;  
  8.   
  9.     public TrustedSsoAuthenticationToken(){}  
  10.   
  11.     public TrustedSsoAuthenticationToken(String username){  
  12.         this.username = username;  
  13.     }  
  14.   
  15.     public Object getPrincipal(){  
  16.         return this.username;  
  17.     }  
  18.   
  19.     public Object getCredentials() {  
  20.   return null;  
  21.     }  
  22.   
  23.     public void setUsername(String username){  
  24.         this.username = username;  
  25.     }  
  26.   
  27.     public String getUsername(){  
  28.         return this.username;  
  29.     }  
  30.   
  31.     public void clear(){  
  32.         this.username = null;  
  33.     }  
  34.   
  35.     public String toString(){  
  36.         return "username="+this.username;  
  37.     }  
  38. }  

2、建立一个filter,这个filter就要根据实际需要进行extends了,因为我用了jasig cas,所以代码会是如下:

[java]  view plain copy
  1. package com.jajacode.sample.filter.authc  
  2.   
  3. import java.io.IOException;  
  4. import java.security.Principal;  
  5.   
  6. import javax.servlet.Filter;  
  7. import javax.servlet.FilterChain;  
  8. import javax.servlet.FilterConfig;  
  9. import javax.servlet.ServletException;  
  10. import javax.servlet.ServletRequest;  
  11. import javax.servlet.ServletResponse;  
  12. import javax.servlet.http.HttpServletRequest;  
  13. import javax.servlet.http.HttpServletResponse;  
  14. import javax.servlet.http.HttpSession;  
  15.   
  16. import org.apache.shiro.SecurityUtils;  
  17.   
  18. import com.jajacode.sample.authc.TrustedSsoAuthenticationToken;  
  19. import com.jajacode.sample.datasource.DatasourceContextHolder;  
  20.   
  21. public class ShiroSsoFilter implements Filter {  
  22.   
  23.   /** 
  24.    * Represents the constant for where the assertion will be located in 
  25.    * memory. 
  26.    */  
  27.   public static final String CONST_CAS_ASSERTION = "_const_cas_assertion_";  
  28.   
  29.   @Override  
  30.   public void destroy() {  
  31.     // TODO Auto-generated method stub  
  32.   }  
  33.   
  34.   @Override  
  35.   public void doFilter(final ServletRequest servletRequest,  
  36.       final ServletResponse servletResponse, final FilterChain filterChain)  
  37.       throws IOException, ServletException {  
  38.   
  39.     final HttpServletRequest request = (HttpServletRequest) servletRequest;  
  40.     final HttpServletResponse response = (HttpServletResponse) servletResponse;  
  41.     final HttpSession session = request.getSession();  
  42.   
  43.     Principal principal = request.getUserPrincipal();  
  44.     if (principal != null) {  
  45.                         // 这里是多源数据库的选择,系统根据用户组的不同会选择不同的数据库操作  
  46.       DatasourceContextHolder.setGroupType(GroupType.CUSTOMER);  
  47.        
  48.   
  49.       TrustedSsoAuthenticationToken token = new TrustedSsoAuthenticationToken(principal.getName());  
  50.       SecurityUtils.getSubject().login(token);  
  51.       filterChain.doFilter(request, response);  
  52.        
  53.     }  
  54.   
  55.   }  
  56.   
  57.   @Override  
  58.   public void init(FilterConfig arg0) throws ServletException {  
  59.     // TODO Auto-generated method stub  
  60.   
  61.   }  
  62. }  

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

[java]  view plain copy
  1. package com.jajacode.sample.realm;  
  2.   
  3. import java.util.Collection;  
  4. import java.util.HashSet;  
  5.   
  6. import org.apache.shiro.authc.AccountException;  
  7. import org.apache.shiro.authc.AuthenticationException;  
  8. import org.apache.shiro.authc.AuthenticationInfo;  
  9. import org.apache.shiro.authc.AuthenticationToken;  
  10. import org.apache.shiro.authc.SimpleAuthenticationInfo;  
  11. import org.apache.shiro.authc.credential.AllowAllCredentialsMatcher;  
  12. import org.apache.shiro.authz.AuthorizationInfo;  
  13. import org.apache.shiro.authz.SimpleAuthorizationInfo;  
  14. import org.apache.shiro.cache.Cache;  
  15. import org.apache.shiro.realm.AuthorizingRealm;  
  16. import org.apache.shiro.subject.PrincipalCollection;  
  17. import org.apache.shiro.subject.SimplePrincipalCollection;  
  18. import org.springframework.beans.factory.annotation.Autowired;  
  19.   
  20. import com.jajacode.sample.authc.TrustedSsoAuthenticationToken;  
  21. import com.jajacode.sample.domain.account.Permission;  
  22. import com.jajacode.sample.domain.account.Role;  
  23. import com.jajacode.sample.domain.account.User;  
  24.   
  25. /** 
  26.  * 安全认证最主要的实现类 
  27.  * @author Yockii Hsu 
  28.  * 
  29.  */  
  30. public class ShiroSsoRealm extends AuthorizingRealm {  
  31.   
  32.   @Autowired  
  33.   private AccountManager accountManager;  
  34.    
  35.   public ShiroDbRealm(){  
  36.                 // 设置无需凭证,因为从sso认证后才会有用户名  
  37.     setCredentialsMatcher(new AllowAllCredentialsMatcher());  
  38.                 // 设置token为我们自定义的  
  39.     setAuthenticationTokenClass(TrustedSsoAuthenticationToken.class);  
  40.   }  
  41.    
  42.    
  43.   /** 
  44.    * 认证回调函数,登陆时调用 
  45.    */  
  46.   @Override  
  47.   protected AuthenticationInfo doGetAuthenticationInfo(  
  48.       AuthenticationToken authcToken) throws AuthenticationException {  
  49.     TrustedSsoAuthenticationToken token = (TrustedSsoAuthenticationToken)authcToken;  
  50.     //UsernamePasswordToken token = (UsernamePasswordToken) authcToken;  
  51.      
  52.     Object username = token.getPrincipal();  
  53. //    String username = token.getUsername();  
  54.     //不允许无username  
  55.     if(username==null){  
  56.                         // 自定义异常,于前端捕获  
  57.       throw new AccountException("用户名不允许为空!");  
  58.     }  
  59.      
  60.     return new SimpleAuthenticationInfo(token.getPrincipal(), token.getCredentials(), getName());  
  61.   }  
  62.   
  63.   /** 
  64.    * 授权查询回调函数,进行鉴权但缓存中无用户的授权信息时调用 
  65.    */  
  66.   @Override  
  67.   protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {  
  68.     String loginName = (String) principals.fromRealm(getName()).iterator().next();  
  69.     User user = accountManager.findUserByLoginName(loginName);  
  70.     if(user != null){  
  71.       SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();  
  72.       // 将用户权限放入其中,代码略  
  73.   
  74.       return info;  
  75.     }  
  76.     return null;  
  77.   }  
  78.    
  79.   /** 
  80.    * 清空用户关联权限认证,待下次使用时重新加载。 
  81.    * @param principal 
  82.    */  
  83.   public void clearCachedAuthorizationInfo(String principal){  
  84.     SimplePrincipalCollection principals = new SimplePrincipalCollection(principal, getName());  
  85.     clearCachedAuthorizationInfo(principals);  
  86.   }  
  87.   
  88.   /** 
  89.    * 清空所有关联认证 
  90.    */  
  91.   public void clearAllCachedAuthorizationInfo(){  
  92.     Cache cache = getAuthorizationCache();  
  93.     if (cache != null) {  
  94.       for (Object key : cache.keys()) {  
  95.         cache.remove(key);  
  96.       }  
  97.     }  
  98.   }  
  99. }  

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非常方便!


你可能感兴趣的:(apache,shiro)