Shiro —— Spring 環境下的使用

来源:http://www.cnblogs.com/solverpeng/archive/2016/09/22/5891502.html
-Advertisement-
Play Games

一、使用 1.搭建基礎環境 (1)導入 Spring 和 Shiro 的 Jar 包 正常導入 spring jar包 導入日誌包 導入 shiro 包 (2)配置文件 web.xml spring-shiro.xml spring.xml 正常配置即可。 springmvc.xml 正常配置即可。 ...


一、使用

1.搭建基礎環境

(1)導入 Spring 和 Shiro 的 Jar 包

  • 正常導入 spring jar包
  • 導入日誌包
  1. log4j-1.2.15.jar
  2. slf4j-api-1.6.1.jar
  3. slf4j-log4j12-1.6.1.jar
  • 導入 shiro 包
  1. shiro-core-1.2.2.jar
  2. shiro-ehcache-1.2.2.jar
  3. shiro-spring-1.2.2.jar
  4. shiro-web-1.2.2.jar

(2)配置文件

  • web.xml
  1. 讀取所有配置文件
  2. springmvc 的 DispatcherServlet 配置
  3. shiroFilter 的配置(該配置參考的是:shiro-root-1.2.2\samples\spring\src\main\webapp\WEB-INF\web.xml)
  4. 見文章末的兩個 web.xml
  • spring-shiro.xml
  1. 該配置參考:shiro-root-1.2.2\samples\spring\src\main\webapp\WEB-INF\applicationContext.xml
  2. 此次實驗在 shiro.xml 文件中採用的緩存管理器是 ehcache 。需要額外導入ehcache jar包和配置文件。
  3. ehcache 使用的 Hibernate 下的。
  4. ehcache-core-2.4.3.jar(hibernate-release-4.2.4.Final\lib\optional\ehcache\) 和 ehcache.xml(hibernate-release-4.2.4.Final\project\etc\)
  5. 見文章末的兩個 spring-shiro.xml
  6. 註意:(1)緩存的配置(2)自定義Realm
  • spring.xml 正常配置即可。
  • springmvc.xml 正常配置即可。

(3)檢測

  • 添加自定義 Realm,需要繼承自 AuthorizingRealm(即包含認證,也包含授權的 Realm)
  • 添加了一個空的自定義的 Realm ,沒有添加認證和授權邏輯。
  • 啟動項目,檢測能否正常啟動,檢測配置是否正確。

2.登錄

(1)添加登錄頁面

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
    <h4>
        Login Page
    </h4>

    <form action="shiro-login" method="post">
        <input name="userName" type="text"/>
        <input name="password" type="password"/>
        <input type="submit" value="submit">
    </form>
</body>
</html>
login.jsp

(2)對應 Handler 方法

@RequestMapping("/shiro-login")
public String login(String userName, String password) {
    System.out.println("userName:" + userName + ", password:" + password);
    Subject currentUser = SecurityUtils.getSubject();
    if(!currentUser.isAuthenticated()) {
        UsernamePasswordToken token = new UsernamePasswordToken(userName, password);
        token.setRememberMe(true);
        try {
            currentUser.login(token);
        } catch(UnknownAccountException uae) {
            System.out.println("用戶名不正確!");
        } catch(IncorrectCredentialsException ice) {
            System.out.println("密碼不匹配!");
        } catch(LockedAccountException lae) {
            System.out.println("賬戶被鎖定!");
        } catch(AuthenticationException ae) {
            System.out.println("認證失敗!");
        }
    }

  return "success";
}

這裡參考的是官方的 demo :shiro-root-1.2.2\samples\quickstart\src\main\java\Quickstart.java

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.config.IniSecurityManagerFactory;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.Factory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


/**
 * Simple Quickstart application showing how to use Shiro's API.
 *
 * @since 0.9 RC2
 */
public class Quickstart {

    private static final transient Logger log = LoggerFactory.getLogger(Quickstart.class);


