Spring+Shiro權(quán)限控制

權(quán)限控制在做項(xiàng)目中,是必不可少的。而關(guān)于權(quán)限控制,目前跟spring兼容比較好的有spring security和shiro。我的上個項(xiàng)目用的就是shiro,但是我另一個同事寫的,這次想自己嘗試下,研究了下shiro。
shiro官方文檔中說shiro的操作都是基于subject,而subject來自securityManager。所以spring整個shiro就是對securityManager的整合,在用戶訪問的時候需要交給shiro進(jìn)行攔截。shiro會進(jìn)行驗(yàn)證。如果你有這個資源或者角色的權(quán)限,就能正常訪問,否則會進(jìn)行攔截。
本文適合未曾接觸shiro但是對spring等基礎(chǔ)框架有了解及經(jīng)驗(yàn)的小伙伴。因?yàn)楸救艘彩切“滓幻?,如有不對之處,請不吝賜教。 ?(?????)?~~


需要的jar包

shiro需要的jar包就一個,我這里用的是shiro-all-1.3.2.jar


在web.xml中加入filter


<!-- 這個filter要寫在所有filter的最前面,保證他是過濾器中第一個起作用的-->

  <filter>
       <filter-name>shiroFilter</filter-name>
       <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
       <init-param>
    <!-- 缺省為false,表示由SpringApplicationContext管理生命周期,置為true則表示由ServletContainer管理 -->
          <param-name>targetFilterLifecycle</param-name>
          <param-value>true</param-value>
      </init-param>
  </filter>
  <filter-mapping>
     <filter-name>shiroFilter</filter-name>
     <url-pattern>/*</url-pattern>
   </filter-mapping>


創(chuàng)建一個shiro.xml文件

然后在web.xml中添加上去


<!-- 加載所有的配置文件 -->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:applicationContext.xml,classpath:shiro.xml</param-value>
    </context-param>


shiro里面的內(nèi)容


<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
    xmlns:mvc="http://www.springframework.org/schema/mvc"
    xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.3.xsd
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd">

    <!-- 配置shiro -->
   <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
       <!-- 指定Shiro驗(yàn)證用戶登錄的類為自定義的Realm(若有多個Realm,可用[realms]屬性代替) -->
       <property name="realm">
           <bean class="com.xx.shiro.MyRealm"/>
       </property>
    </bean>

<!-- Shiro Filter--> 
<bean id="simplePermFilter" class="org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter"></bean> 

<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean ">
<!-- Shiro的核心安全接口,這個屬性是必須的 -->
    <property name="securityManager" ref="securityManager"/>

<!-- 要求登錄時的鏈接(可根據(jù)項(xiàng)目的URL進(jìn)行替換),非必須的屬性,默認(rèn)會找Web工程根目錄下的[/login.jsp] -->
    <property name="loginUrl" value="/sys/toLogin"/>
 <!-- 登錄成功后要跳轉(zhuǎn)的連接(本例中此屬性用不到,因?yàn)榈卿洺晒蟮奶幚磉壿嬕言贚oginController中硬編碼為main.jsp) -->
    <property name="successUrl" value="/sys/login"/>
 <!--用戶訪問未授權(quán)的資源時,所顯示的連接 -->
    <property name="unauthorizedUrl" value="/sys/toLogin"/>
    <!-- 權(quán)限配置 -->
     <property name="filters">    
           <map>    
               <entry key="roles" value-ref="simplePermFilter"/>  
           </map>    
       </property>   
    <!--
        anon:它對應(yīng)的過濾器里面是空的,什么都沒做,另外.do和.jsp后面的*表示參數(shù),比方說[login.jsp?main]這種
        authc:該過濾器下的頁面必須驗(yàn)證后才能訪問,它是內(nèi)置的org.apache.shiro.web.filter.authc.FormAuthenticationFilter
        注意:對于相似的資源,需要將anon的設(shè)置放在authc前面,anon才會生效,因?yàn)镾hiro是從上往下匹配URL的,匹配成功便不再匹配了
    --> 
     <property name="filterChainDefinitions">
        <value>
            /sys/toLogin        = anon
        /sys/Login          = anon
        /sys/videoList      = authc,rolse[管理員]
        </value>
    </property> 
</bean>
<!-- 保證實(shí)現(xiàn)了Shiro內(nèi)部lifecycle函數(shù)的bean執(zhí)行 -->
<bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>

<!--自定義攔截器,目前用不到,后面再說可以不寫-->
<!--<bean id="anyRoles" class="com.xx.shiro.CustomRolesAuthorizationFilter" />  -->
</beans>


shiro的xml寫完就需要寫shiro驗(yàn)證用戶登錄的類MyRealm.java,我這里就直接在數(shù)據(jù)庫里面讀取了。

MyRealM.java

import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.apache.commons.lang3.builder.ReflectionToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.subject.Subject;
import org.springframework.beans.factory.annotation.Autowired;
import com.liaoliao.admin.entity.AdminUser;
import com.liaoliao.admin.entity.Permission;
import com.liaoliao.admin.service.AdminUserService;
import com.liaoliao.admin.service.PermissionService;


public class MyRealm extends AuthorizingRealm {
    
    @Autowired
    private AdminUserService adminUserService;
    
    @Autowired
    private PermissionService permissionService;
    
    
    /**
     * 為當(dāng)前登錄的Subject授予角色和權(quán)限
     * -----------------------------------------------------------------------------------------------
     * 經(jīng)測試:本例中該方法的調(diào)用時機(jī)為需授權(quán)資源被訪問時
     * 經(jīng)測試:并且每次訪問需授權(quán)資源時都會執(zhí)行該方法中的邏輯,這表明本例中默認(rèn)并未啟用AuthorizationCache
     * 個人感覺若使用了Spring3.1開始提供的ConcurrentMapCache支持,則可靈活決定是否啟用AuthorizationCache
     * 比如說這里從數(shù)據(jù)庫獲取權(quán)限信息時,先去訪問Spring3.1提供的緩存,而不使用Shior提供的AuthorizationCache
     * -----------------------------------------------------------------------------------------------
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals){
        //獲取當(dāng)前登錄的用戶名
        String currentUsername = (String)super.getAvailablePrincipal(principals);
        //從數(shù)據(jù)庫中獲取當(dāng)前登錄用戶的詳細(xì)信息
        AdminUser adminUser = adminUserService.findByUserName(currentUsername);
        SimpleAuthorizationInfo simpleAuthorInfo = new SimpleAuthorizationInfo();
     //   Set<String>  perlist = new HashSet<String>();
       if(adminUser != null){
        //在這里添加的role對應(yīng)的就是權(quán)限的名,比如說,你給一個url添加了roles[管理員],有個叫mopoint的用戶登錄了,
        //他想要訪問這個url,那么在這里就需要給他賦予管理員這個權(quán)限,也就是說
        //這里面simpleAuthorInfo.addRole("管理員");加上的就是管理員三個字。
         simpleAuthorInfo.addRole(adminUser.getAdminGroup().getGroupName());

        //這里我是通過數(shù)據(jù)庫讀取出來然后放入集合里面去的。
         /* List<Permission> pers = permissionService.findByGroupId(adminUser.getAdminGroup().getId());
            if(pers!=null && pers.size()>0){
                for(Permission p:pers){
                    perlist.add(p.getNavigation().getNavigationUrl());
                }
                simpleAuthorInfo.addStringPermissions(perlist);
            } */

        //這里的perlist就是你能訪問的url,比如說你數(shù)據(jù)庫存放的一個url:/sys/videoList;這個url需要權(quán)限[管理員]。 
        //在上面已經(jīng)給你的賬號mopoint加上了role:[管理員],對應(yīng)的,這里需要加上這個url。然后你的賬號就能訪問這個url了。
      //比如配置在shiro.xml中的是/sys/videoList,那么這里的url就是"/sys/videoList";
         String url="/sys/videoList";
         simpleAuthorInfo.addStringPermissions(url);
         return simpleAuthorInfo;
         }else{
        //如果返回空表示用戶訪問失敗,會自動跳轉(zhuǎn)到剛才unauthorizedUrl指定的地址。配置在shiro.xml里面
             return null;
         }
    }

    

    /**
     * 驗(yàn)證當(dāng)前登錄的Subject
     * 當(dāng)在登錄時執(zhí)行Subject.login(),就會調(diào)用下面的這個接口:
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authcToken) throws AuthenticationException {
    //實(shí)際上這個authcToken在用戶登錄時通過currentUser.login(token)傳過來的。
        UsernamePasswordToken token = (UsernamePasswordToken)authcToken;
        if(token.getUsername()==null){
    //沒有返回登錄用戶名對應(yīng)的SimpleAuthenticationInfo對象時,就會在LoginController中拋出UnknownAccountException異常
            return null;
        }
        AdminUser adminUser = adminUserService.findByUserName(token.getUsername());
        if(null != adminUser){
            String username = adminUser.getUserName();
            String password = adminUser.getPassWord();
            AuthenticationInfo authcInfo = new SimpleAuthenticationInfo(username, password, this.getName());
            this.setAuthenticationSession(adminUser);
            return authcInfo;
        }else{
            throw new  UnknownAccountException("用戶帳號不存在!");
        }
    }

    /**
     * 將一些數(shù)據(jù)放到ShiroSession中,以便于其它地方使用
     * 比如Controller里面,使用時直接用HttpSession.getAttribute(key)就可以取到
     */
    private void setAuthenticationSession(Object value){
     /*   Subject currentUser = SecurityUtils.getSubject();
        if(null != currentUser){
            Session session = currentUser.getSession();
            session.setTimeout(1000 * 60 * 60 * 2);
            session.setAttribute("currentUser", value);
        }*/
    }
}



