Fork me on GitHub

Shiro

注意:所有文章除特别说明外,转载请注明出处.

概念

Shiro是简洁易用的安全框架包括(Subject,securityManager,Realms)。权限管理包括:用户认证、用户授权。

1.Subject

这里表示用户,表示Shiro可以表示一个实际的用户,也可以是第三方的服务,系统,或者Job等。在用户认证过程中,可以将subject主体理解为用户或者程序,要去访问系统的资源,系统需要对subject进行身份认证。

注意:每个Subject实例都会绑定对应的SecurityManager。

2.SecurityManager

是整个Shiro的核心,典型的Facade模式,Shiro通过SecurityManager来管理内部组件实例,并通过它来提供安全管理的各种服务。是安全管理器,主体进行认证和授权都是通过securityManager进行的。

2.1.authenticator

认证器,主体进行认证最终通过authenticator进行的。

2.2.authorizer

授权器,主体进行授权最终通过authorizer进行的。

2.3.sessionManager

web应用中一般是用web容器对session进行管理,shiro也提供一套session管理的方式。

2.4.SessionDao

通过SessionDao管理session数据,针对个性化的session数据存储需要使用sessionDao。

2.5.cache Manager

缓存管理器,主要对session和授权数据进行缓存,比如将授权数据通过cacheManager进行缓存管理,和ehcache整合对缓存数据进行管理。

2.6.realms

域,领域,相当于数据源,通过realm存取认证、授权相关数据。

2.6.1.Realms

是连接Shiro和应用程序安全数据之间的桥梁。当进行鉴权和授权的时候,Shiro会查询一个或者多个Realms。Shiro提供一些默认的Realm,例如LDAP, JDBC和INI文件中。

2.7.cryptography

密码管理,提供了一套加密/解密的组件,方便开发。比如提供常用的散列、加/解密等功能。MD5散列算法。

详细架构

1.使用用户的登录信息创建令牌

UsernamePasswordToken token = new UsernamePasswordToken(username, password);

注:token理解为用户令牌,登录的过程被抽象为shiro验证令牌是否具有合法身份以及相关权限。

2.执行登录动作

SecurityUtils.setSecurityManager(securityManager); // 注入SecurityManager
Subject subject = SecurityUtils.getSubject(); // 获取Subject单例对象
subject.login(token); // 登陆

注:SecurityManager负责安全认证与授权。SecurityUtils对象本质上类似spring中的applicationContext。

3.判断用户

因为Shiro本身无法知道所持有令牌的合法性,所以Realm需要设计者自行实现。最重要的一种实现方式,数据库查询。

4.AuthenticationInfo代表了用户的角色信息集合,AuthorizationInfo代表了角色的权限信息集合

实现Realm

Shiro认证

1.导jar包

<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-core</artifactId>
    <version>1.2.3</version>
</dependency>
<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-web</artifactId>
    <version>1.2.3</version>
</dependency>
<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-spring</artifactId>
    <version>1.2.3</version>
</dependency>
<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-ehcache</artifactId>
    <version>1.2.3</version>
</dependency>
<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-quartz</artifactId>
    <version>1.2.3</version>
</dependency>

提示:或者直接导入shiro相关的全部jar包。

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

2.认证流程

1.构建SecurityManager环境

    DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();

2.Subject.login() 提交认证 (主体提交认证请求)

    //0.1这里为了简单,使用SimpleAccountRealm创建简单的realm对象
    SimpleAccountRealm simpleAccountRealm = new SimpleAccountRealm();
    //0.2在执行之前给realm添加一个用户
    @Before
    public void addUser(){
        simpleAccountRealm.addAccount("Brian","Brian_3365");
    }

    //1.1设置SecurityUtils的环境
    SecurityUtils.setSecurityManager(defaultSecurityManager);
    //1.2将simpleAccountRealm设置到环境中来

    defaultSecurityManager.setRealm(simpleAccountRealm);
    //2.获取主体
    Subject subject = SecurityUtils.getSubject();

3.SecurityManager.login() 执行认证

    //3.获取登录的认证数据
    UsernamePasswordToken token = new UsernamePasswordToken("Aaron","Aaron_2254");
    //提交认证
    subject.login(token);

