springboot整合shiro(完整版)

文末加了源碼地址,報錯的朋友可以去下載
---更新于2020/06/15

應(yīng)廣大網(wǎng)友要求本次pom文件代碼貼全了莫绣,
springboot版本用了目前最新的2.3.4.RELEASE,
shiro版本用了目前最新的1.6.0
親測有效,并將持續(xù)更新
---更新于2020/09/20

添加了shiro的一些常見的異常
---更新于2020/09/20

1.shiro是什么?

Shiro是Apache下的一個開源項目捏检。shiro屬于輕量級框架互纯,相對于SpringSecurity簡單的多遭庶,也沒有SpringSecurity那么復(fù)雜。以下是我自己學(xué)習(xí)之后的記錄蓖乘。
官方架構(gòu)圖如下:
官方架構(gòu)圖

2.主要功能

shiro主要有三大功能模塊:

1. Subject:主體锤悄,一般指用戶。
2. SecurityManager:安全管理器嘉抒,管理所有Subject零聚,可以配合內(nèi)部安全組件。(類似于SpringMVC中的DispatcherServlet)
3. Realms:用于進(jìn)行權(quán)限信息的驗證些侍,一般需要自己實現(xiàn)隶症。

3.細(xì)分功能

1. Authentication:身份認(rèn)證/登錄(賬號密碼驗證)。
2. Authorization:授權(quán)岗宣,即角色或者權(quán)限驗證沿腰。
3. Session Manager:會話管理,用戶登錄后的session相關(guān)管理狈定。
4. Cryptography:加密颂龙,密碼加密等。
5. Web Support:Web支持纽什,集成Web環(huán)境措嵌。
6. Caching:緩存,用戶信息芦缰、角色企巢、權(quán)限等緩存到如redis等緩存中。
7. Concurrency:多線程并發(fā)驗證让蕾,在一個線程中開啟另一個線程浪规,可以把權(quán)限自動傳播過去。
8. Testing:測試支持探孝;
9. Run As:允許一個用戶假裝為另一個用戶(如果他們允許)的身份進(jìn)行訪問笋婿。
10. Remember Me:記住我,登錄后顿颅,下次再來的話不用登錄了缸濒。

(更多關(guān)于shiro是什么的文字請自行去搜索引擎找,本文主要記錄springboot與shiro的集成)
首先先創(chuàng)建springboot項目,此處不過多描述庇配。

上代碼:

目錄結(jié)構(gòu):
目錄結(jié)構(gòu)

pom.xml:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.4.RELEASE</version>
        <relativePath/>
    </parent>
    <groupId>com.wsl</groupId>
    <artifactId>spring-shiro-demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>spring-shiro-demo</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
        <spring.shiro.version>1.6.0</spring.shiro.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <!-- shiro -->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>${spring.shiro.version}</version>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <!--頁面模板依賴-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <!--熱部署依賴-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>    
        </plugins>
    </build>
</project>

user.java(用戶實體類):

package com.wsl.bean;

import lombok.AllArgsConstructor;
import lombok.Data;

import java.util.Set;

@Data
@AllArgsConstructor
public class User {
    private String id;
    private String userName;
    private String password;
    /**
     * 用戶對應(yīng)的角色集合
     */
    private Set<Role> roles;
}

Role.java(角色對應(yīng)實體類):

package com.wsl.bean;

import lombok.AllArgsConstructor;
import lombok.Data;

import java.util.Set;

@Data
@AllArgsConstructor
public class Role {

    private String id;
    private String roleName;
    /**
     * 角色對應(yīng)權(quán)限集合
     */
    private Set<Permissions> permissions;
}

Permissions.java(權(quán)限對應(yīng)實體類):

package com.wsl.bean;

import lombok.AllArgsConstructor;
import lombok.Data;

@Data
@AllArgsConstructor
public class Permissions {
    private String id;
    private String permissionsName;
}

LoginServiceImpl.java:

package com.wsl.service.impl;

