springboot整合shiro實現前后端分離項目


Shiro 是一個強大、簡單易用的 Java 安全框架,可使認證、授權、加密,會話過程更便捷,並可為應用提供安全保障。本節重點介紹下 Shiro 的認證和授權功能。

1 Shiro 三大核心組件

Shiro 有三大核心組件,即 Subject、SecurityManager 和 Realm。先來看一下它們之間的關系。

在這里插入圖片描述

可以看到:應用代碼直接交互的對象是 Subject,也就是說 Shiro 的對外 API 核心就是 Subject;其每個 API 的含義:

Subject:主體,代表了當前 “用戶”,這個用戶不一定是一個具體的人,與當前應用交互的任何東西都是 Subject,如網絡爬蟲,機器人等;即一個抽象概念;所有 Subject 都綁定到 SecurityManager,與 Subject 的所有交互都會委托給 SecurityManager;可以把 Subject 認為是一個門面;SecurityManager 才是實際的執行者;

SecurityManager:安全管理器;即所有與安全有關的操作都會與 SecurityManager 交互;且它管理着所有 Subject;可以看出它是 Shiro 的核心,它負責與后邊介紹的其他組件進行交互,如果學習過 SpringMVC,你可以把它看成 DispatcherServlet 前端控制器;

Realm:域,Shiro 從從 Realm 獲取安全數據(如用戶、角色、權限),就是說 SecurityManager 要驗證用戶身份,那么它需要從 Realm 獲取相應的用戶進行比較以確定用戶身份是否合法;也需要從 Realm 得到用戶相應的角色 / 權限進行驗證用戶是否能進行操作;可以把 Realm 看成 DataSource,即安全數據源。

也就是說對於我們而言,最簡單的一個 Shiro 應用:

  1. 應用代碼通過 Subject 來進行認證和授權,而 Subject 又委托給 SecurityManager;

  2. 我們需要給 Shiro 的 SecurityManager 注入 Realm,從而讓 SecurityManager 能得到合法的用戶及其權限進行判斷。

從以上也可以看出,Shiro 不提供維護用戶 / 權限,而是通過 Realm 讓開發人員自己注入。

接下來我們來從 Shiro 內部來看下 Shiro 的架構,如下圖所示:

Subject:主體,可以看到主體可以是任何可以與應用交互的 “用戶”;

SecurityManager:相當於 SpringMVC 中的 DispatcherServlet 或者 Struts2 中的 FilterDispatcher;是 Shiro 的心臟;所有具體的交互都通過 SecurityManager 進行控制;它管理着所有 Subject、且負責進行認證和授權、及會話、緩存的管理。

Authenticator:認證器,負責主體認證的,這是一個擴展點,如果用戶覺得 Shiro 默認的不好,可以自定義實現;其需要認證策略(Authentication Strategy),即什么情況下算用戶認證通過了;

Authrizer:授權器,或者訪問控制器,用來決定主體是否有權限進行相應的操作;即控制着用戶能訪問應用中的哪些功能;

Realm:可以有 1 個或多個 Realm,可以認為是安全實體數據源,即用於獲取安全實體的;可以是 JDBC 實現,也可以是 LDAP 實現,或者內存實現等等;由用戶提供;注意:Shiro 不知道你的用戶 / 權限存儲在哪及以何種格式存儲;所以我們一般在應用中都需要實現自己的 Realm;

SessionManager:如果寫過 Servlet 就應該知道 Session 的概念,Session 呢需要有人去管理它的生命周期,這個組件就是 SessionManager;而 Shiro 並不僅僅可以用在 Web 環境,也可以用在如普通的 JavaSE 環境、EJB 等環境;所有呢,Shiro 就抽象了一個自己的 Session 來管理主體與應用之間交互的數據;這樣的話,比如我們在 Web 環境用,剛開始是一台 Web 服務器;接着又上了台 EJB 服務器;這時想把兩台服務器的會話數據放到一個地方,這個時候就可以實現自己的分布式會話(如把數據放到 Memcached 服務器);

SessionDAO:DAO 大家都用過,數據訪問對象,用於會話的 CRUD,比如我們想把 Session 保存到數據庫,那么可以實現自己的 SessionDAO,通過如 JDBC 寫到數據庫;比如想把 Session 放到 Memcached 中,可以實現自己的 Memcached SessionDAO;另外 SessionDAO 中可以使用 Cache 進行緩存,以提高性能;

CacheManager:緩存控制器,來管理如用戶、角色、權限等的緩存的;因為這些數據基本上很少去改變,放到緩存中后可以提高訪問的性能

