1. 程式人生 > 其它 >SpringBoot整合Shiro

SpringBoot整合Shiro

一、Shiro 簡介

Apache Shiro 是一個強大且易用的Java安全框架,能夠用於身份驗證、授權、加密和會話管理。

官網:https://shiro.apache.org/

Shiro 功能:

核心功能:
Authentication(認證):使用者登入,身份識別。
Authorization(授權):授權和鑑權,處理使用者和訪問的目標資源之間的許可權。
Session Management(會話管理):即 Session 的管理。
Cryptography(加密):使用者密碼加密。

其他功能:
Web支援:可以非常容易整合到web應用程式中。
快取:快取是Apache Shiro API中的第一級,以確保安全操作保持快速和高效。
併發性:多執行緒環境完成認證和授權。
測試:存在測試支援,可幫助您編寫單元測試和整合測試,並確保程式碼按預期得到保障。
“執行方式”:允許使用者承擔另一個使用者的身份(如果允許)的功能,有時在管理方案中很有用。
“記住我”:記住使用者在會話中的身份,所以使用者只需要強制登入即可。

Shiro 核心物件:
Subject:當前使用者,Subject 可以是一個人,但也可以是第三方服務、守護程序帳戶等和軟體互動的任何物件。
SecurityManager:管理所有Subject,SecurityManager 是 Shiro 框架的核心。
Realms:用於認證和授權,提供擴充套件點,使用者自行實現認證邏輯和授權邏輯。

二、SpringBoot整合Shiro

1:引入pom

<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-spring</artifactId>
    <version>1.7.1</version>
</dependency>

在GroupId為 org.apache.shiro下的ArtifaceId有好幾個shiro-spring;shiro-springboot;shiro-springboot-starter。暫不清楚這三個之間的區別和聯絡,本文中引用的是 shiro-spring,使用的是當前(2021-07)最新版本 1.7.1。

2:增加配置類
配置類有兩個:

  • 新建一個類 ShiroRealm ,此類繼承 AuthorizingRealm ,是框架提供的擴充套件口,使用者通過重寫doGetAuthenticationInfo 方法檢驗使用者登入資訊是否正確,並將使用者資訊存放到 session 中,此方法會丟擲 AuthenticationException 異常,需要在統一異常處理中捕獲此異常。通過重寫 doGetAuthorizationInfo 方法為當前請求的使用者賦予角色和許可權資訊,配合Shiro 提供的 @RequiresRoles 和 @RequiresPermissions 註解完成鑑權。

  • 新建一個類 ShiroConfig ,此類需要新增 @Configuration 註解,此類的作用是嚮應用程式上下文(俗稱的容器)注入使用者新增的shiro配置和自定義功能。

    • 注入 SecurityManager,此物件是Shiro的核心物件之一,Shiro 框架提供了多種 DefaultSecurityManger可供使用,暫不清楚他們之間的區別,本文使用的是 DefaultWebSecurityManager 。通過 setRealm 將上一步新建的認證和授權配置類注入 SecurityManager。

    • 注入 ShiroFilterFactoryBean,此物件是一個過濾器,作用是配置一些預設頁面和過濾規則。setLoginUrl是配置認證失敗,預設要重定向的頁面,可以是一個jsp頁面,也可以是一個RESTFul介面。setFilterChainDefinitionMap是配置認證過濾規則,比如哪些URL不需要認證,接收一個 LinkedHashMap ,key為 url ,value 為認證策略,這裡必須要吐槽認證策略沒有設計成一個列舉。

      • logout:配置退出登入過濾器,其中的具體的退出程式碼Shiro已經替我們實現了,呼叫此介面後,頁面會重定向到setLoginUrl配置的URL。
      • authc:配置需要認證的URL
      • anon:配置不需要認證的URL
    • 注入 authorizationAttributeSourceAdvisor 和 defaultAdvisorAutoProxyCreator ,如果不注入這兩個物件,RequiresRoles 和RequiresPermissions 註解將無法使用。

ShiroRealm:

package com.naylor.shiro.config;


import com.alibaba.fastjson.JSON;
import com.naylor.shiro.dto.UserInfo;
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 java.util.ArrayList;
import java.util.List;