4.Authenticator 执行认证

    //4.执行得到 是否已经得到认证
    subject.isAuthenticated();

5.Realm 根据身份获取验证信息

    //这里可以输出查看结果
    System.out.println("是否已经得到认证:"+subject.isAuthenticated());

    //5.在成功登陆之后,然后subject提供了一个登出的方法
    subject.logout();

Shiro 授权

1.构建SecurityManager环境
2.主体授权
3.SecurityManager授权
4.Authorizer授权
5.Realm 获取角色权限数据

    //0.1这里为了简单,使用SimpleAccountRealm创建简单的realm对象
    SimpleAccountRealm simpleAccountRealm = new SimpleAccountRealm();
    //0.2在执行之前给realm添加一个用户并设置用户角色(admin)
    @Before
    public void addUser(){
        simpleAccountRealm.addAccount("Brian","Brian_3365","admin");
    }

    //1.1设置SecurityUtils的环境
    SecurityUtils.setSecurityManager(defaultSecurityManager);
    //1.2将simpleAccountRealm设置到环境中来

    defaultSecurityManager.setRealm(simpleAccountRealm);
    //2.获取主体
    Subject subject = SecurityUtils.getSubject();
    //3.获取登录的认证数据
            UsernamePasswordToken token = new UsernamePasswordToken("Aaron","Aaron_2254");
            //提交认证
            subject.login(token);
    //4.执行得到 是否已经得到认证
            subject.isAuthenticated();

    //这里可以输出查看结果
    System.out.println("是否已经得到认证:"+subject.isAuthenticated());

    //检查用户角色
    subject.checkRole("admin");

    //5.在成功登陆之后,然后subject提供了一个登出的方法
    subject.logout();

Shiro 自定义Realm

1.内置Realm

1.1 IniRealm

验证:

//0.1 创建IniRealm对象
IniRealm iniRealm = new IniRealm("classpath:user.ini");
//0.2 然后在对应的路径下新建user.ini文件 (用户名 = 密码)
[users]
Aaron = Aaron_2235

//1. 构建SecurityManager环境
DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();
defaultSecurityManager.setRealm(iniRealm);

//1.1设置SecurityUtils的环境
SecurityUtils.setSecurityManager(defaultSecurityManager);
//1.2将simpleAccountRealm设置到环境中来
defaultSecurityManager.setRealm(simpleAccountRealm);
//2.获取主体
Subject subject = SecurityUtils.getSubject();
//3.获取登录的认证数据
UsernamePasswordToken token = new UsernamePasswordToken("Aaron","Aaron_2254");
//提交认证
subject.login(token);

//这里可以输出查看结果
System.out.println("是否已经得到认证:"+subject.isAuthenticated());

授权:

//对应路径下创建user.ini文件
[users]
Aaron = Aaron_2235,admin
[roles]
//管理员用户删除用户的权限
admin = user:delete
//然后在 验证 的代码之后由命令检验角色信息
subject.checkRole("admin");
//以及验证是否具备删除用户权限
subject.checkPermission("user:delete");

1.2 JdbcRealm

//在最开始,需要创建一个数据源
DruidDataSource dataSource = new DruidDataSource();
{
    dataSource.setUrl("jdbc:mysql://localhost:3306/test");
    dataSource.setUsername("root");
    dataSource.setPassword("root");
}

//0.1 创建JdbcRealm对象 其有默认的查询语句
JdbcRealm jdbcRealm = new JdbcRealm();

//0.2 jdbcRealm需要访问数据库,所以需要加入依赖

//0.3 创建jdbc的数据源
jdbcRealm.setDataSource(dataSource);
//在使用jdbcRealm权限查询的时候,需要设置权限开关
jdbcRealm.setPermissionLookupEnable(true);

//1. 构建SecurityManager环境
DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();
//1.2 将jdbcRealm设置到环境中来
defaultSecurityManager.setRealm(jdbcRealm);

//1.1设置SecurityUtils的环境
SecurityUtils.setSecurityManager(defaultSecurityManager);

//2.获取主体
Subject subject = SecurityUtils.getSubject();

//3.获取登录的认证数据
UsernamePasswordToken token = new UsernamePasswordToken("Aaron","Aaron_2254");

