Java安全框架Shiro的快速使用

一、 shiro的工作流程

( 圖片來自開濤大神的 《跟我學Shiro》)

從圖中我們可以得知Subject就是“用戶” SecurityManager 這個安全管理中心是負責對Subject進行管理;我們只需要繼承

AuthorizingRealm 這個類,重寫doGetAuthenticationInfo()-- 登錄驗證方法和
doGetAuthorizationInfo() -- 鑑權方法 實現邏輯即可

二、 shiro的功能
Shiro的具體功能點如下:

(1)身份認證/登錄,驗證用戶是不是擁有相應的身份; (2)授權,即權限驗證,驗證某個已認證的用戶是否擁有某個權限;即判斷用戶是否能做事情,常見的如:驗證某個用戶是否擁有某個角色。或者細粒度的驗證某個用戶對某個資源是否具有某個權限; (3)會話管理,即用戶登錄後就是一次會話,在沒有退出之前,它的所有信息都在會話中;會話可以是普通JavaSE環境的,也可以是如Web環境的; (4)加密,保護數據的安全性,如密碼加密存儲到數據庫,而不是明文存儲; (5)Web支持,可以非常容易的集成到Web環境; Caching:緩存,比如用戶登錄後,其用戶信息、擁有的角色/權限不必每次去查,這樣可以提高效率; (6)shiro支持多線程應用的併發驗證,即如在一個線程中開啓另一個線程,能把權限自動傳播過去; (7)提供測試支持; (8)允許一個用戶假裝爲另一個用戶(如果他們允許)的身份進行訪問; (9)記住我,這個是非常常見的功能,即一次登錄後,下次再來的話不用登錄了。

三、如何quickStart? 1. 首先我們需要添加Shiro的依賴
<properties>
<shiro.version>1.3.2</shiro.version>
</properties>

<!--=====-->
<!--shiro dependences-->
<!--=====-->
<dependency>
  <groupId>org.apache.shiro</groupId>
  <artifactId>shiro-core</artifactId>
  <version>${shiro.version}</version>
</dependency>
<dependency>
  <groupId>org.apache.shiro</groupId>
  <artifactId>shiro-web</artifactId>
  <version>${shiro.version}</version>
</dependency>
<dependency>
  <groupId>org.apache.shiro</groupId>
  <artifactId>shiro-spring</artifactId>
  <version>${shiro.version}</version>
</dependency>

2.重寫Ralm類 實現登錄驗證和授權的方法
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.subject.PrincipalCollection;
import java.util.Set;
// 繼承 AuthorizingRealm類 以重寫授權和驗證的方法;
 當攔截到資源是在我們定義的路徑時;shiro自動調用相對應的方法
