Shiro, apacheshiro

Source: Internet
Author: User

Shiro, apacheshiro

Let's not talk about Spring. First, we try to integrate Shiro into web applications in the simplest way. That is, the simple configuration of Servlet ContextListener, Filter, and ini is used to integrate with web applications.

Web. xml:

<listener>    <listener-class>org.apache.shiro.web.env.EnvironmentLoaderListener</listener-class></listener><context-param>    <param-name>shiroEnvironmentClass</param-name>    <param-value>org.apache.shiro.web.env.IniWebEnvironment</param-value></context-param><context-param>    <param-name>shiroConfigLocations</param-name>    <param-value>classpath:shiro_web.ini</param-value></context-param>

 

In the above configuration, I registered a Listener -- org. apache. shiro. web. env. EnvironmentLoaderListener.
This class is mainly used to implement ServletContextListener and create and destroy WebEnvironment along with the ServletContext event.
The processing logic for WebEnvironment is all in the parent class-EnvironmentLoader.

 

Class relationship diagram of WebEnvironment:

 

To obtain WebEnvironment, try the following methods:

WebUtils.getRequiredWebEnvironment(servletContext);

 

Two parameters are used in the preceding configuration (in fact, EnvironmentLoader only has these two parameters ).

  • ShiroEnvironmentClass
  • ShiroConfigLocations

 

ShiroEnvironmentClass is used to specify the WebEnvironment implementation class used. The default value is org. apache. shiro. web. env. IniWebEnvironment.
IniWebEnvironment creates an ini instance based on the path of the configured. ini configuration file. If the. ini configuration file cannot be obtained, a ConfigurationException is thrown.

 

Of course, if necessary (for example, changing the configuration format and parsing method...), we can also implement a WebEnvirontment by ourselves and register it through the shiroEnvironmentClass attribute.

 

The shiroConfigLocations parameter specifies the path of the. ini configuration file.
If you do not manually specify the path, he will try to find it in the following two paths:

public static final String DEFAULT_WEB_INI_RESOURCE_PATH = "/WEB-INF/shiro.ini";public static final String DEFAULT_INI_RESOURCE_PATH = "classpath:shiro.ini";


By the way, ResourceUtils is used when IniWebEnvironment searches for. ini configuration. See:

private Ini convertPathToIni(String path, boolean required) {    //TODO - this logic is ugly - it'd be ideal if we had a Resource API to polymorphically encaspulate this behavior    Ini ini = null;    if (StringUtils.hasText(path)) {        InputStream is = null;        //SHIRO-178: Check for servlet context resource and not only resource paths:        if (!ResourceUtils.hasResourcePrefix(path)) {            is = getServletContextResourceStream(path);        } else {            try {                is = ResourceUtils.getInputStreamForPath(path);            } catch (IOException e) {                if (required) {                    throw new ConfigurationException(e);                } else {                    if (log.isDebugEnabled()) {                        log.debug("Unable to load optional path '" + path + "'.", e);                    }                }            }        }        if (is != null) {            ini = new Ini();            ini.load(is);        } else {            if (required) {                throw new ConfigurationException("Unable to load resource path '" + path + "'");            }        }    }    return ini;}


This method first calls ResourceUtils. hasResourcePrefix (path) to check whether the path prefix meets one of the following three conditions:

public static final String CLASSPATH_PREFIX = "classpath:";public static final String URL_PREFIX = "url:";public static final String FILE_PREFIX = "file:";


If the three prefixes do not match, search for them in the Servlet Context.

If one of the three prefixes is met, ResourceUtils. getInputStreamForPath (path) is called. The input stream is obtained in different ways based on the path and its different prefixes.

 

For classpath, call ClassUtils. getResourceAsStream (path); and Call getResourceAsStream (name) through the ClassLoader instance );

For a url, url. openStream ();

For file, new FileInputStream (path) is returned );

Continue to configure web. xml. Add a Filter this time:

<filter>         <filter-name>ShiroFilter</filter-name>             <filter-class>org.apache.shiro.web.servlet.ShiroFilter</filter-class>  </filter><filter-mapping>             <filter-name>ShiroFilter</filter-name>         <url-pattern>/*</url-pattern>          <dispatcher>REQUEST</dispatcher>        <dispatcher>FORWARD</dispatcher>               <dispatcher>INCLUDE</dispatcher>               <dispatcher>ERROR</dispatcher>     </filter-mapping>

 

This is the Filter configured based on the current WebEnvironment instance. It makes no sense to exist independently.

ShiroFilter uses the WebEnvironment instance to securely process all filtered requests.

Some Filter implementations provided by Shiro:


For the moment, regardless of AdviceFilter, The ShiroFilter we use is under AbstractShiroFilter.

Among them, IniShiroFilter has been deprecated SINCE 1.2, but it is a bit interesting to use, but it does not make sense.

IniShiroFilter does not need to configure EnvironmentLoaderListener at the same time. That is to say, there is no WebEnvironment object, and it is a simple Environment.

This is interesting. He can directly write the configuration in. ini to web. xml, for example:

<filter>    <filter-name>ShiroFilter</filter-name>    <filter-class>org.apache.shiro.web.servlet.IniShiroFilter</filter-class>    <init-param>        <param-name>config</param-name>        <param-value>                    [urls]                     /main/logout = logout                    /main/loginPage = anon                       /** = user                       [main]                     user.loginUrl = /main/login                      authc.successUrl = /main/welcome                 myRealm=pac.king.common.security.realm.MainRealm           securityManager.realms=$myRealm                </param-value>    </init-param></filter><filter-mapping>    <filter-name>ShiroFilter</filter-name>    <url-pattern>/*</url-pattern>    <dispatcher>REQUEST</dispatcher>    <dispatcher>FORWARD</dispatcher>    <dispatcher>INCLUDE</dispatcher>    <dispatcher>ERROR</dispatcher></filter-mapping>


Interesting, but meaningless.

Shiro also recommends that users do not configure it like this, and they give several reasons:

  • Security configurations may change frequently, but we do not want to always modify web. xml.
  • Security configurations may become larger and larger, which may affect the readability of web. xml.
  • We try our best to ensure that security configurations are not scattered in various places.

 

In any case, it depends on the user and project.

In addition, we will talk about the web application-related ini configuration.

We have used [main], [users], [roles] and other fragments in previous articles. In web applications, we can try [urls].

[Urls] is also a major selling point of Shiro (the document provider says it can do this without having seen other web frameworks ).

Configure a proprietary filter chain for each URL !!


The format of [urls] is as follows:

URL_Ant_Path_Expression = Path_Specific_Filter_Chain[optional_config]


Use an Ant expression on the left to describe the URL;

On the right is a filter chain separated by commas;

The final optional_config is some additional attributes. For example, it describes the permission for deleting user resources, perms ["user: delete"].

Configure [urls]. The example on the official website is as follows:

[urls]/index.html = anon/user/create = anon/user/** = authc/admin/** = authc, roles[administrator]/rest/** = authc, rest/remoting/rpc/** = authc, perms["remote:invoke"]

 

The URL is a relative path, even if a domain name is changed during deployment.

Note! The order of URL configuration affects the filter chain! This is first match wins.

For example, in the following example, the configuration in the second line does not take effect.

/user/** = authc/user/list = anon


Default filters, such as anon, authc, and users. Which classes do they implement?

Filter Name Class
Anon Org. apache. shiro. web. filter. authc. AnonymousFilter
Authc Org. apache. shiro. web. filter. authc. FormAuthenticationFilter
AuthcBasic Org. apache. shiro. web. filter. authc. BasicHttpAuthenticationFilter
Logout Org. apache. shiro. web. filter. authc. LogoutFilter
NoSessionCreation Org. apache. shiro. web. filter. session. NoSessionCreationFilter
Perms Org. apache. shiro. web. filter. authz. PermissionsAuthorizationFilter
Port Org. apache. shiro. web. filter. authz. PortFilter
Rest Org. apache. shiro. web. filter. authz. HttpMethodPermissionFilterv
Roles Org. apache. shiro. web. filter. authz. RolesAuthorizationFilter
Ssl Org. apache. shiro. web. filter. authz. SslFilter
User Org. apache. shiro. web. filter. authc. UserFilter


When the application starts, it loads all the default filters.

For the definition of the default Filter, see DefaultFilter In The enum class.

Pool of Filters is defined in DefaultFilterChainManager and maintained using LinkedHashMap. DefaultFilterChainManager calls void addDefaultFilters (boolean init) in constructor to put Filters into Map.

 

As the application grows, these URLs become increasingly difficult to manage.

Of course, we can also manage these URLs in the database, but there are always some URLs that require special Filter configuration.

Taking my current work application as an example, we put most URLs into the database for management, and all adopt the authc + perms filter. The perms options are also the URL.

Users with this URL permission can access this URL.

 

But there are always some exceptions and strange things, except for the URL in the database, I am again. I wrote about 30 URLs in ini and matched them with various filters. Of course, pay attention to the influence of order.

With the development, testing, and production environment switching, these filters also need to be enabled/disabled.

I can't delete filters one by one and write them back one by one...

 

See the OncePerRequestFilter field:

private boolean enabled = true;

 

And all default filters inherit OncePerRequestFilter !!


Therefore, I can enable or disable it directly in the INI file, for example:

user.enabled=false


Of course, we can also try to customize a Filter (for example, to dynamically enable/disable all filters based on a specific request or path) and register it in [main.

In addition, in the preceding graph, the AccessControlFilter field is loginUrl, and its default value is:

public static final String DEFAULT_LOGIN_URL = "/login.jsp";

 

The authc (FormAuthenticationFilter) in the filter that we often use has the following attributes:

public static final String DEFAULT_USERNAME_PARAM = "username";public static final String DEFAULT_PASSWORD_PARAM = "password";public static final String DEFAULT_REMEMBER_ME_PARAM = "rememberMe";private String usernameParam = DEFAULT_USERNAME_PARAM;private String passwordParam = DEFAULT_PASSWORD_PARAM;private String rememberMeParam = DEFAULT_REMEMBER_ME_PARAM;


We can use these attributes in the form for authentication + remember me.

Of course, these values can also be changed, for example:

[main]authc.loginUrl = /main/loginauthc.usernameParam = userNameauthc.passwordParam = pwdauthc.rememberMeParam = rememberCookie


When it comes to remember me, its implementation is provided by RememberMeManager, and the default implementation is Cookie-based.


For example, in the constructor of defawebwebsecuritymanager, set CookieRememberMeManager as the default value (field is defined in the parent class defasecursecuritymanager ):

public DefaultWebSecurityManager() {    super();    ((DefaultSubjectDAO) this.subjectDAO).setSessionStorageEvaluator(new DefaultWebSessionStorageEvaluator());    this.sessionMode = HTTP_SESSION_MODE;    setSubjectFactory(new DefaultWebSubjectFactory());    setRememberMeManager(new CookieRememberMeManager());    setSessionManager(new ServletContainerSessionManager());}


It looks good, so I will detect it step by step to see how the RememberMeManager manage works.

When a user logs on, we call Subject. login (token) to use DelegaingSubject as an example. The first step is to directly delegate the verification work to securityManager.

Work is delegated step by step, securityManager-> authenticator-> realm...

After the authentication is passed, the AuthenticationInfo result is returned to securityManager. securityManager passes the result to RememberMeManager and entrusts rememberMe.

Refer to method in AbstractRememberMeManager:

public void onSuccessfulLogin(Subject subject, AuthenticationToken token, AuthenticationInfo info) {    //always clear any previous identity:    forgetIdentity(subject);    //now save the new identity:    if (isRememberMe(token)) {        rememberIdentity(subject, token, info);    } else {        if (log.isDebugEnabled()) {            log.debug("AuthenticationToken did not indicate RememberMe is requested.  " +                    "RememberMe functionality will not be executed for corresponding account.");        }    }}


Step 1: First remove the Cookie. Shiro uses its own SimpleCookie by default and calls its removeFrom method to remove the Cookie ".

Step 2: Check whether the token is a RememberMeAuthenticationToken instance and whether rememberMe = true is set.

Step 3: RememberMe specific work, which is carried out by the subclass of AbstractRememberMeManager.

 

Take CookieRememberMeManager as an example:

protected void rememberSerializedIdentity(Subject subject, byte[] serialized) {    if (!WebUtils.isHttp(subject)) {        if (log.isDebugEnabled()) {            String msg = "Subject argument is not an HTTP-aware instance.  This is required to obtain a servlet " +                    "request and response in order to set the rememberMe cookie. Returning immediately and " +                    "ignoring rememberMe operation.";            log.debug(msg);        }        return;    }    HttpServletRequest request = WebUtils.getHttpRequest(subject);    HttpServletResponse response = WebUtils.getHttpResponse(subject);    //base 64 encode it and store as a cookie:    String base64 = Base64.encodeToString(serialized);    Cookie template = getCookie(); //the class attribute is really a template for the outgoing cookies    Cookie cookie = new SimpleCookie(template);    cookie.setValue(base64);    cookie.saveTo(request, response);}


The code is very simple, and then go to SimpleCookie:

public void saveTo(HttpServletRequest request, HttpServletResponse response) {    String name = getName();    String value = getValue();    String comment = getComment();    String domain = getDomain();    String path = calculatePath(request);    int maxAge = getMaxAge();    int version = getVersion();    boolean secure = isSecure();    boolean httpOnly = isHttpOnly();    addCookieHeader(response, name, value, comment, domain, path, maxAge, version, secure, httpOnly);}private void addCookieHeader(HttpServletResponse response, String name, String value, String comment,                             String domain, String path, int maxAge, int version,                             boolean secure, boolean httpOnly) {    String headerValue = buildHeaderValue(name, value, comment, domain, path, maxAge, version, secure, httpOnly);    response.addHeader(COOKIE_HEADER_NAME, headerValue);    if (log.isDebugEnabled()) {        log.debug("Added HttpServletResponse Cookie [{}]", headerValue);    }}


But after all, many people do not like cookies... We can also implement RememberMeManager on our own and register it (still injecting it to securityManger ):

rememberMeManager = com.my.impl.RememberMeManagersecurityManager.rememberMeManager = $rememberMeManager


We use the RememberMeAuthenticationToken inherited by UsernamePasswordToken to provide the rememberMe feature.

boolean isRememberMe();


For example, we can use this in the Realm verification method:

UsernamePasswordToken uToken = (UsernamePasswordToken)token;uToken.setRememberMe(true);

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.