Spring Boot使用Shiro

1、Shiro能做什麼

  • shiro能做認證、授權、加密、會話管理、web集成、其他框架集成。
  • shiro不僅能用在web應用中,非web應用一樣能使用。
  • shiro的認證、授權都是通過檢驗每個接口實現的,所以通過Filter來控制是最合理的,實際上Shiro也是通過Filter來實現的。認證和授權是兩個不同的概念,但是授權是建立在認證通過基礎之上的,所以可以看到shiro中AuthorizingRealm是繼承了AuthenticatingRealm的,說明授權功能是涵蓋了認證功能的。
  • shiro的會話功能是依賴cookie的,使用的是HttpServletRequest裏的getSession(true)來創建session並返回cookie。
  • 在shiro使用中我們會配置一個ShiroFilterFactoryBean,這是一個FactoryBean,getObject()返回的是AbstractShiroFilter,AbstractShiroFilter裏面有一個Filter鏈表,我們通過ShiroFilterFactoryBean將自定義的Filter塞到filters中,然後塞到FilterChainManager中,然後FilterChainManager被塞到SpringShiroFitler,SpringShiroFitler是AbstractShiroFilter的子類,這就是ShiroFilterFactoryBean涉及的Filter。註冊到spring的filterChain中的只有一個Filter,名字是shiroFilter,類型是SpringShiroFitler,這和我們上面ShiroFilterFactoryBean#getObject()返回的結果是一致的。
  • 上述ShiroFilterFactoryBean中域成員filters,shiro默認會塞12個Filter進去,在這個枚舉裏面org.apache.shiro.web.filter.mgt.DefaultFilter。
  • 使用Shiro,你往往需要定義一個繼承AuthorizingRealm如MyAuthorizingRealm類,這個類型的作用是給用戶完成自定義的認證和授權,所以AuthorizingRealm提供了2個方法doGetAuthenticationInfo()和doGetAuthorizationInfo()用來完成認證和授權的邏輯。你需要手動將MyAuthorizingRealm設置到SecurityManager中,那他什麼時候被調用?當調用Subject#login()的時候,一般是在登錄接口中調用。
  • 每一次請求,Shiro都會去尋找Session,如果沒有找到Session,就會拋出一個org.apache.shiro.session.UnknownSessionException,然後認證失敗,根據失敗後的處理,可能跳轉到登錄頁面,可能。。。
  • Shiro的Subject中有一個authenticated域,我們可以通過調用isAuthenticated()來獲取是否通過了認證,這個值是什麼時候被設置進去的?

2、shiro源碼的一些事項

a、Shiro會檢測應用中所有繼承Filter的bean

ShiroFilterFactoryBean繼承了BeanPostProcessor,所以他會檢查所有spring管理的bean,並把繼承了Filter的bean加入到ShiroFilterFactoryBean的filters域中,這麼做的原因是什麼呢?

public class ShiroFilterFactoryBean implements FactoryBean, BeanPostProcessor {
    /**
     * Inspects a bean, and if it implements the {@link Filter} interface, automatically adds that filter
     * instance to the internal {@link #setFilters(java.util.Map) filters map} that will be referenced
     * later during filter chain construction.
     */
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        if (bean instanceof Filter) {
            log.debug("Found filter chain candidate filter '{}'", beanName);
            Filter filter = (Filter) bean;
            applyGlobalPropertiesIfNecessary(filter);
            getFilters().put(beanName, filter);
        } else {
            log.trace("Ignoring non-Filter bean '{}'", beanName);
        }
        return bean;
    }

    /**
     * Does nothing - only exists to satisfy the BeanPostProcessor interface and immediately returns the
     * {@code bean} argument.
     */
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }

}

 b、Shiro會修改Web應用中Filter的FilterChain

AbstractShiroFilter#doFilterInternal()方法會修改整個filterChain,注意

