1. 程式人生 > >Shiro 管理多個realm 實現前後臺分離

Shiro 管理多個realm 實現前後臺分離

使用shiro 由於公司的業務上的需求前後臺公用的一張表,要實現前臺使用者和後臺使用者的分離攔截需要書寫多個realm 用來驗證前臺使用者還是後臺使用者。直接上程式碼
1.書寫一個自定的token UsernamePasswordUsertypeToken 繼承UsernamePasswordToken 用來判斷使用者型別UsernamePasswordUsertypeToken 多出一個欄位用來區分使用者型別

package com.jscredit.zxypt.shiro;

import org.apache.shiro.authc.UsernamePasswordToken;

/**
 * 拓展shiro 中的UsernamePasswordToken
 * @author
liyaqiang * */
public class UsernamePasswordUsertypeToken extends UsernamePasswordToken { private static final long serialVersionUID = 1L; private String usertype ; public String getUsertype() { return usertype; } public void setUsertype(String usertype) { this
.usertype = usertype; } public UsernamePasswordUsertypeToken(String loginName, String password, String usertype) { super(loginName, password); this.usertype = usertype; } }

定義前臺的驗證的realm

public class UserLoginRealm  extends AuthorizingRealm{

    private
final Logger LOGGER = LoggerFactory.getLogger(UserLoginRealm.class); private static final String ALGORITHM = "MD5"; @Autowired private UserService userService; @Autowired private PermissionService permissionService; @Autowired private UserKzService userKzService; public UserLoginRealm(){ super(); } /** * 為當前登入的Subject授予角色和許可權 ,該方法的呼叫時機為需授權資源被訪問時 * 並且每次訪問需授權資源時都會執行該方法中的邏輯,這表明本例中預設並未啟用AuthorizationCache * 個人感覺若使用了Spring3.1開始提供的ConcurrentMapCache支援,則可靈活決定是否啟用AuthorizationCache * 比如說這裡從資料庫獲取許可權資訊時,先去訪問Spring3.1提供的快取,而不使用Shior提供的AuthorizationCache */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { ShiroLoginUser shiroLoginUser = (ShiroLoginUser) principals.fromRealm(getName()).iterator().next(); String username = shiroLoginUser.getAccount(); if (username != null) { SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); // 查詢使用者授權資訊 List<Permission> perList = permissionService.getShiro("O"); if (perList != null && perList.size() != 0) { for (Permission permission : perList) { info.addStringPermission(permission.getPmsnCode()); } return info; } } return null; } /** * 驗證前臺當前登入的Subject 本例中該方法的呼叫時機為UserLoginController.login()方法中執行Subject.login()時 */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authcToken) throws AuthenticationException { // 這個authcToken是從userLoginController裡面currentUser.login(token)傳過來的 UsernamePasswordUsertypeToken fronttoken = (UsernamePasswordUsertypeToken) authcToken; LOGGER.debug("驗證當前Subject時獲取到token為" + ReflectionToStringBuilder.toString(fronttoken, ToStringStyle.MULTI_LINE_STYLE)); User users = userService.getUserByName(fronttoken.getUsername()); UserKz userKz= userKzService.selectByPrimaryKey(users.getUserId()); String usertype =userKz.getUserType(); // Shiro完成對比邏輯,返回和令牌相關的正確的驗證資訊,第一個引數填登入使用者名稱,第二個引數填合法的登入密碼 if (users != null&&usertype!="00") { ShiroLoginUser shiroLoginUser = new ShiroLoginUser(users.getUserId(), users.getAccount(),userKz.getUserType()); AuthenticationInfo authcInfo = new SimpleAuthenticationInfo(shiroLoginUser,users.getPassword(), getName()); this.setSession("shiroLoginUser", shiroLoginUser); return authcInfo; } else { throw new AuthenticationException(); } // 沒有返回登入使用者名稱對應的SimpleAuthenticationInfo物件時,就會在LoginController中丟擲UnknownAccountException異常 } /** * 更新使用者授權資訊快取. */ public void clearCachedAuthorizationInfo(String principal) { SimplePrincipalCollection principals = new SimplePrincipalCollection(principal, getName()); clearCachedAuthorizationInfo(principals); } /** * 清除所有使用者授權資訊快取. */ public void clearAllCachedAuthorizationInfo() { Cache<Object, AuthorizationInfo> cache = getAuthorizationCache(); if (cache != null) { for (Object key : cache.keys()) { cache.remove(key); } } } @PostConstruct public void initCredentialsMatcher() {// MD5加密 HashedCredentialsMatcher matcher = new HashedCredentialsMatcher(ALGORITHM); setCredentialsMatcher(matcher); } /** * 將一些資料放到ShiroSession中,以便於其它地方使用 * * 比如Controller,使用時直接用HttpSession.getAttribute(key)就可以取到 */ private void setSession(Object key, Object value) { Subject subject = SecurityUtils.getSubject(); if (null != subject) { Session session = subject.getSession(); LOGGER.debug("Session預設超時時間為[" + session.getTimeout() + "]毫秒"); if (null != session) { session.setAttribute(key, value); } } } }

2.後臺realm

public class LoginRealm extends AuthorizingRealm {

    private final Logger LOGGER = LoggerFactory.getLogger(LoginRealm.class);

    private static final String ALGORITHM = "MD5";

    @Autowired
    private UserService userService;
    @Autowired
    private PermissionService permissionService;
    @Autowired
    private UserKzService userKzService;

    public LoginRealm() {
        super();
    }

    /**
     * 驗證當前登入的Subject 本例中該方法的呼叫時機為LoginController.login()方法中執行Subject.login()時
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authcToken)
            throws AuthenticationException {
        // 這個authcToken是從LoginController裡面currentUser.login(token)傳過來的
        UsernamePasswordUsertypeToken token = (UsernamePasswordUsertypeToken) authcToken;
        LOGGER.debug("驗證當前Subject時獲取到token為"
                + ReflectionToStringBuilder.toString(token, ToStringStyle.MULTI_LINE_STYLE));
        User users = userService.getUserByName(token.getUsername());
        UserKz userKz =userKzService.selectByPrimaryKey(users.getUserId());
        String usertype =userKz.getUserType();
            // Shiro完成對比邏輯,返回和令牌相關的正確的驗證資訊,第一個引數填登入使用者名稱,第二個引數填合法的登入密碼
            if (users != null&&usertype.equals("00")) {
                ShiroUser shiroUser = new ShiroUser(users.getUserId(), users.getAccount());
                AuthenticationInfo authcInfo = new SimpleAuthenticationInfo(shiroUser,
                        users.getPassword(), getName());
                this.setSession("shiroUser", shiroUser);
                return authcInfo;
            } else {
                throw new AuthenticationException();
            }
            // 沒有返回登入使用者名稱對應的SimpleAuthenticationInfo物件時,就會在LoginController中丟擲UnknownAccountException異常


    }

    /**
     * 為當前登入的Subject授予角色和許可權 ,該方法的呼叫時機為需授權資源被訪問時
     * 並且每次訪問需授權資源時都會執行該方法中的邏輯,這表明本例中預設並未啟用AuthorizationCache
     * 個人感覺若使用了Spring3.1開始提供的ConcurrentMapCache支援,則可靈活決定是否啟用AuthorizationCache
     * 比如說這裡從資料庫獲取許可權資訊時,先去訪問Spring3.1提供的快取,而不使用Shior提供的AuthorizationCache
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        ShiroUser shiroUser = (ShiroUser) principals.fromRealm(getName()).iterator().next();
        String username = shiroUser.getAccount();
        if (username != null) {
            SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();

            // 查詢使用者授權資訊
            List<Permission> perList = permissionService.getShiro("O");

            if (perList != null && perList.size() != 0) {
                for (Permission permission : perList) {
                    info.addStringPermission(permission.getPmsnCode());
                }
                return info;
            }
        }
        return null;
    }

    /**
     * 更新使用者授權資訊快取.
     */
    public void clearCachedAuthorizationInfo(String principal) {
        SimplePrincipalCollection principals = new SimplePrincipalCollection(principal, getName());
        clearCachedAuthorizationInfo(principals);
    }

    /**
     * 清除所有使用者授權資訊快取.
     */
    public void clearAllCachedAuthorizationInfo() {
        Cache<Object, AuthorizationInfo> cache = getAuthorizationCache();
        if (cache != null) {
            for (Object key : cache.keys()) {
                cache.remove(key);
            }
        }
    }

    @PostConstruct
    public void initCredentialsMatcher() {// MD5加密
        HashedCredentialsMatcher matcher = new HashedCredentialsMatcher(ALGORITHM);
        setCredentialsMatcher(matcher);
    }

    /**
     * 將一些資料放到ShiroSession中,以便於其它地方使用
     *
     * 比如Controller,使用時直接用HttpSession.getAttribute(key)就可以取到
     */
    private void setSession(Object key, Object value) {
        Subject subject = SecurityUtils.getSubject();
        if (null != subject) {
            Session session = subject.getSession();
            LOGGER.debug("Session預設超時時間為[" + session.getTimeout() + "]毫秒");
            if (null != session) {
                session.setAttribute(key, value);
            }
        }
    }
}

3.書寫總的管理realm

public class DefautModularRealm extends org.apache.shiro.authc.pam.ModularRealmAuthenticator {

       private Map<String, Object> definedRealms;  

        /** 
         * 多個realm實現 
         */  
        @Override  
        protected AuthenticationInfo doMultiRealmAuthentication(Collection<Realm> realms, AuthenticationToken token) {  
            return super.doMultiRealmAuthentication(realms, token);  
        }  
        /** 
         * 呼叫單個realm執行操作 
         */  
        @Override  
        protected AuthenticationInfo doSingleRealmAuthentication(Realm realm,AuthenticationToken token) {  

            // 如果該realms不支援(不能驗證)當前token  
            if (!realm.supports(token)) {  
                throw new ShiroException("token錯誤!");  
            }  
            AuthenticationInfo info = null;  
            try {  
                info = realm.getAuthenticationInfo(token);  

                if (info == null) {  
                    throw new ShiroException("token不存在!");  
                }  
            } catch (Exception e) {  
                throw new ShiroException("使用者名稱或者密碼錯誤!");  
            }  
            return info;  
        } 


        /** 
         * 判斷登入型別執行操作 
         */  
        @Override  
        protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken)throws AuthenticationException {  
            this.assertRealmsConfigured();  
            Realm realm = null;  
            UsernamePasswordUsertypeToken token = (UsernamePasswordUsertypeToken) authenticationToken;  
           //判斷是否是後臺使用者
            if (token.getUsertype().equals("admin")) {  
                realm = (Realm) this.definedRealms.get("loginRealm");  
            }  
            else{
                realm = (Realm) this.definedRealms.get("userloginRealm");  
            }

            return this.doSingleRealmAuthentication(realm, authenticationToken);  
        }  

        /** 
         * 判斷realm是否為空 
         */  
        @Override  
        protected void assertRealmsConfigured() throws IllegalStateException {  
            this.definedRealms = this.getDefinedRealms();  
            if (CollectionUtils.isEmpty(this.definedRealms)) {  
                throw new ShiroException("值傳遞錯誤!");  
            }  
        }  

        public Map<String, Object> getDefinedRealms() {  
            return this.definedRealms;  
        }  

        public void setDefinedRealms(Map<String, Object> definedRealms) {  
            this.definedRealms = definedRealms;  
        }  






}

4.書寫包含使用者型別的token繼承 UsernamePasswordToken

public class UsernamePasswordUsertypeToken extends UsernamePasswordToken {

