Springboot整合Shiro:簡潔的身份認證

shiro.gif

*******完整代碼在文章最下面,轉載請說明出處,謝謝 *******

簡單的web應用進行身份認證的流程:
1.對未認證的用戶請求進行攔截,跳轉到認證頁面。
2.用戶通過用戶名+密碼及其他憑證進行身份認證最住,認證成功跳轉成功頁面,認證失敗提示相關失敗信息怠惶。

根據流程涨缚,采用springboot+shiro進行快速開發(fā)。
使用到的相關pom依賴:

        <!-- web 依賴 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!-- thymeleaf 依賴 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>

        <!-- shiro相關依賴 -->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>1.3.2</version>
        </dependency>

1.對未認證的用戶請求進行攔截策治,跳轉到認證頁面脓魏。
(0) 這里需要shiro的攔截器配置,新建ShiroConfig配置類览妖,配置過濾器

/**
 * @Description springboot中的Shiro配置類
 * @Author 張小黑的貓
 * @data 2019-05-22 17:17
 */
@Configuration
public class ShiroConfig {
    
    /**
     * 配置Shiro的Web過濾器轧拄,攔截瀏覽器請求并交給SecurityManager處理
     * @return
     */
    @Bean
    public ShiroFilterFactoryBean webFilter(){

        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();

        //配置攔截鏈 使用LinkedHashMap,因為LinkedHashMap是有序的,shiro會根據添加的順序進行攔截
        // Map<K,V> K指的是攔截的url V值的是該url是否攔截
        Map<String,String> filterChainMap = new LinkedHashMap<String,String>(16);

        //authc:所有url都必須認證通過才可以訪問; anon:所有url都都可以匿名訪問,先配置anon再配置authc讽膏。
        filterChainMap.put("/login","anon");
        filterChainMap.put("/**", "authc");

        //設置攔截請求后跳轉的URL.
        shiroFilterFactoryBean.setLoginUrl("/login");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainMap);
        return shiroFilterFactoryBean;
    }
}
  1. Shiro的用戶認證
    先說下shiro認證的流程:
    創(chuàng)建SecurityManager安全管理器 > 主體Subject提交認證信息 > SecurityManager安全管理器認證 > SecurityManager調用Authenticator認證器認證 >Realm驗證
    有幾個概念:
  • Subject:主體檩电,代表了當前“用戶”;所有Subject都綁定到SecurityManager府树,與Subject的所有交互都會委托給SecurityManager俐末;可以把Subject認為是一個門面;SecurityManager才是實際的執(zhí)行者奄侠;
  • SecurityManager安全管理器:所有與安全有關的操作都會與SecurityManager交互卓箫;且它管理著所有Subject;負責與后邊介紹的其他組件進行交互垄潮。(類似于SpringMVC中的DispatcherServlet控制器)
  • Realm:域烹卒,Shiro從從Realm獲取安全數據(如用戶闷盔、角色、權限)旅急,就是說SecurityManager要驗證用戶身份逢勾,那么它需要從Realm獲取相應的用戶進行比較以確定用戶身份是否合法;也需要從Realm得到用戶相應的角色/權限進行驗證用戶是否能進行操作藐吮;可以把Realm看成DataSource溺拱,即安全數據源。

(1)首先在ShiroConfig配置類中創(chuàng)建SecurityManager安全管理器

public SecurityManager securityManager(){
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        return securityManager;
    }

注意:SecurityManager導包的時候選org.apache.shiro.mgt.SecurityManager;而不是java.lang.SecurityManager
(2)SecurityManager安全管理器需要到realm中去驗證認證信息谣辞,所以給SecurityManager設置Realm迫摔。
Shiro的Realm分為IniRealmJdbcRealm以及自定義realm泥从,我們這里使用自定義的realm實現業(yè)務邏輯句占。新建自定義Realm類CustomRealm繼承AuthorizingRealm

public class CustomRealm extends AuthorizingRealm {
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        return null;
    }

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        return null;
    }
}

(3)重寫AuthorizingRealm中的認證方法doGetAuthenticationInfo歉闰。