Cryptography:密碼模塊,Shiro 提高了一些常見的加密組件用於如密碼加密 / 解密的。

2 Shiro 身份和權限認證

2.1 Shiro 身份認證

我們分析下 Shiro 身份認證的過程,首先看一下官方給出的認證圖。

在這里插入圖片描述

從圖中可以看到,這個過程包括五步。

Step1:應用程序代碼調用 Subject.login(token) 方法后,傳入代表最終用戶身份的 AuthenticationToken 實例 Token。

Step2:將 Subject 實例委托給應用程序的 SecurityManager(Shiro 的安全管理)並開始實際的認證工作。這里開始了真正的認證工作。

Step3、4、5:SecurityManager 根據具體的 Realm 進行安全認證。從圖中可以看出,Realm 可進行自定義(Custom Realm)。

2.2 Shiro 權限認證

權限認證,也就是訪問控制,即在應用中控制誰能訪問哪些資源。在權限認證中,最核心的三個要素是:權限、角色和用戶。

權限(Permission):即操作資源的權利,比如訪問某個頁面,以及對某個模塊的數據進行添加、修改、刪除、查看操作的權利。
角色(Role):指的是用戶擔任的角色,一個角色可以有多個權限。
用戶(User):在 Shiro 中,代表訪問系統的用戶,即上面提到的 Subject 認證主體。

它們之間的的關系可以用下圖來表示:

在這里插入圖片描述

一個用戶可以有多個角色,而不同的角色可以有不同的權限,也可有相同的權限。比如說現在有三個角色,1 是普通角色,2 也是普通角色,3 是管理員,角色 1 只能查看信息,角色 2 只能添加信息,管理員對兩者皆有權限,而且還可以刪除信息。

3 Spring Boot 集成 Shiro

因為是demo,沒有引入數據庫表,有需要的可以自己建,也不難,就5張表:用戶表、角色表、權限表、用戶角色關聯表、角色權限關聯表。有需要的話還可以加一張部門表

項目結構如下:

 

 

3.1 依賴導入

Spring Boot 2.0.4 集成 Shiro 需要導入如下 starter 依賴:

<!--引入shiro-->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring-boot-starter</artifactId>
            <version>1.5.3</version>
        </dependency>
3.2 自定義 Realm

自定義 Realm 需要繼承 AuthorizingRealm 類,該類封裝了很多方法,且繼承自 Realm 類。

繼承 AuthorizingRealm 類后,我們需要重寫以下兩個方法。

doGetAuthenticationInfo() 方法:用來驗證當前登錄的用戶,獲取認證信息。
doGetAuthorizationInfo() 方法:為當前登錄成功的用戶授予權限和分配角色。

具體實現如下,相關注解請見代碼注釋:

package com.zdyl.springboot_shiro.shiro.realms;

import com.zdyl.springboot_shiro.shiro.utils.CurrentCredentialsMatcher;
import com.zdyl.springboot_shiro.shiro.utils.JwtToken;
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.authc.credential.CredentialsMatcher;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;

/**
 * 4 自定義realm
 */
public class CustomerRealm extends AuthorizingRealm {


    // 設置realm的名稱
    @Override
    public void setName(String name) {
        super.setName("customRealm");
    }

    /**
     * 大坑!,必須重寫此方法,不然Shiro會報錯
     */
    @Override
    public boolean supports(AuthenticationToken token) {
        return token instanceof JwtToken;
    }

    /**
     * 授權
     *
     * @param principalCollection
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        System.out.println("進來了授權");
        JwtToken jwtToken = (JwtToken) principalCollection.getPrimaryPrincipal();
        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
        authorizationInfo.addStringPermission("zdyl:test:1");
        return authorizationInfo;
    }

    /**
     * 認證
     *
     * @param authenticationToken
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        System.out.println("進來了認證");
        JwtToken jwtToken = (JwtToken) authenticationToken;
        jwtToken.setUsername("1");
        String token = jwtToken.getToken();
        // 第二步:根據用戶輸入的userCode從數據庫查詢
        // ....
        // 如果查詢不到返回null
        //數據庫中用戶賬號是zhangsansan
        /*if(!userCode.equals("zhangsansan")){//
            return null;
        }*/
        // 模擬從數據庫查詢到密碼
        String password = "111112";
        // 如果查詢到返回認證信息AuthenticationInfo
        SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(
                jwtToken, password, this.getName());
        return simpleAuthenticationInfo;
    }

    /**
     * 自定義密碼匹配器
     * @param credentialsMatcher
     */
    @Override
    public void setCredentialsMatcher(CredentialsMatcher credentialsMatcher) {
        //自定義密碼匹配器
        CurrentCredentialsMatcher currentCredentialsMatcher = new CurrentCredentialsMatcher();

        super.setCredentialsMatcher(currentCredentialsMatcher);
    }
}

