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中進行前後臺用戶的區分。進而執行自己的業務邏輯啦

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章