Shiro使用总结

近期写一个权限管理相关的东西,突然想结合业界的安全框架写,然后搜了一下,发现了Shiro和Spring Security,通过搜索比较二者异同(学习难度,功能强大程度,使用广泛度),决定首先上手下Shiro,实践完毕之后,留个使用总结。

Shiro安全框架做了什么

首先安全框架做的是什么事情呢?是处理包括用户登录注册、用户角色关系、用户权限校验相关的各种事情。
如果不使用安全框架,那么传统Web系统我们如何处理用户登录注册、用户角色关系、用于权限校验呢?
我以前试过使用过滤器来做:
1. 所有请求首先经过过滤器,在过滤器中编码用户权限的校验逻辑。
2. 至于用户登录注册等账户信息查询、密码比对的事情,也是手动编码处理的。
3. 用户角色关系是通过存放实体类的属性和Session解决。

那么使用了安全框架之后,安全框架在哪些方面可以帮助我们简化开发?
1. 首先,权限校验的部分,Shiro内置了各种过滤器应对不同情况,此外还在方法上使用注解,通过产生代理类的方式对方法调用鉴权。
2. 用户登录注册的账户信息获取、密码获取部分,Shiro形式化了整个流程,通过继承实现自己的Realm可以获取账户信息,存入Session,如果账户不存在Shiro还定义了各种情况下的异常,密码可以使用Shiro内置的CredentialsMatcher对比校验
3. 对于用户和角色的关联关系,在Shiro的Realm中也可以编写指定。
4. 对于内置实现,如果自己觉得功能不够,还能自定义后加入到Shiro中使用。
5. 内置实现从密码对比、Session管理、Cookies管理、缓存管理(主要是用户信息,权限信息的缓存)、过滤器都有,都可以自定义。

综上,Shiro主要是形式化了账户方面、权限方面的流程,完成了一个这部分的基本实现,做到需要的功能都有默认实现,需要自定义的部分对外开放。

Shiro起步走:如何使用

这一部分内容和官网内容是一致的,附上官网地址:http://shiro.apache.org/spring.html
学习过程中参考了很多《和开涛学Shiro》的内容,一并附上地址:http://jinnianshilongnian.iteye.com/category/305053

Maven依赖

笔者主要是将Shiro使用在了Web工程的后端API这一层,所以以这一方面的使用为例。Web工程用到了Spring,所以引入的依赖包含了core、web、Spring三部分

<!-- shiro -->
<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-core</artifactId>
    <version>${shiro.version}</version>
    <exclusions>
        <exclusion>
            <artifactId>slf4j-api</artifactId>
            <groupId>org.slf4j</groupId>
        </exclusion>
    </exclusions>
</dependency>
<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-web</artifactId>
    <version>${shiro.version}</version>
</dependency>
<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-spring</artifactId>
    <version>${shiro.version}</version>
</dependency>
<!-- end of shiro -->

配置文件的修改

因为是将Shiro用到了web工程上,所以所有请求都要经过Shiro,同时因为使用了Spring,本来经由Spring 的请求也要让Shiro来过一手,对应的,Web.xml改为这样:

<!-- shiro配置 开始 -->
<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>
<!-- shiro配置 结束 -->

可以看到,主要是让Shiro对Spring扩展的过滤器来过滤所有请求。

Shiro自身配置

这里不是整合Spring的部分,而是Shiro本身配置的部分了,其可以写在Spring 的父ApplicationContext.xml内,一个可供参考的最简配置如下:

更全面的配置,可以参考开涛的Shiro教程的配置,地址如下:https://github.com/zhangkaitao/shiro-example/blob/master/shiro-example-chapter16/src/main/resources/spring-config-shiro.xml

<!-- 配置权限管理器-->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
    <!-- ref 对应我们写的 realm  userRealm -->
    <!-- 该类的定义见本节的“特异性设置”部分 -->
    <property name="realm" ref="userRealm"/>