protected void doFilterInternal(ServletRequest servletRequest, ServletResponse servletResponse, final FilterChain chain)
        throws ServletException, IOException {

    Throwable t = null;

    try {
        final ServletRequest request = prepareServletRequest(servletRequest, servletResponse, chain);
        final ServletResponse response = prepareServletResponse(request, servletResponse, chain);

        final Subject subject = createSubject(request, response);

        //noinspection unchecked
        subject.execute(new Callable() {
            public Object call() throws Exception {
                updateSessionLastAccessTime(request, response);
                executeChain(request, response, chain);
                return null;
            }
        });
    } catch (ExecutionException ex) {
        t = ex.getCause();
    } catch (Throwable throwable) {
        t = throwable;
    }

    if (t != null) {
        if (t instanceof ServletException) {
            throw (ServletException) t;
        }
        if (t instanceof IOException) {
            throw (IOException) t;
        }
        //otherwise it's not one of the two exceptions expected by the filter method signature - wrap it in one:
        String msg = "Filtered request failed.";
        throw new ServletException(msg, t);
    }
}

protected void executeChain(ServletRequest request, ServletResponse response, FilterChain origChain)
        throws IOException, ServletException {
    FilterChain chain = getExecutionChain(request, response, origChain);
    chain.doFilter(request, response);
}

protected FilterChain getExecutionChain(ServletRequest request, ServletResponse response, FilterChain origChain) {
    FilterChain chain = origChain;

    FilterChainResolver resolver = getFilterChainResolver();
    if (resolver == null) {
        log.debug("No FilterChainResolver configured.  Returning original FilterChain.");
        return origChain;
    }

    FilterChain resolved = resolver.getChain(request, response, origChain);
    if (resolved != null) {
        log.trace("Resolved a configured FilterChain for the current request.");
        chain = resolved;
    } else {
        log.trace("No FilterChain configured for the current request.  Using the default.");
    }

    return chain;
}

org.apache.shiro.web.filter.mgt.PathMatchingFilterChainResolver#getChain()方法

public FilterChain getChain(ServletRequest request, ServletResponse response, FilterChain originalChain) {
    FilterChainManager filterChainManager = getFilterChainManager();
    if (!filterChainManager.hasChains()) {
        return null;
    }

    String requestURI = getPathWithinApplication(request);

    // in spring web, the requestURI "/resource/menus" ---- "resource/menus/" bose can access the resource
    // but the pathPattern match "/resource/menus" can not match "resource/menus/"
    // user can use requestURI + "/" to simply bypassed chain filter, to bypassed shiro protect
    if(requestURI != null && !DEFAULT_PATH_SEPARATOR.equals(requestURI)
            && requestURI.endsWith(DEFAULT_PATH_SEPARATOR)) {
        requestURI = requestURI.substring(0, requestURI.length() - 1);
    }


    //the 'chain names' in this implementation are actually path patterns defined by the user.  We just use them
    //as the chain name for the FilterChainManager's requirements
    for (String pathPattern : filterChainManager.getChainNames()) {
        if (pathPattern != null && !DEFAULT_PATH_SEPARATOR.equals(pathPattern)
                && pathPattern.endsWith(DEFAULT_PATH_SEPARATOR)) {
            pathPattern = pathPattern.substring(0, pathPattern.length() - 1);
        }

        // If the path does match, then pass on to the subclass implementation for specific checks:
        if (pathMatches(pathPattern, requestURI)) {
            if (log.isTraceEnabled()) {
                log.trace("Matched path pattern [" + pathPattern + "] for requestURI [" + Encode.forHtml(requestURI) + "].  " +
                        "Utilizing corresponding filter chain...");
            }
            return filterChainManager.proxy(originalChain, pathPattern);
        }
    }

    return null;
}

3、一個請求在Shiro中處理流程

AbstractShiroFilter#doFilterInternal()

AbstractShiroFilter#createSubject()

createSubject是一個非常重要的流程,其中Builder()構造函數裏有個非常重要的操作

protected WebSubject createSubject(ServletRequest request, ServletResponse response) {
        return new WebSubject.Builder(getSecurityManager(), request, response).buildWebSubject();
}

subjectContext這個類型裝載了很多信息