import com.wsl.bean.Permissions;
import com.wsl.bean.Role;
import com.wsl.bean.User;
import com.wsl.service.LoginService;
import org.springframework.stereotype.Service;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

@Service
public class LoginServiceImpl implements LoginService {

    @Override
    public User getUserByName(String getMapByName) {
        return getMapByName(getMapByName);
    }

    /**
     * 模擬數(shù)據(jù)庫查詢
     *
     * @param userName 用戶名
     * @return User
     */
    private User getMapByName(String userName) {
        Permissions permissions1 = new Permissions("1", "query");
        Permissions permissions2 = new Permissions("2", "add");
        Set<Permissions> permissionsSet = new HashSet<>();
        permissionsSet.add(permissions1);
        permissionsSet.add(permissions2);
        Role role = new Role("1", "admin", permissionsSet);
        Set<Role> roleSet = new HashSet<>();
        roleSet.add(role);
        User user = new User("1", "wsl", "123456", roleSet);
        Map<String, User> map = new HashMap<>();
        map.put(user.getUserName(), user);
        
        Set<Permissions> permissionsSet1 = new HashSet<>();
        permissionsSet1.add(permissions1);
        Role role1 = new Role("2", "user", permissionsSet1);
        Set<Role> roleSet1 = new HashSet<>();
        roleSet1.add(role1);
        User user1 = new User("2", "zhangsan", "123456", roleSet1);
        map.put(user1.getUserName(), user1);
        return map.get(userName);
    }
}

自定義Realm用于查詢用戶的角色和權(quán)限信息并保存到權(quán)限管理器:

CustomRealm.java

package com.wsl.shiro;

import com.wsl.bean.Permissions;
import com.wsl.bean.Role;
import com.wsl.bean.User;
import com.wsl.service.LoginService;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
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 org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.StringUtils;

public class CustomRealm extends AuthorizingRealm {

    @Autowired
    private LoginService loginService;

