1. 程式人生 > >(轉)shiro權限框架詳解06-shiro與web項目整合(下)

(轉)shiro權限框架詳解06-shiro與web項目整合(下)

tex web項目 ssd ndis form認證 lec rfi 出身 javadoc

http://blog.csdn.net/facekbook/article/details/54962975

shiro和web項目整合,實現類似真實項目的應用

  • web項目中認證
  • web項目中授權
  • shiro緩存
  • sessionManager使用
  • 驗證碼功能實現
  • 記住我功能實現

web項目中認證

實現方式

修改CustomRealmdoGetAuthenticationInfo 方法,從數據庫中獲取用戶信息,CustomRealm 返回查詢到的用戶信息,包括(加密後的密碼字符串和salt以及上文中的菜單)。

修改 doGetAuthenticationInfo 方法

@Autowired
private SysService sysService;

/**
 * 用於認證
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) 
            throws AuthenticationException {
    //第一步:通過token獲取身份信息
    String userCode = (String) token.getPrincipal();

    //從數據庫中查詢賬號信息是否存在
    SysUser sysUser = null; 
    try {
        sysUser = sysService.findSysUserByUserCode(userCode);
    } catch (Exception e1) {
        e1.printStackTrace();
    }

    //如果查詢不到返回null
    if(sysUser==null){
        return null;
    }

    //第二步:通過獲取的身份信息進行數據庫查詢
    String password = sysUser.getPassword();

    //組裝ActiveUser類
    ActiveUser activeUser = new ActiveUser();
    activeUser.setUserid(sysUser.getId());
    activeUser.setUsercode(sysUser.getUsercode());
    activeUser.setUsername(sysUser.getUsername());

    //查詢菜單信息
    List<SysPermission> menus = null;
    try {
        menus = sysService.findMenuListByUserId(sysUser.getUsercode());
    } catch (Exception e) {
        e.printStackTrace();
    }
    activeUser.setMenus(menus);

    //得到鹽
    String salt = sysUser.getSalt();

    //如果查詢到結果返回AuthenticationInfo
    AuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(activeUser, password,ByteSource.Util.bytes(salt), "");

    return authenticationInfo;
}
  • 1

設置憑證匹配器

在我們的數據庫存儲的是MD5散列值,在自定義的realm中需要自定義設置散列算法以及散列次數。這裏和前面介紹的散列認證的配置方式類似。

<!-- 自定義的realm -->
<bean id="customRealm" class="cn.itcast.ssm.shiro.CustomRealm">
<!--將匹配器設置到realm中 -->
    <property name="credentialsMatcher" ref="credentialsMatcher" />
</bean>
<!--定義憑證匹配器 -->
<bean id="credentialsMatcher" class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
<!-- 設置hash散列算法 -->
    <property name="hashAlgorithmName" value="md5" />
    <!-- 設置hash散列次數 -->
    <property name="hashIterations" value="1" />
</bean>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

驗證認證功能

數據庫存在兩條用戶數據,具體如下:
技術分享
其中:張三 的密碼是 111111。當然也可以自己修改密碼:

SELECT MD5(‘密碼‘+‘鹽‘)
  • 1
  • 1

如果可能正常登錄則沒有問題。

授權

實現方式

修改 CustomRealm 中的 doGetAuthorizationInfo 方法從數據庫中查詢授權信息。
這裏講解註解式授權和jsp標簽授權方法。

修改 doGetAuthorizationInfo 方法

/**
  * 用於授權
  */
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {

    //獲取身份信息,這個字段是在認證通過後返回的,也就是通過執行認證方法返回的AuthenticationInfo類的第一個屬性
    ActiveUser activeUser = (ActiveUser) principals.getPrimaryPrincipal();

    //通過userId查詢數據庫獲取該身份信息的所有權限。
    List<SysPermission> permissionList = null;
    try {
        permissionList = sysService.findPermissionListByUserId(activeUser.getUserid());
       } catch (Exception e) {
        e.printStackTrace();
       }
    List<String> permissions = new ArrayList<>();
    if(permissionList!=null){
        for(SysPermission p:permissionList){
            permissions.add(p.getPercode());
        }
    }

    //查詢到權限信息,然後返回權限信息
    SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
    //將查詢到的授權信息填充到SimpleAuthorizationInfo中
    simpleAuthorizationInfo.addStringPermissions(permissions);
    return simpleAuthorizationInfo;
}
  • 1

controller類的AOP支持

