Shiro安全框架入门使用方法

来源:互联网 发布:知满天孙景民 编辑:程序博客网 时间:2024/04/30 07:00

框架介绍

Apache Shiro是一个强大且易用的Java安全框架,执行身份验证、授权、密码学和会话管理。使用Shiro的易于理解的API,您可以快速、轻松地获得任
何应用程序,从最小的移动应用程序到最大的网络和企业应用程序。

Shrio的主要功能:

  • Authentication:用户认证(登录)
  • Authorization:权限控制
  • Session Management:会话管理
  • Cryptography:数据加密
  • Web Support:支持web的API
  • Caching:缓存
  • Concurrency:支持多线程应用程序
  • Testing:测试的支持
  • “Run As”:假设一个用户为另一个用户的身份
  • “Remember Me”:在Session中保存用户身份

基本原理

Shiro的基本架构:

Shiro有三个核心的概念:Subject、SecurityManager和Realms。

  • Subject:Subject实质上是一个当前执行用户的特定的安全“视图”,开发者所写的应用代码就通过Subject与Shiro框架进行交互。所有Subject实例都必须绑定到一个SecurityManager上,当使用一个Subject实例时,Subject实例会和SecurityManager进行交互,完成相应操作。

  • SecurityManager:SecurityManager是Shiro的核心部分,作为一种“保护伞”对象来协调内部安全组件共同构成一个对象图。开发人员并不直接操作SecurityManager,而是通过Subject来操作SecurityManager来完成各种安全相关操作。

  • Realms:Realms担当Shiro和应用程序的安全数据之间的“桥梁”或“连接器”。从本质来讲,Realm是一个特定安全的DAO,Realm中封装了数据操作的模块和用户自定义的认证匹配过程。SecurityManager可能配置多个Realms,但至少要有一个。

使用方法

注:该示例基于一个Maven管理的SSH项目

1.在Maven中添加Shiro依赖

<dependency>    <groupId>org.apache.shiro</groupId>    <artifactId>shiro-all</artifactId>    <version>1.2.3</version></dependency>

注:Shiro官方现在不推荐这种用法,因为可能会导致Maven运行错误,详见

2.在web.xml添加核心过滤器,且必须放在Struts2核心过滤器前面

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

3.在Spring配置文件中添加如下代码,且放在事务管理器之前

<!--Shiro安全框架产生代理子类的方式: 使用cglib方式,放在事务管理器之前--><aop:aspectj-autoproxy proxy-target-class="true" />

4.在Spring配置文件中配置Shiro,建议单独出一个applicationContext-shiro.xml进行配置(编写完成后记得在总的配置文件中引入该配置文件)

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" xmlns:p="http://www.springframework.org/schema/p"    xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx"    xmlns:aop="http://www.springframework.org/schema/aop"    xsi:schemaLocation="http://www.springframework.org/schema/beans        http://www.springframework.org/schema/beans/spring-beans.xsd        http://www.springframework.org/schema/aop        http://www.springframework.org/schema/aop/spring-aop.xsd        http://www.springframework.org/schema/tx        http://www.springframework.org/schema/tx/spring-tx.xsd        http://www.springframework.org/schema/context        http://www.springframework.org/schema/context/spring-context.xsd">    <!-- SecurityManager配置 -->    <!-- 配置Realm域 -->    <!-- 密码比较器 -->    <!-- 配置缓存:ehcache缓存 -->    <!-- 安全管理 -->    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">        <!-- 引用自定义的realm -->        <property name="realm" ref="authRealm"/>        <!-- 缓存 -->        <property name="cacheManager" ref="shiroEhcacheManager"/>    </bean>    <!-- 自定义权限认证 -->    <bean id="authRealm" class="com.songzheng.demo.shiro.AuthRealm">        <property name="userService" ref="userService"/>        <!-- 自定义密码加密算法  -->        <property name="credentialsMatcher" ref="passwordMatcher"/>    </bean>    <!-- 设置密码加密策略 md5hash -->    <bean id="passwordMatcher" class="com.songzheng.demo.shiro.DemoCredentialsMatcher"/>    <!-- filter-name这个名字的值来自于web.xml中filter的名字 -->    <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">        <property name="securityManager" ref="securityManager"/>        <!--登录页面  -->        <property name="loginUrl" value="/index.jsp"></property>        <!-- 登录成功后 -->              <property name="successUrl" value="/home.action"></property>        <property name="filterChainDefinitions">            <!-- /**代表下面的多级目录也过滤 -->            <value>                /index.jsp* = anon                /home* = anon                /sysadmin/login/login.jsp* = anon                /sysadmin/login/logout.jsp* = anon                /login* = anon                /logout* = anon                /components/** = anon                /css/** = anon                /images/** = anon                /js/** = anon                /make/** = anon                /skin/** = anon                /stat/** = anon                /ufiles/** = anon                /validator/** = anon                /resource/** = anon                /** = authc                /*.* = authc            </value>        </property>    </bean>    <!-- 用户授权/认证信息Cache, 采用EhCache  缓存 -->    <bean id="shiroEhcacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">        <property name="cacheManagerConfigFile" value="classpath:ehcache-shiro.xml"/>    </bean>    <!-- 保证实现了Shiro内部lifecycle函数的bean执行 -->    <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>    <!-- 生成代理,通过代理进行控制 -->    <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"          depends-on="lifecycleBeanPostProcessor">        <property name="proxyTargetClass" value="true"/>    </bean>    <!-- 安全管理器 -->    <bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">        <property name="securityManager" ref="securityManager"/>    </bean></beans>

