1. 程式人生 > >shiro安全框架

shiro安全框架

cto 也會 用戶信息 三個參數 protected 編寫 hash算法 doc cond

應用:

認證 授權 加密 會話管理(單點登錄) 緩存 與web集成

技術分享圖片

Subject主體,代表了當前“用戶”,這個用戶不一定是一個具體的人,與當前應用交互的任何東西都是Subject,如網絡爬蟲,機器人等;即一個抽象概念;所有Subject都綁定到SecurityManager,與Subject的所有交互都會委托給SecurityManager;可以把Subject認為是一個門面;SecurityManager才是實際的執行者;

SecurityManager安全管理器;即所有與安全有關的操作都會與SecurityManager交互;且它管理著所有Subject;可以看出它是Shiro的核心,它負責與後邊介紹的其他組件進行交互,如果學習過SpringMVC,你可以把它看成DispatcherServlet前端控制器;

Realm域,Shiro從Realm獲取安全數據(如用戶、角色、權限),就是說SecurityManager要驗證用戶身份,那麽它需要從Realm獲取相應的用戶進行比較以確定用戶身份是否合法;也需要從Realm得到用戶相應的角色/權限進行驗證用戶是否能進行操作;可以把Realm看成DataSource,即安全數據源

使用步驟:

過濾器的配置:

共有10個過濾器 但是實際使用的時候只要配置一個核心過濾器就行了。

<!--註意:如果是和struts2整合的話,shiro的filter必須在struts2的filter之前,否則action無法創建-->
<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> </filter> <filter-mapping> <filter-name>shiroFilter</filter-name> <url-pattern>/*
</url-pattern> </filter-mapping>

過濾器種類:

技術分享圖片

產生代理類的方式:

<!--放在applicationContext.xml事務管理器聲明前-->
<!--產生Shiro控制器的方式 使用cglib生成代理-->
<aop:aspectj-autoproxy proxy-target-class="true" />

applicationContext-shiro.xml(web層):

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<description>Shiro與spring整合的配置文件(天然整合)</description>
<!-- SecurityManager配置 -->
<!-- 配置Realm域 -->
<!-- 密碼比較器 -->
<!-- 代理如何生成? 用工廠來生成Shiro的相關過濾器 -->
<!-- 配置緩存:ehcache緩存 -->
<!-- 安全管理 -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
2<property name="realm" ref="authRealm" /><!-- 引用自定義的realm -->
<!-- 二級緩存 -->
<property name="cacheManager" ref="shiroEhcacheManager" />
</bean>
<!-- 自定義權限認證 -->
<bean id="authRealm" class="com.zixue.shiro.AuthRealm">
<!-- class由程序員定義 -->
<property name="userService" ref="userService" />
<!-- 自定義密碼加密算法 -->
<property name="credentialsMatcher" ref="passwordMatcher" />
<!-- ref由程序員定義 -->
</bean>
<!-- 設置密碼加密策略 md5hash -->
<bean id="passwordMatcher" class="com.zixue.shiro.CustomCredentialsMatcher" />
<!-- class由程序員定義 -->
<!-- filter-name這個名字的值來自於web.xml中filter的名字 -->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"><!--這個才是生產過濾器的根源  web.xml的DelegatingFilterProxy並不能直接生成-->
<property name="securityManager" ref="securityManager" />
<!--登錄頁面 -->
<!--接收loginAction中的subject.login(token)方法,調用了Realm域中的認證方法(反射來實現調用)-->
<property name="loginUrl" value="/index.jsp"></property>
<!-- 登錄成功後 -->
<property name="successUrl" value="/home.action"></property>
<property name="filterChainDefinitions">
<!-- /**代表下面的多級目錄也過濾 -->
<value>
/index.jsp* = anon <!-- (過濾器名) -->
/home* = anon
/sysadmin/login/login.jsp* = anon
/sysadmin/login/logout.jsp* = anon
/login* = anon
/logout* = anon
/components/** = anon
/css/** = anon
/images/** = anon
/js/** = anon
/make/** = anon
/skin/** = anon
/stat/** = anon
/ufiles/** = anon
/validator/** = anon
/resource/** = anon
/** = authc     <!--根目錄下所有資源及子目錄 -->
/*.* = authc       <!-- 本目錄所有資源 -->
</value>
</property>
</bean>
<!-- 用戶授權/認證信息Cache, 采用EhCache 緩存 -->
<bean id="shiroEhcacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
<property name="cacheManagerConfigFile" value="classpath:ehcache-shiro.xml" />
<!-- 引入ehcache-shiro.xml -->
</bean>
<!-- 保證實現了Shiro內部lifecycle函數的bean執行 -->
<bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.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>
</beans>

在applicationContext.xml文件中加載shiro配置文件:

<import resource="classpath:spring/applicationContext-shiro.xml"></import>

ehcache-shiro.xml 緩存(和applicationContext.xml一起)

<?xml version="1.0" encoding="UTF-8"?>
<ehcache updateCheck="false" name="shiroCache">
<defaultCache
maxElementsInMemory="10000"
eternal="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
overflowToDisk="false"
diskPersistent="false"
diskExpiryThreadIntervalSeconds="120"/>
</ehcache>

編寫密碼比較器:

1.Encrypt.java(使用Md5Hash算法 放在工具包下)

public class Encrypt {
/*
* 散列算法一般用於生成數據的摘要信息,是一種不可逆的算法,一般適合存儲密碼之類的數據,
* 常見的散列算法如MD5、SHA等。一般進行散列時最好提供一個salt(鹽),比如加密密碼“admin”,
* 產生的散列值是“21232f297a57a5a743894a0e4a801fc3”,
* 可以到一些md5解密網站很容易的通過散列值得到密碼“admin”,
* 即如果直接對密碼進行散列相對來說破解更容易,此時我們可以加一些只有系統知道的幹擾數據,
* 如用戶名和ID(即鹽);這樣散列的對象是“密碼+用戶名+ID”,這樣生成的散列值相對來說更難破解。
*/
//高強度加密算法,不可逆
public static String md5(String password, String salt){
return new Md5Hash(password,salt,2).toString();
}
public static void main(String[] args) {
System.out.println(new Md5Hash("123456","tony",2).toString());
}
}