上面配置方面的工作已經(jīng)做完了,現(xiàn)在就到登錄的controller中使用。

LoginController.java


@Controller("adminLogin")
@RequestMapping("/sys")
public class LoginController {

    /**
     * 跳轉(zhuǎn)到登錄頁面
     * @param request
     * @return
     */
    @RequestMapping("/toLogin")
    public String toLogin(HttpServletRequest request){
        return "page/login";
    }

    //用戶退出
       @RequestMapping("/logout")
        public String logout(HttpSession session){
            String currentUser = (String)session.getAttribute("currentUser");
            System.out.println("用戶[" + currentUser + "]準(zhǔn)備登出");
            SecurityUtils.getSubject().logout();
            System.out.println("用戶[" + currentUser + "]已登出");
            return InternalResourceViewResolver.REDIRECT_URL_PREFIX + "/login";
        }

    /**
     * 驗(yàn)證登錄:
     * @param request
     * @param response
     * @param userName
     * @param passCode
     * @return
     */
    @RequestMapping("/Login")
    public String Login(HttpServletRequest request,HttpServletResponse response,String userName,String passWord){
            UsernamePasswordToken token=new UsernamePasswordToken();
            token.setRememberMe(true);
            //獲取當(dāng)前的Subject
            Subject currentUser = SecurityUtils.getSubject();
            try {
                currentUser.login(token);
                System.out.println("對用戶[" + token.getUsername() + "]進(jìn)行登錄驗(yàn)證...驗(yàn)證通過");
            }catch(Exception e){
            //這里細(xì)分,大概有五種異常,有興趣可以點(diǎn)擊文章最后的鏈接去看看。
                e.printStackTrace();
                System.out.println("用戶名或密碼不正確");
                request.setAttribute("message_login", "用戶名或密碼不正確");
                return InternalResourceViewResolver.FORWARD_URL_PREFIX +"/sys/toLogin";
            }    
            //驗(yàn)證是否登錄成功,這里的isAuthenticated()方法有時候不怎么靈通,具體我也不知道啥原因,歡迎小伙伴找我探討~
            if(currentUser.isAuthenticated()){
                AminUser au=adminUserService.findByUserName(userName);
                AdminUser sessionAu= (AdminUser) request.getSession().getAttribute("adminUser");
                if(au == null && sessionAu == null){
                    System.out.println(111);
                    return InternalResourceViewResolver.FORWARD_URL_PREFIX +"/sys/toLogin";
                }
                request.setAttribute("adminUser", au);
                HttpSession session = request.getSession();
                session.setAttribute("adminUser", au);
                session.setAttribute("token", token);
                List<Permission> pList=permissionService.findByGroupId(au.getAdminGroup().getId());
                request.setAttribute("list", pList);    
                return "forward:/sys/theHome";
            }else{
                System.out.println("未通過!");
                token.clear();
                return InternalResourceViewResolver.REDIRECT_URL_PREFIX +"/sys/toLogin";
            }
      }

}


