Spring Boot 整合 Shiro(Thymeleaf 版本)

早期的 Spring Boot 和 Shiro 的整合是通過改造 Spring MVC 和 Shiro 的整合而來噪猾。簡單來說公般,就是將 Spring MVC 和 Shiro 的整合在 Spring Boot 中以 Java 代碼配置的方式再寫一遍恒削。

不過捧请,后來 Shiro 官方提供了和 Spring Boot 整合的 Starter 芙贫,并提供了官方教程:https://shiro.apache.org/spring-boot.html

用 Starter 整合

依賴

<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-spring-boot-web-starter</artifactId> <!-- 注意這里有個(gè) web -->
    <version>1.7.0</version>
</dependency>

必要的兩項(xiàng)配置

# thymeleaf 的配置略

shiro:
  loginUrl: "/login-page" # 未通過『認(rèn)證』時(shí)精居,跳轉(zhuǎn)顯示的頁面
  unauthorizedUrl: "/403-page" # 未通過『鑒權(quán)』時(shí)锄禽,跳轉(zhuǎn)顯示的頁面
  • 配置類

    @Configuration
    public class ShiroConfig {
    
      /*
       * 在沒有配置 Realm 和 DefaultWebSecurityManager 的情況下,
       * Shiro 的 starter 默認(rèn)使用的是 IniRealm靴姿,它會(huì)要求你在 classpath 下提供一個(gè)名為 shiro.ini 的文件沃但。
       */
    
      @Bean
      public ShiroFilterChainDefinition shiroFilterChainDefinition() {
          DefaultShiroFilterChainDefinition chain = new DefaultShiroFilterChainDefinition();
    
          chain.addPathDefinition("/login-page", "anon");
          chain.addPathDefinition("/403-page", "anon");
          chain.addPathDefinition("/login", "anon");
          chain.addPathDefinition("/logout", "logout");
          chain.addPathDefinition("/hello", "anon");
    
          chain.addPathDefinition("/**", "authc");
    
          return chain;
      }
    
      /*
      // 不再需要。高版本的 spring boot 默認(rèn)的 aop 方案已經(jīng)指定為成 cglib 了佛吓。
      @Bean
      @DependsOn("lifecycleBeanPostProcessor")
      public DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator() {
          DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
          // 強(qiáng)制指定注解的底層實(shí)現(xiàn)使用 cglib 方案
          defaultAdvisorAutoProxyCreator.setProxyTargetClass(true);
          return defaultAdvisorAutoProxyCreator;
      }
      */
    }
    

LoginController 和 PageController

@Slf4j
@Controller
public class LoginController {

    @ExceptionHandler({Exception.class})
    public String exception(Exception e) {
        if (e instanceof IncorrectCredentialsException)
            log.warn("密碼錯(cuò)誤", e);
        else
            log.warn("其它錯(cuò)誤", e);

        return "redirect:/failure-page";
    }

    @RequestMapping("/login")
    public String login(String username, String password) {

        Subject subject = SecurityUtils.getSubject();
        UsernamePasswordToken token = new UsernamePasswordToken(username, password);

        subject.login(token);

        log.info("登錄成功");
        return "redirect:/success-page";
    }
}

@Slf4j
@Controller
public class PageController {

    @RequestMapping("/login-page")
    public String loginPage(Model model) {
        return "login";
    }

    @RequestMapping("/403-page")
    public String _403Page() {
        return "403";
    }


    @RequestMapping("/success-page")
    public String successPage() {
        return "success";
    }

    @RequestMapping("/failure-page")
    public String failurePage() {
        return "failure";
    }

    @RequestMapping("/hello-page")
    public String helloPage() {
        return "hello";
    }
}

使用注解

如果是以 Shiro 整合 Spring宵晚、Spring MVC 的方式來整合 Spring Boot垂攘,那么要在項(xiàng)目中使用 Shiro 的注解,那還是需要像之前的內(nèi)容那樣進(jìn)行配置淤刃,并且解決 $Proxy 問題晒他。

不過在 shiro 官方提供 shiro 整合 starter 的包之后,以 starter 包的方式整合 spring boot 不需要再進(jìn)行額外配置钝凶,即可使用仪芒。

按慣例:

  • 在配置文件中配置『認(rèn)證』等規(guī)則唁影;

  • 通過注解配置『鑒權(quán)』規(guī)則耕陷。