/**
* @ClassName ShiroRealm
* @類描述 realm(領域、範圍)不太清楚這裡用這個單詞是什麼寓意。此類繼承 AuthorizingRealm ,是框架給使用者留下的兩個擴充套件點,doGetAuthenticationInfo 擴充套件登入認證的邏輯;doGetAuthorizationInfo 擴充套件授權鑑權的邏輯
*
* @Author MingliangChen
* @Email [email protected]
* @Date 2021-07-08 9:28
* @Version 1.0.0
**/
public class ShiroRealm extends AuthorizingRealm {


    /**
     * 授權
     * 在訪問介面前,為當前登入使用者賦予角色和許可權
     * 實際應用中從資料庫中查詢使用者擁有的角色和許可權資訊
     *
     * @param principalCollection
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        String principal = JSON.toJSONString(principalCollection);
        System.out.println(principal);
        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
        UserInfo userInfo = (UserInfo)principalCollection.getPrimaryPrincipal();
       //根據使用者名稱查詢出使用者角色和許可權,並交給shiro管理。實際應用中使用者角色和許可權從資料庫獲取
        if (userInfo.getUserName().equals("cml")) {
            simpleAuthorizationInfo = buildUserCmlRolePermission();
        } else if (userInfo.getUserName().equals("admin")) {
            simpleAuthorizationInfo = buildUserAdminRolePermission();
        } else if (userInfo.getUserName().equals("hn")) {
            simpleAuthorizationInfo = buildUserHnRolePermission();
        }
        return simpleAuthorizationInfo;
    }


    /**
     * 登入認證
     * 儲存使用者資訊到session中
     * 在呼叫登入介面後會進入到此方法(/common/singin)
     *
     * @param authenticationToken
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        String authToken = JSON.toJSONString(authenticationToken);
        System.out.println("authToken:" + authToken);
        String userName = authenticationToken.getPrincipal().toString();
        UserInfo userInfo = this.getUserInfoByUserName(userName);
        SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(userInfo, userInfo.getPassword(), getName());
        return simpleAuthenticationInfo;
    }


    /**
     * 構造使用者名稱為admin的使用者的角色和許可權
     * 實際應用中使用者角色許可權資訊從資料庫中獲取
     * @return
     */
    private SimpleAuthorizationInfo buildUserAdminRolePermission() {
        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
        List<String> roles = new ArrayList<>();
        roles.add("roleA");
        roles.add("roleB");
        roles.add("roleC");
        simpleAuthorizationInfo.addRoles(roles);
        List<String> permissions = new ArrayList<>();
        permissions.add("permissionsA");
        permissions.add("permissionsB");
        permissions.add("permissionsC");
        simpleAuthorizationInfo.addStringPermissions(permissions);
        return simpleAuthorizationInfo;
    }


    /**
     * 構造使用者名稱為cml的使用者的角色和許可權
     * 實際應用中使用者角色許可權資訊從資料庫中獲取
     * @return
     */
    private SimpleAuthorizationInfo buildUserCmlRolePermission() {
        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
        List<String> roles = new ArrayList<>();
        roles.add("roleA");
        roles.add("roleB");
        simpleAuthorizationInfo.addRoles(roles);
        List<String> permissions = new ArrayList<>();
        permissions.add("permissionsA");
        permissions.add("permissionsB");
        simpleAuthorizationInfo.addStringPermissions(permissions);
        return simpleAuthorizationInfo;
    }


    /**
     * 構造使用者名稱為hn的使用者的角色和許可權
     * 實際應用中使用者角色許可權資訊從資料庫中獲取
     * @return
     */
    private SimpleAuthorizationInfo buildUserHnRolePermission() {
        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
        List<String> roles = new ArrayList<>();
        roles.add("roleA");
        List<String> permissions = new ArrayList<>();
        permissions.add("permissionsAA");
        simpleAuthorizationInfo.addStringPermissions(permissions);
        simpleAuthorizationInfo.addRoles(roles);
        return simpleAuthorizationInfo;
    }


    /**
     * 獲取使用者資訊根據使用者名稱
     * 實際應用場景中是從資料庫查詢使用者資訊並根據需求組裝 userInfo 物件
     *
     * @param userName
     * @return
     */
    private UserInfo getUserInfoByUserName(String userName) {
        UserInfo userInfo = new UserInfo().setId("112233445566778899").setUserName(userName).setRealName("陳明亮").setUserType(5).setNation("中國").setPassword("123456");
        return userInfo;
    }
}

ShiroConfig:

package com.naylor.shiro.config;