    public static void main(String[] args) {

        // The easiest way to create a Shiro SecurityManager with configured
        // realms, users, roles and permissions is to use the simple INI config.
        // We'll do that by using a factory that can ingest a .ini file and
        // return a SecurityManager instance:

        // Use the shiro.ini file at the root of the classpath
        // (file: and url: prefixes load from files and urls respectively):
        Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
        SecurityManager securityManager = factory.getInstance();

        // for this simple example quickstart, make the SecurityManager
        // accessible as a JVM singleton.  Most applications wouldn't do this
        // and instead rely on their container configuration or web.xml for
        // webapps.  That is outside the scope of this simple quickstart, so
        // we'll just do the bare minimum so you can continue to get a feel
        // for things.
        SecurityUtils.setSecurityManager(securityManager);

        // Now that a simple Shiro environment is set up, let's see what you can do:

        // get the currently executing user:
        Subject currentUser = SecurityUtils.getSubject();

        // Do some stuff with a Session (no need for a web or EJB container!!!)
        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 + "]");
        }

        // let's login the current user so we can check against roles and permissions:
        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 more exceptions here (maybe custom ones specific to your application?
            catch (AuthenticationException ae) {
                //unexpected condition?  error?
            }
        }

        //say who they are:
        //print their identifying principal (in this case, a username):
        log.info("-->User [" + currentUser.getPrincipal() + "] logged in successfully.");

        //test a role:
        if (currentUser.hasRole("schwartz")) {
            log.info("-->May the Schwartz be with you!");
        } else {
            log.info("Hello, mere mortal.");
        }

        //test a typed permission (not instance-level)
        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.");
        }

        //a (very powerful) Instance Level permission:
        if (currentUser.isPermitted("winnebago:drive:eagle5")) {
            log.info("-->You are permitted to 'drive' the winnebago with license plate (id) 'eagle5'.  " +
                    "Here are the keys - have fun!");
        } else {
            log.info("Sorry, you aren't allowed to drive the 'eagle5' winnebago!");
        }

        //all done - log out!
        currentUser.logout();

        System.exit(0);
    }
}
Quickstart.java

說明一下:

官方 demo 演示的是一個 java 項目,而不是一個 web 項目,不同點是:web 項目下,shiro 的大管家是由容器去創建的,而不需要我們手動去獲取。

(3)檢測能否正常運行,若能,則證明登錄測試成功。

3.認證

(1)實現自定義 Realm 的 認證方法。

@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
    System.out.println("開始認證!");
    UsernamePasswordToken upToken = (UsernamePasswordToken) token;
    String username = upToken.getUsername();
    Object credentials = "123456";
    SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(username, credentials, this.getName());
    return info;
}

@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
    System.out.println("開始認證!");
    UsernamePasswordToken upToken = (UsernamePasswordToken) token;
    String username = upToken.getUsername();

    User user = new User();
    user.setId(1000);
    user.setUserName(username);
    user.setPassword("123456");
    user.getRoleNames().add("admin");

    return new SimpleAuthenticationInfo(user, user.getPassword(), this.getName());
}

說明一下:

其中 username 是從頁面獲取到的,而密碼是通過查詢資料庫獲取的。註意標紅加粗的地方。

實現的參考:org.apache.shiro.realm.jdbc.JdbcRealm

(2)測試,此時密碼不為 "123456"能否登錄。測試認證是否成功。

4.授權

(1)實現自定義 Realm 的授權方法。

@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
    System.out.println("開始授權!");
    String username = (String)this.getAvailablePrincipal(principalCollection);
    System.out.println("userName:" + username);

    Set<String> roleNames = new HashSet<>();
    roleNames.add("admin");

    SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(roleNames);

    return info;
}

或:User 對象已經包含角色信息,不需要再次查詢資料庫。

@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
    System.out.println("開始授權!");
    User user = (User) principalCollection.getPrimaryPrincipal();
    Set<String> roleNames = user.getRoleNames();
    return new SimpleAuthorizationInfo(roleNames);
}

實現的參考:org.apache.shiro.realm.jdbc.JdbcRealm