//4.这里可以输出查看结果
System.out.println("是否已经得到认证:"+subject.isAuthenticated());

2.自定义Realm(安全数据源)

Shiro从Realm域获取安全数据(如用户、角色、权限),所以SecurityManager需要验证身份,那么就需要从Realm中获取相应的用户进行比较来确定用户身份是否合法,同时也需要从Realm中得到用户相应的角色、权限进行验证用户是否能够进行操作。

public class CustomRealm extends AuthorizingRealm{

    //此方法是用来做授权
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        //获取用户
        String username = (String)PrincipalCollection.getPrincipalCollection();

        //从数据库或者缓存中获取角色数据,数据库操作
        Set<String> roles = getRolesByUserName(username);

        Set<String> permissions = getPermissionsByUserName(username);

        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();

        //向其中加入相应的角色和权限
        simpleAuthorizationInfo.setStringPermissions(permissions);
        simpleAuthorizationInfo.setRoles(roles);

        //开始授权操作
        return simpleAuthorizationInfo;
    }

    private Set<String> getRolesByUserName(String username){
        Set<String> sets = new HashSet<>();
        sets.add("admin");
        sets.add("user");
        return sets;
    }

    private Set<String> getPermissionsByUserName(String username){
        Set<String> sets = new HashSet<>();
        sets.add("user:delete");
        sets.add("user:select");
        return sets;
    }

    //此方法是用来做认证
    //AuthenticationToken 主体传过来的认证信息
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {

        //这里从简(数据库)
        Map<String, String> userMap = new HashMap<>();
        {
            //userMap.put("Aaron","Aaron_2254");
            //Shiro加密实验部分
            userMap.put("Aaron","md5加密后的密文");
            //Shiro加密加盐
            userMap.put("Aaron","md5加密加盐后密文");
            super.setName("cusRealm");
        }
        //1.从主体传递的认证信息中获取用户名
        UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
        String username = token.getUsername();

        //2.通过用户名去到数据库中获取凭证
        String password = getPasswordByUserName(username);
        if(password == null ){
            return null;
        }

        //用户存在 返回对象simpleAuthenticationInfo
        SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo("Aaron",password,"cusRealm");

        //这里登录的主要信息:实体类对象,但是该实体类对象一定是根据token的username查询得到的

        //在这里需要将盐设置到simpleAuthenticationInfo
        simpleAuthenticationInfo.setCredentialsSalt(ByteSource.Util.bytes("Aaron"));

        return simpleAuthenticationInfo;
        }

        //2.1 模拟数据库
        private String getPasswordByUserName(String username){
            return userMap.get(username);
        }
    }

    //Shiro 加密设置 将明文密码改成密文密码
    public static void main(String[] args){
        Md5Hash md5Hash = new Md5Hash("Aaron_2254");
        System.out.println(md5Hash.toString());
    }

    //然后在验证信息里面使用加密后的密文
}

测试类:测试上面两个自定义的Realm是否正确。

Shiro在Controller中实现

//这里只截取主要部分
//获取提交主体
Subject subject = SecurityUtils.getSubject();
//根据用户名获取数据库登录信息
UsernamePasswordToken token = new UsernamePasswordToken(uid,"");
try {
    //提交登录信息
    subject.login(token);
} catch (AuthenticationException e) {
    LOG.error(e.getMessage());
    //返回403界面
    response.sendRedirect("/403.html");
}

...

Shiro加密

1.Shiro散列配置

1.1 HashedCredentialsMatcher

//0.1 自定义Realm,这里的自定义customRealm沿用上面的自定义Realm
CustomRealm customRealm = new CustomRealm();

//1. 构建SecurityManager环境
DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();
//1.2 将jdbcRealm设置到环境中来
defaultSecurityManager.setRealm(jdbcRealm);

//构建加密算法
HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();
//设置加密算法的名称
matcher.setHashAlgorithmName("md5");
//设置加密的次数
matcher.setHashIterations(1);

//在自定义的Realm中设置加密的matcher对象
customRealm.setCredentialsMatcher(matcher);


//1.1设置SecurityUtils的环境
SecurityUtils.setSecurityManager(defaultSecurityManager);