import org.apache.shiro.authc.Authenticator;
import org.apache.shiro.mgt.DefaultSecurityManager;
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.apache.shiro.web.mgt.WebSecurityManager;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.handler.SimpleMappingExceptionResolver;


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


/**
* @ClassName ShiroConfgi
* @類描述 Shiro 配置
* @Author MingliangChen
* @Email [email protected]
* @Date 2021-07-08 9:24
* @Version 1.0.0
**/


@Configuration
public class ShiroConfig {




    /**
     * 注入安全管理
     * 為shiro框架核心物件,可注入不同的SecurityNamager物件,另外可根據實際需求通過securityManager的set方法自定義安全管理物件
     * @return
     */
    @Bean(name = "securityManager")
    public SecurityManager securityManager() {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(buildShiroRealm());
        return securityManager;
    }


    /**
     * 注入認證、授權
     * @return
     */
    @Bean(name = "shiroRealm")
    public ShiroRealm buildShiroRealm() {
        ShiroRealm shiroRealm = new ShiroRealm();
        return shiroRealm;
    }


    /**
     * 注入過濾器
     * 通過setLoginUrl配置認證失敗,重定向的uri地址,可以是一個頁面,也可以是一個RESTFul介面
     *
     * @param securityManager
     * @return
     */
    @Bean(name = "shiroFilter")
    public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        Map<String, String> map = new HashMap<>();
        //退出登入
        map.put("/logout", "logout");
        //對所有URI認證
        map.put("/**", "authc");
        // 設定不用認證的URI
        map.put("/common/login", "anon");
        map.put("/common/singin", "anon");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(map);


        shiroFilterFactoryBean.setSecurityManager(securityManager);
        //認證失敗重定向URI
        shiroFilterFactoryBean.setLoginUrl("/common/login");


        return shiroFilterFactoryBean;
    }


    /**
     * 加入註解的使用,不加入這個註解不生效
     *
     * @param securityManager
     * @return
     */
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
        return authorizationAttributeSourceAdvisor;
    }


    /**
     * 加入註解的使用,不加入這個註解不生效
     *
     * @return
     */
    @Bean
    @ConditionalOnMissingBean
    public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
        DefaultAdvisorAutoProxyCreator defaultAAP = new DefaultAdvisorAutoProxyCreator();
        defaultAAP.setProxyTargetClass(true);
        return defaultAAP;
    }




}

3:增加全域性異常處理
捕獲異常,防止將tomcat的錯誤頁面直接拋給使用者。

配置檔案中增加以下配置:
出現錯誤時, 直接丟擲異常。這兩個配置是為了讓404異常正常丟擲

spring.mvc.throw-exception-if-no-handler-found=true
spring.resources.add-mappings=false

GlobalException

package com.naylor.shiro.handler;


import com.naylor.shiro.dto.GlobalResponseEntity;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authz.AuthorizationException;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.NoHandlerFoundException;


/**
* @ClassName GlobalException
* @類描述
* @Author MingliangChen
* @Email [email protected]
* @Date 2021-07-08 14:23
* @Version 1.0.0
**/


//@RestControllerAdvice("com.naylor")
@RestControllerAdvice()
@ResponseBody
@Slf4j
public class GlobalException {




    /**
     * 處理511異常
     * @param e
     * @return
     */
    @ExceptionHandler(AuthenticationException.class)
    public ResponseEntity<Object> handleAuthenticationException(AuthenticationException e) {
        return new ResponseEntity<>(
                new GlobalResponseEntity<>(false, "511",
                        e.getMessage() == null ? "認證失敗" : e.getMessage()),
                HttpStatus.NETWORK_AUTHENTICATION_REQUIRED);
    }




    /**
     * 處理401異常
     * @param e
     * @return
     */
    @ExceptionHandler(AuthorizationException.class)
    public ResponseEntity<Object> handleAuthorizationException(AuthorizationException e) {
        return new ResponseEntity<>(
                new GlobalResponseEntity<>(false, "401",
                        e.getMessage() == null ? "未授權" : e.getMessage()),
                HttpStatus.UNAUTHORIZED);
    }




    /**
     * 處理404異常
     *
     * @return
     */
    @ExceptionHandler(NoHandlerFoundException.class)
    public ResponseEntity<Object> handleNoHandlerFoundException(NoHandlerFoundException e) {
        return new ResponseEntity<>(
                new GlobalResponseEntity(false, "404",
                        e.getMessage() == null ? "請求的資源不存在" : e.getMessage()),
                HttpStatus.NOT_FOUND);
    }