<!--開啟aop,對類代理 -->
<aop:config proxy-target-class="true"></aop:config>
<!-- 開啟shiro註解支持 -->
<bean
     class="
org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
    <property name="securityManager" ref="securityManager" />
</bean>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

在ItemsController類方法上添加註解

//商品信息方法
@RequestMapping("/queryItems")
@RequiresPermissions("item:query")//通過註解的方式進行授權
public ModelAndView queryItems(HttpServletRequest request) throws Exception {

    System.out.println(request.getParameter("id"));

    //調用service查詢商品列表
    List<ItemsCustom> itemsList = itemsService.findItemsList(null);

    ModelAndView modelAndView = new ModelAndView();
    modelAndView.addObject("itemsList", itemsList);
    // 指定邏輯視圖名
    modelAndView.setViewName("itemsList");

    return modelAndView;
}
  • 1

jsp標簽授權

在jsp頁面添加shiro taglib

<%@ taglib uri="http://shiro.apache.org/tags" prefix="shiro" %>
  • 1
  • 1

shiro包括的jsp標簽

標簽名稱標簽條件(均顯示標簽內容)
<shiro:authenticated> 登錄之後
<shiro:notAuthenticated> 不在登錄狀態時
<shiro:guest> 用戶在沒有RememberMe時
<shiro:user> 用戶在RememberMe時
<shiro:hasAnyRoles name="abc,123" > 在有abc或者123角色時
<shiro:hasRole name="abc"> 擁有角色abc
<shiro:lacksRole name="abc"> 沒有角色abc
<shiro:hasPermission name="abc"> 擁有權限資源abc
<shiro:lacksPermission name="abc"> 沒有abc權限資源
<shiro:principal> 顯示用戶身份名稱
<shiro:principal property="username"/> 顯示用戶身份中的屬性值

修改itemsList.jsp文件

    <!-- 具有item:update權限才顯示修改鏈接,沒有的話不顯示。相當於if(hasPermission(item:update)) -->
<shiro:hasPermission name="item:update">
    <a href="${pageContext.request.contextPath }/items/editItems.action?id=${item.id}">修改</a>
</shiro:hasPermission>

授權測試

當調用controller的一個方法時,由於該方法加了@RequiresPermissions("item:query") 註解,shiro會調用realm獲取數據庫中的權限信息,看 item:query 是否在權限數據中存在,如果不存在就拒絕訪問,如果存在就授權通過

當展現一個jsp頁面時,頁面中如果遇到 <shiro:hasPermission name="item:update"> 標簽,shiro調用realm獲取數據庫中的權限信息,看item:update 是否在權限數據中存在,如果不存在就不顯示標簽包含內容,如果存在則顯示。

在這裏只要遇到註解或shiro jsp標簽授權,就會調用realm查詢數據庫,在這裏需要引入緩存解決。

shiro緩存

針對授權時頻繁查詢數據庫的問題,引入shiro緩存。

緩存流程

用戶認證通過。
用戶第一次授權:調用realm查詢數據庫。
用戶第二次授權:不調用realm查詢數據庫,直接從緩存中讀取授權信息。

使用 ehcache

添加Ehcache的jar包
技術分享
配置Ehcache配置文件:
新建shiro-ehcache.xml

<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:noNamespaceSchemaLocation="../config/ehcache.xsd">
    <!--diskStore:緩存數據持久化的目錄 地址  -->
    <diskStore path="E:\develop\ehcache" />
    <defaultCache 
        maxElementsInMemory="1000" 
        maxElementsOnDisk="10000000"
        eternal="false" 
        overflowToDisk="false" 
        diskPersistent="false"
        timeToIdleSeconds="120"
        timeToLiveSeconds="120" 
        diskExpiryThreadIntervalSeconds="120"
        memoryStoreEvictionPolicy="LRU">
    </defaultCache>
</ehcache>
  • 1

配置cacheManager

<!--securityManager安全管理器 -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
    <property name="realm" ref="customRealm" />
    <property name="cacheManager" ref="cacheManager"/>
</bean>
<!-- 定義shiro緩存管理器 -->
<bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
        <property name="cacheManagerConfigFile" value="classpath:shiro-ehcache.xml"/>
</bean>
  • 1

清空緩存

當用戶權限修改後,用戶再次登錄shiro會自動調用realm從數據庫獲取權限數據,如果在修改權限後想立即清除緩存則可以調用realm的clearCache方法清除。
CustomRealm 中定義clearCached方法:

    /**
     * 清除緩存方法
     */
    public void clearCache(){
        PrincipalCollection principals = SecurityUtils.getSubject().getPrincipals();
        super.clearCache(principals);   

    }
  • 1

