補習系列(6)- springboot 整合 shiro 一指禪

目標

  1. 瞭解ApacheShiro是什麼,能作什麼;
  2. 經過QuickStart 代碼領會 Shiro的關鍵概念;
  3. 能基於SpringBoot 整合Shiro 實現URL安全訪問;
  4. 掌握基於註解的方法,以實現靈活定製。

1、Apache Shiro是什麼

Apache Shiro 是一個強大且易用的Java安全框架,用於實現身份認證、鑑權、會話管理及加密功能。
框架提供了很是簡單且易於上手的API,能夠支持快速爲web應用程序實現安全控制能力。
官網地址
github 地址html

Shiro 能作什麼

Apache Shiro 的設計初衷是讓安全管理變得易於上手和容易理解,它能夠實現:java

  • 鑑別用戶身份,是否本系統註冊的A用戶;
  • 管理用戶權限,是否有某個角色,或某些權限;
  • 即便沒有web或EJB容器,也可使用Session API
  • 能夠聚合一個或多個用戶權限數據源而且以用戶視圖的形式統一表現出來
  • 實現單點登陸功能(SSO)
  • 無需登陸即可實現記住我這一功能

有什麼特性

官網-Featuresgit

主要概念 包括了
Authentication(身份鑑別)、Authorization(權限管理)、Session Management(會話管理)、Cryptography(加密)
這號稱軟件安全的四大基石.. 關於幾個概念,用下面的表格說明:github

名稱 解釋
Authentication(身份鑑別) 指鑑別登陸用戶的身份
Authorization(權限認證) 決定用戶是否有權訪問某物
Session Management(會話管理) 支持獨立的會話管理
Cryptography(加密) 利用加密算法保證數據安全

其餘特性非核心,可是很是有用web

  • web應用支持
    如JavaEE、Spring的整合支持
  • 緩存
    用於提高安全管理的效率
  • 併發
    可支持多線程應用
  • 測試
    能夠經過單元測試和集成測試驗證程序的安全性
  • Run As
    容許用戶將某一身份賦予另外一用戶(在一些行政管理軟件中經常使用)
  • Remember Mes
    在Session(會話)期間記住用戶身份,當只有強制要求登陸是才須要用戶登陸

架構說明

看看下面的圖:算法

圖中涉及了若干個模塊,關於每一個模塊的大體做用以下:spring

Subject
交互實體,對應於當前用戶。數據庫

SecurityManager
安全管理器,Shiro最核心的模塊,管理各安全模塊的工做;apache

Authenticator
身份鑑別組件,執行和反饋用戶的認證(登陸),
該組件從Realm中獲取用戶信息。segmentfault

Authentication Strategy
若是配置了多個Realm,該怎麼協調?這就用到策略

Authorizer
權限認證,顧名思義,就是用於負責用戶訪問控制的模塊。

SessionManager
會話管理器,在Web環境中Shiro通常會沿用Servlet容器的會話。
但脫離了Web環境就會使用獨立的會話管理。

SessionDAO
執行會話持久化的工具

CacheManager
一個緩存管理器,可爲 Shiro 的其餘組件提供緩存能力。

Cryptography
加密組件,提供了大量簡單易用的安全加密API

到這裏,不須要爲這麼多的模塊而苦惱,在使用Shiro時,只須要緊緊記住下面的實體關係,便不會產生理解上的困難。

簡而言之
應用程序依賴於 Subject 實體來標識當前的用戶,而SecurityManager 則經過Realm接口讀取數據,進而實現 Subject 的關聯管理。

2、快速入門

爲了幫助讀者更快速理解Shiro,下面上一段QuickStart的代碼

// 加載 shiro.ini並構造 SecurityManager
Factory<org.apache.shiro.mgt.SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
org.apache.shiro.mgt.SecurityManager securityManager = factory.getInstance();

// 設置當前的 SecurityManager對象
SecurityUtils.setSecurityManager(securityManager);

// 獲取當前用戶
Subject currentUser = SecurityUtils.getSubject();

// 操做會話
Session session = currentUser.getSession();
session.setAttribute("someKey", "aValue");
String value = (String) session.getAttribute("someKey");
if (value.equals("aValue")) {
    log.info("Retrieved the correct value! [" + value + "]");
}