    /**
     * 捕獲執行時異常
     *
     * @param e
     * @return
     */
    @ExceptionHandler(RuntimeException.class)
    public ResponseEntity<Object> handleRuntimeException(RuntimeException e) {
        log.error("handleRuntimeException:", e);
        return new ResponseEntity<>(
                new GlobalResponseEntity(false, "500",
                        e.getMessage() == null ? "執行時異常" : e.getMessage().replace("java.lang.RuntimeException: ", "")),
                HttpStatus.INTERNAL_SERVER_ERROR);
    }


    /**
     * 捕獲一般異常
     * 捕獲未知異常
     *
     * @param e
     * @return
     */
    @ExceptionHandler(Exception.class)
    public ResponseEntity<Object> handleException(Exception e) {
        return new ResponseEntity<>(
                new GlobalResponseEntity<>(false, "555",
                        e.getMessage() == null ? "未知異常" : e.getMessage()),
                HttpStatus.INTERNAL_SERVER_ERROR);
    }


}

4:增加統一的RESTFul響應結構體
GlobalResponse:

package com.naylor.shiro.handler;


import com.alibaba.fastjson.JSON;
import com.naylor.shiro.dto.GlobalResponseEntity;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;


import javax.annotation.Resource;


/**
* @ClassName GlobalResponse
* @類描述
* @Author MingliangChen
* @Email [email protected]
* @Date 2021-07-08 14:22
* @Version 1.0.0
**/


@RestControllerAdvice("com.naylor")
public class GlobalResponse  implements ResponseBodyAdvice<Object> {


    /**
     * 攔截之前業務處理,請求先到supports再到beforeBodyWrite
     * <p>
     * 用法1:自定義是否攔截。若方法名稱(或者其他維度的資訊)在指定的常量範圍之內,則不攔截。
     *
     * @param methodParameter
     * @param aClass
     * @return 返回true會執行攔截;返回false不執行攔截
     */
    @Override
    public boolean supports(MethodParameter methodParameter, Class<? extends HttpMessageConverter<?>> aClass) {
        //TODO 過濾
        return true;
    }


    /**
     * 向客戶端返回響應資訊之前的業務邏輯處理
     * <p>
     * 用法1:無論controller返回什麼型別的資料,在寫入客戶端響應之前統一包裝,客戶端永遠接收到的是約定的格式
     * <p>
     * 用法2:在寫入客戶端響應之前統一加密
     *
     * @param responseObject     響應內容
     * @param methodParameter
     * @param mediaType
     * @param aClass
     * @param serverHttpRequest
     * @param serverHttpResponse
     * @return
     */
    @Override
    public Object beforeBodyWrite(Object responseObject, MethodParameter methodParameter,
                                  MediaType mediaType,
                                  Class<? extends HttpMessageConverter<?>> aClass,
                                  ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) {
        //responseObject是否為null
        if (null == responseObject) {
            return new GlobalResponseEntity<>("55555", "response is empty.");
        }
        //responseObject是否是檔案
        if (responseObject instanceof Resource) {
            return responseObject;
        }
        //該方法返回值型別是否是void
        //if ("void".equals(methodParameter.getParameterType().getName())) {
        //  return new GlobalResponseEntity<>("55555", "response is empty.");
        //}
        if (methodParameter.getMethod().getReturnType().isAssignableFrom(Void.TYPE)) {
            return new GlobalResponseEntity<>("55555", "response is empty.");
        }
        //該方法返回值型別是否是GlobalResponseEntity。若是直接返回,無需再包裝一層
        if (responseObject instanceof GlobalResponseEntity) {
            return responseObject;
        }
        //處理string型別的返回值
        //當返回型別是String時,用的是StringHttpMessageConverter轉換器,無法轉換為Json格式
        //必須在方法體上標註RequestMapping(produces = "application/json; charset=UTF-8")
        if (responseObject instanceof String) {
            String responseString = JSON.toJSONString(new GlobalResponseEntity<>(responseObject));
            return responseString;
        }
        //該方法返回的媒體型別是否是application/json。若不是,直接返回響應內容
        if (!mediaType.includes(MediaType.APPLICATION_JSON)) {
            return responseObject;
        }


        return new GlobalResponseEntity<>(responseObject);
    }
}