驗證碼功能實現

實現方式

shiro使用FormAuthenticationFilter進行表單認證,驗證碼校驗的功能應該加在FormAuthenticationFilter中,在認證之前進行驗證碼校驗。

自定義FormAuthenticationFilter

public class CustomFormAuthenticationFilter extends FormAuthenticationFilter{

    /**
     * 原AuthenticationFilter驗證方法
     */
    @Override
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {

        //獲取正確的驗證碼和用戶輸入的驗證碼進行比對
        HttpServletRequest httpServletRequest = (HttpServletRequest)request;

        HttpSession session = httpServletRequest.getSession();

        //從session獲取正確驗證碼
        String validateCode = (String) session.getAttribute("validateCode");
        //取出頁面的驗證碼
        String randomcode = (String) httpServletRequest.getParameter("randomcode");
        if(validateCode!=null && randomcode!=null && !validateCode.equals(randomcode)){
            //驗證碼不相同,給shiroLoginFailure屬性設置值
            request.setAttribute("shiroLoginFailure","randomcodeError");
            //拒絕訪問,不再校驗賬號和密碼
            return true;
        }

        return super.onAccessDenied(request, response);
    }

}
  • 1

配置自定義的FormAuthenticationFilter

在applicationContext-shiro.xml文件中配置

    <!-- 自定義form認證過濾器 -->
    <bean id="formAuthenticationFilter" class="cn.itcast.ssm.shiro.CustomFormAuthenticationFilter">
        <!--表單中賬號的name屬性的值-->
        <property name="usernameParam" value="account"/>
        <!--表單中賬號的password屬性的值-->
        <property name="passwordParam" value="accountPassword"/>
    </bean>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

修改 shiroFilter 配置

    <!-- web.xml中shiro的filter對應的bean -->
    <!-- shiro的web過濾器 -->
    <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
        <property name="securityManager" ref="securityManager" />
        <!-- logiUrl認證提交地址,如果沒有認證通過將會請求此地址進行認證,請求此地址將由formAuthenticationFilter進行表單認證 -->
        <property name="loginUrl" value="/login.action" />
        <!-- 認證成功後統一跳轉到first.action,建議不配置,shiro認證成功自動到上一個鏈接 -->
        <property name="successUrl" value="/first.action" />
        <!-- 通過unauthorizedUrl指定沒有權限時跳轉頁面 -->
        <property name="unauthorizedUrl" value="/refuse.jsp" />
        <!-- 自定義filter配置 -->
        <property name="filters">
            <map>
                <entry key="authc" value-ref="formAuthenticationFilter"/>
            </map>
        </property>
  • 1

修改 LoginControllerlogin 方法

    @RequestMapping("/login")
    public String login(HttpServletRequest request)throws Exception{

        //如果登錄失敗從request中獲取認證異常信息,shiroLoginFailure就是shiro異常類的全限定名
        String exceptionClassName = (String) request.getAttribute("shiroLoginFailure");
        if(exceptionClassName!=null){
            if(UnknownAccountException.class.getName().equals(exceptionClassName)){
                throw new CustomException("賬號不存在");
            }else if(IncorrectCredentialsException.class.getName().equals(exceptionClassName)){
                throw new CustomException("用戶名或密碼錯誤");
            }else if("randomcodeError".equals(exceptionClassName)){
                throw new CustomException("驗證碼錯誤");
            }else{
                throw new Exception();//最終在異常處理器生成未知錯誤
            }
        }
        //此方法不處理登錄成功(認證成功),shiro認證成功會自動跳轉到上一個請求路徑。
        //登錄失敗還到login頁面
        return "login";
    }
  • 1

在登錄頁面添加驗證碼

    <TR>
        <TD>用戶名:</TD>
        <TD colSpan="2"><input type="text" id="usercode" name="account" style="WIDTH: 130px" />   </TD>
    </TR>
    <TR>
        <TD>密 碼:</TD>
        <TD><input type="password" id="pwd" name="accountPassword" style="WIDTH: 130px" /></TD>
    </TR>
    <TR>
        <TD>驗證碼:</TD>
        <TD><input id="randomcode" name="randomcode" size="8" /> <img id="randomcode_img" src="${baseurl}validatecode.jsp" alt="" width="56" height="20" align=‘absMiddle‘ /> <a href=javascript:randomcode_refresh()>刷新</a></TD>
    </TR> 
  • 1

