1. 程式人生 > >shiro與springmvc整合

shiro與springmvc整合

飛哥路徑 1.1shiro與springweb專案的整合

shiro 與 springweb 基於 url攔截,整合注意兩點 : 1、shiro 與 spring整合 2、加入shiro對web應用的支援

1.2 加入shiro的jar包

shiro-spring(依賴web) shiro-web shiro-core 1.3 web.xml新增shiro filter

<filter>
    <filter-name>shiroFilter</filter-name>
    <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
    <!-- 設定true由servlet容器控制filter的生命週期 -->
    <init-param>
        <param-name>targetFilterLifecycle</param-name>
        <param-value>true</param-value>
    </init-param>
    <!-- 設定spring容器filter的bean id,如果不設定則找與filter-name一致的bean-->
    <init-param>
        <param-name>targetBeanName</param-name>
        <param-value>shiroFilter</param-value>
    </init-param>
</filter>
<filter-mapping>
    <filter-name>shiroFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

1.4 applicationContext-shiro.xml

<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
    <property name="securityManager" ref="securityManager" />
    <!-- loginUrl認證提交地址,如果沒有認證將會請求此地址進行認證,請求此地址將由formAuthenticationFilter進行表單認證 -->
    <property name="loginUrl" value="/login.action" />
             <!-- 通過unauthoeizedUrl 指定沒有許可權操作時跳轉頁面 -->
    <property name="unauthorizedUrl" value="/refuse.jsp" />
    <!-- 過慮器鏈定義,從上向下順序執行,一般將/**放在最下邊 -->
    <property name="filterChainDefinitions">
        <value>
            <!-- 退出攔截,請求logout.action執行退出操作 -->
            /logout.action = logout
            <!-- 無權訪問頁面 -->
            /refuse.jsp = anon
            <!-- roles[XX]表示有XX角色才可訪問 -->
            /item/list.action = roles[item],authc
                             <!--對靜態資源的匿名訪問-->
            /js/** anon
            /images/** anon
            /styles/** anon
            /validatecode.jsp anon
            /item/* authc
            <!--authc是認證後能夠訪問 user表示身份認證通過或通過記住我認證通過的可以訪問 -->
            /** = authc
        </value>
    </property>
</bean>

<!-- 安全管理器 -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
    <property name="realm" ref="userRealm" />
</bean>

<!-- 自定義 realm -->
<bean id="userRealm" class="cn.ty.ssm.realm.CustomRealm1"></bean>

1.5 靜態資源

對靜態資源設匿名訪問:

            /js/** anon
            /images/** anon
            /styles/** anon

1.6 登入

1.6.1 原理

使用FormAuthenticationFilter過濾器實現,原理如下:

  • 將使用者沒有認證 時,請求loginurl進行認證。使用者身份和使用者密碼提交到loginurl。
  • FormAuthenticationFilter攔截住取出request中的username和password(兩個引數名稱是可以配置的)。
  • FormAuthenticationFilter呼叫realm傳入一個token(username和password)。
  • realm認證時根據username查詢使用者資訊(在Activeuser中儲存,包括menus、userid、usercode、username)。
  • 如果查詢不到,realm返回null。

1.6.2 登入頁面

由於FormAuthenticationFilter 的使用者身份和密碼的input的預設值(username和password),修改頁面的賬號和密碼的input的名稱為username和password。

1.6.2 實現

// 使用者登陸提交 //登陸提交地址,和applicationContext-shiro.xml中配置的loginurl一致 @RequestMapping("/login") public String loginsubmit(Model model, HttpServletRequest request) throws Exception {

    // shiro在認證過程中出現錯誤後將異常類路徑通過request返回
    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.6.3 認證攔截過濾器

在applicationContext-shiro.xml /** = authc

1.7 退出

不用我們去實現退出,只要去訪問一個退出的url(該url是可以不存在的),由LogoutFilter攔截住,自動消除Session

/logout.action = logout

1.8認證資訊在頁面顯示

1.8.1 修改realm設定完整認證資訊

從資料庫查詢 使用者資訊,將使用者選單、usercode、username等設定在SimpleAuthenticationInfo中

protected AuthenticationInfo doGetAuthenticationInfo(
        AuthenticationToken token) throws AuthenticationException {

    // token是使用者輸入的使用者名稱和密碼 
    // 第一步從token中取出使用者名稱
    String userCode = (String) token.getPrincipal();

    // 第二步:根據使用者輸入的userCode從資料庫查詢
    SysUser sysUser = null;
    try {
        sysUser = sysService.findSysUserByUserCode(userCode);
    } catch (Exception e1) {
        // TODO Auto-generated catch block
        e1.printStackTrace();
    }

    // 如果查詢不到返回null
    if(sysUser==null){//
        return null;
    }
    // 從資料庫查詢到密碼
    String password = sysUser.getPassword();

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

    // 如果查詢到返回認證資訊AuthenticationInfo

    //activeUser就是使用者身份資訊
    ActiveUser activeUser = new ActiveUser();

    activeUser.setUserid(sysUser.getId());
    activeUser.setUsercode(sysUser.getUsercode());
    activeUser.setUsername(sysUser.getUsername());
    //..

    //根據使用者id取出選單
    List<SysPermission> menus  = null;
    try {
        //通過service取出選單 
        menus = sysService.findMenuListByUserId(sysUser.getId());
    } catch (Exception e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
    //將使用者選單 設定到activeUser
    activeUser.setMenus(menus);
    //將activeUser設定simpleAuthenticationInfo
    SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(
            activeUser, password,ByteSource.Util.bytes(salt), this.getName());

    return simpleAuthenticationInfo;
}

1.8.2 將認證資訊在首頁顯示

由於session 由shiro管理,需要修改首頁的controller方法,將session中的資料通過model傳到頁面。

   //系統首頁
@RequestMapping("/index")
public String index(Model model)throws Exception{   
    //主體
    Subject subject = SecurityUtils.getSubject();
    //身份
    ActiveUser activeUser = (ActiveUser) subject.getPrincipal();
    model.addAttribute("activeUser", activeUser);
    return "/index";
}

1.9 授權過濾器的測試

1.9.1 使用PermissionsAuthorizationFilter

在applicationContext-shiro.xml中配置url所對應的許可權。 測試流程 1、在applicationContext-shiro.xml中配filter規則 /items/queryItems.action = perms[item:query] 2、使用者在認證通過後,請求/items/queryItems.action 3、被PermissionsAuthorizationFilter攔截,發現需要”item:query”許可權 4、PermissionsAuthorizationFilter 呼叫realm中的doGetAuthorizationInfo 5、PermissionsAuthorizationFilter 對item:query和從realm中獲取 的許可權進行對比,如果”item:query”在realm返回的許可權列表中,授權通過。

1.9.2 建立refuse.jsp

1.9.3 問題總結

1、在applicationContext-shiro.xml中配置過濾器連線,需要將全部的url和許可權對應起來進行配置,比較麻煩,不方便使用。 2、每次授權都需要呼叫realm查詢資料庫,對於效能有很大影響,可以通過shiro快取來解決。

1.10 shiro 過濾器

過濾器簡稱 對應的java類 anon org.apache.shiro.web.filter.authc.AnonymousFilter authc org.apache.shiro.web.filter.authc.FormAuthenticationFilter authcBasic org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter perms org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter port org.apache.shiro.web.filter.authz.PortFilter rest org.apache.shiro.web.filter.authz.HttpMethodPermissionFilter roles org.apache.shiro.web.filter.authz.RolesAuthorizationFilter ssl org.apache.shiro.web.filter.authz.SslFilter user org.apache.shiro.web.filter.authc.UserFilter logout org.apache.shiro.web.filter.authc.LogoutFilter

  • anon:例子/admins/**=anon 沒有引數,表示可以匿名使用。

authc:例如/admins/user/**=authc表示需要認證(登入)才能使用,FormAuthenticationFilter是表單認證,沒有引數

roles:例子/admins/user/* = roles[admin],引數可以寫多個,多個時必須加上引號,並且引數之間用逗號分割,當有多個引數時,例如admins/user/*=roles[“admin,guest”],每個引數通過才算通過,相當於hasAllRoles()方法。

perms:例子/admins/user/* = perms[user:add:],引數可以寫多個,多個時必須加上引號,並且引數之間用逗號分割,例如/admins/user/=perms[“user:add:,user:modify:”],當有多個引數時必須每個引數都通過才通過,想當於isPermitedAll()方法。

rest:例子/admins/user/* = rest[user],根據請求的方法,相當於/admins/user/*=perms[user:method] ,其中method為post,get,delete等。

port:例子/admins/user/** = port[8081],當請求的url的埠不是8081是跳轉到schemal://serverName:8081?queryString,其中schmal是協議http或https等,serverName是你訪問的host,8081是url配置裡port的埠,queryString 是你訪問的url裡的?後面的引數。

authcBasic:例如/admins/user/**=authcBasic沒有引數表示httpBasic認證

ssl:例子/admins/user/**=ssl沒有引數,表示安全的url請求,協議為https

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

  • anon,authcBasic,auchc,user是認證過濾器,
  • perms,roles,ssl,rest,port是授權過濾器

1.11 認證

1.11.1 需求

修改realm的doGetAuthenticationInfo從資料庫查詢使用者資訊,realm返回的使用者資訊中包括(md5加密後的串和salt),實現讓shiro進行雜湊串 的校驗 。 新增憑證匹配器 1.11.2 修改doGetAuthenticationInfo從資料庫中獲取

//realm的認證方法,從資料庫查詢使用者資訊 @Override protected AuthenticationInfo doGetAuthenticationInfo( AuthenticationToken token) throws AuthenticationException {

    // token是使用者輸入的使用者名稱和密碼 
    // 第一步從token中取出使用者名稱
    String userCode = (String) token.getPrincipal();

    // 第二步:根據使用者輸入的userCode從資料庫查詢
    SysUser sysUser = null;
    try {
        sysUser = sysService.findSysUserByUserCode(userCode);
    } catch (Exception e1) {
        // TODO Auto-generated catch block
        e1.printStackTrace();
    }

    // 如果查詢不到返回null
    if(sysUser==null){//
        return null;
    }
    // 從資料庫查詢到密碼
    String password = sysUser.getPassword();

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

    // 如果查詢到返回認證資訊AuthenticationInfo

    //activeUser就是使用者身份資訊
    ActiveUser activeUser = new ActiveUser();

    activeUser.setUserid(sysUser.getId());
    activeUser.setUsercode(sysUser.getUsercode());
    activeUser.setUsername(sysUser.getUsername());
    //..

    //根據使用者id取出選單
    List<SysPermission> menus  = null;
    try {
        //通過service取出選單 
        menus = sysService.findMenuListByUserId(sysUser.getId());
    } catch (Exception e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
    //將使用者選單 設定到activeUser
    activeUser.setMenus(menus);

    //將activeUser設定simpleAuthenticationInfo
    SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(
            activeUser, password,ByteSource.Util.bytes(salt), this.getName());

    return simpleAuthenticationInfo;
}

1.11.3 設定憑證匹配器

新增憑證匹配器實現md5加密校驗。 修改applicationContext-shiro.xml:

<bean id="credentialsMatcher"
    class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
    <property name="hashAlgorithmName" value="md5" />
    <property name="hashIterations" value="1" />
</bean>
<bean id="userRealm" class="cn.itcast.ssm.realm.CustomRealm1">
    <property name="credentialsMatcher" ref="credentialsMatcher" />
</bean>

1.12 授權

1.12.1 需求

修改realm的doGetAutheticationInfo從資料庫查詢使用者資訊,realm返回的使用者資訊中包括(md5加密後的串和salt),實現讓shiro進行雜湊串 的校驗 。 使用註解式授權方法 使用jsp標籤授權方法 1.12.2 doGetAutheticationInfo從資料庫查詢使用者資訊

1、將SysService注入到realm中

public class CustomRealm extends AuthorizingRealm { //注入service @Autowired private SysService sysService;

2、呼叫SysService方法 查詢使用者

//realm的認證方法,從資料庫查詢使用者資訊 @Override protected AuthenticationInfo doGetAuthenticationInfo( AuthenticationToken token) throws AuthenticationException ){ // token是使用者輸入的使用者名稱和密碼 // 第一步從token中取出使用者名稱 String userCode = (String) token.getPrincipal();

    // 第二步:根據使用者輸入的userCode從資料庫查詢
    SysUser sysUser = null;
    try {
        sysUser = sysService.findSysUserByUserCode(userCode);
    } catch (Exception e1) {
        // TODO Auto-generated catch block
        e1.printStackTrace();
    }

    // 如果查詢不到返回null
    if(sysUser==null){//
        return null;
    }
    // 從資料庫查詢到密碼
    String password = sysUser.getPassword();

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

    // 如果查詢到返回認證資訊AuthenticationInfo

    //activeUser就是使用者身份資訊
    ActiveUser activeUser = new ActiveUser();

    activeUser.setUserid(sysUser.getId());
    activeUser.setUsercode(sysUser.getUsercode());
    activeUser.setUsername(sysUser.getUsername());
    //..

   

    //將activeUser設定simpleAuthenticationInfo
    SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(
            activeUser, password,ByteSource.Util.bytes(salt), this.getName());

    return simpleAuthenticationInfo;
}


// 用於授權
@Override

protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {

    //從 principals獲取主身份資訊
    //將getPrimaryPrincipal方法返回值轉為真實身份型別(在上邊的doGetAuthenticationInfo認證通過填充到SimpleAuthenticationInfo中身份型別),
    ActiveUser activeUser =  (ActiveUser) principals.getPrimaryPrincipal();

    //根據身份資訊獲取許可權資訊
    //從資料庫獲取到許可權資料
    List<SysPermission> permissionList = null;
    try {
        permissionList = sysService.findPermissionListByUserId(activeUser.getUserid());
    } catch (Exception e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
    //單獨定一個集合物件 
    List<String> permissions = new ArrayList<String>();
    if(permissionList!=null){
        for(SysPermission sysPermission:permissionList){
            //將資料庫中的許可權標籤 符放入集合
            permissions.add(sysPermission.getPercode());
        }
    }


/*  List<String> permissions = new ArrayList<String>();
    permissions.add("user:create");//使用者的建立
    permissions.add("item:query");//商品查詢許可權
    permissions.add("item:add");//商品新增許可權
    permissions.add("item:edit");//商品修改許可權

*/ //…

    //查到許可權資料,返回授權資訊(要包括 上邊的permissions)
    SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
    //將上邊查詢到授權資訊填充到simpleAuthorizationInfo物件中
    simpleAuthorizationInfo.addStringPermissions(permissions);

    return simpleAuthorizationInfo;
}