(2)測試,添加對應的頁面,然後在 Shiro 配置文件中配置訪問對應的頁面需要什麼樣的角色才能訪問。

5.認證和授權的資源數據從資料庫中獲取

(1)受保護資源和需要角色許可權間的關係存在在 shiro.xml 文件中。

<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
    <property name="securityManager" ref="securityManager"/>
    <property name="loginUrl" value="/login.jsp"/>
    <property name="unauthorizedUrl" value="/unauthorized.jsp"/>
    <property name="filterChainDefinitions">
        <value>
            /user.jsp = authc
            /admin.jsp = roles[admin]
            /** = anon
        </value>
    </property>
</bean>

(2)要想改為從資料庫中獲取,思路就是:filterChainDefinitions 屬性值能從 Java 文件中獲取。

參看:filterChainDefinitions 屬性的官方使用

public void setFilterChainDefinitions(String definitions) {
    Ini ini = new Ini();
    ini.load(definitions);
    Section section = ini.getSection("urls");
    if(CollectionUtils.isEmpty(section)) {
        section = ini.getSection("");
    }

    this.setFilterChainDefinitionMap(section);
}

public void setFilterChainDefinitionMap(Map<String, String> filterChainDefinitionMap) {
    this.filterChainDefinitionMap = filterChainDefinitionMap;
}

實際上放的是一個 Map。那麼來看看具體的 Map 存放的是怎麼的一些數據格式?在標紅的代碼處打個斷點,可以看到如下內容:

可以看到, Map 內容的就是在 shiro.xml 通過屬性 filterChainDefinitions 定義的值。

(3)具體操作

/**
 * @author solverpeng
 * @create 2016-09-21-18:59
 */
public class FilterChainDefinitionMapBuilder {
    public Map<String, String> getFilterChainDefinitionMap() {
        Map<String, String> filterChainDefinitionMap = new HashMap<>();
        filterChainDefinitionMap.put("/admin.jsp", "roles[admin],authc");
        filterChainDefinitionMap.put("/user.jsp", "authc");
        filterChainDefinitionMap.put("/**", "anon");
        return filterChainDefinitionMap;
    }

}

更改配置:

<bean id="filterChainDefinitionMapBuilder" class="com.nucsoft.shiro.shiro.FilterChainDefinitionMapBuilder"/>

<bean id="filterChainDefinitionMap" factory-bean="filterChainDefinitionMapBuilder" factory-method="getFilterChainDefinitionMap"/>

<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
    <property name="securityManager" ref="securityManager"/>
    <property name="loginUrl" value="/login.jsp"/>
    <property name="unauthorizedUrl" value="/unauthorized.jsp"/>
    <property name="filterChainDefinitionMap" ref="filterChainDefinitionMap"/>
</bean>

此時,資源信息可以通過 Java 方法來處理,而此時,這些數據就可以從資料庫中獲取。

6.鹽值加密

加密指的是對密碼的加密,用戶註冊時,將密碼使用一定的加密方式存放到資料庫中,用戶登錄的時候,同樣以相同的加密方式進行比對

在什麼地方進行的對比?

註意:不是在自定義認證的時候對比的。在 MyRealm 中

@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
    System.out.println("開始認證!");
    UsernamePasswordToken upToken = (UsernamePasswordToken) token;
    String username = upToken.getUsername();

    User user = new User();
    user.setId(1000);
    user.setUserName(username);
    user.setPassword("42e56621cf3adc9ecc261936188d31d7");
    user.getRoleNames().add("admin");

    String hashedCredentials = user.getPassword();
    ByteSource credentialsSalt = ByteSource.Util.bytes("abcd");
    String realmName = getName();
    SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user, hashedCredentials, credentialsSalt,
            realmName);
    return info;
}

的這個方法的作用,只是根據傳入的 Token 獲取到 username,從而到資料庫中查詢對應的 User 對象,以及鹽值信息,然後返回封裝這些信息的 SimpleAuthenticationInfo  的對象。

密碼的對比是在這之後對比的,來看返回後,返回到了 org.apache.shiro.realm.AuthenticatingRealm#getAuthenticationInfo 這個方法

