Java 权限框架 Shiro 实战二:与spring集成、filter机制

Shiro和Spring的集成,涉及到很多相关的配置,涉及到shiro的filer机制以及它拥有的各种默认filter,涉及到shiro的权限判断标签,权限注解,涉及到session管理等等方面。

1. 配置

首先需要在web.xml中专门负责接入shiro的filter:

    <!-- shiro 安全过滤器 -->
    <filter>
        <filter-name>shiroFilter</filter-name>
        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
        <async-supported>true</async-supported>
        <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>

并且需要放在所有filter中靠前的位置,比如需要放在siteMesh的过滤器之前。

DelegatingFilterProxy 表示这是一个代理filter,它会将实际的工作,交给spring配置文件中 id="shiroFilter" 的bean来处理:

public class DelegatingFilterProxy extends GenericFilterBean {
    private String contextAttribute;
    private WebApplicationContext webApplicationContext;
    private String targetBeanName;
    private boolean targetFilterLifecycle = false;
    private volatile Filter delegate;
    private final Object delegateMonitor = new Object();
    @Override
    protected void initFilterBean() throws ServletException {
        synchronized (this.delegateMonitor) {
            if (this.delegate == null) {
                // If no target bean name specified, use filter name.
                if (this.targetBeanName == null) {
                    this.targetBeanName = getFilterName();
                }
                // Fetch Spring root application context and initialize the delegate early,
                // if possible. If the root application context will be started after this
                // filter proxy, we‘ll have to resort to lazy initialization.
                WebApplicationContext wac = findWebApplicationContext();
                if (wac != null) {
                    this.delegate = initDelegate(wac);
                }
            }
        }
    }
public abstract class GenericFilterBean implements
        Filter, BeanNameAware, EnvironmentAware, ServletContextAware, InitializingBean, DisposableBean {
    @Override
    public final void init(FilterConfig filterConfig) throws ServletException {
        Assert.notNull(filterConfig, "FilterConfig must not be null");
        if (logger.isDebugEnabled()) {
            logger.debug("Initializing filter ‘" + filterConfig.getFilterName() + "‘");
        }
        this.filterConfig = filterConfig;
        // Set bean properties from init parameters.
        try {
            PropertyValues pvs = new FilterConfigPropertyValues(filterConfig, this.requiredProperties);
            BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
            ResourceLoader resourceLoader = new ServletContextResourceLoader(filterConfig.getServletContext());
            bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, this.environment));
            initBeanWrapper(bw);
            bw.setPropertyValues(pvs, true);
        }
        catch (BeansException ex) {
            String msg = "Failed to set bean properties on filter ‘" +
                filterConfig.getFilterName() + "‘: " + ex.getMessage();
            logger.error(msg, ex);
            throw new NestedServletException(msg, ex);
        }
        // Let subclasses do whatever initialization they like.
        initFilterBean();
        if (logger.isDebugEnabled()) {
            logger.debug("Filter ‘" + filterConfig.getFilterName() + "‘ configured successfully");
        }
    }

// Let subclasses do whatever initialization they like.

initFilterBean();

Filter 接口的 init 方法调用 initFilterBean(), 而该方法在子类中进行实现,它先获得 this.targetBeanName = getFilterName(); bean的名称,也就是id,然后对其进行初始化:this.delegate = initDelegate(wac); 其实就是从bean工厂中根据bean的名称找到bean.

    protected Filter initDelegate(WebApplicationContext wac) throws ServletException {
        Filter delegate = wac.getBean(getTargetBeanName(), Filter.class);
        if (isTargetFilterLifecycle()) {
            delegate.init(getFilterConfig());
        }
        return delegate;
    }