5:使用者登入認證
使用者資訊都存放在 Subject 物件中,使用者登入認證的過程只需呼叫其 login 方法即可,login方法內部會呼叫 doGetAuthenticationInfo 擴充套件點完成登入的認證。

package com.naylor.shiro.controller;


import com.naylor.shiro.dto.User;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.springframework.web.bind.annotation.*;


/**
* @ClassName LoginController
* @類描述
* @Author MingliangChen
* @Email [email protected]
* @Date 2021-07-08 9:51
* @Version 1.0.0
**/


@RestController
@RequestMapping("/common")
public class CommonController {


    /**
     * 提示需要登入
     * @return
     */
    @GetMapping(value = "/login")
    public String login() {
        return "請登入";
    }


    /**
     * 登入
     * @param user
     * @return
     */
    @PostMapping("/singin")
    public String singIn(@RequestBody User user) {
        UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(user.getUserName(), user.getPassword());
        SecurityUtils.getSubject().login(usernamePasswordToken);
        return "登入成功";
    }






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

6:使用者請求鑑權
通過 @RequiresRoles 和 @RequiresPermissions 註解的配合使用,完成對後端介面的鑑權。鑑權的邏輯其實就是從Shiro 中取出當前使用者擁有的角色和許可權,然後和RESTFul介面上面註解的角色和許可權進行對比,如果包含那麼就鑑權通過,允許訪問,否則就丟擲401異常。

鑑權原理:
debug 到AuthorizingRealm類的 isPermitted 方法,該方法接收兩個引數,Permission為RESTFul介面上面新增的許可權相關注解,AuthorizationInfo是當前請求使用者擁有的角色和許可權。鑑權的原理就是判斷AuthorizationInfo 中是否包含Permission。

獲取使用者資訊和session資訊:
通過 SecurityUtils 工具類中的 getSubject方法獲取使用者的登入資訊和sessionId

package com.naylor.shiro.controller;


import com.naylor.shiro.dto.UserInfo;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.apache.shiro.authz.annotation.RequiresRoles;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.Subject;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;


/**
* @ClassName AnimalController
* @類描述
* @Author MingliangChen
* @Email [email protected]
* @Date 2021-07-08 13:49
* @Version 1.0.0
**/


@RestController
@RequestMapping("/animal")
public class AnimalController {




    @GetMapping("/cat")
    public String cat() {
        Subject subject = SecurityUtils.getSubject();
        UserInfo userInfo = (UserInfo) SecurityUtils.getSubject().getPrincipal();
        String sessionId = String.valueOf(SecurityUtils.getSubject().getSession().getId());
        return "cat";
    }


    @RequiresRoles({"roleA"})
    @RequiresPermissions("permissionsAA")
    @GetMapping("/fish")
    public String fish() {
        return "fish";
    }


    @RequiresRoles({"roleA", "roleB"})
    @GetMapping("/dog")
    public String dog() {
        return "dog";
    }


    @RequiresPermissions("permissionsC")
    @GetMapping("/tiger")
    public String tiger() {
        Boolean a = SecurityUtils.getSubject().hasRole("roleC");
        Boolean b = SecurityUtils.getSubject().isPermitted("permissionsC");
        return "tiger";
    }
}

7:測試
使用postman模擬使用者請求進行測試。

a. 在沒有登入的情況下,呼叫任何介面都會重定向到 /common/login 介面,該介面返回“請登入”。即使是訪問一個不存在的頁面也會重定向,因為我們在ShiroConfig 中配置的是全域性認證。

b.呼叫登入介面登入,注意使用者名稱需要和程式碼中寫死的使用者名稱一致

c.呼叫受限介面

admin 使用者有 tiger 介面的許可權,呼叫之後介面正常返回 tiger ; 沒有 fish 介面的許可權,呼叫之後返回 “Subject does not have permission [permissionsAA]”

8:總結
本文演示了SpringBoot整合 Shiro ,基於 Session 來管理使用者會話,實現使用者和web服務的認證和鑑權。Shiro 作為一個古老的框架,歷史悠久,功能和拓展性也特別的強,如使用者可以自定義 SessionMode=HTTP 從而可以達到web服務橫向擴容的目的;也可以結合 JWT 搭建無狀態的web服務;還可以搭建 oauth2 。但是後兩者並不推薦,在分散式系統和微服務應用中,推薦使用SpringBootSecutiryOauth2來搭建自己的授權服務。

郵箱:[email protected]
技術交流QQ群:1158377441