//2.获取主体
Subject subject = SecurityUtils.getSubject();

//3.获取登录的认证数据
UsernamePasswordToken token = new UsernamePasswordToken("Aaron","Aaron_2254");

//4.这里可以输出查看结果
System.out.println("是否已经得到认证:"+subject.isAuthenticated());

1.2 自定义Realm中使用散列

//Shiro 加密设置 将明文密码改成密文密码
public static void main(String[] args){
    Md5Hash md5Hash = new Md5Hash("Aaron_2254");
    System.out.println(md5Hash.toString());
}

//然后在验证信息里面使用加密后的密文

1.3 盐的使用

//1.加盐表示在MD5加密的基础上加上随机数
public static void main(String[] args){
    //加密之后再加上随机数
    Md5Hash md5Hash = new Md5Hash("Aaron_2254","Aaron");
    System.out.println(md5Hash.toString());
}

//2.然后将生成的随机数加到原先的Realm认证中
SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo("Aaron",password,"cusRealm");
//在这里需要将盐设置到simpleAuthenticationInfo
simpleAuthenticationInfo.setCredentialsSalt(ByteSource.Util.bytes("Aaron"));

Shiro与SpringMVC集成

Shiro实战

0.pom.xml

在原先pom.xml的基础上加上如下的shiro依赖。

<!--Shiro 相关-->
<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-core</artifactId>
    <version>1.4.0</version>
</dependency>

<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-spring</artifactId>
    <version>1.4.0</version>
</dependency>

<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-web</artifactId>
    <version>1.4.0</version>
</dependency>

1.配置前端过滤器(web.xml)

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns="http://java.sun.com/xml/ns/javaee"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
    id="WebApp_ID" version="3.0">
    <display-name>Shiro_Project</display-name>

    <welcome-file-list>
        <welcome-file>index.jsp</welcome-file>
    </welcome-file-list>


    <!-- 配置spring容器的路径 -->
      <context-param>
              <param-name>contextConfigLocation</param-name>
              <param-value>classpath*:/spring-context-*.xml</param-value>
      </context-param>
      <!-- 对spring开始监听 -->
      <listener>
          <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
      </listener>

    <!-- 配置springmvc.xml文件的位置 -->
     <servlet>
         <servlet-name>SpringMVC</servlet-name>
         <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
         <init-param>
             <param-name>contextConfigLocation</param-name>
             <param-value>classpath:springmvc.xml</param-value>
         </init-param>
         <load-on-startup>1</load-on-startup>
     </servlet>
      <servlet-mapping>
         <servlet-name>SpringMVC</servlet-name>
         <url-pattern>/</url-pattern>
     </servlet-mapping>

    <!-- 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>shiroFilter</param-name>
            <param-value>true</param-value>
        </init-param>
    </filter>

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

    <!-- shiro配置 结束 -->
</web-app>

注意:由于项目通过Spring管理,所以所有的配置原则上都是交给Spring,那么这里的DelegatingFilterProxy的功能就是通知Spring将所有的Filter叫个ShiroFilter管理。

2.在classpath路径下编写spring-context.xml文件

在此文件下需要配置cacheManager(缓存框架,是因为shiro的session是自己实现的)。

<!-- 缓存bean -->
<bean id="cacheManager" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean">
    <property name="configLocation" value="classpath:${ehcache.file}"></property>
</bean>

<!--在pom.xml文件中配置该缓存的依赖-->
<dependency>
  <groupId>net.sf.ehcache</groupId>
  <artifactId>ehcache-core</artifactId>
  <version>2.6.9</version>
</dependency>
   <dependency>
     <groupId>org.apache.shiro</groupId>
     <artifactId>shiro-ehcache</artifactId>
     <version>1.2.3</version>
   </dependency>

3.在classpath路径下编写spring-shiro-web.xml文件 上面文件只是补充