在这里配置了核心的SecurityManager,SecurityManager中注入了realm和cacheManager,因此需要配置realm和cacheManager,Realm是自定义的,其中注入了userService,用来进行查询数据库数据的相关操作,还注入了credentialsMatcher属性,这个是开发者自定义的比较器。配置CacheManager时,使用了ehcache,在最后也进行了配置,并且编写了ehcache的配置文件,这些操作在下述步骤进行。

在shiroFilter的配置中配置了要过滤的目录,其中一个*号表示匹配当前目录后的参数,两个*号表示匹配该目录及其子目录及参数。等号后面是Shiro各类过滤器的简称,anon表示匿名过滤器,表示可以匿名访问这些资源,authc表示需要验证用户身份才能访问,还有8种过滤器,在此就不再详述了。

5.Ehcache的配置文件

ehcache-shiro.xml

<?xml version="1.0" encoding="UTF-8"?><ehcache updateCheck="false" name="shiroCache">    <defaultCache        maxElementsInMemory="10000"        eternal="false"        timeToIdleSeconds="120"        timeToLiveSeconds="120"        overflowToDisk="false"        diskPersistent="false"        diskExpiryThreadIntervalSeconds="120"/></ehcache>

6.编写自定义的credentialsMatcher

DemoCredentialsMatcher.java

public class DemoCredentialsMatcher extends SimpleCredentialsMatcher {    /**     * 密码比较的规则     * token:用户在界面输入的用户名和密码     * info: 从数据库中得到的加密数据     */    @Override    public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {        // 获取用户输入的密码,并加密        UsernamePasswordToken upToken = (UsernamePasswordToken) token;        String md5Pwd = Encrypt.md5(new String(upToken.getPassword()), upToken.getUsername());        // 获取数据库中的加密密码        String pwd = (String) info.getCredentials();        // 返回比较结果        return this.equals(md5Pwd, pwd);    }}

Encrypt是一个进行MD5加密的工具类

Encrypt.java

public class Encrypt {    public static String md5(String password, String salt) {        return new Md5Hash(password, salt, 2).toString();    }}

7.编写自定义的realm

AuthRealm.java

public class AuthRealm extends AuthorizingRealm {    private IUserService userService;    public void setUserService(IUserService userService) {        this.userService = userService;    }    /**     * 授权,当jsp页面遇到shiro标签会执行该方法     */    @Override    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection pc) {        System.out.println("授权");        User user = (User) pc.fromRealm(this.getName()).iterator().next();  // 根据realm名字找到对应的realm        List<String> permissions = new ArrayList<String>();        Set<Role> roles = user.getRoles();        for (Role role : roles) {            Set<Module> modules = role.getModules();            for (Module module : modules) {                permissions.add(module.getName());            }        }        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();        info.addStringPermissions(permissions);     // 添加用户的权限        return info;    }    /**     * 认证     */    @Override    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {        System.out.println("认证");        UsernamePasswordToken upToken = (UsernamePasswordToken) token;        List<User> list = userService.find("from User u where u.userName = ?", User.class, new String[]{upToken.getUsername()});        if (list != null && list.size() > 0) {            User user = list.get(0);            AuthenticationInfo info = new SimpleAuthenticationInfo(user, user.getPassword(), this.getName());            return info;        }        return null;    // 返回null抛出异常    }}

8.登录操作

try {    // 1、得到Subject    Subject subject = SecurityUtils.getSubject();    // 2、调用登录方法---AuthRealm#doGetAuthenticationInfo()    subject.login(new UsernamePasswordToken(username, password));    // 3、登录成功    User user = (User) subject.getPrincipal();    // 4、放入session    session.put(SysConstant.CURRENT_USER_INFO, user);} catch (Exception e) {    e.printStackTrace();    request.put("errorInfo", "用户名或密码错误!");    return "login";}return SUCCESS;

在Shiro框架中,如果登陆失败,则会抛出异常,所有在使用Subject的代码外要使用try-catch,当捕获异常,表示用户身份验证失败。