而 shiroFilter在spring中的配置如下:

    <!-- Shiro的Web过滤器 -->
    <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
        <property name="securityManager" ref="securityManager"/>
        <property name="loginUrl" value="/login"/>
        <property name="successUrl" value="/"/>
        <property name="unauthorizedUrl" value="/unauthorized"/>
        <property name="filters">
            <util:map>
                <entry key="authc" value-ref="passThruAuthenticationFilter"/>
            </util:map>
        </property>
        <property name="filterChainDefinitions">
            <value>
                /reg/** = anon    <!-- 注册相关  -->
                /login = authc
                /logout = logout
                /authenticated = authc
                /loginController = anon
                /js/** = anon
                /css/** = anon
                /img/** = anon
                /html/** = anon
                /font-awesome/** = anon
           <!-- /** = anon
                /user/modifyPassword = perms["user:update", "user:select"]
           -->
                /** = user
            </value>
        </property>
    </bean>

上面的shiroFilter的配置又引出了 securityManager 和 shiro 的filter机制和他自带的一些filter.

2. securityManager 级相关配置

在上一篇文章 Java 权限框架 Shiro 实战一:理论基础 中我们知道securityManager是shiro的顶层对象,它管理和调用其它所有子系统,负责系统的安全。我们知道shiro有两个类型的securityManager一个是JavaSE环境,默认是DefaultSecurityManager一个是web环境,默认是DefaultWebSecurityManager。所以我们web环境肯定应该使用后者。我们从顶层对象一层一层向下配置。先看securityManager如何配置:

    <!-- 相当于调用SecurityUtils.setSecurityManager(securityManager) -->
    <bean class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
        <property name="staticMethod" value="org.apache.shiro.SecurityUtils.setSecurityManager"/>
        <property name="arguments" ref="securityManager"/>
    </bean>

上面的配置相当于调用SecurityUtils.setSecurityManager(securityManager) ,来注入了下面配置的 securityManager(DefaultWebSecurityManager) :

    <!-- 安全管理器 -->
    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
        <property name="realm" ref="userRealm"/>
        <property name="cacheManager" ref="cacheManager"/>
        <property name="rememberMeManager" ref="rememberMeManager"/>
    </bean>

它默认使用的session管理器是 ServletContainerSessionManager,所以上面没有配置,所以就使用默认值。配置了就会覆盖下面的默认值:

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

显然 securityManager 最重要的工作就是用户登录认证和获得用户的权限等相关信息,所以 realm 是其最重要的依赖:

    <!-- Realm实现 -->
    <bean id="userRealm" class="com.ems.shiro.UserRealm">
        <property name="credentialsMatcher" ref="credentialsMatcher"/>
        <property name="cachingEnabled" value="false"/>
    </bean>

要理解上面userRealm的配置,就的先理解 UserRealm 的继承体系:

UserRealm 继承  AuthorizingRealm 显然是为了获取权限信息,对用户进行访问控制;继承AuthenticatingRealm显然是为了获得用户的认证信息,对用户进行认证。而 credentialsMatcher 就是 AuthenticatingRealm 使用来进行密码验证的依赖的组件:

public abstract class AuthenticatingRealm extends CachingRealm implements Initializable {/**
     * Credentials matcher used to determine if the provided credentials match the credentials stored in the data store.
     */
    private CredentialsMatcher credentialsMatcher;

再看其credentialsMatcher bean的配置:

    <!-- 凭证匹配器(验证登录密码是否正确) -->
    <bean id="credentialsMatcher" class="com.ems.shiro.RetryLimitHashedCredentialsMatcher">
        <constructor-arg ref="cacheManager"/>
        <property name="hashAlgorithmName" value="SHA-256"/>
        <property name="hashIterations" value="2"/>
        <property name="storedCredentialsHexEncoded" value="true"/>
    </bean>

配置就是 hash加密的相关参数:hash算法,hash迭代次数等。到这里 shiro 登录验证的配置就完了。至于获取用户信息和用户的权限的信息,都在userRealm中实现了:

public class UserRealm extends AuthorizingRealm {
    @Autowired
    private UserService userService;
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        String userName = (String)principals.getPrimaryPrincipal();
        User user = userService.getUserByUserName (userName );
        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
        authorizationInfo.setRoles(userService.findRolesByUserId(user.getId()));
        authorizationInfo.setStringPermissions(userService.findPermissionsByUserId(user.getId()));
        return authorizationInfo;
    }
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        String userName = (String)token.getPrincipal();
        User user = userService.getUserByUserName(userName);
        if(user == null) {
            throw new UnknownAccountException();//没找到账户
        }
        if(user.getLocked() == 0) {
            throw new LockedAccountException(); //帐号锁定
        }
        if(user.getLocked() == 2){
            throw new AuthenticationException("account was inactive");
        }
        SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
                user.getUserName(),
                user.getPassword(), // 密码
                ByteSource.Util.bytes(user.getCredentialsSalt()),    // salt=no+salt
                getName()  // realm name
        );
        return authenticationInfo;
    }