@Override
    /**
     * 認證
     */
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        //1.獲取用戶輸入的賬號
        String username = (String)token.getPrincipal();
        //2.通過username從數據庫中查找到user實體
         User user = getUserByUserName(username);
        if(user == null){
            return null;
        }
        //3.通過SimpleAuthenticationInfo做身份處理
        SimpleAuthenticationInfo simpleAuthenticationInfo =
                new SimpleAuthenticationInfo(user,user.getPassword(),getName());
        //4.用戶賬號狀態(tài)驗證等其他業(yè)務操作
        if(!user.getAvailable()){
            throw new AuthenticationException("該賬號已經被禁用");
        }
        //5.返回身份處理對象
        return simpleAuthenticationInfo;
    }

上面的token憑證是來自主體Subject.login(token)提交時的token,主體的提交下面會說到辖众。
這里還有個比較有意思的點:new SimpleAuthenticationInfo(user,user.getPassword(),getName());中的參數問題卓起。第一個參數是從數據庫中獲取的User對象和敬,第二個參數是數據庫獲取的密碼,第三個參數是當前Realm的名稱戏阅。其中第一個參數傳username也可以昼弟,那么傳usernameUser對象的區(qū)別是啥呢?Shiro為我們提供了獲取當前用戶信息的方法:

     * shiro獲取當前用戶
     * @return
     */
    private User currentUser(){
        User currentUser = (User) SecurityUtils.getSubject().getPrincipal();
        return  currentUser;
    }

有些業(yè)務場景需要獲取當前用戶的信息(用戶的狀態(tài)等等)進行業(yè)務操作奕筐,那么上面的區(qū)別就顯而易見了舱痘。如果傳的是username那么用戶的其他信息就拿不到了。
(4) 將自定義Realm注入到SecurityManager安全管理器中

public SecurityManager securityManager(){
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        //將自定義的realm交給SecurityManager管理
        securityManager.setRealm(new CustomRealm());
        return securityManager;
    }

(5) Shiro配置類的過濾器中啟用安全管理器离赫,即shiroFilterFactoryBean中配置SecurityManager

//設置securityManager
        shiroFilterFactoryBean.setSecurityManager(securityManager());

(6) 主體提交認證信息芭逝,即登錄請求

 @PostMapping("/login")
    public String login(String username, String password, Model model){

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

        try {
            //主體提交登錄請求到SecurityManager
            currentUser.login(token);
        }catch (IncorrectCredentialsException ice){
            model.addAttribute("msg","密碼不正確");
        }catch(UnknownAccountException uae){
            model.addAttribute("msg","賬號不存在");
        }catch(AuthenticationException ae){
            model.addAttribute("msg","狀態(tài)不正常");
        }
        if(currentUser.isAuthenticated()){
            System.out.println("認證成功");
            model.addAttribute("currentUser",currentUser());
            return "success";
        }else{
            token.clear();
            return "login";
        }
    }

到這基本認證的流程就已經通了。在這里貼一下項目結構和全部代碼渊胸,供大家參考:(不建議直接全部復制粘貼)

image.png

pom.xml:

<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!-- shiro相關依賴 -->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>1.3.2</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

ShiroConfig.java:

/**
 * @Description springboot中的Shiro配置類
 * @Author 張小黑的貓
 * @data 2019-05-22 17:17
 */
@Configuration
public class ShiroConfig {

    /**
     * 配置Shiro核心 安全管理器 SecurityManager
     * SecurityManager安全管理器:所有與安全有關的操作都會與SecurityManager交互旬盯;且它管理著所有Subject;負責與后邊介紹的其他組件進行交互翎猛。(類似于SpringMVC中的DispatcherServlet控制器)
     */
    public SecurityManager securityManager(){
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        //將自定義的realm交給SecurityManager管理
        securityManager.setRealm(new CustomRealm());
        return securityManager;
    }