2.密碼比較器 CustomCredentialsMatcher.java(和web並級的shiro包下)

public class CustomCredentialsMatcher extends SimpleCredentialsMatcher {
/**
* 提供一個專門用於進行密碼比較的方法
* token:代表用戶在界面輸入的用戶名密碼
* AuthenticationInfo info: 代表數據庫中的用戶信息
* 
* Md5Hash算法
* 將用戶在界面上輸入的明文-----------------加密成密文
* 將用戶在數據庫中的密文獲取出來---------------跟用戶輸入的明文加密後的結果進行比較------------如果相同證明密碼也正確,此時返回值就是true
* 
* 返回值:  true代表密碼比較成功
*        false代表密碼比較失敗,此時程序也會報出異常
*/
public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
//1.向下轉型 
UsernamePasswordToken upToken = (UsernamePasswordToken) token;
//2.將用戶輸入的原始密碼進行加密    Md5Hash的參數說明:第一個參數是明文     第二個參數:代表鹽     第三個參數:叠代次數
//new Md5Hash(new String(upToken.getPassword()),upToken.getUsername(),2);
String encryptPwd = Encrypt.md5(new String(upToken.getPassword()), upToken.getUsername());
//3.跟數據庫串密文進行比較
String pwd = info.getCredentials().toString();
//return encryptPwd.equals(pwd);
return super.equals(pwd, encryptPwd);
}
}

配置(applicationContext-shiro.xml已配好 記得修改路徑):

//設置密碼加密策略 md5hash
<bean id="passwordMatcher" class="com.zixue.shiro.CustomCredentialsMatcher">

編寫自定義Realm域:

作用:

1.調用業務層和CustomCredentialsMatcher密碼比較器(所以需要註入屬性)

2.進行認證和授權操作

AuthRealm.java(和web並級的shiro包下):

public class AuthRealm extends AuthorizingRealm {    
private UserService userService;
public void setUserService(UserService userService) {
this.userService = userService;
}

/**
* 認證   (登錄)
* AuthenticationInfo  返回值:代表用戶的認證信息
 *                     如果返回值不為null,說明用戶名正確,程序就會繼續定位到密碼比較器,進行密碼的比較
*                     如果返回值為null,程序就會拋出異常
*/
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
//向下轉型
UsernamePasswordToken upToken = (UsernamePasswordToken) token;
//1.調用業務方法,實現根據用戶名查詢
List<User> userList = userService.find("from User where userName=?", User.class, new String[]{upToken.getUsername()});
//2.如果用戶存在,就一步驗證密碼是否正確
if(userList!=null && userList.size()>0){
User user = userList.get(0);//取出查詢的用戶對象
//用戶名正確了    第一個參數:用戶對象       第二個參數:代表用戶的密碼     第三個參數:realm的名字,只要是一個字符串就可以
return new SimpleAuthenticationInfo(user,user.getPassword(),this.getName());//返回密碼比較器的類(進入我們註入的密碼比較器的對象)
}
//3.返回結果
return null;
}