在使用注解的情況下,realm 中的 doGetAuthorization 方法不是靠 RolesAuthorizationFilter 和 PermissionsAuthorizationFilter 中的 subject.hasRole()subject.isPermitted() 方法觸發(fā)据沈,而是『靠 AOP 反射注解信息來觸發(fā)的』哟沫。

注解實(shí)現(xiàn)鑒權(quán)的原理

在使用注解進(jìn)行鑒權(quán)的情況下,Shiro 并未使用 RolesAuthorizationFilter 和 PermissionsAuthorizationFilter锌介,而是使用 AOP 反射注解信息嗜诀,在 Controller 的代理類中使用 subject.hashRole()subject.isPermitted() 方法觸發(fā)鑒權(quán)功能。

我們可以模擬這個(gè)方案:

  • 自定義注解

    @Target({ElementType.TYPE, ElementType.METHOD})
    @Retention(RetentionPolicy.RUNTIME)
    public @interface RequiresRole {
      public String[] value() default "";
    }
    
  • 在 Spring boot 中引入 AOP 依賴:

    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-aop</artifactId>
    </dependency>
    
  • 實(shí)現(xiàn) Controller 的切面類:

    @Aspect
    @Component
    public class HelloControllerAspect {
    
      @Around("execution(* com.example.controller.HelloController.*(..))")
      public Object around(ProceedingJoinPoint jp) {
          try {
    
              ...
    
              return jp.proceed();
    
          } catch (Throwable e) {
              e.printStackTrace();
          }
    
          return null;
      }
    } 
    
  • 在切面類的方法中孔祸,獲取 Controller 的 Method隆敢,并進(jìn)一步獲取其上的注解

    try {
        MethodSignature methodSignature = (MethodSignature) jp.getSignature();
        Method method = methodSignature.getMethod();
    
        RequiresRole requiresRole = method.getAnnotation(RequiresRole.class);
        if (requiresRole != null) {
            String[] roles = requiresRole.value();
            System.out.println("執(zhí)行本方法需要 " + Arrays.toString(roles) + " 權(quán)限");
            // for (String role : roles) {
            //     if (subject.hashRole("role"))
            //         return jp.proceed();
            // }
        }
    
        return jp.proceed();
    
    } catch (Throwable e) {
        e.printStackTrace();
    }
    

Shiro 標(biāo)簽庫

Shiro 為頁面<small>(jsp、thymeleaf)</small>提供了一套標(biāo)簽庫崔慧,以結(jié)合權(quán)限管理功能使用拂蝎。

<!-- 引入依賴 -->
<dependency>
  <groupId>com.github.theborakompanioni</groupId>
  <artifactId>thymeleaf-extras-shiro</artifactId>
  <version>2.0.0</version>
</dependency>

使用 Shiro 標(biāo)簽庫時(shí),在配置類中有個(gè)開關(guān)配置要打開:

@Bean(name = "shiroDialect")
public ShiroDialect shiroDialect() {
    return new ShiroDialect();
}

例如:

<html lang="zh_CN" xmlns:th="http://www.thymeleaf.org" xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">
      
<shiro:hasRole name="admin">
    <button type="button" class="btn btn-outline btn-default">
        <i class="glyphicon glyphicon-heart" aria-hidden="true"></i>
    </button>
</shiro:hasRole>

部分實(shí)例匯總:

<table border="1" cellpadding="0" cellspacing="0">
    <tr>
        <td> shiro:guest </td>
        <td> <shiro:guest>游客能看到這段內(nèi)容</shiro:guest> </td>
    </tr>
    <tr>
        <td> shiro:user </td>
        <td> <shiro:user>用戶能看到這段內(nèi)容</shiro:user> </td>
    </tr>
    <tr>
        <td> shiro:notAuthenticated </td>
        <td> <shiro:notAuthenticated>未通過『認(rèn)證』能看到這段內(nèi)容</shiro:notAuthenticated> </td>
    </tr>
    <tr>
        <td> shiro:authenticated </td>
        <td> <shiro:authenticated>通過了『認(rèn)證』能看到這段內(nèi)容</shiro:authenticated> </td>
    </tr>
    <tr>
        <td> shiro:hashRole </td>
        <td> <shiro:hasRole name="admin">有『admin』角色能看到這段內(nèi)容</shiro:hasRole> </td>
    </tr>
    <tr>
        <td> shiro:lacksRole </td>
        <td> <shiro:lacksRole name="admin">缺『admin』角色能看到這段內(nèi)容</shiro:lacksRole> </td>
    </tr>
    <tr>
        <td> shiro:hasPermission </td>
        <td> <shiro:hasPermission name="user:insert">有『user:insert』權(quán)限能看到這段內(nèi)容</shiro:hasPermission> </td>
    </tr>
    <tr>
        <td> shiro:lacksPermission </td>
        <td> <shiro:lacksPermission name="user:insert">缺『user:insert』權(quán)限能看到這段內(nèi)容</shiro:lacksPermission> </td>
    </tr>