    /**
     * 配置Shiro的Web過濾器胖翰,攔截瀏覽器請求并交給SecurityManager處理
     * @return
     */
    @Bean
    public ShiroFilterFactoryBean webFilter(){

        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        //設置securityManager
        shiroFilterFactoryBean.setSecurityManager(securityManager());

        //配置攔截鏈 使用LinkedHashMap,因為LinkedHashMap是有序的,shiro會根據添加的順序進行攔截
        // Map<K,V> K指的是攔截的url V值的是該url是否攔截
        Map<String,String> filterChainMap = new LinkedHashMap<String,String>(16);
        //配置退出過濾器logout切厘,由shiro實現
        filterChainMap.put("/logout","logout");
        //authc:所有url都必須認證通過才可以訪問; anon:所有url都都可以匿名訪問,先配置anon再配置authc萨咳。
        filterChainMap.put("/login","anon");
        filterChainMap.put("/**", "authc");

        //設置默認登錄的URL.
        shiroFilterFactoryBean.setLoginUrl("/login");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainMap);
        return shiroFilterFactoryBean;
    }
}

CustomRealm.java:

/**
 * @Description: shiro的自定義realm
 * Realm:域,Shiro從從Realm獲取安全數據(如用戶疫稿、角色培他、權限)鹃两,就是說SecurityManager要驗證用戶身份,那么它需要從Realm獲取相應的用戶進行比較以確定用戶身份是否合法舀凛;也需要從Realm得到用戶相應的角色/權限進行驗證用戶是否能進行操作怔毛;可以把Realm看成DataSource,即安全數據源腾降。
 * @Author 張小黑的貓
 * @data 2019-05-22 17:51
 */
public class CustomRealm extends AuthorizingRealm {
    @Override
    /**
     * 認證
     */
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        //1.獲取用戶輸入的賬號
        String username = (String)token.getPrincipal();
        //2.通過username從數據庫中查找到user實體
         User user = getUserByUserName(username);
        if(user == null){
            return null;
        }
        //3.通過SimpleAuthenticationInfo做身份處理
        SimpleAuthenticationInfo simpleAuthenticationInfo =
                new SimpleAuthenticationInfo(user,user.getPassword(),getName());
        //4.用戶賬號狀態(tài)驗證等其他業(yè)務操作
        if(!user.getAvailable()){
            throw new AuthenticationException("該賬號已經被禁用");
        }
        //5.返回身份處理對象
        return simpleAuthenticationInfo;
    }

    @Override
    /**
     * 授權
     */
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principal) {
        return null;
    }

    /**
     * 模擬通過username從數據庫中查找到user實體
     * @param username
     * @return
     */
    private User getUserByUserName(String username){
        List<User> users = getUsers();
        for(User user : users){
            if(user.getUsername().equals(username)){
                return user;
            }
        }
        return null;
    }

    /**
     * 模擬數據庫數據
     * @return
     */
    private List<User> getUsers(){
        List<User> users = new ArrayList<>();
        users.add(new User("張小黑的貓","123qwe",true));
        users.add(new User("張小黑的狗","123qwe",false));
        return users;
    }

}

User.java

/**
 * @Description 用戶
 * @Author 張小黑的貓
 * @data 2019-05-22 19:18
 */
public class User {
    private String username;
    private String password;
    private Boolean available;

    public User(String username, String password, Boolean available) {
        this.username = username;
        this.password = password;
        this.available = available;
    }

    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;
    }

    public Boolean getAvailable() {
        return available;
    }

    public void setAvailable(Boolean available) {
        this.available = available;
    }
}

LoginController.java:

/**
 * @Description 登錄
 * @Author 張小黑的貓
 * @data 2019-05-22 18:17
 */
@Controller
public class LoginController {

    @GetMapping("/login")
    public String login(){
        return "login";
    }


    @PostMapping("/login")
    public String login(String username, String password, Model model){

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

        try {
            //主體提交登錄請求到SecurityManager
            currentUser.login(token);
        }catch (IncorrectCredentialsException ice){
            model.addAttribute("msg","密碼不正確");
        }catch(UnknownAccountException uae){
            model.addAttribute("msg","賬號不存在");
        }catch(AuthenticationException ae){
            model.addAttribute("msg","狀態(tài)不正常");
        }
        if(currentUser.isAuthenticated()){
            System.out.println("認證成功");
            model.addAttribute("username",username);
            return "success";
        }else{
            token.clear();
            return "login";
        }
    }
}