/**
 * 授權---------------在jsp頁面碰到shiro標簽時,就要執行授權方法
*/
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection pc) {
//1.獲取當前登錄的用戶信息
User user = (User)pc.fromRealm(this.getName()).iterator().next();
//2.通過對象導航,得到角色列表
Set<Role> roleSet = user.getRoles();
List<String> moduleList = new ArrayList<String>();
//3.遍歷角色列表,得到每個角色 
for (Role role : roleSet) {
//通過對象導航,得到角色下面的模塊列表
Set<Module> moduleSet = role.getModules();
for (Module module : moduleSet) {
if(!moduleList.contains(module.getName())){
moduleList.add(module.getName());
}
}
}
//設置返回值
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
info.addStringPermissions(moduleList);
return info;
}
}

將編寫的AuthRealm域配置好(applicationContext-shiro.xml已配好 記得修改路徑):

//自定義權限認證
<bean id="autoRealm" class="com.zixue.shiro.AuthRealm">
<property name="userService" ref="userService"/>
//自定義密碼加密算法
<propetty name="credentialsMatcher" ref="passwordMatcher">
</bean>

登陸操作:

loginAction

作用:應用程序與Shiro框架進行交互的Subject對象獲取

public class LoginAction extends BaseAction {
private static final long serialVersionUID = 1L;
private String username;
private String password;
//SSH傳統登錄方式
public String login() throws Exception {
//        if(true){
//            String msg = "登錄錯誤,請重新填寫用戶名密碼!";
//            this.addActionError(msg);
//            throw new Exception(msg);
//        }
//        User user = new User(username, password);
//        User login = userService.login(user);
//        if (login != null) {
//            ActionContext.getContext().getValueStack().push(user);
//            session.put(SysConstant.CURRENT_USER_INFO, login);    //記錄session
//            return SUCCESS;
//        }
//        return "login";            if(UtilFuns.isEmpty(username)){                
return "login";
}
try {
//1.應用程序與Shiro框架進行交互的Subject對象如何獲取 ?
Subject subject = SecurityUtils.getSubject();
//2.AuthenicationToken是接口  所以只能new子類的對象
AuthenticationToken token = new UsernamePasswordToken(username, password);
//3.實現登錄操作
subject.login(token);//進入AuthRealm類中的方法doGetAuthenticationInfo
//4.從Shiro中獲取登錄的用戶信息
User user = (User)subject.getPrincipal();
//5.將用戶信息存入session中
session.put(SysConstant.CURRENT_USER_INFO, user);
} catch (Exception e) {
e.printStackTrace();
request.put("errorInfo", "用戶名或密碼錯誤!!");
return "login";
}
return SUCCESS;
}
//退出
public String logout(){
session.remove(SysConstant.CURRENT_USER_INFO);        //刪除session
SecurityUtils.getSubject().logout();   //登出
return "logout";
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}

測試:

當jsp頁面上出現Shiro標簽就會執行AuthRealm域中的授權方法

1.引入Shiro標簽

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

2.使用Shiro標簽進行授權判斷

<shiro:hasPermission name="貨運管理"><span id="topmenu" onclick="toModule(‘cargo‘);">貨運管理</span><span id="tm_separator"></span></shiro:hasPermission>

技術分享圖片

Shiro運行流程分析:

技術分享圖片

登陸流程:

技術分享圖片

授權流程:

技術分享圖片

關於進入自己賬戶後直接在地址欄輸入權限不同的其他用戶地址,依舊可以進入的

BUG解決:

使用perms過濾器進行權限攔截

1.配置
/sysadmin/deptAction_* = perms["部門管理"]
2.方法加入註解
@RequiresPermissions("部門管理")
3.加入配置
<property name="unauthorizedUrl" value="/index.jsp"></property>

shiro安全框架