<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:mvc="http://www.springframework.org/schema/mvc"
    xsi:schemaLocation="http://www.springframework.org/schema/beans    
                        http://www.springframework.org/schema/beans/spring-beans-3.1.xsd    
                        http://www.springframework.org/schema/context    
                        http://www.springframework.org/schema/context/spring-context-3.1.xsd    
                        http://www.springframework.org/schema/mvc    
                        http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd">

    <!-- 1.缓存管理器 使用Ehcache实现 -->
    <bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
        <property name="cacheManagerConfigFile" value="classpath:ehcache.xml" />
    </bean>

    <!-- 2.加密算法 -->
    <bean id="credentialsMatcher" class="utils.RetryLimitHashedCredentialsMatcher">
        <constructor-arg ref="cacheManager" />
        <property name="hashAlgorithmName" value="md5" />
        <property name="hashIterations" value="2" />
        <property name="storedCredentialsHexEncoded" value="true" />
    </bean>

    <!-- Shiro的Web过滤器,启动shiro授权注解拦截方式,这里配置shiro拦截器 -->
    <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
        <!--配置SecurityManager对象,此属性非常重要-->
        <property name="securityManager" ref="securityManager" />

        <!--登录时的链接-->
        <property name="loginUrl" value="/" />
        <!-- 登录成功后要跳转的连接(本例中此属性用不到,因为登录成功后的处理逻辑在LoginController里硬编码为main.jsp了) -->  
        <!-- <property name="successUrl" value="/"/> -->  

        <!--用户访问未授权的资源时所显示的链接-->
        <property name="unauthorizedUrl" value="/" />

        <!--过滤器链,同时过滤器链是有顺序的,是从上往下匹配,匹配到之后就返回-->
        <property name="filterChainDefinitions">
            <!--内置的过滤规则-->
            <value>
                /authc/admin = roles[admin]
                <!--此表示需要经过认证之后才能访问相应的路径-->
                /authc/** = authc
                <!--此表示不需要任何认证-->
                /** = anon
            </value>
        </property>
    </bean>

    <!-- 创建SecurityManager对象,从而得到自定义用户数据匹配-->
    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
        <!--将自定义的Realm对象设置到SecurityManager环境中-->
        <property name="realm" ref="userRealm" />
    </bean>

    <!-- 创建自定义的Realm,来作为授权和认证的realm -->
    <bean id="userRealm" class="utils.UserRealm">
        <!--以及自定义比对器,比对结果与form提交是否一致-->
        <property name="credentialsMatcher" ref="credentialsMatcher" />
    </bean>

    <!--自定义比对器的bean注入,这一比对器是自定义实现-->
    <bean class="cn.edu.xidian.B.authurity.CredentialMatcher" id="credentialMatcher"/>

    <!--然后上面过程中的过滤器链可以用程序实现,这样不用每次都去添加一次-->
    <bean class="cn.edu.xidian.B.authurity.FilterChainDefinitionMapBuilder" id="filterChainDefinitionMapBuilder"/>
    <bean id="filterChainDefinitionMap" factory-bean="filterChainDefinitionMapBuilder" factory-method="getMap"/>

    <!-- 配置 Bean 后置处理器: 会自动的调用和 Spring 整合后各个组件的生命周期方法. -->  
    <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor" />
</beans>

注意:filterChainDefinitions过滤器中对于路径的配置是有顺序的,当找到匹配的条目之后容器不会在继续寻找。因此带有通配符的路径要放在后面。三条配置的含义是: /authc/admin需要用户有用admin权限、/authc/用户必须登录才能访问、/其他所有路径任何人都可以访问。

4.在classpath路径下配置springmvc.xml文件 没必要配置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:p="http://www.springframework.org/schema/p"-->
<!--    xmlns:context="http://www.springframework.org/schema/context"-->
<!--    xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"-->
<!--    xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:task="http://www.springframework.org/schema/task"-->
<!--    xsi:schemaLocation="http://www.springframework.org/schema/beans -->
<!--       http://www.springframework.org/schema/beans/spring-beans-3.0.xsd-->
<!--       http://www.springframework.org/schema/context -->
<!--       http://www.springframework.org/schema/context/spring-context-3.0.xsd-->
<!--       http://www.springframework.org/schema/tx -->
<!--       http://www.springframework.org/schema/tx/spring-tx-3.0.xsd-->
<!--       http://www.springframework.org/schema/aop-->
<!--       http://www.springframework.org/schema/aop/spring-aop-3.0.xsd-->
<!--       http://www.springframework.org/schema/mvc  -->
<!--         http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd-->
<!--         http://www.springframework.org/schema/task     -->
<!--       http://www.springframework.org/schema/task/spring-task-3.1.xsd">-->

<!--    <context:annotation-config />-->
    <!-- 使用注解时必须 -->
<!--    <mvc:annotation-driven>-->
<!--        <mvc:argument-resolvers>-->
<!--            <bean class="cn.edu.xidian.see.ext.DataTableArgumentResolver"/>-->
<!--        </mvc:argument-resolvers>-->
<!--    </mvc:annotation-driven>-->

<!--    <aop:config proxy-target-class="true"></aop:config>-->

<!--</beans>-->

5.在classpath路径下配置ehcache-shiro.xml文件

<ehcache updateCheck="false" name="shiroCache">  

    <defaultCache  
            maxElementsInMemory="10000"  
            eternal="false"  
            timeToIdleSeconds="120"  
            timeToLiveSeconds="120"  
            overflowToDisk="false"  
            diskPersistent="false"  
            diskExpiryThreadIntervalSeconds="120"  
            />  
</ehcache>  

6.密码规则的实现 - CredentialMatcher 比对登录信息和数据库信息是否一致

public class CredentialMatcher extends SimpleCredentialsMatcher {
    @Override
    public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
        UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) token;
        String password = new String(usernamePasswordToken.getPassword());
        String dbpassword = (String)info.getCredentials();
        return this.equals(password,dbpassword);
    }
}

7.自定义过滤器链的实现 有了这一方法之后就不用在配置文件重复添加过滤链接,接配置文件的getMap()

package cn.edu.xidian.B.authurity;

public class FilterChainDefinitionMapBuilder {

    @Autowired
    private AuthoriseService authoriseService;

    private LinkedHashMap<String, String> filterChainDefinitionMap = null;

    public LinkedHashMap<String, String> getMap(){
        if (filterChainDefinitionMap == null){
            //一个线程访问完抛出之后,其它线程才能争取访问
            synchronized (this){
                if (filterChainDefinitionMap == null){
                    filterChainDefinitionMap = new LinkedHashMap<>();
                    setBeforeUrlPermissions();
                    List<Permission> permissions = authoriseService.getAllPermission();
                    for (Permission permission : permissions){
                        if (permission.getStatus() == 0){
                            //将访问的链接组装起来:perms["pname"] 将之存放到链接的map集合中
                            filterChainDefinitionMap.put(permission.getPurl(), "perms[\"" + permission.getPname() + "\"");
                        }
                        setAfterUrlPermissions();
                    }
                }
            }
        }
        return filterChainDefinitionMap;
    }

    //在用户登录之前以下这些链接能够在不登录的前提下访问
    private void setBeforeUrlPermissions(){
        filterChainDefinitionMap.put("/local/*","anon");
        filterChainDefinitionMap.put("/403.ftl","anon");
        filterChainDefinitionMap.put("/","anon");
    }

    //这些链接需要在用户登录之后才能访问
    private void setAfterUrlPermissions(){
        filterChainDefinitionMap.put("/*","authc");
    }
}

8.自定义Realm AuthRealm

public class ShiroRealm extends AuthorizingRealm{
    @Autowired
    private UserService userService;  

    @Autowired  
    private RoleService roleService;  

    /**  
     * shiro认证  
     */  
    @Override  
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authcToken) throws AuthenticationException {  

        // 这里首先通过登录信息获取登录用户名
        UsernamePasswordToken token = (UsernamePasswordToken) authcToken;  
        String loginName = token.getUsername();  

        // 判断登录时用户名是否为空或者null
        if (loginName != null && !"".equals(loginName)) {  

            //通过登录名来查询响应的数据库中的用户
            User user = userService.getUserByLoginName(loginName);  
            if (user != null) {  
                // 如果身份认证验证成功,返回一个AuthenticationInfo实现  
                return new SimpleAuthenticationInfo(user.getLoginName(), user.getPassword(), getName());  
            }  
        }  
        return null;  
    }  

    /**  
     * shiro授权  
     */  
    @Override  
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {  
        //PrincipalCollection 是一个身份集合,因为在shiro中同时配置多个realm,所以身份信息有多个,从而提供此集合聚合这些信息

        // 使用Shiro提供的方法获取用户名称  
        String loginName = (String) getAvailablePrincipal(principals); 

        if (loginName != null) {  
            String roleId = userService.getRoleIdByLoginName(loginName);  

            // 获取用户的权限  
            List<String> permTokens = roleService.getPermTokens(roleId);  

            SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();  

            if (roleId != null) {  

                // 加入用户角色
                info.addRole(roleId);   
            }  
            if (permTokens != null) { 

                // 加入用户许可标记
                info.addStringPermissions(permTokens);   
            }  
            return info;  
        }  
        return null;  
    }  
}