// 執行登陸
if (!currentUser.isAuthenticated()) {
    UsernamePasswordToken token = new UsernamePasswordToken("lonestarr", "vespa");
    token.setRememberMe(true);
    try {
        currentUser.login(token);
    } catch (UnknownAccountException uae) {
        log.info("There is no user with username of " + token.getPrincipal());
    } catch (IncorrectCredentialsException ice) {
        log.info("Password for account " + token.getPrincipal() + " was incorrect!");
    } catch (LockedAccountException lae) {
        log.info("The account for username " + token.getPrincipal() + " is locked. "
                + "Please contact your administrator to unlock it.");
    } catch (AuthenticationException ae) {
        // unexpected condition? error?
    }
}

// 輸出用戶信息
log.info("User [" + currentUser.getPrincipal() + "] logged in successfully.");

// 檢查角色
if (currentUser.hasRole("schwartz")) {
    log.info("May the Schwartz be with you!");
} else {
    log.info("Hello, mere mortal.");
}

// 檢查權限
if (currentUser.isPermitted("lightsaber:weild")) {
    log.info("You may use a lightsaber ring. Use it wisely.");
} else {
    log.info("Sorry, lightsaber rings are for schwartz masters only.");
}

// 結束,執行註銷
currentUser.logout();

System.exit(0);

上面這段代碼來自 shiro-sample/QuickStart.java
關於代碼的解釋.. 老司機認爲看下注釋是必定能懂的了。

3、SpringBoot 整合 Shiro

咱們嘗試將 Shiro 整合到 SpringBoot 項目,翻了下官網並無太多介紹,
猜測這可能與 SpringBoot 框架還比較新有關係,Shiro是個老框架(2010年出的第一個版本)..
但最終老司機仍是成功找到了 膠合組件:shiro-spring-boot-starter

接下來,爲項目引入依賴:

<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-spring-boot-starter</artifactId>
    <version>1.4.0</version>
</dependency>

接下來,咱們將完成一個 URL訪問安全控制 的示例,經過這個案例
讀者能夠了解到如何根據業務定製必要的功能模塊。

系統設計

圖示中,名爲lilei 的用戶擁有 normal (普通用戶)的角色,而相應的具有customer.profile的讀寫權限。

以上是基於RBAC(基於角色的權限控制) 的設計,RBAC 目前的應用很是普遍

在 web應用訪問中,某些頁面是容許任何人訪問的,某些須要登陸用戶,好比我的中心
而某些頁面須要具有一些特權,好比vip資料.. 以下圖所示:

用戶模塊

一般,在設計用戶權限時都會考慮用戶信息、角色信息以及對應的權限

用戶實體

public static class UserInfo {
    private String username;
    private String passwordHash;
    private String salt;

須要注意到 salt是用於密碼存儲的加鹽值(用於防止暴力破解)
passwordHash 是原始密碼通過加鹽哈希計算後的值(16進制形式)

角色實體

public static class RoleInfo {
    private String roleName;
    private List<String> perms;

爲了簡化,咱們直接將權限用字符串形式表示,一個角色RoleInfo包含了一組權限perm。

用戶管理器

在咱們的樣例中,須要實現一個UserManager類,用於作用戶信息、權限信息的管理。

public class ShiroUserManager {

    // 用戶表
    private final Map<String, UserInfo> users = new HashMap<String, UserInfo>();
    // 角色權限表
    private final Map<String, List<RoleInfo>> userRoles = new HashMap<String, List<RoleInfo>>();

    private static final Logger logger = LoggerFactory.getLogger(ShiroUserManager.class);

    // 密鑰匹配類
    private ShiroHashMatcher matcher;

    public ShiroUserManager(ShiroHashMatcher matcher) {
        this.matcher = matcher;
    }

    public ShiroHashMatcher getMatcher() {
        return this.matcher;
    }

    @PostConstruct
    private void init() {

        // 預置信息
        register("lilei", "111111", "123");
        grant("normal", new RoleInfo("customer", "customer.profile.read"));
        grant("normal", new RoleInfo("customer", "customer.profile.write"));
    }

    /**
     * 獲取用戶信息
     * 
     * @param username
     * @return
     */
    public UserInfo getUser(String username) {
        if (StringUtils.isEmpty(username)) {
            return null;
        }
        return users.get(username);
    }

    /**
     * 獲取權限信息
     * 
     * @param username
     * @return
     */
    public List<RoleInfo> getRoles(String username) {
        if (StringUtils.isEmpty(username)) {
            return Collections.emptyList();
        }
        return userRoles.get(username);
    }