*認證的時候需要返回SimpleAuthenticationInfo,它有3參數構造和4參數構造,第一個參數可以傳用戶名或者用戶,主要是比較密碼的時候從里面取,第二個參數傳數據庫查詢的密碼,第三個參數(如果需要傳的話)傳鹽,至於鹽是什么不知道的可以自行百度,

第四個傳realmName。

 

3.3 自定義 密碼匹配器

驗證密碼的時候默認走的是SimpleCredentialsMatcher的doCredentialsMatch方法,是明文比較的,需要自定義的可以繼承SimpleCredentialsMatcher重寫doCredentialsMatch方法。

如果需要MD5加密比較可以繼承HashedCredentialsMatcher重寫doCredentialsMatch方法。

package com.zdyl.springboot_shiro.shiro.utils;

import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.credential.SimpleCredentialsMatcher;

/**
 * 自定義密碼匹配器
 */
public class CurrentCredentialsMatcher extends SimpleCredentialsMatcher {

    @Override
    public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
        JwtToken jwtToken = (JwtToken) token;
        Object tokenCredentials = jwtToken.getToken();
        Object accountCredentials = this.getCredentials(info);
        return this.equals(tokenCredentials, accountCredentials);
    }

}
3.4 Shiro 配置

自定義 Realm 寫好了,接下來需要配置 Shiro。我們主要配置三個東西:自定義 Realm、安全管理器 SecurityManager、seession管理器sessionManager和 Shiro 過濾器。

首先,配置自定義的 Realm,代碼如下:

package com.zdyl.springboot_shiro.shiro.config;

import com.zdyl.springboot_shiro.shiro.realms.CustomerRealm;
import com.zdyl.springboot_shiro.shiro.utils.AuthFilter;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.session.mgt.SessionManager;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
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.session.mgt.DefaultWebSessionManager;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.servlet.Filter;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * shiro配置
 */
@Configuration
public class ShiroConfig {

    //1創建shiroFilter  負責攔截請求
    @Bean
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        //給filter設置安全管理器
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        Map<String, Filter> filter = new HashMap<>();
        filter.put("oauth2", new AuthFilter());
     //自定義過濾器 shiroFilterFactoryBean.setFilters(filter);
//配置系統受限資源 Map<String, String> filterMap = new LinkedHashMap<>(); // filterMap.put("/test", "anon"); filterMap.put("/**", "oauth2"); shiroFilterFactoryBean.setFilterChainDefinitionMap(filterMap); return shiroFilterFactoryBean; } //2.創建安全管理器 @Bean("securityManager") public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("realm") Realm realm, SessionManager sessionManager) { DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager(); //給安全管理器設置realm defaultWebSecurityManager.setRealm(realm); //給安全管理器設置sessionManager defaultWebSecurityManager.setSessionManager(sessionManager); return defaultWebSecurityManager; } //3.創建自定義realm @Bean("realm") public Realm getRealm() { CustomerRealm customerRealm = new CustomerRealm(); return customerRealm; } //4.創建sessionManager @Bean("sessionManager") public DefaultWebSessionManager sessionManager() { DefaultWebSessionManager defaultWebSessionManager = new DefaultWebSessionManager(); //設置session過期時間3600s defaultWebSessionManager.setGlobalSessionTimeout(3600000L); return defaultWebSessionManager; } /** * Shiro生命周期處理器: * 用於在實現了Initializable接口的Shiro bean初始化時調用Initializable接口回調(例如:UserRealm) * 在實現了Destroyable接口的Shiro bean銷毀時調用 Destroyable接口回調(例如:DefaultSecurityManager) */ @Bean public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() { return new LifecycleBeanPostProcessor(); } /** * 啟用shrio授權注解攔截方式,AOP式方法級權限檢查 */ @Bean public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(DefaultWebSecurityManager securityManager) { AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor(); authorizationAttributeSourceAdvisor.setSecurityManager(securityManager); return authorizationAttributeSourceAdvisor; } }

 

配置 Shiro 過濾器時,我們引入了安全管理器。

至此,我們可以看出,Shiro 配置一環套一環,遵循從 Reaml 到 SecurityManager 再到 Filter 的過程。在過濾器中,我們需要定義一個 shiroFactoryBean,然后將 SecurityManager 引入其中,需要配置的內容主要有以下幾項。