</bean>
<!-- 配置shiro的过滤器工厂类,id- shiroFilter要和我们在web.xml中配置的过滤器一致 -->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
    <!-- 配置的权限管理器 -->
    <property name="securityManager" ref="securityManager"/>
    <!-- 配置登录请求地址-->
    <property name="loginUrl" value="/login.mvc"/>
    <!-- 配置我们在登录页登录成功后的跳转地址,如果你访问的是非/login.mvc地址,则跳到您访问的地址 -->
    <property name="successUrl" value="/"/>
    <!-- 如果您请求的资源不再您的权限范围,则跳转到login请求地址 -->
    <property name="unauthorizedUrl" value="/login.jsp"/>
    <property name="filters">
        <map>
            <entry key="logoutFilter" value-ref="logoutFilter"/>
        </map>
    </property>
    <!-- 权限配置 -->
    <!-- 内置过滤器列表和解释 见:http://shiro.apache.org/web.html#default-filters -->
    <property name="filterChainDefinitions">
        <value>
            <!-- anon表示此地址不需要任何权限即可访问 -->
            /static/** =anon
            /lib/** =anon
            /css/** =anon
            /images/** =anon
            /login.jsp =anon
            /unAuth.jsp =anon
            /error.jsp =anon
            <!-- 登入过滤器 -->
            /login.mvc =authc
            <!-- 登出过滤器 -->
            /logout =logoutFilter
            /** = authc 
        </value>
    </property>
</bean>

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

此外,为了和Spring MVC整合,使得Shiro可以使用注解,还需要在 Spring-MVC的配置文件内加入如下配置:

<!-- 
    官网上的写法和这里不一致,但是意思一样
    <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" depends-on="lifecycleBeanPostProcessor"/>
    不写我认为是父ApplicationContext已经加载了,依赖关系已经有了,不需要说明
-->
<!-- 这里使用cglib的代理方式,是为了让被代理类的其他注解依旧可用 -->
<aop:aspectj-autoproxy proxy-target-class="true"/>
<bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
    <property name="securityManager" ref="securityManager"/>
</bean>

特异性设置

因为大多数东西Shiro都内置实现了,所以只有比较特异的部分——账户信息的获取(包含用户名、密码)、用户权限的获取、用户角色对应关系的获取需要自定义。这三方面,抽象成了Shiro框架Realm组件的两个方法:
* AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken):登录用,获取用户名和密码的方法,一般这里的逻辑是从数据库取出用户名、密码,然后返回成封装好的AuthenticationInfo,具体过程见下面代码
* AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection):授予权限用,调用这个方法的使用,用户已经登录成功了,然后就要将用户关联的权限和角色信息赋予给它,然后将其返回,具体过程见下面代码

@Service
public class UserRealm extends AuthorizingRealm {
    private static final Logger logger= LoggerFactory.getLogger(UserRealm.class);

    @Autowired
    // 从数据库取User信息的Service
    private UserService userService;

    /**
     * 权限认证
        入参是用户身份的集合,一般是一个,从中可以取出当前登陆的用户的用户名
     */
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        // 身份集合,身份和Realm返回的集合对应
        String loginName=(String) principalCollection.fromRealm(getName()).iterator().next();
        // 通过用户名查询当前用户和授权相关的所有信息,返回一个封装好的(自定义的)类AuthUserDTO
        AuthUserDTO user = userService.getAuthUser(loginName);
        SimpleAuthorizationInfo info=new SimpleAuthorizationInfo();
        // 设置角色集合
        info.setRoles(user.getRoles());
        // 设置权限集合
        info.setStringPermissions(user.getPermissions());
        return info;
    }

    /**
     * 登录认证;
       入参是从外界来的请求的广义上的登陆的用户名、密码的封装对象
     */
    protected AuthenticationInfo doGetAuthenticationInfo(
            AuthenticationToken authenticationToken) throws AuthenticationException {
        // 入参对象是广义上的用户名密码的封装,可以是网页表单信息的封装,也可能是生物信息等,这里转型成用户名密码的子类
        UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
        // 通过用户名 从数据库取得真实的登录相关信息,封装成自定义的对象LoginUserDTO
        LoginUserDTO user = userService.getLoginUser((String) token.getPrincipal());

        // 根据情况不同,抛出异常,这些异常都在Shiro中内置了
        if(user == null){
            logger.error("unkonw account,userName:{}",token.getPrincipal());
            throw new UnknownAccountException("unkonw account,userName:" + token.getPrincipal());
        }
        if (user.getStatus() == UserStatusEnum.LOCKED) {
            logger.error("locked account login in,username:{}", token.getPrincipal());
            throw new LockedAccountException("已锁定账户,请联系管理员");
        } else if (user.getStatus() == UserStatusEnum.DELETED) {
            logger.error("deleted account login,username:{}", token.getPrincipal());
            throw new UnknownAccountException("已删除账户");
        }
        // 如果用户存在,则将从数据库取出的用户信息封装成SimpleAuthenticationInfo返回即可
        // 密码对比的流程,交给下一步的CredentialsMatcher处理(Shiro框架调用)
        return new SimpleAuthenticationInfo(user.getUserName(), user.getPwd(),
                ByteSource.Util.bytes(user.getSalt()), getName());
    }

}