1.12.3 開啟controller類aop的支援

對 系統中類的方法給使用者授權,建議 在controller層進行方法授權。在springmvc.xml中配置shiro註解支援,可在controller方法中使用shiro註解配置許可權:

<aop:config proxy-target-class="true"></aop:config>
<!-- 開啟shiro註解支援 -->
<bean
    class="

org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">

1.12.4 在controller方法中添加註解

//商品資訊方法 @RequestMapping("/queryItems") @RequiresPermissions(“item:query”)//執行queryItems需要"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;
}
//方法返回 字串,字串就是邏輯檢視名,Model作用是將資料填充到request域,在頁面展示
@RequestMapping(value="/editItems",method={RequestMethod.GET})
@RequiresPermissions("item:update")//執行此方法需要"item:update"許可權 
public String editItems(Model model,Integer id)throws Exception{

    //將id傳到頁面
    model.addAttribute("id", id);

    //呼叫 service查詢商品資訊
    ItemsCustom itemsCustom = itemsService.findItemsById(id);

    model.addAttribute("itemsCustom", itemsCustom);

    //return "editItem_2";
    return "editItem";

}
//itemsQueryVo是包裝型別的pojo
//在@Validated中定義使用ValidGroup1組下邊的校驗
@RequestMapping("/editItemSubmit")
@RequiresPermissions("item:update")//執行此方法需要"item:update"許可權 
public String editItemSubmit(Model model,Integer id,
            @Validated(value={ValidGroup1.class}) @ModelAttribute(value="itemsCustom") ItemsCustom itemsCustom,
            BindingResult bindingResult,
        //上傳圖片
        MultipartFile pictureFile
        )