上面的登錄方法就算是寫完了,下面是我的數(shù)據(jù)庫設(shè)置:


數(shù)據(jù)庫設(shè)置:


adminUser

字段 類型 大小
id int 11
user_name varchar 255
pass_word varchar 255
status int 1
group_id int 11
add_time datetime 0

adminGrop

字段 類型 大小
id int 11
group_name varchar 255
info varchar 255
status int 11
add_time datetime 0

navigation(這里存放的就是各個資源的路徑)

字段 類型 大小
id int 11
navigation_name varchar 255
navigation_url varchar 255
parent_id int 11

permission (關(guān)聯(lián)navigation表跟adminGroup表)

字段 類型 大小
id int 11
group_id int 11
navigation_id int 11

然后你創(chuàng)建兩個用戶組,一個用戶組的groupName是管理員,另一個叫人事;然后在創(chuàng)建一個用戶admin是屬于管理員這個用戶組的,創(chuàng)建一個用戶aaa是屬于人事這個用戶組的,你
用admin賬號登錄的時候是可以訪問配置在shiro.xml這個文件里面的那個url:/sys/videoList;如果是用aaa登錄的話,你訪問/sys/videoList這個路徑是會跳轉(zhuǎn)到/sys/toLogin這個登錄頁面的。


借鑒的大神的網(wǎng)站博客

http://jadyer.cn/2013/09/30/springmvc-shiro
http://jinnianshilongnian.iteye.com/blog/2024723
http://www.sojson.com/shiro#so604570995


寫到這里,這個第一版的簡陋的shiro權(quán)限管理算是完成了,在后面我加了動態(tài)數(shù)據(jù)庫讀取權(quán)限,還有個自定義的配置。關(guān)于動態(tài)更新不需要重啟服務(wù)器還有點(diǎn)問題,如果小伙伴們有想法,可以聯(lián)系我一起討論或者在下面留言~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容