public Builder(SecurityManager securityManager) {
    if (securityManager == null) {
        throw new NullPointerException("SecurityManager method argument cannot be null.");
    }
    this.securityManager = securityManager;
    this.subjectContext = newSubjectContextInstance();
    if (this.subjectContext == null) {
        throw new IllegalStateException("Subject instance returned from 'newSubjectContextInstance' " +
                "cannot be null.");
    }
    this.subjectContext.setSecurityManager(securityManager);
}

WebSubject.Builder#builderSubject(),可以看到主要還是父類的builderSubject()幹活了

public WebSubject buildWebSubject() {
    Subject subject = super.buildSubject();
    if (!(subject instanceof WebSubject)) {
        String msg = "Subject implementation returned from the SecurityManager was not a " +
                WebSubject.class.getName() + " implementation.  Please ensure a Web-enabled SecurityManager " +
                "has been configured and made available to this builder.";
        throw new IllegalStateException(msg);
    }
    return (WebSubject) subject;
}

Subject.Builder#builderSubject(),最後調用還是SecurityManager的邏輯,注意Subject是interface類型

public Subject buildSubject() {
   return this.securityManager.createSubject(this.subjectContext);
}

DefaultSecurityManager#createSubject(),其中有個resolveSession()方法,他會去讀取Session,如果沒有找到Session就會拋出異常,那麼這個請求就會失敗,會跳轉到登錄頁面去。

public Subject createSubject(SubjectContext subjectContext) {
    //create a copy so we don't modify the argument's backing map:
    SubjectContext context = copy(subjectContext);

    //ensure that the context has a SecurityManager instance, and if not, add one:
    context = ensureSecurityManager(context);

    //Resolve an associated Session (usually based on a referenced session ID), and place it in the context before
    //sending to the SubjectFactory.  The SubjectFactory should not need to know how to acquire sessions as the
    //process is often environment specific - better to shield the SF from these details:
    context = resolveSession(context);

    //Similarly, the SubjectFactory should not require any concept of RememberMe - translate that here first
    //if possible before handing off to the SubjectFactory:
    context = resolvePrincipals(context);

    Subject subject = doCreateSubject(context);

    //save this subject for future reference if necessary:
    //(this is needed here in case rememberMe principals were resolved and they need to be stored in the
    //session, so we don't constantly rehydrate the rememberMe PrincipalCollection on every operation).
    //Added in 1.2:
    save(subject);

    return subject;
}

 

附錄

a、Authentication

Authentication is the process of identity verification– you are trying to verify a user is who they say they are. To do so, a user needs to provide some sort of proof of identity that your system understands and trusts.

The Shiro framework is designed to make authentication as clean and intuitive as possible while providing a rich set of features. Below is a highlight of the Shiro authentication features.

Features

  • Subject Based - Almost everything you do in Shiro is based on the currently executing user, called a Subject. And you can easily retrieve the Subject anywhere in your code. This makes it easier for you to understand and work with Shiro in your applications.

  • Single Method call - The authentication process is a single method call. Needing only one method call keeps the API simple and your application code clean, saving you time and effort.

  • Rich Exception Hierarchy - Shiro offers a rich exception hierarchy to offered detailed explanations for why a login failed. The hierarchy can help you more easily diagnose code bugs or customer services issues related to authentication. In addition, the richness can help you create more complex authentication functionality if needed.

  • ‘Remember Me’ built in - Standard in the Shiro API is the ability to remember your users if they return to your application. You can offer a better user experience to them with minimal development effort.

  • Pluggable data sources - Shiro uses pluggable data access objects (DAOs), called Realms, to connect to security data sources like LDAP and Active Directory. To help you avoid building and maintaining integrations yourself, Shiro provides out-of-the-box realms for popular data sources like LDAP, Active Directory, and JDBC. If needed, you can also create your own realms to support specific functionality not included in the basic realms.

  • Login with one or more realms - Using Shiro, you can easily authenticate a user against one or more realms and return one unified view of their identity. In addition, you can customize the authentication process with Shiro’s notion of an authentication strategy. The strategies can be setup in configuration files so changes don’t require source code modifications– reducing complexity and maintenance effort.

b、Authorization