1.12.5 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”/> 顯示使用者身份中的屬性值 如果有商品修改許可權頁面顯示“修改”連結。

<shiro:hasPermission name=“item:update”> 修改 </shiro:hasPermission>

1.12.6 授權測試

當呼叫controller的一個方法,由於該方法加了@RequiresPermissions(“item:query”),shiro呼叫realm獲取資料庫中的許可權資訊,看”item:query”是否在許可權資料庫中,如果不存在,就拒絕訪問,如果存在就授權通過。 當展示一個jsp頁面時,頁面中如果遇到,shiro呼叫realm獲取資料庫中的許可權資訊,看item:update是否在許可權資料庫中存在,如果不存在就拒絕訪問,如果存在就授權通過。 問題:只要遇到註解或jsp標籤的授權,都會呼叫realm方法查詢資料,需要使用快取解決此問題。

1.13 shiro快取

針對上邊授權頻繁查詢資料庫,需要使用shiro快取。

1.13.1 快取流程

shiro中提供了對認證資訊和授權資訊的快取。shiro預設是關閉認證資訊快取的,對於授權資訊的快取shiro預設是開啟的。主要研究授權資訊快取,因為授權的資料量大。

使用者認證通過: 該使用者第一次授權,呼叫realm查詢資料庫。 該使用者第二次授權:不呼叫realm查詢資料庫,直接從快取中取出授權資訊(許可權識別符號)。