10.Controller实现登录逻辑

@Controller  
@RequestMapping("/login")  
public class LoginController {  

    @Autowired  
    private MenuService menuService;  

    @Autowired  
    private UserService userService;  

    @Autowired  
    private CompanyService companyService;  

    @RequestMapping(method = {RequestMethod.GET})  
    public String login(HttpServletRequest request) {  
        System.out.println("欢迎登陆!");  
        return "/login";  
    }  

    @RequestMapping(method = {RequestMethod.POST})  
    public String loginPost(User user, RedirectAttributes redirectAttributes, HttpServletRequest request) {

        Subject currentUser = SecurityUtils.getSubject();  
        UsernamePasswordToken token = new UsernamePasswordToken(user.getLoginName(), user.getPassword(), user.isRememberMe());  
        try {  
            //用户认证  
            currentUser.login(token);  
        } catch (AuthenticationException e) {  
            System.out.println(e);  
            redirectAttributes.addFlashAttribute("message", "用户名或密码错误!");  
            return "redirect:/login";  
        }  
        if (currentUser.isAuthenticated()) {  
            //登录成功,保存用户相关信息  
            sessionHandle(user, request);  
            //跳转成功页面  
            return "redirect:/index";  
        } else {  
            redirectAttributes.addFlashAttribute("message", "用户名或密码错误!");  
            return "redirect:/login";  
        }  
    }  