public final AuthenticationInfo getAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {

    AuthenticationInfo info = getCachedAuthenticationInfo(token);
    if (info == null) {
        //otherwise not cached, perform the lookup:
        info = doGetAuthenticationInfo(token);
        log.debug("Looked up AuthenticationInfo [{}] from doGetAuthenticationInfo", info);
        if (token != null && info != null) {
            cacheAuthenticationInfoIfPossible(token, info);
        }
    } else {
        log.debug("Using cached authentication info [{}] to perform credentials matching.", info);
    }

    if (info != null) {
        assertCredentialsMatch(token, info);
    } else {
        log.debug("No AuthenticationInfo found for submitted AuthenticationToken [{}].  Returning null.", token);
    }

    return info;
}

其中第一處標紅的地方是調用我們自定義 Realm 的 doGetAuthenticationInfo() 方法以及返回值。

第二處標紅的地方表示如果可以緩存的話,就把此登錄賬戶進行緩存。

第三處才是真正進行比較的地方,看看方法名和參數,見名知意。 token 為表單提交過來的用戶信息,而 info 是我們做認證時從資料庫查詢獲取到的。

詳細來看:org.apache.shiro.realm.AuthenticatingRealm#assertCredentialsMatch

protected void assertCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) throws AuthenticationException {
    CredentialsMatcher cm = getCredentialsMatcher();
    if (cm != null) {
        if (!cm.doCredentialsMatch(token, info)) {
            //not successful - throw an exception to indicate this:
            String msg = "Submitted credentials for token [" + token + "] did not match the expected credentials.";
            throw new IncorrectCredentialsException(msg);
        }
    } else {
        throw new AuthenticationException("A CredentialsMatcher must be configured in order to verify " +
                "credentials during authentication.  If you do not wish for credentials to be examined, you " +
                "can configure an " + AllowAllCredentialsMatcher.class.getName() + " instance.");
    }
}

兩個核心的地方:

第一處標紅是:獲取憑證的匹配器(密碼的匹配器),來看這個介面中封裝了一下什麼信息。

public interface CredentialsMatcher {
    boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info);
}

只有一個 doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) 的方法。

來看它的體系:

看到了 Md5CredentialsMatcher, 然後發現它是一個過期的類,HashedCredentialsMatcher 作為對它的一個替代,需要 setHashAlgorithmName() 來指定加密方式。

這裡直接對 HashedCredentialsMatcher  給出說明:

(1)加密方式:setHashAlgorithmName(String hashAlgorithmName)

(2)加密次數:setHashIterations(int hashIterations)

說了這麼多,如何由我們自己指定 CreaentialsMatcher ?

我自定義的 MyRealm 是 AuthenticatingRealm 它的子類,所以想法是,在 AuthenticatingRealm 調用 getCredentialsMatcher() 之前,就將 CreaentialsMatcher set到 Realm 中。

來看具體操作:

在 MyRealm 中定義一個初始化方法:

public void initCredentialsMatcher() {
    HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
    credentialsMatcher.setHashAlgorithmName("MD5");
    credentialsMatcher.setHashIterations(1000);
    setCredentialsMatcher(credentialsMatcher);
}

指定了加密方式,然後加密次數,然後設置到了 Realm 中。

為了保證在在 AuthenticatingRealm 調用 getCredentialsMatcher() 之前,就將 CreaentialsMatcher set到 Realm 中,在容器初始化的時候就設置。

applicationContext-shiro.xml 的配置:

<bean id="realm" class="com.nucsoft.shiro.shiro.MyRealm" init-method="initCredentialsMatcher"/>

第二處標紅進行的真正的密碼匹配:cm.doCredentialsMatch(token, info)

詳細來看:org.apache.shiro.authc.credential.HashedCredentialsMatcher#doCredentialsMatch

public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
    Object tokenHashedCredentials = hashProvidedCredentials(token, info);
    Object accountCredentials = getCredentials(info);
    return equals(tokenHashedCredentials, accountCredentials);
}