securityManager会在需要的时候回调上面 的 doGetAuthorizationInfo 和 doGetAuthenticationInfo 方法,从realm中获得登录认证信息和用户权限信息。至于 rememberMeManager 主要是实现使用cookie表示我已经登录过了,下次不需要重新登录,这一个功能,也就是“记住我”登录过这一功能:

    <!-- rememberMe管理器 -->
    <bean id="rememberMeManager" class="org.apache.shiro.web.mgt.CookieRememberMeManager">
        <!-- rememberMe cookie加密的密钥 建议每个项目都不一样 默认AES算法 密钥长度(128 256 512 位)-->
        <property name="cipherKey"
                  value="#{T(org.apache.shiro.codec.Base64).decode(‘9FvVhtFLUs0KnA3Kprsdyg==‘)}"/>
        <property name="cookie" ref="rememberMeCookie"/>
    </bean>
    <bean id="rememberMeCookie" class="org.apache.shiro.web.servlet.SimpleCookie">
        <constructor-arg value="rememberMe"/>
        <property name="httpOnly" value="true"/>
        <property name="maxAge" value="2592000"/><!-- 30天 -->
    </bean>

还有cacheManager的配置:

    <!--ehcache-->
    <bean id="ehcacheManager" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean">
        <property name="configLocation" value="classpath:ehcache/ehcache.xml"/>
    </bean>
    <bean id="springCacheManager" class="org.springframework.cache.ehcache.EhCacheCacheManager">
        <property name="cacheManager" ref="ehcacheManager"/>
    </bean>
    <!-- 缓存管理器 -->
    <bean id="cacheManager" class="com.ems.shiro.SpringCacheManagerWrapper">
        <property name="cacheManager" ref="springCacheManager"/>
    </bean>

使用的是 EhCache.

3. Shiro 的filter机制和自带的filter

Shiro的filter是基于Servlet的Filter接口实现的。我们通过Shiro提供的form登录filter:FormAuthenticationFilter 和 ShiroFilter 看看其实现:

 

继承中的每一层都实现了一些功能:

1> NameableFilter:实现给filter取名的功能(Allows a filter to be named via JavaBeans-compatible)

/**
 * Allows a filter to be named via JavaBeans-compatible*/
public abstract class NameableFilter extends AbstractFilter implements Nameable {
    /**
     * The name of this filter, unique within an application.
     */
    private String name;

2> OncePerRequestFilter : 保证对于同一个request,fiter只执行一次(Filter base class that guarantees to be just executed once per request)

/**
 * Filter base class that guarantees to be just executed once per request,
 * on any servlet container. It provides a {@link #doFilterInternal}
 * method with HttpServletRequest and HttpServletResponse arguments.*/
public abstract class OncePerRequestFilter extends NameableFilter {

3> AdviceFilter: SpringMVC风格的过滤器(就是preHandle, postHandle,afterCompletion 三接口的过滤器)