  • 默認登錄的 URL:身份認證失敗會訪問該 URL。
  • 認證成功之后要跳轉的 URL。
  • 權限認證失敗后要跳轉的 URL。
  • 需要攔截或者放行的 URL:這些都放在一個 Map 中。

通過上面的代碼,我們也了解到, Map 中針對不同的 URL有不同的權限要求,下表總結了幾個常用的權限。

在這里插入圖片描述

3.5 自定義AuthenticationToken認證的時候傳參用(用戶名、密碼、token)
package com.zdyl.springboot_shiro.shiro.utils;

import org.apache.shiro.authc.AuthenticationToken;

public class JwtToken implements AuthenticationToken {
    String serialVersionUID = "8939244780389542801";
    private String token;
    private String username;

    public JwtToken(String token) {
        this.token = token;
    }

    @Override
    public Object getPrincipal() {
        return null;
    }

    @Override
    public Object getCredentials() {
        return null;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getToken() {
        return token;
    }

    public void setToken(String token) {
        this.token = token;
    }
}

 

3.6 自定義過濾器
package com.zdyl.springboot_shiro.shiro.utils;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.web.filter.authc.AuthenticatingFilter;
import org.apache.shiro.web.util.WebUtils;
import org.springframework.http.HttpStatus;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMethod;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

/**
 * 自定義過濾器
 */
public class AuthFilter extends AuthenticatingFilter {


    /**
     * 獲取token
     *
     * @param servletRequest
     * @param servletResponse
     * @return
     * @throws Exception
     */
    @Override
    protected AuthenticationToken createToken(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception {
        String requestToken = getRequestToken((HttpServletRequest) servletRequest);
        JwtToken jwtToken = new JwtToken(requestToken);
        return jwtToken;
    }

    /**
     * 對跨域提供支持
     *
     * @param request
     * @param response
     * @return
     * @throws Exception
     */
    @Override
    protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        HttpServletResponse httpServletResponse = (HttpServletResponse) response;
        httpServletResponse.setHeader("Access-control-Allow-Origin", httpServletRequest.getHeader("Origin"));
        httpServletResponse.setHeader("Access-Control-Allow-Methods", "GET,POST,OPTIONS,PUT,DELETE");
        httpServletResponse.setHeader("Access-Control-Allow-Headers", httpServletRequest.getHeader("Access-Control-Request-Headers"));
        // 跨域時會首先發送一個option請求,這里我們給option請求直接返回正常狀態
        if (httpServletRequest.getMethod().equals(RequestMethod.OPTIONS.name())) {
            httpServletResponse.setStatus(HttpStatus.OK.value());
            return false;
        }
        return super.preHandle(request, response);
    }

