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 = logout1.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.31.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>