    /**
     * 添加用戶
     * 
     * @param username
     * @param password
     * @param salt
     * @return
     */
    public UserInfo register(String username, String password, String salt) {
        if (StringUtils.isEmpty(username) || StringUtils.isEmpty(password) || StringUtils.isEmpty(salt)) {
            return null;
        }

        // 生成加鹽密碼Hash值
        String passwordHash = matcher.getCredentialHash(password, salt);

        logger.info("user {} register with passHash :{}", username, passwordHash);
        UserInfo user = new UserInfo(username, passwordHash, salt);
        users.put(username, user);

        return user;
    }

    /**
     * 受權操做
     * 
     * @param username
     * @param role
     */
    public void grant(String username, RoleInfo role) {
        if (userRoles.containsKey(username)) {

            userRoles.get(username).add(role);
        } else {
            List<RoleInfo> roleList = new ArrayList<RoleInfo>();
            roleList.add(role);
            userRoles.put(username, roleList);
        }
    }

在上面的實現中,咱們僅僅將用戶、角色信息放在內存中管理,並內置了名爲lilei的用戶角色。
在真實應用中,用戶權限須要經過持久層(DB)實現

密鑰算法

咱們基於Shiro的基礎類HashedCredentialsMatcher進行了擴展。
選用SHA-256哈希算法,設置迭代次數爲1024。

public class ShiroHashMatcher extends HashedCredentialsMatcher {

    public ShiroHashMatcher() {
        setHashAlgorithmName(Sha256Hash.ALGORITHM_NAME);
        setHashIterations(1024);
        setStoredCredentialsHexEncoded(true);
    }

    public String getCredentialHash(Object credentials, Object salt) {
        return new SimpleHash(this.getHashAlgorithmName(), credentials, salt, this.getHashIterations()).toHex();
    }

Realm實現

在Shiro 框架中, Realm 是用做用戶權限信息查詢的接口,咱們的實現以下:

public class ShiroRealm extends AuthorizingRealm {

    private static final Logger logger = LoggerFactory.getLogger(ShiroRealm.class);

    private ShiroUserManager userManager;

    public ShiroRealm(ShiroUserManager userManager) {
        this.setCredentialsMatcher(userManager.getMatcher());
        this.userManager = userManager;
    }

    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        logger.info("check authorization info");

        SimpleAuthorizationInfo authInfo = new SimpleAuthorizationInfo();

        // 獲取當前用戶
        UserInfo userInfo = (UserInfo) principals.getPrimaryPrincipal();

        // 查詢角色信息
        List<RoleInfo> roleInfos = userManager.getRoles(userInfo.getUsername());

        if (roleInfos != null) {
            for (RoleInfo roleInfo : roleInfos) {

                authInfo.addRole(roleInfo.getRoleName());

                if (roleInfo.getPerms() != null) {
                    for (String perm : roleInfo.getPerms()) {
                        authInfo.addStringPermission(perm);
                    }
                }
            }
        }

        return authInfo;
    }

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        logger.info("check authentication info");

        String username = (String) token.getPrincipal();

        // 獲取用戶信息
        UserInfo user = userManager.getUser(username);

        if (user == null) {
            return null;
        }

        SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(user, user.getPasswordHash(),
                ByteSource.Util.bytes(user.getSalt()), getName());
        return authenticationInfo;
    }

Bean 註冊

將實現好的 ShiroRealm 註冊爲Bean,並初始化 WebSecurityManager

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


    @Bean
    public ShiroRealm realm() {
        ShiroRealm realm = new ShiroRealm(userManager());
        return realm;
    }

   
    @Bean
    public ShiroUserManager userManager() {
        return new ShiroUserManager(matcher());
    }


    @Bean
    public ShiroHashMatcher matcher() {
        return new ShiroHashMatcher();
    }

定義攔截鏈

攔截器鏈經過 ShiroFilterFactoryBean實現定製,實現以下:

@Bean
    public ShiroFilterFactoryBean filter(org.apache.shiro.mgt.SecurityManager securityManager) {
        logger.info("config shiro filter");
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager);