 /** * A Servlet Filter that enables AOP-style &quot;around&quot; advice for a ServletRequest via * preHandle(javax.servlet.ServletRequest, javax.servlet.ServletResponse), * postHandle(javax.servlet.ServletRequest, javax.servlet.ServletResponse), * and afterCompletion(javax.servlet.ServletRequest, javax.servlet.ServletResponse, Exception)hooks. */public abstract class AdviceFilter extends OncePerRequestFilter {
    protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
        return true;
    }
    @SuppressWarnings({"UnusedDeclaration"})
    protected void postHandle(ServletRequest request, ServletResponse response) throws Exception {
    }
    @SuppressWarnings({"UnusedDeclaration"})
    public void afterCompletion(ServletRequest request, ServletResponse response, Exception exception) throws Exception {
    }

4> PathMatchingFilter:该过滤器仅仅处理指定的路径(比如上面的配置:/js/** = anon,表示对 /js/ 目录和其子目录的请求,交给anon过滤器处理)

/**
 * <p>Base class for Filters that will process only specified paths and allow all others to pass through.</p>*/
public abstract class PathMatchingFilter extends AdviceFilter implements PathConfigProcessor {

5> AccessControlFilter: 实现提供对资源的访问控制,没有权限时,重定向到登录页面,登录之后跳转到原来的那个页面

/**
 * Superclass for any filter that controls access to a resource and may redirect the user to the login page
 * if they are not authenticated.  This superclass provides the method
 * saveRequestAndRedirectToLogin(javax.servlet.ServletRequest, javax.servlet.ServletResponse)
 * which is used by many subclasses as the behavior when a user is unauthenticated.*/
public abstract class AccessControlFilter extends PathMatchingFilter {

6> AuthenticationFilter: 实现对访问用户的认证要求,也就是必须登录了才能访问

/**
 * Base class for all Filters that require the current user to be authenticated. This class encapsulates the
 * logic of checking whether a user is already authenticated in the system while subclasses are required to perform
 * specific logic for unauthenticated requests.*/
public abstract class AuthenticationFilter extends AccessControlFilter {

7> AuthenticatingFilter: 实现判断用户是否有权限访问某资源。

/**
 * An AuthenticationFilter that is capable of automatically performing an authentication attempt
 * based on the incoming request.*/
public abstract class AuthenticatingFilter extends AuthenticationFilter {

8> FormAuthenticationFilter:shiro提供的用于实现用户登录功能,如果我们打算自己实现登录,那么我们应用 PassThruAuthenticationFilter 来替代

/**
 * Requires the requesting user to be authenticated for the request to continue, and if they are not, forces the user
 * to login via by redirecting them to the setLoginUrl(String) you configure.
 * If you would prefer to handle the authentication validation and login in your own code, consider using the
 * PassThruAuthenticationFilter instead, which allows requests to the loginUrl to pass through to your application‘s code directly.*/
public class FormAuthenticationFilter extends AuthenticatingFilter {

9> PassThruAuthenticationFilter : 用于我们自己在controller中实现登录逻辑时替代FormAuthenticationFilter

/**
 * An authentication filter that redirects the user to the login page when they are trying to access
 * a protected resource. However, if the user is trying to access the login page, the filter lets
 * the request pass through to the application code.
 * The difference between this filter and the FormAuthenticationFilter is that
 * on a login submission (by default an HTTP POST to the login URL), the FormAuthenticationFilter filter
 * attempts to automatically authenticate the user by passing the username and password request parameter values to
 * Subject.login(AuthenticationToken) directly.
 * Conversely, this controller always passes all requests to the loginUrl through, both GETs and POSTs.   * This is useful in cases where the developer wants to write their own login behavior, which should include a
 * call to Subject.login(AuthenticationToken) at some point.  For example, if the developer has their own custom MVC  * login controller or validator, this PassThruAuthenticationFilter may be appropriate.*/
public class PassThruAuthenticationFilter extends AuthenticationFilter {
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
        if (isLoginRequest(request, response)) {
            return true;
        } else {
            saveRequestAndRedirectToLogin(request, response);
            return false;
        }
    }
}

10> Shiro 自带的filter:

Shiro自身提供了很多的默认filter 来供我们使用,主要分为两种:一是 登录认证相关的filter;一是权限访问控制相关的filter;

登录认证相关的filter有:

1)filter名称: anon, 实现类org.apache.shiro.web.filter.authc.AnonymousFilter,主要用于静态资源的访问,表示无需登录就可以访问;

2)filter名称: authc, 实现类org.apache.shiro.web.filter.authc.FormAuthenticationFilter,主要用于表单登录,没有登录则跳转登录url;

3)filter名称: user, 实现类org.apache.shiro.web.filter.authc.UserFilter,主要用于要求用户已经登录或者通过“记住我”功能登录了也行。

4)filter名称: logout, 实现类org.apache.shiro.web.filter.authc.LogoutFilter,主要用于用户登出

5)filter名称: authcBasic,authc的简化形式,略。

权限访问控制相关的filter有:

1)filter名称: roles, 实现类org.apache.shiro.web.filter.authc.RolesAuthorizationFilter,主要用于验证用户必须拥有某角色,才能继续访问;

2)filter名称: perms, 实现类org.apache.shiro.web.filter.authc.PermissionsAuthorizationFilter,主要用于验证用户必须拥有某权限,才能继续访问;

3)filter名称: ssl, 实现类org.apache.shiro.web.filter.authc.SslFilter,主要用于要求访问协议是https才能访问,不然跳转到https的443短裤;

4)filter名称: port rest noSessionCreation,略。