    private void sessionHandle(User user, HttpServletRequest request) {  
        HttpSession session = request.getSession();  
        User loginUser = userService.getUserByLoginName(user.getLoginName());  
        if(loginUser != null){  
            session.setAttribute("companyId", loginUser.getCompanyId());  
            session.setAttribute("username", loginUser.getNickName());  
            session.setAttribute("userId", loginUser.getId());  
        }  
        //menuService.updateMenuInSession(request);  
    }  
}  

11.本地LocalController实现

package cn.edu.xidian.B.controller;

@Controller
@RequestMapping("/local")
public class LocalController {

    private static Logger LOG = Logger.getLogger(LocalController.class);

    @Autowired
    private StudentService studentService;
    @Autowired
    private TeacherService teacherService;

    @RequestMapping("/")
    public String login() {
        return "local/local";
    }

    @RequestMapping("/dologin")
    public String dologin(HttpServletRequest request, HttpServletResponse response, Model model)
            throws Exception {

        HttpSession session = request.getSession();

        String uid = request.getParameter("uid");
        System.out.println(uid);

        Subject subject = SecurityUtils.getSubject();
        UsernamePasswordToken token = new UsernamePasswordToken(uid,"");
        try {
            subject.login(token);
        } catch (AuthenticationException e) {
            LOG.error(e.getMessage());
//            response.sendRedirect("/403");
            model.addAttribute("message", "登录失败,系统没有您的信息");
            return "local/err";
        }
        if (uid == null) {
            model.addAttribute("message", "uid 为空");
            return "local/err";
        } else {
            session.setAttribute("uid", uid);
            if (studentService.findStudentBySno(uid) != null) {
                response.sendRedirect("/student/main");
                } else if (teacherService.findByStaffno(uid) != null) {
                if(teacherService.findByStaffno(uid).getType() == 2){
                    response.sendRedirect("/teacher/main");
                } else {
                    response.sendRedirect("/testteacher/main");
                }
            } else {
                model.addAttribute("message", "不在表中 uid = " + uid);
                return "local/err";
            }
        }
        return null;
    }
}

12.前台实现 在这里省略