其中 tokenHashedCredentials 是從表單提交過來的密碼經同樣的加密方式處理後的憑證;accountCredentials 是從資料庫中取出來的憑證信息。

關註點:是怎麼對錶單密碼進行加密處理的

詳細來看:

org.apache.shiro.authc.credential.HashedCredentialsMatcher#hashProvidedCredentials(org.apache.shiro.authc.AuthenticationToken, org.apache.shiro.authc.AuthenticationInfo)

protected Object hashProvidedCredentials(AuthenticationToken token, AuthenticationInfo info) {
    Object salt = null;
    if (info instanceof SaltedAuthenticationInfo) {
        salt = ((SaltedAuthenticationInfo) info).getCredentialsSalt();
    } else {
        //retain 1.0 backwards compatibility:
        if (isHashSalted()) {
            salt = getSalt(token);
        }
    }
    return hashProvidedCredentials(token.getCredentials(), salt, getHashIterations());
}

org.apache.shiro.authc.credential.HashedCredentialsMatcher#hashProvidedCredentials(java.lang.Object, java.lang.Object, int)

protected Hash hashProvidedCredentials(Object credentials, Object salt, int hashIterations) {
    String hashAlgorithmName = assertHashAlgorithmName();
    return new SimpleHash(hashAlgorithmName, credentials, salt, hashIterations);
}

hashAlgorithmName:這個就是在憑證匹配器中定義的加密方式。

核心:進行加密的就是這個:

new SimpleHash(hashAlgorithmName, credentials, salt, hashIterations)

hashAlgorithmName: 加密方式

credentials:表單提交的密碼

salt:鹽值

hashIterations:加密次數

用戶註冊的時候,向資料庫存入密碼的時候,可以使用此種方式對密碼進行加密。

來看一個測試:

public static void main(String[] args) {
    String hashAlgorithmName = "MD5";
    Object credentials = "123456";
    ByteSource salt = ByteSource.Util.bytes("abcd");
    int hashIterations = 1000;
    SimpleHash simpleHash = new SimpleHash(hashAlgorithmName, credentials, salt, hashIterations);
    System.out.println(simpleHash);
}

說了這麼多,還沒有說 自定義認證方法的鹽值是如何添加的:

com.nucsoft.shiro.shiro.MyRealm#doGetAuthenticationInfo

來看:

@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
    System.out.println("開始認證!");
    UsernamePasswordToken upToken = (UsernamePasswordToken) token;
    String username = upToken.getUsername();

    User user = new User();
    user.setId(1000);
    user.setUserName(username);
    user.setPassword("42e56621cf3adc9ecc261936188d31d7");
    user.getRoleNames().add("admin");

    String hashedCredentials = user.getPassword();
    ByteSource credentialsSalt = ByteSource.Util.bytes("abcd");
    String realmName = getName();
    SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user, hashedCredentials, credentialsSalt,
            realmName);
    return info;
}

標紅的地方就是加鹽值後的處理方式。

在註冊的時候,隨機生成鹽值,同用戶信息存入資料庫中。

7.關於細粒度的基於註解的授權和基於標簽庫的授權,本篇文章不進行說明。

二、總結

介紹了 Spring 環境下 shiro 的使用,包括環境的搭建,以及是如何配置的,自定義 Realm 可以完成自定義認證和自定義授權,也可以完成憑證匹配器的設置。

以及具體是怎麼完成自定義認證和授權的,也將受保護的資源與訪問的許可權從xml文件中轉到了 java 類中,為後續從資料庫中讀取提供了方便。

也介紹了加密的方式:加密類型,加密次數,加密鹽值,以及具體是如何加密的。並沒有講明在真實項目中是如何使用的,以後有機會寫文章來說明。

三、詳細配置文件

1.web.xml

(1)shiro 官方 demo 中的 web.xml

<?xml version="1.0" encoding="UTF-8"?>