實現記住我功能

用戶登錄選擇”記住我”選項,本次登錄成功會向cookie寫身份信息,下次登錄從cookie中取出身份信息實現自動登錄。

用戶身份信息相關類實現 java.io.Serializable 接口

向cookie記錄身份信息的對象需要實現序列號接口,如下:

public class ActiveUser implements java.io.Serializable {
public class SysPermission implements java.io.Serializable{

配置 rememberMeManager

<!--配置記住我cookie-->
<bean id="rememberMeCookie" class="org.apache.shiro.web.servlet.SimpleCookie">
    <!-- rememerMe是cookie名稱 -->
    <constructor-arg value="rememberMe"/>
    <property name="maxAge" value="2592000"/>
</bean>
<!-- rememberMeManager管理器,寫cookie,取出cookie生成用戶信息 -->
<bean id="rememberMeManager" class="org.apache.shiro.web.mgt.CookieRememberMeManager">
        <property name="cookie" ref="rememberMeCookie"/>
</bean>

添加到securityManager中

    <!--securityManager安全管理器 -->
    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
        <property name="realm" ref="customRealm" />
        <property name="cacheManager" ref="cacheManager"/>
        <!-- 記住我 -->
        <property name="rememberMeManager" ref="rememberMeManager"/>
    </bean>
  • 1

修改登錄頁面

<TR>
    <TD></TD>
    <TD><input type="checkbox" name="rememberMe">記住我</TD>
</TR> 

修改rememberMe的input名稱

在前面的配置中修改了賬號和密碼的input的name屬性,”記住我”的name屬性值也可以修改

    <!-- 自定義form認證過濾器 -->
    <bean id="formAuthenticationFilter" class="cn.itcast.ssm.shiro.CustomFormAuthenticationFilter">
        <!--表單中賬號的name屬性的值-->
        <property name="usernameParam" value="account"/>
        <!--表單中賬號的password屬性的值-->
        <property name="passwordParam" value="accountPassword"/>
        <!-- 修改記住我的name屬性的值 -->
        <property name="rememberMeParam" value="rememberMe"/>
    </bean>
  • 1

測試記住我功能

選擇自動登錄後,需要查看cookie是否有rememberMe
技術分享

使用UserFilter

在前一篇博客中有說明UserFilter的功能如下:

user:例如/admins/user/**=user沒有參數表示必須存在用戶, 身份認證通過或通過記住我認證通過的可以訪問,當登入操作時不做檢查

我們修改applicationContext-shiro.xml配置文件

    <!-- shiro的web過濾器 -->
    <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
        <property name="securityManager" ref="securityManager" />
        <!-- logiUrl認證提交地址,如果沒有認證通過將會請求此地址進行認證,請求此地址將由formAuthenticationFilter進行表單認證 -->
        <property name="loginUrl" value="/login.action" />
        <!-- 認證成功後統一跳轉到first.action,建議不配置,shiro認證成功自動到上一個鏈接 -->
        <property name="successUrl" value="/first.action" />
        <!-- 通過unauthorizedUrl指定沒有權限時跳轉頁面 -->
        <property name="unauthorizedUrl" value="/refuse.jsp" />
        <!-- 自定義filter配置 -->
        <property name="filters">
            <map>
                <entry key="authc" value-ref="formAuthenticationFilter"/>
            </map>
        </property>

        <!-- 過濾器鏈定義,從上向下順序執行,一般將/**放在最後面 -->
        <property name="filterChainDefinitions">
            <value>
                <!--靜態資源可以匿名訪問 -->
                /images/** = anon
                /js/** = anon
                /styles/** = anon
                /validatecode.jsp = anon
                <!-- 請求logout.action地址,shiro去清除session -->
                /logout.action = logout
                <!-- 配置需要授權的url,查詢商品需要有商品查詢權限 -->
                <!-- /items/queryItems.action = perms[item:query] /items/editItems.action 
                    = perms[item:update] -->

                <!-- 配置記住我或認證通過可以訪問地址 -->
                /index.jsp = user
                /first.action = user
                /welcome.jsp = user
                <!-- /**=authc 表示所有的url都需要認證才能訪問 -->
                /** = authc
            </value>
        </property>
    </bean>

blog項目的下載地址

點擊進入下載頁面

(轉)shiro權限框架詳解06-shiro與web項目整合(下)