Shiro —— Spring 环境下的使用

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Shiro —— Spring 环境下的使用相关的知识,希望对你有一定的参考价值。

一、使用

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>

    <servlet-mapping>
        <servlet-name>remoting</servlet-name>
        <url-pattern>/remoting/*</url-pattern>
    </servlet-mapping>

    <!-- ==================================================================
         Welcome file list
         ================================================================== -->
    <welcome-file-list>
        <welcome-file>index.jsp</welcome-file>
    </welcome-file-list>

</web-app>
web.xml

(2)真实的环境的下 web.xml

技术分享
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
         version="3.1">

    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:applicationContext*.xml</param-value>
    </context-param>

    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

    <!-- 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>

    <servlet>
        <servlet-name>dispatcherServlet</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>dispatcherServlet</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
</web-app>
web.xml

2.spring-shiro.xml

(1)官方 demo 中的 applicationContext.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.
  -->
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="
       http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">

    <!-- Sample RDBMS data source that would exist in any application - not Shiro related. -->
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="org.hsqldb.jdbcDriver"/>
        <property name="url" value="jdbc:hsqldb:mem:shiro-spring"/>
        <property name="username" value="sa"/>
    </bean>
    <!-- Populates the sample database with sample users and roles. -->
    <bean id="bootstrapDataPopulator" class="org.apache.shiro.samples.spring.BootstrapDataPopulator">
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <!-- Simulated business-tier "Manager", not Shiro related, just an example -->
    <bean id="sampleManager" class="org.apache.shiro.samples.spring.DefaultSampleManager"/>

    <!-- =========================================================
         Shiro Core Components - Not Spring Specific
         ========================================================= -->
    <!-- Shiro‘s main business-tier object for web-enabled applications
         (use DefaultSecurityManager instead when there is no web environment)-->
    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
        <property name="cacheManager" ref="cacheManager"/>
        <!-- Single realm app.  If you have multiple realms, use the ‘realms‘ property instead. -->
        <property name="sessionMode" value="native"/>
        <property name="realm" ref="jdbcRealm"/>
    </bean>

    <!-- Let‘s use some enterprise caching support for better performance.  You can replace this with any enterprise
         caching framework implementation that you like (Terracotta+Ehcache, Coherence, GigaSpaces, etc -->
    <bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
        <!-- Set a net.sf.ehcache.CacheManager instance here if you already have one.  If not, a new one
             will be creaed with a default config:
             <property name="cacheManager" ref="ehCacheManager"/> -->
        <!-- If you don‘t have a pre-built net.sf.ehcache.CacheManager instance to inject, but you want
             a specific Ehcache configuration to be used, specify that here.  If you don‘t, a default
             will be used.:
        <property name="cacheManagerConfigFile" value="classpath:some/path/to/ehcache.xml"/> -->
    </bean>

    <!-- Used by the SecurityManager to access security data (users, roles, etc).
         Many other realm implementations can be used too (PropertiesRealm,
         LdapRealm, etc. -->
    <bean id="jdbcRealm" class="org.apache.shiro.samples.spring.realm.SaltAwareJdbcRealm">
        <property name="name" value="jdbcRealm"/>
        <property name="dataSource" ref="dataSource"/>
        <property name="credentialsMatcher">
            <!-- The ‘bootstrapDataPopulator‘ Sha256 hashes the password
                 (using the username as the salt) then base64 encodes it: -->
            <bean class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
                <property name="hashAlgorithmName" value="SHA-256"/>
                <!-- true means hex encoded, false means base64 encoded -->
                <property name="storedCredentialsHexEncoded" value="false"/>
            </bean>
        </property>
    </bean>

    <!-- =========================================================
         Shiro Spring-specific integration
         ========================================================= -->
    <!-- Post processor that automatically invokes init() and destroy() methods
         for Spring-configured Shiro objects so you don‘t have to
         1) specify an init-method and destroy-method attributes for every bean
            definition and
         2) even know which Shiro objects require these methods to be
            called. -->
    <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>

    <!-- Enable Shiro Annotations for Spring-configured beans.  Only run after
         the lifecycleBeanProcessor has run: -->
    <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"
          depends-on="lifecycleBeanPostProcessor"/>
    <bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
        <property name="securityManager" ref="securityManager"/>
    </bean>

    <!-- Secure Spring remoting:  Ensure any Spring Remoting method invocations can be associated
         with a Subject for security checks. -->
    <bean id="secureRemoteInvocationExecutor" class="org.apache.shiro.spring.remoting.SecureRemoteInvocationExecutor">
        <property name="securityManager" ref="securityManager"/>
    </bean>

    <!-- Define the Shiro Filter here (as a FactoryBean) instead of directly in web.xml -
         web.xml uses the DelegatingFilterProxy to access this bean.  This allows us
         to wire things with more control as well utilize nice Spring things such as
         PropertiesPlaceholderConfigurer and abstract beans or anything else we might need: -->
    <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
        <property name="securityManager" ref="securityManager"/>
        <property name="loginUrl" value="/s/login"/>
        <property name="successUrl" value="/s/index"/>
        <property name="unauthorizedUrl" value="/s/unauthorized"/>
        <!-- The ‘filters‘ property is not necessary since any declared javax.servlet.Filter bean
             defined will be automatically acquired and available via its beanName in chain
             definitions, but you can perform overrides or parent/child consolidated configuration
             here if you like: -->
        <!-- <property name="filters">
            <util:map>
                <entry key="aName" value-ref="someFilterPojo"/>
            </util:map>
        </property> -->
        <property name="filterChainDefinitions">
            <value>
                /favicon.ico = anon
                /logo.png = anon
                /shiro.css = anon
                /s/login = anon
                # allow WebStart to pull the jars for the swing app:
                /*.jar = anon
                # everything else requires authentication:
                /** = authc
            </value>
        </property>
    </bean>

</beans>
applicationContext.xml

(2)实验中的 applicationContext-shiro.xml

技术分享
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
        <property name="cacheManager" ref="cacheManager"/>
        <property name="realm" ref="realm"/>
    </bean>

    <bean id="realm" class="com.nucsoft.shiro.shiro.MyRealm"/>

    <bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
        <property name="cacheManagerConfigFile" value="classpath:ehcache-shiro.xml"/>
    </bean>

    <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>

    <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"
          depends-on="lifecycleBeanPostProcessor"/>
    <bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
        <property name="securityManager" ref="securityManager"/>
    </bean>

    <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>
</beans>
applicationContext-shiro.xml
技术分享
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
        <property name="cacheManager" ref="cacheManager"/>
        <property name="realm" ref="realm"/>
    </bean>

    <bean id="realm" class="com.nucsoft.shiro.shiro.MyRealm"/>

    <bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
        <property name="cacheManagerConfigFile" value="classpath:ehcache-shiro.xml"/>
    </bean>

    <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>

    <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"
          depends-on="lifecycleBeanPostProcessor"/>
    <bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
        <property name="securityManager" ref="securityManager"/>
    </bean>

    <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>
</beans>
applicationContext-shiro.xml

3.springmvc.xml

技术分享
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd">

    <context:component-scan base-package="com.nucsoft.shiro.handler"/>

    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/pages/"/>
        <property name="suffix" value=".jsp"/>
    </bean>

    <mvc:annotation-driven/>
    <mvc:default-servlet-handler/>
</beans>
dispatcherServlet-servlet.xml

四、shiro 中默认的过滤器名称以及使用

1.anno,匿名就可以访问,e:/admins/** = anno

2.authc,认证后可以访问,e:/user/** = authc

3.authcBasic,没有参数,表示需要通过 httpBasic 验证,如果不通过,跳转到登录页面。e:/user/** = authcBasic

4.logout

5.noSessionCreation,阻止在请求期间创建新的会话,以保证无状态的体验。

6.perms,e1:/admins/**=perms[user:add:*] ,e2:/admins/users/**=perms["user:add:*,user:modify:*"]

7.post,指定请求访问的端口,e:/admins/**=port[8080]

8.rest,根据请求的方法,e:/admins/user/**=perms[user.method],其中mothod 为 post,get,delete 等。

9.roles,角色过滤器,判断当前用户是否拥有指定的角色。

10.ssl,没有参数,表示协议为 https。

11.user,表示必须存在用户。

以上是关于Shiro —— Spring 环境下的使用的主要内容,如果未能解决你的问题,请参考以下文章

spring整合shiro框架

Spring Boot使用Shiro实现登录授权认证

JavaSE环境下的shiro(源自腾讯课堂)

Spring boot后台搭建二集成Shiro添加Remember Me

如何使用 Spring Boot 配置 Shiro

[Shiro]Shiro整合Spring Boot 自动化配置