总结:在这一系列实现之后,我们就能通过Shiro拦截认证来拦截用户的登录情况。对,还有数据库的实现,特别需要注意的是权限与用户对应,然后用户的类型与权限对应等。实践之后就会觉得,其实并没有那么简单,还是需要考虑很多情况的。


下面是在前期学习的时候,做的一些记录

通过注解配置授权

这里授权有两种方式,编程方式和注解方式。编程方式,subject.hasRole();

1.在pom.xml中加入相关依赖

//加入aspectjweaver包
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.8.13</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aop</artifactId>
    <version>5.0.2.RELEASE</version>
</dependency>

2.在springmvc.xml中增加配置

<!--开启Shiro注解-->
<aop:config proxy-target-class="true"/>
<bean class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>
<bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
    <!--设置Shiro的securityManager-->
    <property name="securityManager" ref="securityManager"/>
</bean>

3.使用

在Controller中使用注解@RequiresPermissions("admin:delete");测试只有admin拥有delete权限才能访问。
在Controller中使用注解@RequiresRoles("admin");测试只有是admin角色才能访问。

Shiro过滤器

1.Shiro内置过滤器

1.认证

anon
authBasic
authc
user
logout

2.授权

perms
roles
ssl
port

2.自定义ShiroFilter


filterChainDefinitions - 授权规则定义

Shiro通过ShiroFilterFactoryBean类的filterChainDefinitions(授权规则定义)属性转换为一个filterChainDefinitionMap,转换完成后交给ShiroFilterFactoryBean保管。

//拦截数据库url
public class FilterChainDefinitionMapBuilder {
    @Autowired
    private SysAuthoriseService sysAuthoriseService;

    private LinkedHashMap<String,String> filterChainDefinitionMap = null;
    public LinkedHashMap<String,String> getMap() {

        if(filterChainDefinitionMap == null) {
            synchronized (this) {
                if(filterChainDefinitionMap == null) {
                    filterChainDefinitionMap = new LinkedHashMap<>();
                    setBeforeUrlPermissions();
                    List<SysPermission> sysPermissions = sysAuthoriseService.getAllSysPermission();
                    for(SysPermission p : sysPermissions) {
                        if(p.getStatus() == 0)
                            filterChainDefinitionMap.put(p.getPurl(), "perms[\"" + p.getPname() + "\"]");
                    }
                    setAfterUrlPermissions();
                }
            }
        }
        return filterChainDefinitionMap;
    }

    private void setBeforeUrlPermissions() {
        filterChainDefinitionMap.put("/local/*", "anon");
        filterChainDefinitionMap.put("/403.html", "anon");
        filterChainDefinitionMap.put("/","anon");
        filterChainDefinitionMap.put("/student/logout","anon");
        filterChainDefinitionMap.put("/teacher/logout","anon");
    }

    private void setAfterUrlPermissions() {
        //需要用户登录才能访问
        filterChainDefinitionMap.put("/*", "authc");
    }
}

注意:此处使用的拦截器还需要在spring.xml文件中配置。

<bean class="cn.edu.xidian.see.authurity.FilterChainDefinitionMapBuilder" id = "filterChainDefinitionMapBuilder"></bean>
<bean id="filterChainDefinitionMap" factory-bean="filterChainDefinitionMapBuilder" factory-method="getMap"></bean>

Shiro会话(Session)管理

1.SessionManager

//1.首先在pom.xml中加入Redis依赖
<dependency>
   <groupId>redis.clients</groupId>
   <artifactId>jedis</artifactId>
   <version>2.7.3</version>
 </dependency>

//2.然后重写SessionDao
public class RedisSessionDao extends AbstractSessionDAO{
    //在重写这个SessionDao之后由增删改查等方法

}

2.SessionDAO

3.Redis实现Session共享

4.Redis实现Session共享存在的问题


本文标题:Shiro

文章作者:Bangjin-Hu

发布时间:2019年10月15日 - 09:22:26

最后更新:2020年03月30日 - 08:02:19

原始链接:http://bangjinhu.github.io/undefined/Shiro/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。

Bangjin-Hu wechat
欢迎扫码关注微信公众号,订阅我的微信公众号.
坚持原创技术分享,您的支持是我创作的动力.