</table>
標(biāo)簽 說明
guest 用戶沒有身份驗(yàn)證時(shí)顯示相應(yīng)信息惶室,即游客訪問信息温自。
user 用戶已經(jīng)身份驗(yàn)證/記住我登錄后顯示相應(yīng)的信息。
authenticated 用戶已經(jīng)身份驗(yàn)證通過皇钞,即 Subject.login 登錄成功悼泌,不是記住我登錄的。
notAuthenticated 用戶已經(jīng)身份驗(yàn)證通過夹界,即沒有調(diào)用 Subject.login 進(jìn)行登錄馆里,包括記住我自動(dòng)登錄的也屬于未進(jìn)行身份驗(yàn)證。
principal 相當(dāng)于 ((User)Subject.getPrincipals()).getUsername() 可柿。
lacksPermission 如果當(dāng)前 Subject 沒有權(quán)限將顯示 body 體內(nèi)容鸠踪。
hasRole 如果當(dāng)前 Subject 有角色將顯示 body 體內(nèi)容。
hasAnyRoles 如果當(dāng)前 Subject 有任意一個(gè)角色<small>(或的關(guān)系)</small>將顯示body體內(nèi)容趾痘。
lacksRole 如果當(dāng)前 Subject 沒有角色將顯示 body 體內(nèi)容慢哈。
hasPermission 如果當(dāng)前 Subject 有權(quán)限將顯示 body 體內(nèi)容

補(bǔ)充:Start 中包含的配置(了解、自學(xué))

Key Default Value Description
shiro.enabled true Enables Shiro’s Spring module
shiro.web.enabled true Enables Shiro’s Spring web module
shiro.annotations.enabled true Enables Spring support for Shiro’s annotations
shiro.sessionManager.deleteInvalidSessions true Remove invalid session from session storage
shiro.sessionManager.sessionIdCookieEnabled true Enable session ID to cookie, for session tracking
shiro.sessionManager.sessionIdUrlRewritingEnabled true Enable session URL rewriting support
shiro.userNativeSessionManager false If enabled Shiro will manage the HTTP sessions instead of the container
shiro.sessionManager.cookie.name JSESSIONID Session cookie name
shiro.sessionManager.cookie.maxAge -1 Session cookie max age
shiro.sessionManager.cookie.domain null Session cookie domain
shiro.sessionManager.cookie.path null Session cookie path
shiro.sessionManager.cookie.secure false Session cookie secure flag
shiro.rememberMeManager.cookie.name rememberMe RememberMe cookie name
shiro.rememberMeManager.cookie.maxAge one year RememberMe cookie max age
shiro.rememberMeManager.cookie.domain null RememberMe cookie domain
shiro.rememberMeManager.cookie.path null RememberMe cookie path
shiro.rememberMeManager.cookie.secure false RememberMe cookie secure flag
shiro.loginUrl / login.jsp Login URL used when unauthenticated users are redirected to login page
shiro.successUrl / Default landing page after a user logs in (if alternative cannot be found in the current session)
shiro.unauthorizedUrl null Page to redirect user to if they are unauthorized (403 page)

補(bǔ)充:不使用 Starter 整合(了解永票、自學(xué))

<!-- 自動(dòng)依賴導(dǎo)入 shiro-core 和 shiro-web -->
<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-spring</artifactId>
    <version>1.5.3</version>
</dependency>

將 Shiro 的配置信息<small>(spring-shiro.xml 和 spring-web.xml)</small>以 Java 代碼配置的形式改寫:

@Configuration
public class ShiroConfig {