login.html:

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Login</title>
</head>
<body>
<form action="/login" method="post">
    <span th:text="${msg}" style="color: red"></span><br>
    用戶名:<input type="text" name="username"><br>
    密&emsp;碼:<input type="password" name="password"><br>
    <input type="submit" value="Login">
</form>
</body>
</html>

success.html:

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>success</title>
</head>
<body>
<span th:text="'歡迎你,'+${username}"></span>
</body>
</html>

共同學習拣度,歡迎指正修改~ 喵喵喵?
下一篇文章:Springboot整合Shiro: 詳細的權限管理

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市螃壤,隨后出現的幾起案子抗果,更是在濱河造成了極大的恐慌,老刑警劉巖奸晴,帶你破解...
    沈念sama閱讀 216,496評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件冤馏,死亡現場離奇詭異,居然都是意外死亡寄啼,警方通過查閱死者的電腦和手機逮光,發(fā)現死者居然都...
    沈念sama閱讀 92,407評論 3 392
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來墩划,“玉大人涕刚,你說我怎么就攤上這事∫野铮” “怎么了杜漠?”我有些...
    開封第一講書人閱讀 162,632評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長察净。 經常有香客問我驾茴,道長,這世上最難降的妖魔是什么氢卡? 我笑而不...
    開封第一講書人閱讀 58,180評論 1 292
  • 正文 為了忘掉前任锈至,我火速辦了婚禮,結果婚禮上译秦,老公的妹妹穿的比我還像新娘峡捡。我一直安慰自己,他們只是感情好诀浪,可當我...
    茶點故事閱讀 67,198評論 6 388
  • 文/花漫 我一把揭開白布棋返。 她就那樣靜靜地躺著,像睡著了一般雷猪。 火紅的嫁衣襯著肌膚如雪睛竣。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,165評論 1 299
  • 那天求摇,我揣著相機與錄音射沟,去河邊找鬼殊者。 笑死,一個胖子當著我的面吹牛验夯,可吹牛的內容都是我干的猖吴。 我是一名探鬼主播,決...
    沈念sama閱讀 40,052評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼挥转,長吁一口氣:“原來是場噩夢啊……” “哼海蔽!你這毒婦竟也來了?” 一聲冷哼從身側響起绑谣,我...
    開封第一講書人閱讀 38,910評論 0 274
  • 序言:老撾萬榮一對情侶失蹤党窜,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后借宵,有當地人在樹林里發(fā)現了一具尸體幌衣,經...
    沈念sama閱讀 45,324評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,542評論 2 332
  • 正文 我和宋清朗相戀三年壤玫,在試婚紗的時候發(fā)現自己被綠了豁护。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,711評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡欲间,死狀恐怖楚里,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情括改,我是刑警寧澤腻豌,帶...
    沈念sama閱讀 35,424評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站嘱能,受9級特大地震影響,放射性物質發(fā)生泄漏虱疏。R本人自食惡果不足惜惹骂,卻給世界環(huán)境...
    茶點故事閱讀 41,017評論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望做瞪。 院中可真熱鬧对粪,春花似錦、人聲如沸装蓬。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,668評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽牍帚。三九已至儡遮,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間暗赶,已是汗流浹背鄙币。 一陣腳步聲響...
    開封第一講書人閱讀 32,823評論 1 269
  • 我被黑心中介騙來泰國打工肃叶, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人十嘿。 一個月前我還...
    沈念sama閱讀 47,722評論 2 368
  • 正文 我出身青樓因惭,卻偏偏與公主長得像,于是被迫代替她去往敵國和親绩衷。 傳聞我的和親對象是個殘疾皇子蹦魔,可洞房花燭夜當晚...
    茶點故事閱讀 44,611評論 2 353

推薦閱讀更多精彩內容