1.13.2 使用ehcache EhCache 是一個純Java的程序內快取框架,它具有記憶體和磁碟儲存,快取載入器,快取擴充套件,快取異常處理程式,與redis功能類似

1.13.2.1 新增Ehcache的jar包

net.sf.ehcache ehcache-core 2.5.0 org.apache.shiro shiro-ehcache 1.2.3

1.13.2.2 配置cacheManager

在applicationContext-shiro.xml中配置快取管理器。

<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
    <property name="realm" ref="userRealm" />
    <property name="cacheManager" ref="cacheManager"/>
</bean>

1.13.2.3 配置shiro-ehcache.xml

1.13.2.4 快取清空

如果使用者正常退出,快取自動清空; 如果使用者非正常退出,快取自動清空; 如果修改了使用者的許可權,而使用者不退出系統,修改的許可權無法立即生效。 需要手動進行程式設計實現。 在許可權修改後呼叫realm的clearCached方法 realm中定義clearCached方法: 下面的程式碼正常開發時 要放在service中呼叫。 在service中,許可權修改後呼叫realm下邊的方法。 //清除快取 public void clearCached() { PrincipalCollection principals = SecurityUtils.getSubject().getPrincipals(); super.clearCache(principals); }

在許可權修改後呼叫realm中的方法,realm已經由spring管理,所以從spring中獲取realm例項,呼叫clearCached方法。

@Controller public class ClearShiroCache {

//注入realm
@Autowired
private CustomRealm customRealm;

@RequestMapping("/clearShiroCache")
public String clearShiroCache(){

    //清除快取,將來正常開發要在service呼叫customRealm.clearCached()
    customRealm.clearCached();

    return "success";
}

1.14 sessionManager

和shiro整後,使用shiro的session 管理。shiro提供了sessionDao操作,sessionDao操作會話 資料。 配置sessionManager:

<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
    <property name="realm" ref="userRealm" />
    <property name="sessionManager" ref="sessionManager" />
</bean>
<bean id="sessionManager" class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager">
    <!-- session的失效時長,單位毫秒 -->
    <property name="globalSessionTimeout" value="600000"/>
    <!-- 刪除失效的session -->
    <property name="deleteInvalidSessions" value="true"/>
</bean>