        // 定義URL攔截鏈
        Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
        // 容許匿名用戶訪問首頁
        filterChainDefinitionMap.put("/shiro/index", "anon");
        // 定義註銷路徑
        filterChainDefinitionMap.put("/shiro/logout", "logout");
        // 全部用戶界面都須要身份驗證,不然會跳轉到loginurl,由FormAuthenticationFilter處理
        filterChainDefinitionMap.put("/shiro/user/**", "authc");
        // 爲login路徑定義攔截,由FormAuthenticationFilter處理
        filterChainDefinitionMap.put("/shiro/login", "authc");
        // 全部vip路徑要求具有vip角色權限
        filterChainDefinitionMap.put("/shiro/vip/**", "roles[vip]");
        // 指定loginurl 路徑
        shiroFilterFactoryBean.setLoginUrl("/shiro/login");
        // 登陸成功後跳轉路徑
        shiroFilterFactoryBean.setSuccessUrl("/shiro/user/");
        // for un authenticated
        shiroFilterFactoryBean.setUnauthorizedUrl("/shiro/unauth");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);

        // 自定義filters,可覆蓋默認的Filter列表,參考 DefaultFilter
        Map<String, Filter> filters = new LinkedHashMap<String, Filter>();

        // 定製logout 過濾,指定註銷後跳轉到登陸頁(默認爲根路徑)
        LogoutFilter logoutFilter = new LogoutFilter();
        logoutFilter.setRedirectUrl("/shiro/login");
        filters.put("logout", logoutFilter);

        // 定製authc 過濾,指定登陸表單參數
        FormAuthenticationFilter authFilter = new FormAuthenticationFilter();
        authFilter.setUsernameParam("username");
        authFilter.setPasswordParam("password");
        filters.put("authc", authFilter);

        shiroFilterFactoryBean.setFilters(filters);
        return shiroFilterFactoryBean;
    }

跟着老司機的註釋,上面代碼應該不難理解(儘管有點冗長),filterChainDefinitionMap的定義中,
key對應於url路徑,而value則對應了過濾器的縮寫,Shiro內置的過濾器可參考DefaultFilter枚舉

配置 過濾器 功能
anon AnonymousFilter 可匿名訪問
authc FormAuthenticationFilter form表單登陸攔截
authcBasic BasicHttpAuthenticationFilter basic登陸攔截
logout LogoutFilter 註銷處理
noSessionCreation NoSessionCreationFilter 禁止建立會話
perms PermissionsAuthorizationFilter 指定權限
port PortFilter 指定端口
rest HttpMethodPermissionFilter HttpMethod轉換
roles RolesAuthorizationFilter 指定角色
ssl SslFilter 須要https
user UserFilter 已登陸或Rememberme

深刻一點
FormAuthenticationFilter 實現了表單登陸的攔截邏輯:

  1. 若是當前沒有登陸,則跳轉到 loginUrl;
  2. 若是是登陸請求,則執行登陸操做,成功後跳轉到 loginSuccessUrl
  3. 若是登陸失敗,將當前的異常信息寫入請求上下文,由業務處理。

扒一扒源碼,能夠看到相應的邏輯實現:

protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
        if (isLoginRequest(request, response)) {
            if (isLoginSubmission(request, response)) {
                if (log.isTraceEnabled()) {
                    log.trace("Login submission detected.  Attempting to execute login.");
                }
                return executeLogin(request, response);
            } else {
                if (log.isTraceEnabled()) {
                    log.trace("Login page view.");
                }
                //allow them to see the login page ;)
                return true;
            }
        } else {
            if (log.isTraceEnabled()) {
                log.trace("Attempting to access a path which requires authentication.  Forwarding to the " +
                        "Authentication url [" + getLoginUrl() + "]");
            }
            saveRequestAndRedirectToLogin(request, response);
            return false;
        }
    }

isLoginSubmission 方法的判斷中,認爲來自 loginUrl 的 POST 請求就是登陸操做。

protected boolean isLoginSubmission(ServletRequest request, ServletResponse response) {
        return (request instanceof HttpServletRequest) && WebUtils.toHttp(request).getMethod().equalsIgnoreCase(POST_METHOD);
    }

在登陸失敗後,寫入上下文信息,這裏使用的是異常類的名稱

protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException e,
                                     ServletRequest request, ServletResponse response) {
        if (log.isDebugEnabled()) {
            log.debug( "Authentication exception", e );
        }
        setFailureAttribute(request, e);
        //login failed, let request continue back to the login page:
        return true;
    }

    protected void setFailureAttribute(ServletRequest request, AuthenticationException ae) {
        String className = ae.getClass().getName();
        request.setAttribute(getFailureKeyAttribute(), className);
    }

看到這裏,你應該能理解爲何在過濾鏈定義中,loginUrl 也須要被攔截了。

filterChainDefinitionMap.put("/shiro/login", "authc");

Controller 類

基於上面的分析後,咱們即可以輕鬆的完成Controller的編寫,以下:

@Controller
@RequestMapping("/shiro")
public class ShiroController {

    /**
     * 登陸界面,展現登陸表單
     * 
     * @return
     */
    @GetMapping("/login")
    public String login() {
        return "shiro/login";
    }

    /**
     * 登陸表單處理
     * 
     * @return
     */
    @PostMapping("/login")
    public String doLogin(HttpServletRequest servletRequest, final RedirectAttributes redirectAttrs) {

        // FormAuthenticationFilter已經作了登陸校驗處理,
        // 若登陸成功會跳轉到loginSuccessUrl,這裏只作異常處理
        String errorException = (String) servletRequest
                .getAttribute(FormAuthenticationFilter.DEFAULT_ERROR_KEY_ATTRIBUTE_NAME);

        // 登陸失敗,errorException 非空
        if (!StringUtils.isEmpty(errorException)) {
            // 設置錯誤消息,執行跳轉
            redirectAttrs.addFlashAttribute("loginErrorMsg", "LoginFailed:" + errorException);
            return "redirect:/shiro/login";
        }
        return "OK";
    }

    /**
     * 用戶信息界面
     * 
     * @return
     */
    @GetMapping("/user")
    @ResponseBody
    public String user() {
        Subject subject = SecurityUtils.getSubject();
        UserInfo user = (UserInfo) subject.getPrincipals().getPrimaryPrincipal();
        return "Welcome back, " + user.getUsername();
    }
    
    /**
     * VIP 用戶信息界面
     * 
     * @return
     */
    @GetMapping("/vip")
    @ResponseBody
    public String userVip() {
        Subject subject = SecurityUtils.getSubject();
        UserInfo user = (UserInfo) subject.getPrincipals().getPrimaryPrincipal();
        return "Hi, " + user.getUsername() + ", This is for the vip";
    }
    
    /**
     * 匿名訪問界面
     * 
     * @return
     */
    @GetMapping("/annon/*")
    @ResponseBody
    public String annon() {
        return "this is the content anyone can access";
    }

    /**
     * 無權限界面
     * 
     * @return
     */
    @GetMapping("/unauth")
    @ResponseBody
    public String unauth() {
        return "you are no allow to access";
    }

登陸頁面

登陸頁面爲一個簡單的HTML界面,包含一個POST表單,使用username/password做爲請求參數。
在登陸失敗時由Controller跳轉回登陸頁,並顯示出錯信息,效果以下:

4、註解的使用

前面的例子演示了 Shiro的經典用法,然而,老司機認爲註解會更好用。
Shiro 的註解是基於AOP實現的,在方法上聲明所須要的權限,相比URL攔截要更加靈活。

shiro-spring-boot-starter 爲咱們自動注入了AOP 代理配置,可直接使用註解。

若是使用了註解,咱們能夠對url 啓用匿名訪問,這樣訪問控制則經過註解和異常處理來實現。

// 對於全部shiroan路徑一概不攔截
        filterChainDefinitionMap.put("/shiroan/**", "anon");

權限註解

/**
     * vip 界面,須要vip角色
     * 
     * @return
     */
    @RequiresRoles("vip")
    @GetMapping("/vip")
    @ResponseBody
    public String vip() {
        return "this is the vip info";
    }

    /**
     * home 界面,須要登陸
     * 
     * @return
     */
    @RequiresAuthentication
    @GetMapping("/home")
    @ResponseBody
    public String home() {
        return "this is the home page";
    }

    /**
     * 資料界面,須要資料權限
     * 
     * @return
     */
    @RequiresPermissions("customer.profile.read")
    @GetMapping("/profile")
    @ResponseBody
    public String profile() {
        return "this is the profile info";
    }

    /**
     * 讀取相冊界面,須要詳情權限
     * 
     * @return
     */
    @RequiresPermissions("customer.album.read")
    @GetMapping("/album")
    @ResponseBody
    public String album() {
        return "this is the album info";
    }

@RequiredRoles、@RequiredPermissions、@RequiredAuthentication 定義了方法執行所需的權限。
除此以外,Shiro還內置了其餘註解,以下:

名稱 功能
@RequiresRoles 指定的角色能夠訪問
@RequiresPermissions 指定的權限能夠訪問
@RequiresAuthentication 登陸用戶能夠訪問
@RequiresGuest 僅遊客能夠訪問
@RequiresUser 已登陸或 "記住我"的用戶

在訪問方法未經過權限檢查時,會拋出AuthorizationException,咱們須要定義一個攔截器進行處理

攔截器

/**
     * 自定義攔截,處理鑑權異常
     * 
     * @author atp
     *
     */
    @ControllerAdvice(assignableTypes = ShiroAnnotateController.class)
    public static class AuthExceptionHandler {