        private static final long serialVersionUID = 1L;
        private String usertype ;

        public String getUsertype() {
            return usertype;
        }
        public void setUsertype(String usertype) {
            this.usertype = usertype;
        }

        public UsernamePasswordUsertypeToken(String loginName, String password, String usertype) {

            super(loginName, password);

            this.usertype = usertype;

        }

}

4.將配置寫入到shiro的配置檔案中

 <!-- 使用者授權資訊Cache 快取在本機記憶體,不支援叢集 -->
    <bean id="cacheManager" class="org.apache.shiro.cache.MemoryConstrainedCacheManager"/>

    <!-- 繼承自AuthorizingRealm的自定義Realm,即指定Shiro驗證使用者登入的類為自定義的ShiroDbRealm.java -->
    <bean id="loginRealm" class="com.jscredit.base.shiro.LoginRealm">
        <property name="cacheManager" ref="cacheManager"/>
    </bean>

     <!-- 繼承自AuthorizingRealm的自定義Realm,即指定Shiro驗證前臺使用者登入的類為自定義的ShiroDbRealm.java -->
    <bean id="userloginRealm" class="com.jscredit.zxypt.shiro.UserLoginRealm">
        <property name="cacheManager" ref="cacheManager"/>
    </bean>

    <!--多個realm 的集中管理  -->
    <bean id="defineModularRealmAuthenticator" class=" com.jscredit.zxypt.shiro.DefautModularRealm"> 
        <property name="definedRealms">    
            <map>    
                <entry key="loginRealm" value-ref="loginRealm" />    
                <entry key="userloginRealm" value-ref="userloginRealm" />    
            </map>   
        </property>  
        <property name="authenticationStrategy">    
            <bean class="org.apache.shiro.authc.pam.FirstSuccessfulStrategy" />    
        </property> 
    </bean>   
    <!-- Shiro預設會使用Servlet容器的Session,可通過sessionMode屬性來指定使用Shiro原生Session -->
    <!-- 即<property name="sessionMode" value="native"/>,詳細說明見官方文件 -->
    <!-- 這裡主要是設定自定義的單Realm應用,若有多個Realm,可使用'realms'屬性代替 -->
    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
         <property name="authenticator" ref="defineModularRealmAuthenticator" /> 
     <!--    <property name="realm" ref="loginRealm"/> -->
        <property name="realms"  >
            <list>
               <bean id="loginRealm" class="com.jscredit.base.shiro.LoginRealm" /> 
               <bean id="userloginRealm" class="com.jscredit.zxypt.shiro.UserLoginRealm" /> 
            </list>
        </property>
        <property name="cacheManager" ref="cacheManager"/>
    </bean>

這樣就可以在controller 中根據前後臺傳入不同的type 值 ,然後總的realm 會根據當前型別來判斷執行哪個realm 進而執行不同的驗證。然後我們再在realm中進行前後臺使用者的區分。進而執行自己的業務邏輯啦