其实如果仅仅是从数据库取数据,Shiro还提供了JDBCRealm的实现,可以定义SQL,返回需要的字段。

经过如上配置,Shiro就整合入Spring,可以与Spring、SPring MVC一起使用了。以上都是必须的配置,除此之外,还有很多可选部分的配置。
可选部分,例如SessionDAO、sessionManager、Cookie、credentialsMatcher、Filter,如果要自定义,都是首先继承Shiro内的骨架类,实现需要的部分后,将Bean注册到Spring容器中,然后通过XML声明,配置到需要这个Bean 的其他Shiro组件内。具体可见开涛写的例子

Shiro 形式化流程的总结

这里要说的Shiro内部,数据到底是如何流转的,也就是流程是什么样的,主要讨论两部分流程:

用户登录流程

  • 首先,访问登录的form的页面,这个页面的请求没有被Shiro过滤器拦下,也就是配了匿名过滤器如/login.jsp =anon
  • 填写用户名、密码等信息,提交表单
  • 提交的地址配置被Shiro的FormAuthenticationFilter,别名authc的过滤器拦截,如/login.mvc =authc。这个过滤器会提取出用户名、密码。
  • 将用户名密码封装成AuthenticationToken,进行登录操作。登录操作中,会调用ModularRealmAuthenticator对象的doAuthenticate方法,内部会调用所有Realm,一一调用其doGetAuthenticationInfo方法
  • 在每一步调用doSingleRealmAuthentication方法后,利用返回的AuthenticationInfo对象和传入的AuthenticationToken对象,调用CredentialsMatcherdoCredentialsMatch方法对比密码,出错抛出异常,没有出错则返回AuthenticationInfo,最后将其整合成一个AuthenticationInfo的集合。至此登录成功。
  • 其后,调用Realm的doGetAuthorizationInfo方法,授予用户角色和权限
  • 对请求,启动权限校验流程
    登录流程

权限校验流程

权限校验流程分两个部分,对于Shiro过滤器,是一种,对于Shiro注解,是另一种

  • 对于过滤器:配好过滤器后,当有请求到达的时候,调用过滤器的isAccessAllowed方法,判断请求是否允许通过,不同过滤器通过的策略不同,可能是根据用户、权限、是否登录等等
  • 对于注解:使用注解可以脱离过滤器,单独使用,也就是说,如果一个方法不是Web的API,没有用过滤器映射拦截URL,也可以通过注解实现权限校验,注解可以校验角色、权限表达式的信息,和Realm的doGetAuthorizationInfo方法填充的信息对应。
    验权

笔者写了一个使用到Shiro的工程,在Spring MVC、Spring和MyBatis框架的基础上整合Shiro,使用到了Shiro的过滤器、注解和SessionDAO,其中SessionDAO的使用还 有待完善,github地址:https://github.com/QiangL/pollutioncms
该工程的com.pollutioncms包下的内容才使用了SSM和Shiro,另一个包下使用的是Struts、Spring和Hibernate,也就是说该工程还涉及到SSM和SSH的整合使用。

猜你喜欢

转载自blog.csdn.net/lqadam/article/details/80529231