Authorization, also called access control, is the process of determining access rights to resources in an application. In other words, determining “who has access to what.” Authorization is used to answer security questions like, “is the user allowed to edit accounts”, “is this user allowed to view this web page”, “does this user have access to this button?” These are all decisions determining what a user has access to and therefore all represent authorization checks.

Authorization is a critical element of any application but it can quickly become very complex. Shiro’s goal is to eliminate much of the complexity around authorization so that you can more easily build secure software. Below is a highlight of the Shiro authorization features.

Features

  • Subject-based - Almost everything you do in Shiro is based on the currently executing user, called a Subject. And you can easily access the subject retrieve the Subject and checks its roles, permissions, or other relevant attributes anywhere in your code. This makes it easier for you to understand and work with Shiro in your applications.

  • Checks based on roles or permissions - Since the complexity of authorization differs greatly between applications, Shiro is designed to be flexible, supporting both role-based security and permission-based security based on your projects needs.

  • Powerful and intuitive permission syntax - As an option, Shiro provides an out-of-the-box permission syntax, called Wildcard Permissions, that help you model the fine grained access policies your application may have. By using Shiro’s Wildcard Permissions you get an easy-to-process and human readable syntax. Moreoever, you don’t have to go through the time-consuming effort and complexity of creating your own method for representing your access policies.

  • Multiple enforcement options - Authorization checks in Shiro can be done through in-code checks, JDK 1.5 annotations, AOP, and JSP/GSP Taglibs. Shiro’s goal is to give you the choice to use the option you think are best based on your preferences and project needs.

  • Strong caching support - Any of the modern open-source and/or enterprise caching products can be plugged in to Shiro to provide a fast and efficient user-experience. For authorization, caching is crucial for performance in larger environments or with more complex policies using back-end security data sources.

  • Pluggable data sources - Shiro uses pluggable data access objects, referred to as Realms, to connect to security data sources where you keep your access control information, like an LDAP server or a relational database. To help you avoid building and maintaining integrations yourself, Shiro provides out-of-the-box realms for popular data sources like LDAP, Active Directory, and JDBC. If needed, you can also create your own realms to support specific functionality not included in the basic realms.

  • Supports any data model - Shiro can support any data model for access control—it doesn’t force a model on you. Your realm implementation ultimately decides how your permissions and roles are grouped together and whether to return a “yes” or a “no” answer to Shiro. This feature allows you to architect your application in the manner you chose and Shiro will bend to support you.

c、Cryptography

Cryptography is the practice of protecting information from undesired access by hiding it or converting it into nonsense so no one else can read it. Shiro focuses on two core elements of Cryptography: ciphers that encrypt data like email using a public or private key, and hashes (aka message digests) that irreversibly encrypt data like passwords.

Shiro Cryptography’s primary goal is take what has traditionally been an extremely complex field and make it easy for the rest of us while providing a robust set of cryptography features.

Simplicity Features

  • Interface-driven, POJO based - All of Shiro’s APIs are interface-based and implemented as POJOs. This allows you to easily configure Shiro Cryptography components with JavaBeans-compatible formats like JSON, YAML, Spring XML and others. You can also override or customize Shiro as you see necessary, leveraging its API to save you time and effort.

  • Simplified wrapper over JCE - The Java Cryptography Extension (JCE) can be complicated and difficult to use unless you’re a cryptography expert. Shiro’s Cryptography APIs are much easier to understand and use, and they dramatically simplify JCE concepts. So now even Cryptography novices can find what they need in minutes rather than hours or days. And you won’t sacrifice any functionality because you still have access to more complicated JCE options if you need them.

  • “Object Orientifies” cryptography concepts - The JDK/JCE’s Cipher and Message Digest (Hash) classes are abstract classes and quite confusing, requiring you to use obtuse factory methods with type-unsafe string arguments to acquire instances you want to use. Shiro ‘Object Orientifies’ Ciphers and Hashes, basing them on a clean object hierarchy, and allows you to use them by simple instantiation.

  • Runtime Exceptions - Like everywhere else in Shiro, all cryptography exceptions are RuntimeExceptions. You can decide whether or not to catch an exception based on your needs.