    /**
     * @MethodName doGetAuthorizationInfo
     * @Description 權(quán)限配置類
     * @Param [principalCollection]
     * @Return AuthorizationInfo
     * @Author WangShiLin
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        //獲取登錄用戶名
        String name = (String) principalCollection.getPrimaryPrincipal();
        //查詢用戶名稱
        User user = loginService.getUserByName(name);
        //添加角色和權(quán)限
        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
        for (Role role : user.getRoles()) {
            //添加角色
            simpleAuthorizationInfo.addRole(role.getRoleName());
            //添加權(quán)限
            for (Permissions permissions : role.getPermissions()) {
                simpleAuthorizationInfo.addStringPermission(permissions.getPermissionsName());
            }
        }
        return simpleAuthorizationInfo;
    }

    /**
     * @MethodName doGetAuthenticationInfo
     * @Description 認(rèn)證配置類
     * @Param [authenticationToken]
     * @Return AuthenticationInfo
     * @Author WangShiLin
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        if (StringUtils.isEmpty(authenticationToken.getPrincipal())) {
            return null;
        }
        //獲取用戶信息
        String name = authenticationToken.getPrincipal().toString();
        User user = loginService.getUserByName(name);
        if (user == null) {
            //這里返回后會報出對應(yīng)異常
            return null;
        } else {
            //這里驗證authenticationToken和simpleAuthenticationInfo的信息
            SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(name, user.getPassword().toString(), getName());
            return simpleAuthenticationInfo;
        }
    }
}

ShiroConfig.java斩跌,把CustomRealm和SecurityManager等注入到spring容器中:

package com.wsl.config;

import com.wsl.shiro.CustomRealm;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.HashMap;
import java.util.Map;

@Configuration
public class shiroConfig {
    
    @Bean
    @ConditionalOnMissingBean
    public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
        DefaultAdvisorAutoProxyCreator defaultAAP = new DefaultAdvisorAutoProxyCreator();
        defaultAAP.setProxyTargetClass(true);
        return defaultAAP;
    }

    //將自己的驗證方式加入容器
    @Bean
    public CustomRealm myShiroRealm() {
        CustomRealm customRealm = new CustomRealm();
        return customRealm;
    }

    //權(quán)限管理,配置主要是Realm的管理認(rèn)證
    @Bean
    public SecurityManager securityManager() {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(myShiroRealm());
        return securityManager;
    }

    //Filter工廠捞慌,設(shè)置對應(yīng)的過濾條件和跳轉(zhuǎn)條件
    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        Map<String, String> map = new HashMap<>();
        //登出
        map.put("/logout", "logout");
        //對所有用戶認(rèn)證
        map.put("/**", "authc");
        //登錄
        shiroFilterFactoryBean.setLoginUrl("/login");
        //首頁
        shiroFilterFactoryBean.setSuccessUrl("/index");
        //錯誤頁面耀鸦,認(rèn)證不通過跳轉(zhuǎn)
        shiroFilterFactoryBean.setUnauthorizedUrl("/error");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(map);
        return shiroFilterFactoryBean;
    }

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

LoginController.java:我們編寫一個簡單的登錄方法,一個index頁的查詢方法啸澡,一個add方法揭糕,一個admin方法,對應(yīng)不同的角色或權(quán)限攔截

package com.wsl.controller;

import com.wsl.bean.User;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authz.AuthorizationException;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.apache.shiro.authz.annotation.RequiresRoles;
import org.apache.shiro.subject.Subject;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@Slf4j
public class LoginController {

    @GetMapping("/login")
    public String login(User user) {
        if (StringUtils.isEmpty(user.getUserName()) || StringUtils.isEmpty(user.getPassword())) {
            return "請輸入用戶名和密碼锻霎!";
        }
        //用戶認(rèn)證信息
        Subject subject = SecurityUtils.getSubject();
        UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(
                user.getUserName(),
                user.getPassword()
        );
        try {
            //進(jìn)行驗證著角,這里可以捕獲異常,然后返回對應(yīng)信息
            subject.login(usernamePasswordToken);
//            subject.checkRole("admin");
//            subject.checkPermissions("query", "add");
        } catch (UnknownAccountException e) {
            log.error("用戶名不存在旋恼!", e);
            return "用戶名不存在吏口!";
        } catch (AuthenticationException e) {
            log.error("賬號或密碼錯誤!", e);
            return "賬號或密碼錯誤冰更!";
        } catch (AuthorizationException e) {
            log.error("沒有權(quán)限产徊!", e);
            return "沒有權(quán)限";
        }
        return "login success";
    }

    @RequiresRoles("admin")
    @GetMapping("/admin")
    public String admin() {
        return "admin success!";
    }

    @RequiresPermissions("query")
    @GetMapping("/index")
    public String index() {
        return "index success!";
    }

    @RequiresPermissions("add")
    @GetMapping("/add")
    public String add() {
        return "add success!";
    }
}

注解驗證角色和權(quán)限的話無法捕捉異常,從而無法正確的返回給前端錯誤信息蜀细,所以我加了一個類用于攔截異常舟铜,具體代碼如下
MyExceptionHandler.java
package com.wsl.filter;

import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.authz.AuthorizationException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;

@ControllerAdvice
@Slf4j
public class MyExceptionHandler {

    @ExceptionHandler
    @ResponseBody
    public String ErrorHandler(AuthorizationException e) {
        log.error("沒有通過權(quán)限驗證!", e);
        return "沒有通過權(quán)限驗證奠衔!";
    }
}

打開網(wǎng)頁 http://localhost:8080/login?userName=wsl&password=123456

登錄成功

然后輸入index地址:http://localhost:8080/index

index訪問成功

換zhangsan賬號登錄后再訪問index
http://localhost:8080/login?userName=zhangsan&password=123456
http://localhost:8080/add

權(quán)限控制訪問失敗

最近看好多人都項目報錯谆刨,我把源碼放到碼云上,大家可以自行下載:
源碼地址

最后加一些常見的shiro異常:

1. AuthenticationException 認(rèn)證異常

Shiro在登錄認(rèn)證過程中归斤,認(rèn)證失敗需要拋出的異常痊夭。 AuthenticationException包含以下子類:

1.1. CredentitalsException 憑證異常

IncorrectCredentialsException 不正確的憑證
ExpiredCredentialsException 憑證過期

1.2. AccountException 賬號異常

ConcurrentAccessException: 并發(fā)訪問異常(多個用戶同時登錄時拋出)
UnknownAccountException: 未知的賬號
ExcessiveAttemptsException: 認(rèn)證次數(shù)超過限制
DisabledAccountException: 禁用的賬號
LockedAccountException: 賬號被鎖定
UnsupportedTokenException: 使用了不支持的Token

2. AuthorizationException: 授權(quán)異常

Shiro在登錄認(rèn)證過程中,授權(quán)失敗需要拋出的異常脏里。 AuthorizationException包含以下子類:

2.1. UnauthorizedException:

拋出以指示請求的操作或?qū)φ埱蟮馁Y源的訪問是不允許的她我。

2.2. UnanthenticatedException:

當(dāng)尚未完成成功認(rèn)證時,嘗試執(zhí)行授權(quán)操作時引發(fā)異常迫横。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末番舆,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子矾踱,更是在濱河造成了極大的恐慌恨狈,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,214評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件介返,死亡現(xiàn)場離奇詭異拴事,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)圣蝎,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評論 2 382
  • 文/潘曉璐 我一進(jìn)店門刃宵,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人徘公,你說我怎么就攤上這事牲证。” “怎么了关面?”我有些...
    開封第一講書人閱讀 152,543評論 0 341
  • 文/不壞的土叔 我叫張陵坦袍,是天一觀的道長。 經(jīng)常有香客問我等太,道長捂齐,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,221評論 1 279
  • 正文 為了忘掉前任缩抡,我火速辦了婚禮奠宜,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘瞻想。我一直安慰自己压真,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 64,224評論 5 371
  • 文/花漫 我一把揭開白布蘑险。 她就那樣靜靜地躺著滴肿,像睡著了一般。 火紅的嫁衣襯著肌膚如雪佃迄。 梳的紋絲不亂的頭發(fā)上泼差,一...
    開封第一講書人閱讀 49,007評論 1 284
  • 那天,我揣著相機(jī)與錄音呵俏,去河邊找鬼拴驮。 笑死,一個胖子當(dāng)著我的面吹牛柴信,可吹牛的內(nèi)容都是我干的套啤。 我是一名探鬼主播,決...
    沈念sama閱讀 38,313評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼随常,長吁一口氣:“原來是場噩夢啊……” “哼潜沦!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起绪氛,我...
    開封第一講書人閱讀 36,956評論 0 259
  • 序言:老撾萬榮一對情侶失蹤唆鸡,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后枣察,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體争占,經(jīng)...
    沈念sama閱讀 43,441評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡燃逻,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,925評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了臂痕。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片伯襟。...
    茶點故事閱讀 38,018評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖握童,靈堂內(nèi)的尸體忽然破棺而出姆怪,到底是詐尸還是另有隱情,我是刑警寧澤澡绩,帶...
    沈念sama閱讀 33,685評論 4 322
  • 正文 年R本政府宣布稽揭,位于F島的核電站,受9級特大地震影響肥卡,放射性物質(zhì)發(fā)生泄漏溪掀。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,234評論 3 307
  • 文/蒙蒙 一步鉴、第九天 我趴在偏房一處隱蔽的房頂上張望膨桥。 院中可真熱鬧,春花似錦唠叛、人聲如沸只嚣。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽册舞。三九已至,卻和暖如春障般,著一層夾襖步出監(jiān)牢的瞬間调鲸,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評論 1 261
  • 我被黑心中介騙來泰國打工挽荡, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留藐石,地道東北人。 一個月前我還...
    沈念sama閱讀 45,467評論 2 352
  • 正文 我出身青樓定拟,卻偏偏與公主長得像于微,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子青自,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,762評論 2 345

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