權限控制的方式
從類别上分,有兩大類:
- 認證:你是誰?–識别使用者身份。
- 授權:你能做什麼?–限制使用者使用的功能。
權限的控制級别
從控制級别(模型)上分:
- URL級别-粗粒度
- 方法級别-細粒度
- 頁面級别-自定義标簽(顯示)
- 資料級别-最細化的(資料)
URL級别的權限控制-粗粒度
在web.xml中配置一個過濾器filter,在過濾器中,對請求的位址進行解析,字元串截取:
url.substring()…把上下文前面的路徑都截取掉,剩下user_login.action。
過濾器代碼:
以通過查詢資料庫,來判斷,目前登入使用者,是否可以通路user_login.action。
url級别控制,每次請求過程中隻控制一次 ,相比方法級别權限控制 是粗粒度的 !URL級别權限控制,基于Filter實作。
方法級别的權限控制-細粒度
aop面向切面的程式設計,在方法執行之前,進行權限判斷,如果沒有權限,抛出異常,終止方法的繼續運作。
自定義注解 在需要權限控制方法上, 添加需要的權限資訊
代理 (Spring AOP ),在目标方法運作時 進行增強 ,通過反射技術擷取目标方法上注解中權限 , 查詢資料庫擷取目前登陸使用者具有權限,進行比較。
相比URL級别權限控制, 可以控制到伺服器端執行的每個方法,一次請求中可以控制多次。
頁面(顯示)級别的權限控制-自定義标簽
頁面顯示的權限控制,通常是通過 自定義标簽來實作
資料級别的權限控制
在每條資料上增加一個字段,該字段記錄了權限的值。資料和權限綁定。
代碼,你在查詢資料的時候,需要去權限和使用者對應表中,通過目前登入使用者的條件,查詢出你的資料權限。然後再将資料權限作為一個條件,放到業務表中進行查詢。進而限制了資料的通路。
權限系統的資料表設計
資源:使用者要通路的目标,通常是服務中的程式或檔案
權限:使用者具有通路某資源的能力
角色:權限的集合,為了友善給使用者授權。
使用者:通路系統的’人’。
表對象實體:
- 使用者(User)表:通路系統的使用者,比如使用者登入要用
- 權限(Function)表:系統某個功能允許通路而對應的權限
- 角色(Role)表:角色是權限的集合(權限組),友善使用者授權。
表對象之間的關系:
- 使用者和角色關系表:一個使用者對應N個角色,一個角色可以授予N個使用者—》多對多關系
- 角色和權限關系表:一個角色包含N個權限,一個權限可以屬于N個角色—》多對多關系
完整的權限相關表:
URL級别權限控制包含:資源表、權限表、角色表、使用者表,以及相關關系(都是多對多),共7張表。
方法級别的權限控制包含:功能權限、角色、使用者,以及相關關系(都是多對多),共5張表。
但Apache Shiro架構支援的URL級别權限控制,是将資源和資源權限對應關系配置到了配置檔案中,不需要表的支撐,隻需要5張表了。
Apache Shiro權限控制
Apache Shiro 可以不依賴任何技術使用, 可以直接和web整合,通常在企業中和Spring 結合使用。
Authentication: 認證 — 使用者登入
Authorization : 授權 —- 功能權限管理
通過引入Maven坐标導入shiro
官方建議:不推薦直接引入shiro-all,依賴比較多,原因怕有jar沖突。官方推薦根據需要單獨導入jar。
Shiro基本原理
Shiro的架構的體系結構:
Shiro權限控制流程的原理:
應用代碼 —- 調用Subject (shiro的Subject 就代表目前登陸使用者) 控制權限 —- Subject 在shiro架構内部 調用 Shiro SecurityManager 安全管理器 —– 安全管理器調用 Realm (程式和安全資料連接配接器 )。
Subject要進行任何操作,都必須要調用安全管理器(對我們來說是自動的)。
而安全管理器會調用指定的Realms對象,來連接配接安全資料。
Realms用來編寫安全代碼邏輯和通路安全資料,是連接配接程式和安全資料的橋梁。
URL級别的權限控制
配置整合和url級别認證
配置過濾器web.xml:放在struts的前端控制器之前配置,但放在openEntitymanage之後。
<!-- shiro權限過濾器 -->
<filter>
<!-- 這裡的 filter-name 要和 spring 的 applicationContext-shiro.xml 裡的 org.apache.shiro.spring.web.ShiroFilterFactoryBean
的 bean name 相同 -->
<filter-name>shiroSecurityFilter</filter-name>
<!-- spring的代理過濾器類:以前的過濾器 -->
<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>shiroSecurityFilter</filter-name>
<url-pattern>
@Component("bosRealm")
public class BosRealm extends AuthorizingRealm{
//注入ehcache的緩存區域
@Value("BosShiroCache")//注入緩存具體對象的名字,該名字在ehcache.xml中配置的
public void setSuperAuthenticationCacheName(String authenticationCacheName){
super.setAuthenticationCacheName(authenticationCacheName);
}
//注入service
@Autowired
private UserService userService;
//注入角色dao
@Autowired
private RoleDao roleDao;
//注入功能的dao
@Autowired
private FunctionDao functionDao;
//授權方法:擷取使用者的權限資訊
//授權:回調方法
//如果傳回null,說明沒有權限,shiro會自動跳到<property name="unauthorizedUrl" value="/unauthorized.jsp" />
//如果不傳回null,根據配置/page_base_subarea.action = roles["weihu"],去自動比對
//給授權提供資料的
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
//給目前使用者授權的權限(功能權限、角色)
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
//兩種方式:
//方式1:工具類來擷取(首長-)
// User user=(User)SecurityUtils.getSubject().getPrincipal();
//方式2:通過參數擷取首長(推薦)
User user = (User) principals.getPrimaryPrincipal();
//實際:需要根據目前使用者的角色和功能權限來建構一個授權資訊對象,交給安全管理器
if (user.getUsername().equals("admin")) {
//如果是超級管理者
//查詢出所有的角色,給認證資訊對象
List<Role> roleList = roleDao.findAll();
for (Role role : roleList) {
authorizationInfo.addRole(role.getCode());
}
//查詢出所有的功能權限,給認證對象
List<Function> functionList = functionDao.findAll();
for (Function function : functionList) {
authorizationInfo.addStringPermission(function.getCode());
}
} else {
//如果是普通使用者
List<Role> roleList = roleDao.findByUsers(user);
for (Role role : roleList) {
authorizationInfo.addRole(role.getCode());
//導航查詢,擷取某角色的擁有的功能權限
Set<Function> functions = role.getFunctions();
for (Function function : functions) {
authorizationInfo.addStringPermission(function.getCode());
}
}
}
return authorizationInfo;//将授權資訊交給安全管理器接口。
}
//認證:回調,認證管理器會将認證令牌放到這裡(action層的令牌AuthenticationToken)
//發現如果傳回null,抛出使用者不存在的異常UnknownAccountException
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
//使用者名密碼令牌(action傳過來)
UsernamePasswordToken upToken = (UsernamePasswordToken) token;
//調用業務層來查詢(根據使用者名來查詢使用者,無需密碼)
User user = userService.findByUsername(upToken.getUsername());
//判斷使用者是否存在
if (user == null) {
//使用者不存在
return null;//抛出異常
} else {
//使用者名存在
//參數1:使用者對象,将來要放入session,資料庫查詢出來的使用者
//參數2:憑證(密碼):密碼校驗:校驗的動作交給shiro
//參數3:目前使用的Realm在Spring容器中的名字(bean的名字,自動在spring容器中尋找)
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(user, user.getPassword(), super.getName());
return authenticationInfo;//密碼校驗失敗,會自動抛出IncorrectCredentialsException
}
}
}
ApplicatonContext.xml:
<!-- service需要spring掃描 -->
<context:component-scan base-package="cn.aric.bos.service,cn.aric.bos.web,cn.aric.bos.auth.realm" />
<!-- shiro安全管理器 -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<!-- 注入 Realm連接配接安全資料-->
<property name="realm" ref="bosRealm"></property>
<!-- 注入shiro的緩存管理器 -->
<property name="cacheManager" ref="shiroCacheManager"/>
</bean>
使用者認證(退出)以及修改密碼
@Action(value="user_logout",results={@Result(name=LOGIN,type="redirect",location="/login.jsp")})
public String logout() throws Exception {
//shiro退出
Subject subject = SecurityUtils.getSubject();
subject.logout();
//跳轉登陸頁面
return LOGIN;
}
// @Action(value="user_editPassword",results={@Result(name=JSON,type=JSON)})
@Action("user_editPassword")
public String editPassword() throws Exception {
//擷取Principal就是擷取目前使用者
User loginUser = (User) SecurityUtils.getSubject().getPrincipal();
model.setId(loginUser.getId());
//頁面結果
HashMap<String,Object> resultMap = new HashMap<String,Object>();
try {
//調用service進行修改密碼
userService.updateUserPassword(model);
//修改成功
resultMap.put("result", true);
} catch (Exception e) {
e.printStackTrace();
//修改失敗
resultMap.put("result", false);
}
//将結果壓入棧頂
ActionContext.getContext().getValueStack().push(resultMap);
//轉換為json
return JSON;
}
使用者授權(授權)—自定義Ream
資料庫資料添加,applicationContext.xml配置
<property name="filterChainDefinitions">
<value>
/login.jsp = anon
/validatecode.jsp = anon
/js/** = anon
/css/** = anon
/images/** = anon
/user_login.action = anon
/page_base_staff.action = anon
/page_base_region.action = perms["region"]
/page_base_subarea.action = roles["weihu"]
/page_qupai_noticebill_add.action = perms["noticebill"]
/page_qupai_quickworkorder.action = roles["kefu"]
/** = authc
</value>
</property>
代碼在上面的BosRealm的中,protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals)
RoleDao省略。
方法級别的權限控制
啟用Shiro注解
需要 Shiro 的 Spring AOP 內建來掃描合适的注解類以及執行必要的安全邏輯。
ApplicationContext.xml
<!-- 開啟權限控制的注解功能并且配置aop -->
<!-- 後處理器:通過動态代理在某bean執行個體化的前增強。:自己去找權限注解 -->
<bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>
<!-- 切面自動代理:相當于以前的AOP标簽配置
advisor:切面 advice:通知
-->
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"
depends-on="lifecycleBeanPostProcessor">
</bean>
<!-- Advisor切面配置:授權屬性的切面 -->
<bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
<!-- 注入安全管理器 -->
<property name="securityManager" ref="securityManager"/>
</bean>
在需要權限控制的目标方法上面使用shiro的注解:
@RequiresAuthentication 需要使用者登入
subject.isAuthenticated() 必須傳回true
@ RequiresUser
subject.isAuthenticated() 傳回true 或者subject.isRemembered() 傳回true
“Remember Me”服務:
認證機制 基于 session
被記憶機制 基于 cookie (subject.isAuthenticated() 傳回 false )
@ RequiresGuest 與 @RequiresUser 相反,不能認證也不能被記憶。
@ RequiresRoles 需要角色
@RequiresPermissions 需要權限
異常
動态代理異常
解決方案:
配置ApplicationContext.xml,設定代理為cglib代理(對目标類代理)
<!-- 切面自動代理:相當于以前的AOP标簽配置 -->
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"
depends-on="lifecycleBeanPostProcessor" >
<!-- 設定aop的代理使用CGLIB代理 -->
<property name="proxyTargetClass" value="true"/>
</bean>
方案二:
<aop:config proxy-target-class="true" />
1
類型轉換異常
解決方案:遞歸向上尋找泛型的類型。
//遞歸向上 查找
Class actionClass =this.getClass();
//向父類遞歸尋找泛型
while(true){
//得到帶有泛型的類型,如BaseAction<Userinfo>
Type type = actionClass.getGenericSuperclass();
if(type instanceof ParameterizedType){
//轉換為參數化類型
ParameterizedType parameterizedType = (ParameterizedType) type;
//擷取泛型的第一個參數的類型類,如Userinfo
Class<T> modelClass = (Class<T>) parameterizedType.getActualTypeArguments()[0];
//執行個體化模型對象
try {
model=modelClass.newInstance();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
break;
}
//尋找父類
actionClass=actionClass.getSuperclass();
}
空指針異常
解決方案1:使用public 的Setter方法上的注解直接注入Service。
SubareaAction:
//注入service
private SubareaService subareaService;
@Autowired
public void setSubareaService(SubareaService subareaService) {
this.subareaService = subareaService;
}
解決方案2:
@Autowire還放到私有聲明上,
在struts.xml中覆寫常量(開啟自動裝配政策):
值預設是false,struts2預設注入采用的是構造器注入(從spring中尋找的bean)
改成true,struts2會采用setter方法注入
頁面标簽(實作頁面内容定制顯示)
<!-- 引入Shiro标簽 -->
<%@ taglib uri="http://shiro.apache.org/tags" prefix="shiro"%>
頁面拿Session中的user對象: 代表user對象。
程式中拿Session中的user對象:SecurityUtils.getSubject().getPrincipal()
資源通配符和權限通配符可便捷開發。
代碼級别
使用代碼程式設計的方式,直接在程式中使用Subject對象,調用内部的一些API。(有代碼侵入)
//代碼級别的權限控制(授權):功能權限和角色權限:兩套機制:boolean判斷,異常判斷
//授權的權限控制
//====布爾值判斷
//功能權限
if(subject.isPermitted("staff")){
//必須擁有staff功能權限才能執行代碼
System.out.println("我是一段代碼。。。。。");
}
//角色權限
if(subject.hasRole("weihu")){
//必須擁有staff功能權限才能執行代碼
System.out.println("我是一段代碼。。。。。");
}
//====異常判斷
//功能權限
try {
subject.checkPermission("staff");
//有權限
} catch (AuthorizationException e) {
// 沒權限
e.printStackTrace();
}
//角色權限
try {
subject.checkRole("weihu");
//有權限
} catch (AuthorizationException e) {
// 沒權限
e.printStackTrace();
}
---------------------
作者:宏微
來源:CSDN
原文:https://blog.csdn.net/shuaicihai/article/details/58391481
版權聲明:本文為部落客原創文章,轉載請附上博文連結!