1剧腻、環(huán)境搭建
1曹动、在web.xml中配置過濾器
<!--shiro過濾器-->
<filter>
<filter-name>shiroFilter</filter-name>
<filter-class>
org.springframework.web.filter.DelegatingFilterProxy
</filter-class>
<init-param>
<param-name>targetFilterLifecycle</param-name>
<param-value>true</param-value>
</init-param>
<!--<init-param>
<param-name>targetBeanName</param-name>
<param-value>自定義的shrio bean的id</param-value>
</init-param>-->
</filter>
<filter-mapping>
<filter-name>shiroFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
注意谚中,過濾器的名字shiroFilter可以自定義,但是spring配置文件中的bean的id默認(rèn)要與該filter-name相同林艘,如果想要改變默認(rèn)設(shè)置盖奈,可以配置targetBeanName屬性
2、在spring配置文件中集成shrio
<!-- shiro的核心配置 -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="cacheManager" ref="cacheManager"/>
<property name="realm" ref="jdbcRealm"/>
<!--注入rememberMe cookie管理器-->
<property name="rememberMeManager" ref="rememberMeManager"/>
</bean>
<!-- 配置緩存 -->
<bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
<property name="cacheManagerConfigFile" value="classpath:ehcache.xml"/>
</bean>
<!-- 配置realm-->
<bean id="jdbcRealm" class="com.fan.shiro.realms.ShiroRealm">
<!--以下用于比較密碼-->
<property name="credentialsMatcher">
<bean class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
<!--加密算法-->
<property name="hashAlgorithmName" value="MD5"/>
<!--加密次數(shù)-->
<property name="hashIterations" value="1024"/>
</bean>
</property>
</bean>
<!-- 生命周期bean 自動調(diào)用在spring IOC容器中 shiro bean的生命周期方法 -->
<bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>
<!-- 啟用IOC容器中 使用shiro注解 必須配置 lifecycleBeanPostProcessor-->
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"
depends-on="lifecycleBeanPostProcessor"/>
<bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
<property name="securityManager" ref="securityManager"/>
</bean>
<!-- 配置shiroFilter
id必須和web.xml文件中 配置的shiro過濾器 名字一致
-->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<property name="securityManager" ref="securityManager"/>
<property name="loginUrl" value="/user/loginView"/>
<property name="successUrl" value="/user/listView"/>
<property name="unauthorizedUrl" value="/user/unauthorizedView"/>
<property name="filterChainDefinitionMap" ref="filterDefinitionMap"/>
<!--
配置攔截的資源
anon :可以匿名訪問
authc :需要認(rèn)證訪問 北启,即需要登錄
roles:角色
perms:權(quán)限卜朗,權(quán)限前面需要加角色名,中間用冒號隔開
logout: 將用戶session清空 啟用緩存后不清空session再登錄咕村,如果登錄失敗還是維持原用戶的登錄狀態(tài)
不配置的 就是可以匿名訪問
-->
<!-- <property name="filterChainDefinitions">
<value>
/user/login = anon
/user/registerView = anon
/user/register = anon
/user/logout = logout
/user/addView = roles[emp]
/user/modifyView = roles[manager]
/user/removeView = roles[admin]
/user/** = user
/static/** = anon
</value>
</property>-->
</bean>
<bean id="filterChainDefinitionMapBuilder" class="com.fan.shiro.util.FilterChainDefinitionMapBuilder"/>
<bean id="filterDefinitionMap" factory-bean="filterChainDefinitionMapBuilder" factory-method="buildFilterChainDefinitionMap"/>
<!--shiro Cookie相關(guān)配置 用于記住我-->
<!--手動指定cookie-->
<bean id="rememberMeCookie" class="org.apache.shiro.web.servlet.SimpleCookie">
<constructor-arg value="rememberMe"/>
<property name="httpOnly" value="true"/>
<!--設(shè)置cookie的有效時間 單位為秒-->
<property name="maxAge" value="#{60*60*24*15}"/>
</bean>
<!-- rememberMe管理器 -->
<bean id="rememberMeManager" class="org.apache.shiro.web.mgt.CookieRememberMeManager">
<property name="cipherKey" value="#{T(org.apache.shiro.codec.Base64).decode('6ZmI6I2j5Y+R5aSn5ZOlAA==')}"/>
<!--注入自定義cookie(主要是設(shè)置壽命, 默認(rèn)的一年太長)-->
<property name="cookie" ref="rememberMeCookie"/>
</bean>
<!--shiro配置結(jié)束-->
shiro框架的一切功能都是圍繞securityManager展開场钉,所以集成shiro最核心的bean就是org.apache.shiro.web.mgt.DefaultWebSecurityManager,在這個bean中可以實現(xiàn)緩存懈涛、認(rèn)證授權(quán)和記住我等多種功能逛万。除了securityManager,還有一個配置也很重要批钠,那就是之前配置在web.xml中的過濾器的工廠org.apache.shiro.spring.web.ShiroFilterFactoryBean宇植。這個bean的id默認(rèn)與filter-name相同,它的內(nèi)部持有了securityManage實例埋心,內(nèi)部可以配置認(rèn)證成功后指向的資源指郁、認(rèn)證資源和未授權(quán)指向的資源以及角色權(quán)限配置。
2拷呆、shiro配置詳解
注:shiro中的資源指服務(wù)器內(nèi)一切內(nèi)容闲坎,包括java代碼颅眶、接口宛裕、js、css隅津、圖片和一切其他資源
1项秉、過濾器工廠類
<!-- 配置shiroFilter
id必須和web.xml文件中 配置的shiro過濾器 名字一致
-->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<property name="securityManager" ref="securityManager"/>
<property name="loginUrl" value="/user/loginView"/>
<property name="successUrl" value="/user/listView"/>
<property name="unauthorizedUrl" value="/user/unauthorizedView"/>
</bean>
這個類用來生成過濾器實例绣溜,并且提供了眾多屬性供過濾器使用:
<!--這個屬性就是securityManager實例-->
<property name="securityManager" ref="securityManager"/>
<!--分別表示:1、認(rèn)證請求的資源娄蔼,可以是一個登錄界面也可以是一個接口
2怖喻、認(rèn)證成功后請求的資源
3、用戶請求未授權(quán)資源后前往的資源
-->
<property name="loginUrl" value="/user/loginView"/>
<property name="successUrl" value="/user/listView"/>
<property name="unauthorizedUrl" value="/user/unauthorizedView"/>
還可以配置shiro的授權(quán)岁诉,有兩種配置方法
1罢防、在配置文件中寫入
<!--
配置攔截的資源
anon :可以匿名訪問
authc :需要認(rèn)證訪問 ,即需要登錄
roles:角色
perms:權(quán)限唉侄,權(quán)限前面需要加角色名咒吐,中間用冒號隔開
logout: 將用戶session清空 啟用緩存后不清空session再登錄,如果登錄失敗還是維持原用戶的登錄狀態(tài)
不配置的: 就是默認(rèn)可以匿名訪問
-->
<property name="filterChainDefinitions">
<value>
/user/login = anon
/user/registerView = anon
/user/register = anon
/user/logout = logout
/user/addView = roles[emp]
/user/modifyView = roles[manager]
/user/removeView = roles[admin]
/user/** = user
/static/** = anon
</value>
</property>
2属划、自己創(chuàng)建工廠生成filterChainDefinitionMap
shiroFilter中有一個filterChainDefinitionMap對象屬性恬叹,它是一個實現(xiàn)了Map接口的對象,我們可以利用自建工廠添加攔截配置
<!--在這里面filterChainDefinitionMapBuilder就是我們自己實現(xiàn)的工廠同眯,內(nèi)部可以查詢數(shù)據(jù)庫獲得用戶的角色和權(quán)限绽昼,再添加到map集合中 返回到另一個bean中,將這個bean注入到shiroFilter-->
<bean id="filterChainDefinitionMapBuilder"class="com.fan.shiro.util.FilterChainDefinitionMapBuilder"/>
<bean id="filterDefinitionMap" factory-bean="filterChainDefinitionMapBuilder" factorymethod="buildFilterChainDefinitionMap"/>
這里是shiroFilter的filterChainDefinitionMap屬性配置
<!--filterDefinitionMap就是工廠方法返回的map集合-->
<property name="filterChainDefinitionMap" ref="filterDefinitionMap"/>
工廠類由自己實現(xiàn) 類名稱和方法自行定義须蜗,這里使用了如下實現(xiàn)
package com.fan.shiro.util;
import com.fan.shiro.pojo.FilterChainDefinition;
import com.fan.shiro.service.IFilterChainDefinitionService;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.LinkedHashMap;
import java.util.List;
public class FilterChainDefinitionMapBuilder {
@Autowired
private IFilterChainDefinitionService filterChainDefinitionService;
public LinkedHashMap<String, String> buildFilterChainDefinitionMap() {
//調(diào)用業(yè)務(wù)層從數(shù)據(jù)庫中查出攔截資源的列表
List<FilterChainDefinition> filterChainDefinitions = filterChainDefinitionService.findAllFilterChainDefinition();
LinkedHashMap<String, String> map = new LinkedHashMap<>();
for (FilterChainDefinition f : filterChainDefinitions) {
map.put(f.getApi(), f.getFilter());
}
return map;
}
}
3硅确、注意
1目溉、不論在配置文件中直接配置,還是利用工廠返回map菱农,攔截資源的配置都是按照順序先后執(zhí)行缭付,例如:
<property name="filterChainDefinitions">
<value>
<!--兩個相同的register接口,按照先配置的那個執(zhí)行循未,即anon 可以匿名訪問-->
/user/register = anon
/user/register = authc
<!--/user/**包含了下面兩個接口陷猫,所以根據(jù)順序原則,下面兩個接口無論是什么配置都是anon 可
以匿名訪問-->
/user/** = anon
/user/modifyView = authc
/user/removeView = user
</value>
</property>
所以的妖,即使在數(shù)據(jù)庫中添加配置數(shù)據(jù)的時候也一定要注意先后順序绣檬。
2、權(quán)限和認(rèn)證(記住我)可以進(jìn)行組合
比如:authc,roles[pay]表示這個角色必須在認(rèn)證后才能生效嫂粟,在記住我狀態(tài)時無法訪問娇未,必須先登錄認(rèn)證
2、securityManager
securityManager是shiro的核心星虹,重要功能都需要它實現(xiàn)
1忘蟹、緩存
<property name="cacheManager" ref="cacheManager"/>
這里使用了ecach緩存
<!-- 配置緩存 -->
<bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
<property name="cacheManagerConfigFile" value="classpath:ehcache.xml"/>
</bean>
2、realm實現(xiàn)認(rèn)證和授權(quán)
<property name="realm" ref="jdbcRealm"/>
realm想要同時具有認(rèn)證和授權(quán)的功能搁凸,需要繼承AuthorizingRealm類
<!-- 配置realm. -->
<bean id="jdbcRealm" class="com.woniu.shiro.realms.ShiroRealm">
<!--密碼比較器 認(rèn)證時將密碼加密后與數(shù)據(jù)庫密碼比較-->
<property name="credentialsMatcher">
<bean class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
<property name="hashAlgorithmName" value="MD5"></property>
<property name="hashIterations" value="1024"></property>
</bean>
</property>
</bean>
以下是reaml的實現(xiàn)
package com.fan.shiro.realms;
import com.fan.shiro.pojo.Permission;
import com.fan.shiro.pojo.Role;
import com.fan.shiro.pojo.User;
import com.fan.shiro.service.IPermissionService;
import com.fan.shiro.service.IRoleService;
import com.fan.shiro.service.IUserService;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
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.util.ByteSource;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public class ShiroRealm extends AuthorizingRealm {
@Autowired
IUserService userService;
@Autowired
IRoleService roleService;
@Autowired
IPermissionService permissionService;
//認(rèn)證
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
//從authenticationToken取出UsernamePasswordToken
UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
String userName = token.getUsername();
User user = userService.findUserByUserName(userName);
if (user == null) {
throw new UnknownAccountException("用戶不存在媚值!");
}
if (user.getStatus().equals("0")) {
throw new LockedAccountException("該用戶已被鎖定!");
}
//獲取鹽值
ByteSource byteSource = ByteSource.Util.bytes(userName);
//通過鹽值加密驗證用戶 密碼不匹配會拋出IncorrectCredentialsException異常
SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(user, user.getPassword(), byteSource, this.getName());
//通過驗證后將用戶對象存儲到session中
Session session = SecurityUtils.getSubject().getSession();
session.setAttribute("userInfo", user);
return simpleAuthenticationInfo;
}
//授權(quán)
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
User user = (User) principalCollection.getPrimaryPrincipal();
Set<String> roles = new HashSet<>();
//查出用戶的角色 并將所有角色添加到set集合中
List<Role> roleList = roleService.findRoleByUserId(user.getUserId());
for (Role r : roleList) {
roles.add(r.getRoleName());
}
//將set注入到SimpleAuthorizationInfo實例中
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo(roles);
//查出用戶權(quán)限
List<Permission> permissionList = permissionService.findPermissionByUserId(user.getUserId());
List<String> permissions = new ArrayList<>();
for (Permission p : permissionList) {
permissions.add(p.getPermissionName());
}
//調(diào)用addStringPermissions將權(quán)限列表加入simpleAuthorizationInfo
simpleAuthorizationInfo.addStringPermissions(permissions);
//返回實例 告知shiro框架該用戶具有的角色和權(quán)限
return simpleAuthorizationInfo;
}
}
3护糖、rememberMe記住我
記住我功能與認(rèn)證功能不一樣褥芒,記住我功能開啟后,shiro會檢測客戶端是否有rememberme cookie嫡良,如果有就會對該cookie進(jìn)行驗證锰扶,如果沒有就會生成一個cookie并返回給客戶端。在攔截配置中user關(guān)鍵字就是針對記住我寝受。==記住我在controller中必須在Subject的login方法之前調(diào)用==
<!--手動指定cookie-->
<bean id="rememberMeCookie" class="org.apache.shiro.web.servlet.SimpleCookie">
<constructor-arg value="rememberMe"/>
<property name="httpOnly" value="true"/>
<!--設(shè)置cookie的有效時間 單位為秒-->
<property name="maxAge" value="#{60*60*24*15}"/>
</bean>
<!-- rememberMe管理器 -->
<bean id="rememberMeManager"class="org.apache.shiro.web.mgt.CookieRememberMeManager">
<!--對cookie進(jìn)行加密-->
<property name="cipherKey" value="# {T(org.apache.shiro.codec.Base64).decode('6ZmI6I2j5Y+R5aSn5ZOlAA==')}"/>
<!--注入自定義cookie(主要是設(shè)置壽命, 默認(rèn)的一年太長)-->
<property name="cookie" ref="rememberMeCookie"/>
</bean>
3坷牛、其它配置
<!-- 生命周期bean 自動調(diào)用在spring IOC容器中 shiro bean的生命周期方法 -->
<bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>
<!-- 啟用IOC容器中 使用shiro注解 必須配置 lifecycleBeanPostProcessor-->
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"
depends-on="lifecycleBeanPostProcessor">
<property name="proxyTargetClass" value="true" />
</bean>
<bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
<property name="securityManager" ref="securityManager"/>
</bean>
4、controller層如何實現(xiàn)認(rèn)證
@RequestMapping("/login")
@ResponseBody
public ResponseResult login(User user, String remember) {
ResponseResult responseResult = new ResponseResult("200", "登錄成功很澄!");
//1京闰、獲取當(dāng)前Subject用戶
Subject currentUser = SecurityUtils.getSubject();
//2、判斷當(dāng)前用戶是否已經(jīng)認(rèn)證過
if (currentUser.isAuthenticated()) {
responseResult.setCode("200");
responseResult.setMessage("用戶已登錄甩苛!");
return responseResult;
}
//3蹂楣、獲得UsernamePasswordToken
UsernamePasswordToken token = new UsernamePasswordToken(user.getUserName(), user.getPassword());
//4、進(jìn)行用戶身份驗證
try {
//4.1判斷用戶是否設(shè)置了記住我選項讯蒲,該方法必須在認(rèn)證之前調(diào)用
boolean flag = false;
if (remember != null) {
flag = "remember".equals(remember);
}
token.setRememberMe(flag);
//4.2 調(diào)用Subject的login方法進(jìn)行驗證 如果驗證不通過會拋出異常在catch中處理 驗證通過則將用戶加入
//session中
currentUser.login(token);
} catch (UnknownAccountException uae) {
responseResult.setCode("404");
responseResult.setMessage("用戶不存在痊土!");
return responseResult;
} catch (IncorrectCredentialsException ice) {
responseResult.setCode("400");
responseResult.setMessage("密碼錯誤!");
return responseResult;
} catch (LockedAccountException lae) {
responseResult.setCode("300");
responseResult.setMessage("該用戶已被鎖定墨林!");
return responseResult;
} catch (AuthenticationException ae) {
responseResult.setCode("500");
responseResult.setMessage("登錄失敗赁酝,請重試犯祠!");
return responseResult;
}
return responseResult;
}
3、總結(jié)
Apache Shiro是一個強(qiáng)大且易用的Java安全框架,執(zhí)行身份驗證酌呆、授權(quán)衡载、密碼和會話管理。使用Shiro的易于理解的API,可以快速肪笋、輕松地獲得任何應(yīng)用程序,從最小的移動應(yīng)用程序到最大的網(wǎng)絡(luò)和企業(yè)應(yīng)用程序,JavaSE環(huán)境和JavaEE環(huán)境都可以使用 (抄來的)