public class ShiroRestRealm extends AuthorizingRealm { private final IAccountService accountService; public ShiroRestRealm(IAccountService accountService) { this.accountService = accountService; }

// 支持的Token類型
@Override public boolean supports(AuthenticationToken token) { return token instanceof UsernamePasswordToken || token instanceof UserIDToken; } /** * 驗證身份信息,但是這裏不負責比對的邏輯,我們只需根據principle(原則)返回AuthenticationInfo(攜帶了用戶名和密碼) * 比對的邏輯將由shiro框架完成 * * 因爲無狀態,我們每次都要驗證 * @param token * @return * @throws AuthenticationException */ @Override// 登錄驗證方法 protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { SimpleAuthenticationInfo authInfo = null; if (token instanceof UsernamePasswordToken) { UsernamePasswordToken upToken = (UsernamePasswordToken) token; String account = upToken.getUsername(); final String passwd = accountService.findPasswd(account); authInfo = new SimpleAuthenticationInfo(account, passwd, getName()); }else if (token instanceof UserIDToken){ UserIDToken userIDToken= (UserIDToken) token; authInfo=new SimpleAuthenticationInfo(userIDToken.getUserId(),userIDToken.getUserId(),getName()); } return authInfo; } /** * 鑑權,無需比對 * 只需根據用戶標識,返回其擁有的資源權限組合,比對的邏輯將由shiro框架完成。 * @param principals * @return */ //權限驗證方法 @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { SimpleAuthorizationInfo simpleAuthorInfo = new SimpleAuthorizationInfo(); //userid String userId = (String) super.getAvailablePrincipal(principals); //userid可訪問的所有url的集合 Set<String> permissions = accountService.findPermissionsById(userId); //添加至AuthorInfo simpleAuthorInfo.addStringPermissions(permissions); return simpleAuthorInfo; }}

三、 Shiro的配置
我們使用了Java代碼的方式去配置Shiro 我們也可以使用XML的形式在Spring-shiro.XML中配置我們需要配置的內容
使用Java代碼配置Shiro的方式:

@ImportResource("classpath:spring-context.xml")
@Configuration  // 可以認爲這是spring配置的Java版
public class ShiroConfig {
  /**<bean id="shiroFilter" class="ShiroFilterFactoryBean" />
   * 總過濾器
   * @param securityManager 依賴的安全管理器
   * @param authcFilter 自定義權限過濾器
   * */
  @Bean
  @Autowired
  public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager, StatelessAuthcFilter authcFilter) {
    ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
    /*<!-- securityManager -->
        <property name="securityManager" ref="securityManager" />*/
    bean.setSecurityManager(securityManager);

    /*註冊實際的攔截器*/
    Map<String, Filter> filters = bean.getFilters();
    filters.put("stateLessAuthcFilter", authcFilter);// 自定義的無狀態http權限驗證過濾器
    filters.put("anon", new AnonymousFilter());// shiro提供不做任何處理的過濾器
    filters.put("noSessionCreation", new NoSessionCreationFilter());// shiro提供的過濾器

    // bean.setFilters(filters);
    /*登錄跳轉鏈接*/
    bean.setLoginUrl("/rbac/account/login");
    bean.setUnauthorizedUrl("/rbac/account/unauthorized");

    /*-------------*/
    /*不同的url用不同的過濾器攔截*/
    bean.getFilterChainDefinitionMap().put("/rbac/account/login", "anon"); // 不攔截的寫在前面
    bean.getFilterChainDefinitionMap().put("/rbac/account/unauthorized*", "anon"); // 不攔截的寫在前面
    // 生產環境開啓權限和身份驗證
    if (System.getProperty("spring.profiles.active") == null ||
        System.getProperty("spring.profiles.active").equals("production")) {
      // 其餘所有路徑都會被攔截
      bean.getFilterChainDefinitionMap().put("/**", "noSessionCreation,stateLessAuthcFilter");
      // 添加路徑攔截
    } else {
      bean.getFilterChainDefinitionMap().put("/**", "anon");
    }

    return bean;
  }

  @Bean
  public StatelessAuthcFilter authcFilter(AccountService accountService) {
    StatelessAuthcFilter authcFilter = new StatelessAuthcFilter();
    authcFilter.setAccountService(accountService);
    return authcFilter;
  }

  @Bean
  @Autowired
  public DefaultWebSecurityManager securityManager(Realm realm) {
    DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
    securityManager.setRealm(realm);

    final DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
    sessionManager.setSessionIdCookieEnabled(false);
    sessionManager.setSessionValidationSchedulerEnabled(false);
    securityManager.setSessionManager(sessionManager);
    // 禁止session被創建
    securityManager.setSubjectFactory(new DefaultWebSubjectFactory() {
      @Override
      public Subject createSubject(SubjectContext context) {
        context.setSessionCreationEnabled(false);
        return super.createSubject(context);
      }
    });
    // 禁用使用Sessions 作爲存儲策略的實現
    final DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();
    subjectDAO.setSessionStorageEvaluator(new DefaultSessionStorageEvaluator() {
      @Override
      public boolean isSessionStorageEnabled() {
        return false;
      }
    });
    securityManager.setSubjectDAO(subjectDAO);
    SecurityUtils.setSecurityManager(securityManager);

    return securityManager;
  }

  @Bean
  @Autowired
  public Realm realm(@Qualifier("accountService") IAccountService accountService) {
    return new ShiroRestRealm(accountService);
  }