Cipher Features

  • OO Hierarchy - Unlike the JCE, Shiro Cipher representations follow an Object-Oriented class hierarchy that match their mathematical concepts: AbstractSymmetricCipherService, DefaultBlockCipherService, AesCipherService, etc. This allows you to easily override existing classes and extend functionality as needed.

  • Just instantiate a class - Unlike the JCE’s confusing factory methods using String token arguments, using Shiro Ciphers are much easier - just instantiate a class, configure it with JavaBeans properties as necessary, and use it as desired. For example, new AesCipherService().

  • More secure default settings - The JCE Cipher instances assume a ‘lowest common denominator’ default and do not automatically enable more secure options. Shiro will automatically enable the more secure options to ensure your data is as safe as it can be by default, helping you prevent accidental security holes.

Hash Features

  • Default interface implementations - Shiro provides default Hash (aka Message Digests in the JDK) implementations out-of-the-box, such as MD5, SHA1, SHA-256, et al. This provides a type-safe construction method (e.g. new Md5Hash(data)) instead of being forced to use type-unsafe string factory methods in the JDK.

  • Built-in Hex and Base64 conversion - Shiro Hash instances can automatically provide Hex and Base-64 encoding of hashed data via their toHex() and toBase64() methods. So now you do not need to figure out how to correctly encode the data yourself.

  • Built-in Salt and repeated hashing support - Salts and repeated hash iterations are very valuable tools when hashing data, especially when it comes to protecting user passwords. Shiro’s Hash implementations support salts and multiple hash iterations out of the box so you don’t have to repeat this logic anywhere you might need it.

d、Session Management

Sessions are buckets of data that your users carry with them for a period of time when using your application. Sessions have traditionally been exclusive to web or EJB environments. No more! Shiro enables sessions for any application environment. Further, Shiro offers to a host of other great features to help you manage sessions.

Features

  • POJO/J2SE based (IoC friendly) - Everything in Shiro (including all aspects of Sessions and Session Management) is interface-based and implemented with POJOs. This allows you to easily configure all session components with any JavaBeans-compatible configuration format, like JSON, YAML, Spring XML or similar mechanisms. You can also easily extend Shiro’s components or write your own as necessary to fully customize session management functionality.

  • Session Storage - Because Shiro’s Session objects are POJO-based, session data can be easily stored in any number of data sources. This allows you to customize exactly where your application’s session data resides - for example, the file system, an enterprise cache, a relational database, or proprietary data store.

  • Easy and Powerful Clustering - Shiro’s sessions can be easily clustered using any of the readily-available networked caching products, like Ehcache, Coherence, GigaSpaces, et. al. This means you can configure session clustering for Shiro once and only once, and no matter what web container you deploy to, your sessions will be clustered the same way. No need for container-specific configuration!

  • Heterogeneous Client Access - Unlike EJB or Web sessions, Shiro sessions can be ‘shared’ across various client technologies. For example, a desktop application could ‘see’ and ‘share’ the same physical session used by the same user in a server-side web application. We are unaware of any framework other than Shiro that can support this.

  • Event listeners - Event listeners allow you to listen to lifecycle events during a session’s lifetime. You can listen for these events and react to them for custom application behavior - for example, updating a user record when their session expires.

  • Host address retention – Shiro Sessions retain the IP address of the host from where the session was initiated. This allows you to determine where the user is located and react accordingly (mostly useful in intranet environments where IP association is deterministic).

  • Inactivity/expiration support – Sessions expire due to inactivity as expected, but they can be prolonged via a touch() method to keep them ‘alive’ if desired. This is useful in Rich Internet Application (RIA) environments where the user might be using a desktop application, but may not be regularly communicating with the server, but the server session should not expire.

  • Transparent web use - Shiro’s web support implements the HttpSession interface and all of it’s associated APIs. This means you can use Shiro sessions in existing web applications and you don’t need to change any of your existing web code.

  • Can be used for SSO - Because Shiro session’s are POJO based, they are easily stored in any data source, and they can be ‘shared’ across applications if needed. This can be used to provide a simple sign-on experience since the shared session can retain authentication state.