        @ExceptionHandler(value = { AuthorizationException.class })
        public ResponseEntity<String> handle(AuthorizationException e, HandlerMethod m) {

            logger.info("Authorization Failed {} -- {}", e.getClass(), e.getMessage());

            String msg = "not allow to access";
            if (e instanceof UnauthorizedException) {

                // 沒有權限
                msg = "you have no permissions";
            } else if (e instanceof UnauthenticatedException) {

                // 未登陸
                msg = "you must login first";
            }
            return ResponseEntity.status(HttpStatus.FORBIDDEN).body(msg);
        }

    }

登陸邏輯

一樣,因爲沒有了過濾鏈,咱們須要自行實現 login 邏輯,代碼很是簡單:

/**
     * 模擬登陸接口
     * 
     * @param username
     * @param password
     * @return
     */
    @RequestMapping("/login")
    @ResponseBody
    public String login(@RequestParam("username") String username, @RequestParam("password") String password) {

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

        try {
            // 執行登陸
            subject.login(token);

        } catch (UnknownAccountException e) {

            // 未知用戶
            logger.warn("the account {}  is not found", username);

            return "account not found";
        } catch (IncorrectCredentialsException e) {

            // 用戶或密碼不正確
            logger.warn("the account or password is not correct");
            return "account or password not correct";

        }
        return "login success";
    }

一些常見的登陸異常以下表,可按業務須要使用:

異常 描述
UnknownAccountException 找不到用戶
IncorrectCredentialsException 用戶名密碼不正確
LockedAccountException 用戶被鎖定
ExcessiveAttemptsException 密碼重試超過次數
ExpiredCredentialsException 密鑰已通過期

登出的代碼:

@RequestMapping("/logout")
    @ResponseBody
    public String logout() {

        Subject subject = SecurityUtils.getSubject();

        // 執行註銷
        if (subject.isAuthenticated()) {
            subject.logout();
        }
        return "OK";
    }

深刻一點
shiro-spring-boot-starter 爲咱們實現了大量的自動裝配功能,如如下代碼片斷:

@SuppressWarnings("SpringFacetCodeInspection")
@Configuration
@ConditionalOnProperty(name = "shiro.annotations.enabled", matchIfMissing = true)
public class ShiroAnnotationProcessorAutoConfiguration extends AbstractShiroAnnotationProcessorConfiguration {

    @Bean
    @DependsOn("lifecycleBeanPostProcessor")
    @ConditionalOnMissingBean
    @Override
    public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
        return super.defaultAdvisorAutoProxyCreator();
    }

    @Bean
    @ConditionalOnMissingBean
    @Override
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
        return super.authorizationAttributeSourceAdvisor(securityManager);
    }
}

其中,DefaultAdvisorAutoProxyCreator 是AOP實現的關鍵類,有興趣能夠繼續深刻了解

進一步擴展

Shiro 的功能很是靈活,本文中的樣例僅供參考,若是要在生產環境中使用,你須要思考更多方面的東西:

  • 用戶信息、權限的存儲須要數據庫支持;
  • 爲了加速權限校驗的性能,你可使用Cache模塊;
  • 更安全的檢查,好比動態校驗碼,密碼失敗重試次數檢查;
  • 更通用的方案,好比JWT/OAUTH2.0 ,很是適用於微服務架構。

碼雲同步代碼

參考文檔

Shiro-integrating-with-spring
Shiro-integrating-with-springboot
Shiro-1.2.x-refence-waylau
Shirot-SprintBoot優雅整合

小結

Apache Shiro 是一個強大易用的安全框架,其自己也提供了很是多的特性模塊。
本文旨在介紹如何將Shiro與當前流行的SpringBoot 框架結合使用,並提供了極簡單的案例。
筆者在問題求證過程當中經過閱讀部分源碼,更深刻理解了其框架原理。目前認爲,Shiro強大之處
還在於框架保持了簡單易用、靈活擴展的特色,相信這也是許多人青睞它的緣由吧。

最後,歡迎繼續關注"美碼師的補習系列-springboot篇" ,若是以爲老司機的文章還不賴,請多多分享轉發^-^

相關文章
相關標籤/搜索