<!--
  ~ Licensed to the Apache Software Foundation (ASF) under one
  ~ or more contributor license agreements.  See the NOTICE file
  ~ distributed with this work for additional information
  ~ regarding copyright ownership.  The ASF licenses this file
  ~ to you under the Apache License, Version 2.0 (the
  ~ "License"); you may not use this file except in compliance
  ~ with the License.  You may obtain a copy of the License at
  ~
  ~     http://www.apache.org/licenses/LICENSE-2.0
  ~
  ~ Unless required by applicable law or agreed to in writing,
  ~ software distributed under the License is distributed on an
  ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
  ~ KIND, either express or implied.  See the License for the
  ~ specific language governing permissions and limitations
  ~ under the License.
  -->
<web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
         version="2.5">

    <!-- ==================================================================
         Context parameters
         ================================================================== -->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/applicationContext.xml</param-value>
    </context-param>

    <!--
    - Key of the system property that should specify the root directory of this
    - web app. Applied by WebAppRootListener or Log4jConfigListener.
    -->
    <context-param>
        <param-name>webAppRootKey</param-name>
        <param-value>spring-sample.webapp.root</param-value>
    </context-param>

    <!-- ==================================================================
         Servlet listeners
         ================================================================== -->
    <listener>
        <listener-class>org.springframework.web.util.Log4jConfigListener</listener-class>
    </listener>
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

    <!-- ==================================================================
         Filters
         ================================================================== -->
    <!-- Shiro Filter is defined in the spring application context: -->
    <filter>
        <filter-name>shiroFilter</filter-name>
        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
        <init-param>
            <param-name>targetFilterLifecycle</param-name>
            <param-value>true</param-value>
        </init-param>
    </filter>

    <filter-mapping>
        <filter-name>shiroFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

    <!-- ==================================================================
         Servlets
         ================================================================== -->
    <servlet>
        <servlet-name>sample</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>sample</servlet-name>
        <url-pattern>/s/*</url-pattern>
    </servlet-mapping>

    <servlet>
        <servlet-name>remoting</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet
              
您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • http://skeletoncoder.blogspot.com/2006/10/jdbc-tutorials-commit-or-rollback.html JDBC Tutorials: Commit or Rollback transaction in finally block In mo ...
  • 1.Python 函數 函數是組織好的,可重覆使用的,用來實現單一,或相關聯功能的代碼段。 函數能提高應用的模塊性,和代碼的重覆利用率。你已經知道Python提供了許多內建函數,比如print()。但你也可以自己創建函數,這被叫做用戶自定義函數。 定義一個函數 你可以定義一個由自己想要功能的函數,以 ...
  • 註意,這個結構體,要是想在函數之間傳來傳去的話,必須要使用指針。。。。。。。 這個結構體里沒有 指針,這個類型可以說沒有“引用特性”。 被坑了一晚上。特此記錄。 ...
  • 1. 定義:匿名函數(Anonymous functions),也叫閉包函數(closures),允許 臨時創建一個沒有指定名稱的函數。最經常用作回調函數(callback)參數的值。當然,也有其它應用的情況。 2. 用法: 1)作為變數的值: 閉包函數也可以作為變數的值來使用。PHP 會自動把此種 ...
  • 1、線程安全 線程安全就是說多線程訪問同一代碼,不會產生不確定的結果。 2、List類和Set類List類和Set類是Collection集合介面的子介面。Set子介面:無序,不允許重覆。List子介面:有序,可以有重覆元素。 Set和List對比: Set:檢索元素效率低下,刪除和插入效率高,插入 ...
  • 題目: Given a string, find the length of the longest substring without repeating characters.Example:Given "abcabcbb", the answer is "abc", which the len ...
  • 問題: You are given two linked lists representing two non-negative numbers. The digits are stored in reverse order and each of their nodes contain a sin ...
  • 什麼是預設方法-Default Methods 簡單的說,就是可以在介面中定義一個已實現方法,且該介面的實現類不需要實現該方法; 如下示例: 為什麼要有預設方法 主要是為了方便擴展已有介面;如果沒有預設方法,加入給JDK中的某個介面添加一個新的抽象方法,那麼所有實現了該介面的類都得修改,影響將非常大 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...