e、Web Integration

Although Apache Shiro is designed to be used to secure any JVM-based application, it is most commonly used to secure a web application. It greatly simplifies how you secure web applications base on simple URL pattern matching and filter chain definitions. In addition to Shiro’s API, Shiro’s web support includes a rich JSP tag library to control page output.

Features

  • Simple ShiroFilter web.xml definition
    You can enable Shiro for a web application with one simple filter definition in web.xml.

  • Protects all URLs
    Shiro can protect any type of web request that comes into your system. For example, dynamically generated pages, REST request, etc.

  • Innovative Filtering (URL-specific chains)
    Defining URL specific filter chains is much easier and more intuitive than using web.xml because, in Shiro, you can explicitly specify which filters you want to execute for each path and in what order. And with Shiro you can have path-specific configuration for each filter in that chain.

  • JSP Tag support
    The JSP tags allow you to easily control page output based on the current user’s state and access rights.

  • Transparent HttpSession support
    If you are using Shiro’s native sessions, we have implemented HTTP Session API and the Servlet 2.5 API so you don’t have to change any of your existing web code to use Shiro.

f、Integrations

Shiro has been downloaded over one million times and is in production at thousands of companies. One reason: it integrates well with other technologies and frameworks.

Officially Supported Integrations

These integrations are supported by the Apache Shiro Development team. Want to help make them great? Contribute back to the project!

Open Source Community Integrations

  • Stormpath User Management from Stormpath
    Pairing Apache Shiro with Stormpath gives you a full application security and user management system, with little to no coding.

  • Grails from Pivotal and @pledbrook
    Very up-to-date Grails/Shiro Integration, including Grails 2.0 and Shiro 1.2. Adds easy authentication and access control to Grails applications.

  • Apache Isis from Apache
    Apache Isis is a full-stack framework for rapidly developing domain driven apps and RESTful APIs in Java. It uses Apache Shiro 1.2.x for authentication and authorization.

  • Apache Geode from Apache
    Using Apache Shiro to secure Geode endpoints like JMX operations, rest services, web monitoring application, CLI tool, and client server communications.

  • Oauth from @JimmiDyson
    Source code for an OAuth module for Apache Shiro based on Scribe

  • Google App Engine from @cilogi
    Demo of one way to integrate Shiro with App Engine and Google Guice, plus front-end user registration and password management.

  • Play! from @will_sargent
    A very simple integration between Apache Shiro and Play 2.0. If you want to play with Play, this project could use an update handling statelessness since the Shiro 1.2 release.

  • 55Wicket from 55 Minutes
    A nifty set of tools and libraries for enhancing productivity with the Apache Wicket Java web framework, including Shiro Integration.

  • Lift from @timperrett
    Integration between Shiro and the Lift Web framework. Uses Lift’s sitemap Locs instead of Shiro’s built in web.xml resource filters to control access to URLs.

  • Redis Cache Manager from @alexxiyang
    A Redis Cache Manager implementation.

  • Memcached Cache Manager from @mythfish
    A Memcached Cache Manager implementation.

  • JAX-RS from @silb
    Apache Shiro support for the Jersey JAX-RS implementation.

  • Dropwizard from @silb
    A bundle for securing Dropwizard with Apache Shiro.

  • Thymeleaf from @theborakompanioni
    A Thymeleaf dialect for Apache Shiro tags.

  • Krail from @davidsowerby
    Krail provides a framework for rapid Java web development by combining Vaadin, Guice, Apache Shiro, Apache Commons Configuration and others.

  • Rewrite Servlet from ocpsoft
    A highly configurable URL-rewriting tool for Java EE 6+ and Servlet 2.5+ applications

  • Freedomotic from freedomotic
    An open source, flexible, secure Internet of Things (IoT) development framework in Java, useful to build and manage modern smart spaces.

  • FlowLogix Java EE Library from Lenny Primak
    Integrates Java EE applications with Shiro Security, specifically makes Shiro Annotations work with Java EE.

  • Bootique Shiro from Bootique
    Bootique is a minimally opinionated platform for modern runnable Java apps.

Ports

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章