    @Bean
    public Realm realm() {
        SimpleAccountRealm realm = new SimpleAccountRealm();
        realm.addAccount("tom", "123", "admin", "user");
        return realm;
    }

    @Bean
    public DefaultWebSecurityManager securityManager() {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(realm());
        return securityManager;
    }

    @Bean
    public ShiroFilterFactoryBean shirFilter() {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager());

        shiroFilterFactoryBean.setLoginUrl("/login-page");
        shiroFilterFactoryBean.setUnauthorizedUrl("/403-page");

        Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
        filterChainDefinitionMap.put("/login-page", "anon");
        filterChainDefinitionMap.put("/403-page", "anon");
        filterChainDefinitionMap.put("/login", "anon");
        filterChainDefinitionMap.put("/logout", "logout");
        filterChainDefinitionMap.put("/hello", "anon");
        filterChainDefinitionMap.put("/**", "authc");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);

        return shiroFilterFactoryBean;
    }

    /* ################################################################# */

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

    @Bean
    @DependsOn("lifecycleBeanPostProcessor")
    public DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator() {
        DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
        // 強(qiáng)制指定注解的底層實(shí)現(xiàn)使用 cglib 方案
        defaultAdvisorAutoProxyCreator.setProxyTargetClass(true);
        return defaultAdvisorAutoProxyCreator;
    }

    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(DefaultWebSecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
        advisor.setSecurityManager(securityManager);
        return advisor;
    }
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末卵贱,一起剝皮案震驚了整個(gè)濱河市滥沫,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌键俱,老刑警劉巖兰绣,帶你破解...
    沈念sama閱讀 222,865評(píng)論 6 518
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異编振,居然都是意外死亡缀辩,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,296評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門踪央,熙熙樓的掌柜王于貴愁眉苦臉地迎上來臀玄,“玉大人,你說我怎么就攤上這事畅蹂〗∥蓿” “怎么了?”我有些...
    開封第一講書人閱讀 169,631評(píng)論 0 364
  • 文/不壞的土叔 我叫張陵液斜,是天一觀的道長累贤。 經(jīng)常有香客問我,道長少漆,這世上最難降的妖魔是什么臼膏? 我笑而不...
    開封第一講書人閱讀 60,199評(píng)論 1 300
  • 正文 為了忘掉前任,我火速辦了婚禮示损,結(jié)果婚禮上渗磅,老公的妹妹穿的比我還像新娘。我一直安慰自己屎媳,他們只是感情好夺溢,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,196評(píng)論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著烛谊,像睡著了一般风响。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上丹禀,一...
    開封第一講書人閱讀 52,793評(píng)論 1 314
  • 那天状勤,我揣著相機(jī)與錄音,去河邊找鬼双泪。 笑死持搜,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的焙矛。 我是一名探鬼主播葫盼,決...
    沈念sama閱讀 41,221評(píng)論 3 423
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼村斟!你這毒婦竟也來了贫导?” 一聲冷哼從身側(cè)響起抛猫,我...
    開封第一講書人閱讀 40,174評(píng)論 0 277
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎孩灯,沒想到半個(gè)月后闺金,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,699評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡峰档,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,770評(píng)論 3 343
  • 正文 我和宋清朗相戀三年败匹,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片讥巡。...
    茶點(diǎn)故事閱讀 40,918評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡掀亩,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出尚卫,到底是詐尸還是另有隱情归榕,我是刑警寧澤,帶...
    沈念sama閱讀 36,573評(píng)論 5 351
  • 正文 年R本政府宣布吱涉,位于F島的核電站,受9級(jí)特大地震影響外里,放射性物質(zhì)發(fā)生泄漏怎爵。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,255評(píng)論 3 336
  • 文/蒙蒙 一盅蝗、第九天 我趴在偏房一處隱蔽的房頂上張望鳖链。 院中可真熱鬧,春花似錦墩莫、人聲如沸芙委。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,749評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽灌侣。三九已至,卻和暖如春裂问,著一層夾襖步出監(jiān)牢的瞬間侧啼,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,862評(píng)論 1 274
  • 我被黑心中介騙來泰國打工堪簿, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留痊乾,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 49,364評(píng)論 3 379
  • 正文 我出身青樓椭更,卻偏偏與公主長得像哪审,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子虑瀑,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,926評(píng)論 2 361

推薦閱讀更多精彩內(nèi)容