我们上面的shiroFilter的配置中,已经使用过了上面这些自带的filter:

                /reg/** = anon    <!-- 注册相关  -->
                /login = authc
                /logout = logout
                /authenticated = authc
                /loginController = anon
                /js/** = anon
                /css/** = anon
                /img/** = anon
                /html/** = anon
                /font-awesome/** = anon
                /** = user

我们看到 /reg/** 注册相关的,/js/**静态资源都是使用的 anon匿名过滤器,不要求用户已经登录就可以访问。

/** = user 放在最后是要求除了上面那些 url 之外的访问路径,都需要登录认证过或者通过记住我登录认证过。因为路径比较是从上面开始列出来的先开始比较的,匹配了就走该过滤器,不会继续下面的过滤器了。

4. shiro的权限标签

对双方都

时间: 2024-10-23 06:59:19

Java 权限框架 Shiro 实战二:与spring集成、filter机制的相关文章

Java 权限框架 Shiro 实战(一)

Apache Shiro 官网地址:http://shiro.apache.org/ Apache Shiro is a powerful and easy-to-use Java security framework that performs authentication, authorization, cryptography, and session management. With Shiro’s easy-to-understand API, you can quickly and

Java安全框架shiro

shiro是一个强大而且简单易用的Java安全框架,主要功能有认证(就是登陆验证),授权(就是权限管理),加密(就是密码加密),session管理.适用于各种大型或者小型企业应用.和Spring Security比较而言,确实更加简单而且灵活易懂. 1. shiro中的重要概念 要理解shiro,先要理解框架的几个概念: 1) Subject: 代表当前登陆或者访问的用户: 2)Principals:一般指用户名等,唯一表明Subject身份也就是当前用户身份的东西: 3)Credentials

java权限框架 SpringMVC_mybatis or hibernate+ ehcache二级缓存

A 代码生成器(开发利器);  B 阿里数据库连接池druid; C 安全权限框架shiro ; D ehcache 自定义二级缓存  (后续会加入Activiti5 工作流 ) 系统为主流的 springmvc+mybaits 3.2 版本 ,提供maven的pom.xml文件,另免费赠送hibernate版本一套(垮数据库) 1. 有 oracle .msyql.spring3.0.spring4.0  一共 4 套版本全部提供没有打jar没有加密的源代码(最下面截图2.1版本) 2. 支持

SpringMVC_mybatis or hibernate+ ehcache二级缓存java权限框架

A 代码生成器(开发利器);  B 阿里数据库连接池druid; C 安全权限框架shiro ; D ehcache 自定义二级缓存  (后续会加入Activiti5 工作流 ) 系统为主流的 springmvc+mybaits 3.2 版本 ,提供maven的pom.xml文件,另免费赠送hibernate版本一套(垮数据库) 1. 有 oracle .msyql.spring3.0.spring4.0  一共 4 套版本全部提供没有打jar没有加密的源代码(最下面截图2.1版本) 2. 支持

SpringBoot项目+Shiro(权限框架)+Redis(缓存)集成

项目是SpringCloud框架,分布式项目,包括Eureka.Zuul.Config.User-Svr(用户管理的服务,既是服务端也是客户端): SpringCloud框架的SpringBoot 的项目搭建就不再赘述,这里重点介绍如何引入集成 Shiro 框架: Apache Shiro是一个强大且易用的Java安全框架,执行身份验证.授权.密码学和会话管理.使用Shiro的易于理解的API,您可以快速.轻松地获得任何应用程序,从最小的移动应用程序到最大的网络和企业应用程序. 一.数据库设计

Spring实战笔记:Spring集成

一.使用远程服务 远程调用是客户端应用和服务端之间的会话. 1.Spring通过多种远程调用技术支持RPC(remote procedure call,远程过程调用) RPC模型 使用场景 RMI 不考虑网络限制时(例如防火墙),访问/发布基于Java的服务 Hessian 或 Burlap 考虑网络限制时,通过HTTP访问/发布基于Java的服务.Hessian是二进制协议,而Burlap是基于XML的 HTTP invoker 考虑网络限制,并希望使用基于XML或专有的序列化机制实现Java

权限框架 - shiro 授权demo

之前说了权限认证,其实也就是登录验证身份 这次来说说shiro的授权 shiro可以针对角色授权,或者访问资源授权 两者都行,但是在如今的复杂系统中,当然使用后者,如果你是小系统或者私活的话,前者即可,甚至可以不用,我懂的 好吧,上代码: 首先新建一个ini,登陆信息以及权限配置好 1 #用户 2 [users] 3 #eric 用户nathan的密码是123456,拥有boss以及hr两个权限 4 eric=123456,boss,hr 5 merry=123456,hr 6 7 #权限 8

手把手实现Java权限(1)-Shiro介绍

功能介绍 Authentication :身份认证/登录,验证用户是不是拥有相应的身份: Authorization :授权,即权限验证,验证某个已认证的用户是否拥有某个权限:即判断用 户是否能做事情,常见的如:验证某个用户是否拥有某个角色.或者细粒度的验证某个用 户对某个资源是否具有某个权限: Session Manager :会话管理,即用户登录后就是一次会话,在没有退出之前,它的所有信 息都在会话中:会话可以是普通 JavaSE 环境的,也可以是如 Web 环境的: Cryptograph

权限框架 - shiro 自定义realm

上篇文章中是使用的默认realm来实现的简单登录,这仅仅只是个demo,真正项目中使用肯定是需要连接数据库的 首先创建自定义realm文件,如下: 在shiro中注入自定义realm的完全限定类名: 1 [main] 2 # your custom realm path 3 fooRealm=com.lee.shiro.realm.FooRealm 4 # DI such as spring DI 5 securityManager.realms=$fooRealm 自定义realm认证: 1