Shiro Learning 02:Shiro Integrates Java EE

Shiro Integrates Java EE

Project preparation

  1. Create a new MAVEN Web project and introduce dependencies in pom.xml as follows

    <dependencies>
        <!-- shiro Core dependence -->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-core</artifactId>
            <version>1.2.2</version>
        </dependency>
    
        <!-- shiro Yes web Support dependency -->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-web</artifactId>
            <version>1.2.2</version>
        </dependency>
    
        <!-- serlvet-api -->
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>3.0.1</version>
            <scope>provided</scope>
        </dependency>
    
        <dependency>
            <groupId>commons-logging</groupId>
            <artifactId>commons-logging</artifactId>
            <version>1.1.3</version>
        </dependency>
    </dependencies>
    
  2. Create mainServlet,loginServlet,logoutServlet as follows

    @WebServlet(name = "mainServlet", urlPatterns = "/main")
    public class MainAction extends HttpServlet {
        @Override
        protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
            doPost(req, resp);
        }
    
        @Override
        protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
            req.getRequestDispatcher("/WEB-INF/views/main.jsp").forward(req, resp);
        }
    }
    
    @WebServlet(name = "loginServlet", urlPatterns = "/login")
    public class LoginServlet extends HttpServlet {
        @Override
        protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
            doPost(req, resp);
        }
    
        @Override
        protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
            String username = req.getParameter("username");
            String password = req.getParameter("password");
            if ("zhangsan".equals(username) && "666".equals(password)) {
                req.setAttribute("userName", username);
                //Successful landing
                req.getRequestDispatcher("/main").forward(req, resp);
            } else {
                req.setAttribute("errorMsg", "The account password is incorrect");
                req.getRequestDispatcher("/WEB-INF/views/login.jsp").forward(req, resp);
            }
        }
    }
    
    @WebServlet(name = "logoutServlet", urlPatterns = "/logout")
        public class logoutServlet extends HttpServlet {
            @Override
            protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
                doPost(req, resp);
            }
    
        @Override
        protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
            req.getSession().invalidate();
            resp.sendRedirect("/login.jsp");
        }
    }
    

Integrated Shiro

Configuring Shiro filters and listeners in web.xml

Configure Shiro's filter shiroFilter in/webapp/WEB-INF/web.xml and let it intercept all requests. Configure the listener EnvironmentLoaderListener to listen for the destruction and creation of ServletContext and subsequently destroy and create the Shiro environment (mainly the creation of Security Manager).

<!-- To configure Shiro Filter,Intercept all requests -->
<filter>
    <filter-name>shiroFilter</filter-name>
    <filter-class>org.apache.shiro.web.servlet.ShiroFilter</filter-class>
</filter>
<filter-mapping>
    <filter-name>shiroFilter</filter-name>
    <!-- Intercept all requests -->
    <url-pattern>/*</url-pattern>
</filter-mapping>


<!-- Monitor EnvironmentLoaderListener Monitor ServletContext Founded in destruction -->
<listener>
    <listener-class>org.apache.shiro.web.env.EnvironmentLoaderListener</listener-class>
</listener>

<!-- Initialization Shiro Environmental parameters -->
<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.ini</param-value>
</context-param>

Observing the source code, we find that EnvironmentLoader Listener implements the ServletContextListener interface to monitor the creation and destruction of ServletContext. It inherits from EnvironmentLoader and calls its parent initEnvironment() method to create Shiro environment when ServletContext is created. EnvironmentLoader has two important attributes as follows:

/**
 * Servlet Context config param for specifying the {@link WebEnvironment} implementation class to use:
 * {@code shiroEnvironmentClass}
 */
public static final String ENVIRONMENT_CLASS_PARAM = "shiroEnvironmentClass";

/**
 * Servlet Context config param for the resource path to use for configuring the {@link WebEnvironment} instance:
 * {@code shiroConfigLocations}
 */
public static final String CONFIG_LOCATIONS_PARAM = "shiroConfigLocations";

They are all assigned through context-param in web.xml.

Specific source code analysis EnvironmentLoaderListener class for shiro web application entry

Configure interception rules in shiro.ini

Configure Shiro in resources/shiro.ini as follows:

[main]
# Jump to the page after successful login, default is / login.jsp
authc.loginUrl=/login
# Pages that users skip when they don't need roles
roles.unauthorizedUrl=/nopermission.jsp
# Pages that users skip when they don't need permissions
perms.unauthorizedUrl=/nopermission.jsp
# Redirected pages after logout
logout.redirectUrl=/login
[urls]
# Static resources can be accessed anonymously
/static/**=anon
# Accessing employee lists requires authentication and admin roles
/employee=authc,roles[admin]
# Access to department lists requires authentication and department:view privileges
/department=authc,perms["department:view"]
# When logout is requested, the session is captured and cleared by logout
/logout=logout
# All requests require authentication
/**=authc
[users]
admin=password1,admin
user=password2,deptMgr
[roles]
admin=employee:*,department:*
deptMgr=department:view

The path configured in [urls] hits from top to bottom.

The filters in the above [main] and [url] paragraphs are all Shiro filters. The filters in Shiro and their order are as follows:

Filter abbreviation Corresponding java classes
anon org.apache.shiro.web.filter.authc.AnonymousFilter
authc org.apache.shiro.web.filter.authc.FormAuthenticationFilter
authcBasic org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter
roles org.apache.shiro.web.filter.authz.RolesAuthorizationFilter
perms org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter
user org.apache.shiro.web.filter.authc.UserFilter
logout org.apache.shiro.web.filter.authc.LogoutFilter
port org.apache.shiro.web.filter.authz.PortFilter
rest org.apache.shiro.web.filter.authz.HttpMethodPermissionFilter

The functions of each interceptor are as follows:

  1. Anon: Anonymous interceptor, which is accessible without login, is generally used for static resource filtering. For example, / static/**=anon
  2. Auhc: Indicates that authentication (login) is required to be used. For example /**= authc. Its main attributes are as follows:
    • usernameParam: Username parameter name submitted by the form, default to username
    • passwordParam: The name of the password parameter submitted by the form, default to password
    • rememberMeParam: The password parameter name for form submission rememberMe
    • loginUrl: Login page address, default / login.jsp
    • successUrl: If no url to jump is stored in session after successful login, redirect to that address
    • Failure Key Attribute: The key stored in the error message after the login failure, defaulted to shiroLoginFailure
  3. authcBasic: Basic HTTP Authentication Interceptor
  4. Roles: Role authorization interceptor to verify that a user has a resource role. For example, / admin/**=roles[admin]
  5. Perms: Permission authorization interceptor to verify whether a user has resource privileges. For example, / user/create=perms["user:create"]
  6. User: User interceptor. Users have been authenticated / remembered that I can log in. For example, / index=user
  7. logout: Exit interceptor, main attributes, its main attributes are as follows:
    • RedirectUrl: The address redirected after successful exit. For example, logout.redirectUrl=/login
  8. port: port interceptor, the main attributes are as follows:
    • Port: Ports through which requests can pass, such as / test= port[80]. If the user does not access port 80, the request port is automatically changed to port 80 and redirected to port 80.
  9. rest: rest style interceptor, which automatically constructs the permission string according to the request method (GET=read,POST=create,PUT=update,DELETE=delete,HEAD=read,TRACE=read,OPTIONS=read, MKCOL=create)
  10. ssl:SSL interceptor, only the request protocol is https can pass, otherwise automatically jump to https port (443)

Principle of authc implementation of landing filter

We can see that in the original login servlet, we specified the token of users as ["zhangsan","666"], but although the page can jump normally after using this token, the system did not record our login status (re-accessing the new url requires re-entering the account password).

In fact, this reflects the role of authc interceptor, because we configure /**= authc in shiro.ini, which means that all requests will be intercepted by authc interceptor except the url configured above. The interception process of authc interceptor is as follows:

  • When sending a request, the authc interceptor intercepts the request and releases it if the current user is logged in; if the current user is not logged in, it jumps to the path specified by the authc.loginUrl attribute (default is/login.jsp).

  • After jumping to the path defined by authc.loginUrl, authc automatically finds username and password attributes from the request parameters for authentication.

    • If the validation is successful, query whether there is an intercepted url in the cache, jump to the url if there is, and jump to the path defined by authc.successUrl if there is No.
    • If the validation fails, we enter the loginServlet (of course, after the validation succeeds, we also enter the loginServlet), so we only need to deal with the case of validation failure in the loginServlet.
    @WebServlet(name = "loginServlet", urlPatterns = "/login")
    public class LoginServlet extends HttpServlet {
    
        @Override
        protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
            doPost(req, resp);
        }
    
        @Override
        // This method does not handle the case of successful landing, and successful Shiro automatically jumps to the cached URL path.
        protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    
            // Shiro stores the full class name of the last login error in shiroLoginFailure of the request object
            String exceptionClassName = (String) req.getAttribute("shiroLoginFailure");
    
            // Determine the type of error that occurred during the last landing
            if (exceptionClassName != null) {
                if (UnknownAccountException.class.getName().equals(exceptionClassName)) {
                    req.setAttribute("errorMsg", "No account exists");
                } else if (IncorrectCredentialsException.class.getName().equals(exceptionClassName)) {
                    req.setAttribute("errorMsg", "Account exists but password error");
                } else {
                    req.setAttribute("errorMsg", "Other errors");
                }
            }
    
            // Jump to the login page
            req.getRequestDispatcher("/WEB-INF/views/login.jsp").forward(req, resp);
        }
    }
    
    <%@ page contentType="text/html;charset=UTF-8" language="java" %>
    <html>
    <head>
        <title>Sign in</title>
    </head>
    <body>
    ${errorMsg}
    <form action="/login" method="post">
        //Username < input type = "text" name = "username"> < br />
        //Password < input type = "password" name = "password"> < br/>.
        <input type="submit" value="Sign in">
    </form>
    </body>
    </html>
    

Using Shiro tags in jsp

Shiro tags can be used for authorization in jsp. The common tags are as follows

jsp tag Significance
<shiro:authenticated> Display after user logs in
<shiro:notAuthenticated> Display after user is not logged in
<shiro:guest> Display when the user does not have RememberMe
<shiro:user> Display when user RememberMe
<shiro:hasAnyRoles name="role1,role2"> Display when user has role role1 or role 2
<shiro:hasRole name="role1"> Display when user has role role1
<shiro:lacksRole name="role1"> Display when user has no role role1
<shiro:hasPermission name="permission1"> Display when the user has permission 1
<shiro:lacksPermission name="permission1"> Display when the user has no permission to permission1
<shiro:principal> Display user identity name
<shiro:principal property="username"/> Display attribute values in user identity

The steps for using Shiro tags in jsp are as follows:

  1. Reference to Shiro Label Library

    <%@taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>
    
  2. Use Shiro Tags

    <div class="userinfo">
        //Current login user: <shiro:principal/>
        <a href="/logout">Sign out</a>
    </div>
    
    <shiro:hasPermission name="department:edit">
        <a href="/department/edit/1">edit</a>
    </shiro:hasPermission>
    <shiro:hasPermission name="department:edit">
        <a href="/department/delete/1">delete</a>
    </shiro:hasPermission>
    

Tags: Shiro Apache JSP xml

Posted on Mon, 12 Aug 2019 03:43:33 -0700 by alanho