  @Bean
  public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
    return new LifecycleBeanPostProcessor();
  }

  @Bean
  public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
    DefaultAdvisorAutoProxyCreator daap = new DefaultAdvisorAutoProxyCreator();
    daap.setProxyTargetClass(true);
    return daap;
  }

  @Bean
  @Autowired
  public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(DefaultWebSecurityManager securityManager) {
    AuthorizationAttributeSourceAdvisor aasa = new AuthorizationAttributeSourceAdvisor();
    aasa.setSecurityManager(securityManager);
    return aasa;
  }


}


XML配置方式如下、

<?xml version="1.0" encoding="UTF-8"?>  
<beans xmlns="http://www.springframework.org/schema/beans"  
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
    xsi:schemaLocation="http://www.springframework.org/schema/beans   
                        http://www.springframework.org/schema/beans/spring-beans-3.0.xsd"  
    default-lazy-init="true">  

    <description>Shiro Configuration</description>  

    <!-- Shiro's main business-tier object for web-enabled applications -->  
    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">  
        <property name="realm" ref="myShiroRealm" />  
        <property name="cacheManager" ref="cacheManager" />  
    </bean>  

    <!-- 項目自定義的Realm -->  
    <bean id="myShiroRealm" class="com.luo.shiro.realm.MyShiroRealm">  
        <property name="cacheManager" ref="cacheManager" />  
    </bean>  

    <!-- Shiro Filter -->  
    <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">  
        <property name="securityManager" ref="securityManager" />  
        <property name="loginUrl" value="/login.jhtml" />  
        <property name="successUrl" value="/loginsuccess.jhtml" />  
        <property name="unauthorizedUrl" value="/error.jhtml" />  
        <property name="filterChainDefinitions">  
            <value>  
                /index.jhtml = authc  
                /login.jhtml = anon
                /checkLogin.json = anon  
                /loginsuccess.jhtml = anon  
                /logout.json = anon  
                /** = authc  
            </value>  
        </property>  
    </bean>  

    <!-- 用戶授權信息Cache -->  
    <bean id="cacheManager" class="org.apache.shiro.cache.MemoryConstrainedCacheManager" />  

    <!-- 保證實現了Shiro內部lifecycle函數的bean執行 -->  
    <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor" />  

    <!-- AOP式方法級權限檢查 -->  
    <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>  
注意以上兩種方式由於細節問題 並不完全等同, 僅供參考;

這裏有必要說清楚”shiroFilter” 這個bean裏面的各個屬性property的含義:

(1)securityManager:這個屬性是必須的,沒什麼好說的,就這樣配置就好。
(2)loginUrl:沒有登錄的用戶請求需要登錄的頁面時自動跳轉到登錄頁面,可配置也可不配置。
(3)successUrl:登錄成功默認跳轉頁面,不配置則跳轉至”/”,一般可以不配置,直接通過代碼進行處理。
(4)unauthorizedUrl:沒有權限默認跳轉的頁面。
(5)filterChainDefinitions,對於過濾器就有必要詳細說明一下:

1)Shiro驗證URL時,URL匹配成功便不再繼續匹配查找(所以要注意配置文件中的URL順序,尤其在使用通配符時),故filterChainDefinitions的配置順序爲自上而下,以最上面的爲準

2)當運行一個Web應用程序時,Shiro將會創建一些有用的默認Filter實例,並自動地在[main]項中將它們置爲可用自動地可用的默認的Filter實例是被DefaultFilter枚舉類定義的,枚舉的名稱字段就是可供配置的名稱

3)通常可將這些過濾器分爲兩組:

anon,authc,authcBasic,user是第一組認證過濾器

perms,port,rest,roles,ssl是第二組授權過濾器

注意user和authc不同:當應用開啓了rememberMe時,用戶下次訪問時可以是一個user,但絕不會是authc,因爲authc是需要重新認證的
user表示用戶不一定已通過認證,只要曾被Shiro記住過登錄狀態的用戶就可以正常發起請求,比如rememberMe

說白了,以前的一個用戶登錄時開啓了rememberMe,然後他關閉瀏覽器,下次再訪問時他就是一個user,而不會authc

4)舉幾個例子
/admin=authc,roles[admin] 表示用戶必需已通過認證,並擁有admin角色纔可以正常發起’/admin’請求
/edit=authc,perms[admin:edit] 表示用戶必需已通過認證,並擁有admin:edit權限纔可以正常發起’/edit’請求
/home=user 表示用戶不一定需要已經通過認證,只需要曾經被Shiro記住過登錄狀態就可以正常發起’/home’請求

5)各默認過濾器常用如下(注意URL Pattern裏用到的是兩顆星,這樣才能實現任意層次的全匹配)
/admins/**=anon 無參,表示可匿名使用,可以理解爲匿名用戶或遊客
/admins/user/**=authc 無參,表示需認證才能使用
/admins/user/**=authcBasic 無參,表示httpBasic認證
/admins/user/**=user 無參,表示必須存在用戶,當登入操作時不做檢查
/admins/user/**=ssl 無參,表示安全的URL請求,協議爲https
/admins/user/*=perms[user:add:]
參數可寫多個,多參時必須加上引號,且參數之間用逗號分割,如/admins/user/*=perms[“user:add:,user:modify:*”]
當有多個參數時必須每個參數都通過纔算通過,相當於isPermitedAll()方法
/admins/user/**=port[8081]
當請求的URL端口不是8081時,跳轉到schemal://serverName:8081?queryString
其中schmal是協議http或https等,serverName是你訪問的Host,8081是Port端口,queryString是你訪問的URL裏的?後面的參數
/admins/user/**=rest[user]
根據請求的方法,相當於/admins/user/**=perms[user:method],其中method爲post,get,delete等
/admins/user/**=roles[admin]
參數可寫多個,多個時必須加上引號,且參數之間用逗號分割,如/admins/user/**=roles[“admin,guest”]
當有多個參數時必須每個參數都通過纔算通過,相當於hasAllRoles()方法


上文參考了

http://blog.csdn.net/u013142781/article/details/50629708

http://www.cppblog.com/guojingjia2006/archive/2014/05/14/206956.html,更多詳細說明請訪問該鏈接。



3. 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>
</filter>

<filter-mapping>
  <filter-name>shiroFilter</filter-name>
  <url-pattern>/*</url-pattern>
</filter-mapping>
4. 接下來我們寫Controller層代碼就好了
@RequestMapping(value = "/login",method = {RequestMethod.GET,RequestMethod.POST})
@ApiOperation(value = "登錄驗證", /*httpMethod = "GET",*/ /*response = Result.class,*/ notes = "登錄驗證")
public Result login(Account account) {
  if (null == account.getAccount()) {
    return ResultGenerator.genFailResult("請登錄");
  } else {
    try {
      Map<String, Object> resultMap = new HashMap<>();
//check 會報錯 這個時候去調用自定義Ralm中的驗證的方法;如果驗證不通過,會拋出異常,如果捕捉到異常則認爲驗證失敗
     String serverToken = check(account);
      Integer userid = accountService.saveToken(account, serverToken);
      resultMap.put("userid", userid);
      resultMap.put("token", serverToken);
      return ResultGenerator.genSuccessResult(resultMap);
    } catch (AuthenticationException e) { // 失敗
      return ResultGenerator.genFailResult("登錄失敗");
    }
  }

private String check(Account account) {
  final String password = account.getPassword(); // 明文密碼
  /*數據庫中存儲的是md5+鹽加密後的密碼,因此這裏要把加密後的密碼傳入*/
  final String md5Password = MD5Util.md5(password, SysConst.SALT);
  AuthenticationToken token = new UsernamePasswordToken(account.getAccount(), md5Password);
  Subject currentSubject = SecurityUtils.getSubject();
  currentSubject.login(token);// 這句話觸發框架去訪問Realm的doGetAuthenticationInfo
  String serverToken = UUID.randomUUID().toString().replaceAll("-", "");
  return serverToken;
}



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章