    /**
     * 驗證token
     * 當訪問拒絕時是否已經處理了;
     * 如果返回true表示需要繼續處理;
     * 如果返回false表示該攔截器實例已經處理完成了,將直接返回即可。
     * @param servletRequest
     * @param servletResponse
     * @return
     * @throws Exception
     */
    @Override
    protected boolean onAccessDenied(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception {
        //完成token登入
        //1.檢查請求頭中是否含有token
        HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
        String token = getRequestToken(httpServletRequest);
        //2. 如果客戶端沒有攜帶token,攔下請求
        if (null == token || "".equals(token)) {
            responseTokenError(servletResponse, "Token為空,您無權訪問該接口");
            return false;
        }
        //3. 如果有,對進行進行token驗證
        return executeLogin(servletRequest, servletResponse);
    }

    /**
     * 執行認證
     *
     * @param request
     * @param response
     * @return
     * @throws Exception
     */
    @Override
    protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception {
        JwtToken jwtToken = (JwtToken) createToken(request, response);
        try {
            SecurityUtils.getSubject().login(jwtToken);
        } catch (AuthenticationException e) {
            responseTokenError(response, "Token無效,您無權訪問該接口");
            return false;
        }
        return true;
    }


    /**
     * 獲取請求的token
     */
    private String getRequestToken(HttpServletRequest httpRequest) {
        //從header中獲取token
        String token = httpRequest.getHeader("token");

        //如果header中不存在token,則從參數中獲取token
        if (StringUtils.isEmpty(token)) {
            token = httpRequest.getParameter("token");
        }
        if (StringUtils.isEmpty(token)) {
            Cookie[] cks = httpRequest.getCookies();
            if (cks != null) {
                for (Cookie cookie : cks) {
                    if (cookie.getName().equals("yzjjwt")) {
                        token = cookie.getValue();
                        return token;
                    }
                }
            }
        }
        return token;
    }

    /**
     * 無需轉發,直接返回Response信息 Token認證錯誤
     */
    private void responseTokenError(ServletResponse response, String msg) {
        HttpServletResponse httpServletResponse = WebUtils.toHttp(response);
        httpServletResponse.setStatus(HttpStatus.OK.value());
        httpServletResponse.setCharacterEncoding("UTF-8");
        httpServletResponse.setContentType("application/json; charset=utf-8");
        try (PrintWriter out = httpServletResponse.getWriter()) {
            ObjectMapper objectMapper = new ObjectMapper();
            String data = objectMapper.writeValueAsString(new ResponseBean(401, msg, null));
            out.append(data);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
3.7 自定義返回數據類
package com.zdyl.springboot_shiro.shiro.utils;

import lombok.Data;

/**
 * 返回數據
 */
@Data
public class ResponseBean {

    /**
     * 200:操作成功  -1:操作失敗
     **/

    // http 狀態碼
    private int code;

    // 返回信息
    private String msg;

    // 返回的數據
    private Object data;

    public ResponseBean() {
    }

    public ResponseBean(int code, String msg, Object data) {
        this.code = code;
        this.msg = msg;
        this.data = data;
    }

    public static ResponseBean error(String message) {
        ResponseBean responseBean = new ResponseBean();
        responseBean.setMsg(message);
        responseBean.setCode(-1);
        return responseBean;
    }

    public static ResponseBean error(int code, String message) {
        ResponseBean responseBean = new ResponseBean();
        responseBean.setMsg(message);
        responseBean.setCode(code);
        return responseBean;
    }

    public static ResponseBean success(Object data) {
        ResponseBean responseBean = new ResponseBean();
        responseBean.setData(data);
        responseBean.setCode(200);
        responseBean.setMsg("成功");
        return responseBean;
    }

    public static ResponseBean success(String message) {
        ResponseBean responseBean = new ResponseBean();
        responseBean.setData(null);
        responseBean.setCode(200);
        responseBean.setMsg(message);
        return responseBean;
    }

    public static ResponseBean success() {
        ResponseBean responseBean = new ResponseBean();
        responseBean.setData(null);
        responseBean.setCode(200);
        responseBean.setMsg("Success");
        return responseBean;
    }
}
3.8 使用 Shiro 進行認證

至此,我們完成了 Shiro 的准備工作。接下來開始使用 Shiro 進行認證。

使用 http://localhost:8080/test進行身份認證。

請求的時候請求頭攜帶token

 

 

@RestController
public class TestController {

    @RequestMapping("/test")
    public void test() {
        System.out.println("555555555555");
    }
}

我們重點分析下用戶帶token訪問。整個處理過程是這樣的。

首先,根據前端傳來的用戶名和密碼,創建一個 Token。

然后,請求頭攜帶token訪問 http://localhost:8080/test。首先被自定義的過濾器攔截,驗證token。

緊接着,調用 subject.login(token) 進行身份認證——注意,這里傳入了剛剛創建的 Token,如注釋所述,這一步會跳轉入自定義的 Realm,訪問 doGetAuthenticationInfo 方法,開始身份認證。

最后,啟動項目,測試一下。在瀏覽器中請求:http://localhost:8080/test, 首先進行身份認證,此時token不正確或者為空,會返回401提示token為空或者不正確。

3.9 使用 Shiro 進行授權

使用 http://localhost:8080/test進行授權。在需要授權的接口是上增加@RequiresPermissions("zdyl:test:1")

import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class TestController {

    @RequiresPermissions("zdyl:test:1")
    @RequestMapping("/test")
    public void test() {
        System.out.println("555555555555");
    }
}

我們重點分析下授權的過程。整個處理過程是這樣的。

用戶通過了認證環節,請求的資源也就是接口上有@RequiresPermissions("zdyl:test:1")這個注解,就會進到自定義realm中執行doGetAuthorizationInfo方法,該方法有一個入參PrincipalCollection,通俗點說就是用戶信息(用戶名啥的 ),就是認證最后一步返回的SimpleAuthenticationInfo里的第一個參數。通過用戶信息,從數據庫獲取用戶的角色列表和權限列表放進SimpleAuthorizationInfo,最后返回SimpleAuthorizationInfo,后面的工作shiro就幫我們干了。權限列表里如果包含注解上的zdyl:test:1,就可以正常訪問,如果不

包含就會報沒有訪問權限的錯誤。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM