【Java】Shiro整合SSM实战案例

🚀Shiro整合SSM实战案例

📓推荐网站(不断完善中):个人博客

📌个人主页:个人主页

👉相关专栏:Java框架系列专栏

🏝立志赚钱,干活想躺,瞎分享的摸鱼工程师一枚

🏖前言

本文主要讲解了关于Shro权限框架如何整合SSM框架
这在当下虽然不是流行框架,但是针对于初学者刚刚学习入门SSM是非常有参考价值的哦!
希望对大家能有所帮助!

如果你还没有懂什么是Shiro前篇:Shiro的快速入门


1.整合SSM环境

1.1.集成Mybatis

1.1.1.mybatis自动生成工具

pom依赖

<!--引入通用 mapper -->
<dependency>
  <groupId>tk.mybatis</groupId>
  <artifactId>mapper</artifactId>
  <version>4.0.2</version>
</dependency>
<!--spring的jar包-->
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-context</artifactId>
  <version>4.3.17.RELEASE</version>
</dependency>
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-test</artifactId>
  <version>4.3.17.RELEASE</version>
  <scope>test</scope>
</dependency>
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-aspects</artifactId>
  <version>4.3.17.RELEASE</version>
</dependency>
<!--spring jdbc支持包-->
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-jdbc</artifactId>
  <version>5.1.1.RELEASE</version>
</dependency>
<!--spring和mybatis的整合包-->
<dependency>
  <groupId>org.mybatis</groupId>
  <artifactId>mybatis-spring</artifactId>
  <version>1.3.2</version>
</dependency>
<!--mybatis包-->
<dependency>
  <groupId>org.mybatis</groupId>
  <artifactId>mybatis</artifactId>
  <version>3.4.6</version>
</dependency>
<!--数据库连接池和mysql的连接包-->
<dependency>
  <groupId>com.mchange</groupId>
  <artifactId>c3p0</artifactId>
  <version>0.9.5.2</version>
</dependency>
<dependency>
  <groupId>mysql</groupId>
  <artifactId>mysql-connector-java</artifactId>
  <version>5.1.37</version>
  <scope>runtime</scope>
</dependency>
</dependencies>
<!--加载自动生成工具-->
<build>
  <plugins>
    <plugin>
      <groupId>org.mybatis.generator</groupId>
      <artifactId>mybatis-generator-maven-plugin</artifactId>
      <version>1.3.6</version>
      <configuration>
        <configurationFile>${basedir}/src/main/resources/generator/generatorConfig.xml</configurationFile>
        <overwrite>true</overwrite>
        <verbose>true</verbose>
      </configuration>
      <dependencies>
        <dependency>
          <groupId>mysql</groupId>
          <artifactId>mysql-connector-java</artifactId>
          <version>5.1.37</version>
          <scope>runtime</scope>
        </dependency>

        <dependency>
          <groupId>tk.mybatis</groupId>
          <artifactId>mapper</artifactId>
          <version>4.0.2</version>
        </dependency>
      </dependencies>
    </plugin>
  </plugins>
</build>

generatorConfig的配置文件,所在目录要与pom文件中所配置的相同

注意:在windows下系统路径可以写成.\src 在mac下路径要写成./src

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
        PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
        "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration>
    <properties resource="jdbc.properties"/>
    <!--MyBatis3Simple-->
    <context id="mysql" targetRuntime="MyBatis3Simple">

        <!-- 通用mapper的代码生成插件 -->
        <plugin type="tk.mybatis.mapper.generator.MapperPlugin">
            <property name="mappers" value="tk.mybatis.mapper.common.Mapper"/>
        </plugin>


        <!--数据库连接的信息:驱动类、连接地址、用户名、密码 -->
        <jdbcConnection driverClass="com.mysql.jdbc.Driver"
                        connectionURL="${jdbc.url}"
                        userId="${jdbc.username}"
                        password="${jdbc.password}">
        </jdbcConnection>
        <!-- targetProject:生成model类的位置 -->
        <javaModelGenerator targetPackage="cn.sr.sys.model" targetProject="./src/main/java"/>

        <!-- targetProject:mapper映射文件生成的位置 -->
        <sqlMapGenerator targetPackage="mapper" targetProject="./src/main/resources"/>

        <!-- targetPackage:mapper接口生成的位置 -->
        <javaClientGenerator targetPackage="cn.sr.sys.dao" targetProject="./src/main/java" type="XMLMAPPER"/>

        <!-- 指定数据库表-->
        <table tableName="sys_user" domainObjectName="User"/>
        <table tableName="sys_role" domainObjectName="Role"/>
        <table tableName="sys_user_role" domainObjectName="UserRole"/>
        <table tableName="sys_resource" domainObjectName="Resource"/>
        <table tableName="sys_role_resource" domainObjectName="RoleResource"/>
    </context>
</generatorConfiguration>
1.1.2.整合spring配置文件

尤其要注意因为使用了逆向工程所以在生成代理接口的时候需要将mapperScannerConfigurer改为tk对应的类

<?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"
       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">
    <!--扫描注解-->
    <context:component-scan base-package="cn.sr" />
    <!--加载配置文件-->
    <context:property-placeholder location="classpath:jdbc.properties"/>
    <!--数据源配置-->
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="driverClass" value="${jdbc.driver}"/>
        <property name="jdbcUrl" value="${jdbc.url}"/>
        <property name="user" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>

    <!--SqlSessionFactory交给Spring管理-->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <!--将数据源注入-->
        <property name="dataSource" ref="dataSource" />
        <!--引入映射文件的位置-->
        <property name="mapperLocations" value="classpath:mapper/*Mapper.xml" />
    </bean>
    <!--让spring生成mapper接口代理实现类-->
    <bean id="mapperScannerConfigurer" class="tk.mybatis.spring.mapper.MapperScannerConfigurer">
        <!--传入工厂-->
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
        <!--扫描包哪些包下的接口-->
        <property name="basePackage" value="cn.sr.sys.dao"/>
    </bean>
</beans>
1.1.3.关于SQL语句

获取用户登陆角色

SELECT
	DISTINCT t2.`name`
FROM
	sys_user_role t1,
	sys_role t2
WHERE
	t1.role_id = t2.id
AND t2.id = 1

获取登陆用户的角色权限

SELECT
	t1.perms
FROM
	sys_resource t1,
	sys_role_resource t2,
	sys_user_role t3
WHERE
	t1.id = t2.resource_id
AND t2.role_id = t3.role_id
AND t1.perms IS NOT NULL
AND t3.user_id = 1

关于Mapper.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.sr.sys.dao.UserMapper">
  <resultMap id="BaseResultMap" type="cn.sr.sys.model.User">
    <!--
      WARNING - @mbg.generated
    -->
    <id column="id" jdbcType="INTEGER" property="id" />
    <result column="username" jdbcType="VARCHAR" property="username" />
    <result column="password" jdbcType="VARCHAR" property="password" />
    <result column="email" jdbcType="VARCHAR" property="email" />
    <result column="phone" jdbcType="VARCHAR" property="phone" />
    <result column="is_delete" jdbcType="INTEGER" property="isDelete" />
    <result column="create_time" jdbcType="TIMESTAMP" property="createTime" />
    <result column="update_time" jdbcType="TIMESTAMP" property="updateTime" />
  </resultMap>

  <select id="selectUserRoleSet" resultType="java.lang.String" parameterType="java.lang.Integer">
        SELECT
        DISTINCT t2.`name`
    FROM
        sys_user_role t1,
        sys_role t2
    WHERE
        t1.role_id = t2.id
    AND t2.id = #{userId}

   </select>

  <select id="selectUserPermissionSet" resultType="java.lang.String"  parameterType="java.lang.Integer">
          SELECT
          t1.perms
      FROM
          sys_resource t1,
          sys_role_resource t2,
          sys_user_role t3
      WHERE
          t1.id = t2.resource_id
      AND t2.role_id = t3.role_id
      AND t1.perms IS NOT NULL
      AND t3.user_id = #{userId}
  </select>
</mapper>
1.1.4.相关代码

关于Mapper层

package cn.sr.sys.dao;

import cn.sr.sys.model.User;
import tk.mybatis.mapper.common.Mapper;

import java.util.Set;

public interface UserMapper extends Mapper<User> {

    /**
     * 获取登录用户所有的角色名称的集合
     */
    public Set<String> getUserRoleSet(Integer userId);

    /**
     * 获取登录用户所有的权限名称的集合
     */
    public Set<String> getUserPermissionSet(Integer userId);
}

关于Service层

package cn.sr.sys.service;

import cn.sr.sys.model.User;

import java.util.Set;

public interface UserService {
    /**
     * 登陆
     */
    public User login(String username, String password);
    /**
     * 获取登录用户所有的角色名称的集合
     */
    public Set<String> getUserRoleSet(Integer userId);
    /**
     * 获取登录用户所有的权限名称的集合
     * @param userId
     * @return
     */
    public Set<String> getUserPermissionSet(Integer userId);
}

关于Impl层

package cn.sr.sys.service.impl;

import cn.sr.sys.dao.UserMapper;
import cn.sr.sys.model.User;
import cn.sr.sys.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.Set;

@Service
public class UserServiceImpl implements UserService {

    //注入用户的mapper类
    @Autowired
    UserMapper mapper;

    @Override
    public User login(String username, String password) {
        User param = new User();
        param.setUsername(username);
        param.setPassword(password);
        return mapper.selectOne(param);
    }

    @Override
    public Set<String> getUserRoleSet(Integer userId) {
        return null;
    }

    @Override
    public Set<String> getUserPermissionSet(Integer userId) {
        return null;
    }
}

测试代码

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:spring-config.xml")
public class UserServiceImplTest {

    @Autowired(required = false)
    UserService service;

    @Test
    public void login() {
        System.out.println(service.login("admin", "123456"));
    }
}

1.2.集成Spring

1.2.1.相关依赖

整合spring与shiro需要导入相关的shiro与spring的依赖

<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-spring</artifactId>
    <version>1.3.2</version>
</dependency>
1.2.2.相关配置

加密通用工具类

public class MD5Util {
  // 散列次数
  private static int hashIterations = 3;
  /**
     * md5加密工具类
     */
  public static String md5(String source, String salt) {
    return new Md5Hash(source, salt, hashIterations).toString();
  }
}

shiro的spring配置

在有集成spring的情况下我们不需要再额外加在默认的shiro.ini文件,而是应该利用spring来管理shiro的加载

所以我们可以创建shiro-config.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.mgt.DefaultSecurityManager">
        <property name="realm" ref="shiroRealm"/>
        <property name="cacheManager" ref="cacheManager"/>
    </bean>
    <!--自定义的Realm-->
    <bean id="shiroRealm" class="cn.ssm.common.shiro.ShiroRealm" />
    <!--缓存管理器-->
    <bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager" />
</beans>

在spring配置文件中引入shiro配置文件

<?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">
  
  	....忽略spring原本配置部分

    <!--导入shiro配置-->
    <import resource="shiro-config.xml"/>
</beans>

测试代码

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:spring-config.xml")
public class ShiroTest {

  @Autowired
  private SecurityManager securityManager;

  @Test
  public void test(){
    //设置安全管理器
    SecurityUtils.setSecurityManager(securityManager);

    String username = "admin";
    //对密码进行加密
    String password = MD5Util.md5("123456","ak47");

    UsernamePasswordToken token = new UsernamePasswordToken(username, password);
    SecurityUtils.getSubject().login(token);

    Subject subject = SecurityUtils.getSubject();
    System.out.println(subject.hasRole("admin"));
    System.out.println(subject.isPermitted("user:create222"));

    // 退出登录
    subject.logout();
  }
}

1.3.Realm访问数据库

将原本的Realm认证信息从自己写死,改成从数据库中获取

注意:因为用户登录后是全局的信息,所以在任何情况下都可以获取到认证部分登陆的用户信息。

1.3.1.服务逻辑代码

关于获取相关数据库中用户的权限信息

UserService

public interface UserService {
    /**
     * 登陆
     */
    public User login(String username, String password);
    /**
     * 获取登录用户所有的角色名称的集合
     *
     * @param userId
     * @return
     */
    public Set<String> getUserRoleSet(Integer userId);

    /**
     * 获取登录用户所有的权限名称的集合
     *
     * @param userId
     * @return
     */
    public Set<String> getUserPermissionSet(Integer userId);
}

UserServiceImpl

@Service
public class UserServiceImpl implements UserService {

    @Autowired(required = false)
    UserMapper mapper;

    @Override
    public User login(String username, String password) {
        User param = new User();
        param.setUsername(username);
        param.setPassword(password);
        return mapper.selectOne(param);
    }

    /**
     * 获取登录用户所有的角色名称的集合
     *
     * @param userId
     * @return
     */
    @Override
    public Set<String> getUserRoleSet(Integer userId) {
        return mapper.selectUserRoleSet(userId);
    }
    /**
     * 获取登录用户所有的权限名称的集合
     * @param userId
     * @return
     */
    @Override
    public Set<String> getUserPermissionSet(Integer userId) {
        return mapper.selectUserPermissionSet(userId);
    }
}
1.3.2.ShiroRealm核心代码

关于将自定义的Realm内容进行修改

@Component
public class MyRealm extends AuthorizingRealm {

  @Autowired
  UserService service;


  // 认证
  @Override
  protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
    //从令牌中获取到用户输入的用户名
    String usernmae = (String) token.getPrincipal();
    //获取用户输入的密码(因为获取的是二进制需要转换)(保证接收到的是密文)
    String password = new String ((char[]) token.getCredentials());
    //进行数据库查询
    User user = service.login(usernmae, password);
    //进行信息比对
    if (user==null) {
      throw new UnknownAccountException("用户名或者密码错误!");
    }
    if ("0".equals(user.getIsDelete())){
      throw new LockedAccountException("账户被锁定,请联系管理员");
    }
    //如果没有异常则表示认证通过则返回一个简单的认证数据模型
    return new SimpleAuthenticationInfo(user,password, getName());
  }

  // 授权
  @Override
  protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
    //获取一个主体对象
    User user = (User) SecurityUtils.getSubject().getPrincipal();
    //初始化一个简单授权对象
    SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
    //给授权对象设置权限
    //模拟从数据库的角色表中获取角色添加信息
    authorizationInfo.setRoles(service.getUserRoleSet(user.getId()));
    //添加到授权信息中
    authorizationInfo.setStringPermissions(service.getUserPermissionSet(user.getId()));

    return authorizationInfo;
  }


}

测试代码

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:spring-config.xml")
public class ShiroRealmTest {
    //注入安全管理器
    @Autowired
    SecurityManager securityManager;

    @Test
    public void realmDemo(){
        //设置安全管理器
        SecurityUtils.setSecurityManager(securityManager);
        String username="admin";
        // 设置你想要的密码
        String password="123456";
        //令牌
        UsernamePasswordToken token=new UsernamePasswordToken(username, MD5Util.md5(password, "juechuang"));
        //获取主体
        Subject subject = SecurityUtils.getSubject();
        //登陆
        subject.login(token);
        //查看权限
        System.out.println(subject.hasRole("系统管理员"));
        System.out.println(subject.isPermitted("sys:user"));
        //登出
        subject.logout();
    }
}

1.4.集成SpringMVC环境

1.4.1.相关配置文件

pom.xml

修改工程类型为web工程,然后引入springmvc和shiro-web模块的依赖

<!-- https://mvnrepository.com/artifact/commons-fileupload/commons-fileupload -->
<dependency>
  <groupId>commons-fileupload</groupId>
  <artifactId>commons-fileupload</artifactId>
  <version>1.3.1</version>
</dependency>
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-webmvc</artifactId>
  <version>4.3.17.RELEASE</version>
</dependency>

<dependency>
  <groupId>org.apache.shiro</groupId>
  <artifactId>shiro-web</artifactId>
  <version>1.3.2</version>
</dependency>
<dependency>
  <groupId>com.fasterxml.jackson.core</groupId>
  <artifactId>jackson-core</artifactId>
  <version>2.9.5</version>
</dependency>

<dependency>
  <groupId>com.fasterxml.jackson.core</groupId>
  <artifactId>jackson-databind</artifactId>
  <version>2.9.5</version>
</dependency>

<dependency>
  <groupId>com.fasterxml.jackson.core</groupId>
  <artifactId>jackson-annotations</artifactId>
  <version>2.9.5</version>
</dependency>

web.xml

添加shiro过滤器

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
                             http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
         id="WebApp_ID" version="3.1">
    <!--加载spring配置文件-->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:spring-config.xml</param-value>
    </context-param>
    <!-- 配置spring 监听器,加载spring-config.xml配置文件 -->
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    <listener>
        <listener-class>org.springframework.web.context.request.RequestContextListener</listener-class>
    </listener>
    <!-- 统一编码 -->
    <filter>
        <filter-name>characterEncodingFilter</filter-name>
        <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
        <init-param>
            <param-name>encoding</param-name>
            <param-value>UTF-8</param-value>
        </init-param>
        <init-param>
            <!--初始化强制编码-->
            <param-name>forceEncoding</param-name>
            <param-value>true</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>characterEncodingFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
    <!--配置前端拦截器-->
    <servlet>
        <servlet-name>springmvc</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>

        <!--contextConfigLocation:指定springmvc配置的加载位置,
          如果不指定则默认加 载WEB-INF/***-servlet.xml(例如springmvc-servlet.xml)。
        -->
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:springmvc-config.xml</param-value>
        </init-param>
    </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>
        <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>

</web-app>

springmvc-config.xml

可以找到之前相关的springmvc的配置文件

<!-- 注解扫描!!!-->
<context:component-scan base-package="cn.springmvc.controller"/>

<mvc:annotation-driven/>
<!-- 映射静态资源 方案1 -->
<mvc:default-servlet-handler />



<!-- 配置视图解析器 -->
<!-- InternalResourceViewResolver:支持JSP视图解析 -->
<!-- viewClass:JstlView表示JSP模板页面需要使用JSTL标签库,所以classpath中必须包含jstl的相关jar包; -->
<!-- prefix 和suffix:查找视图页面的前缀和后缀,最终视图的址为: -->
<!-- 前缀+逻辑视图名+后缀,逻辑视图名需要在controller中返回ModelAndView指定,比如逻辑视图名为hello,-->
<!-- 则最终返回的jsp视图地址 "WEB-INF/view/hello.jsp" -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
    <property name="prefix" value="/WEB-INF/JSP/"/>
    <property name="suffix" value=".jsp"/>
</bean>

<!--配置文件上传的解析器-->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
    <!--允许上传的文件最大大小  单位是byte-->
    <property name="maxUploadSize" value="60000000"/>
</bean>

shiro-config.xml

需要在shiro的配置文件中添加如下内容

将在非web环境中使用的安全管理器org.apache.shiro.mgt.DefaultSecurityManager

替换为web环境的安全管理org.apache.shiro.web.mgt.DefaultWebSecurityManager

<!--安全管理器-->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
    <property name="realm" ref="shiroRealm"/>
    <property name="cacheManager" ref="cacheManager"/>
</bean>
<!--自定义的Realm-->
<bean id="shiroRealm" class="cn.ssm.common.shiro.ShiroRealm" />
<!--缓存管理器-->
<bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager" />

添加权限的过滤规则

<!-- shiro 的Web过滤器 -->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
    <property name="securityManager" ref="securityManager"/>
    <!--如果没有认证将要跳转的登陆地址 -->
    <property name="loginUrl"	 value="/login"/>
    <!-- 配置安全规则 -->
    <property name="filterChainDefinitions">
        <value>
            <!-- 登录页面不拦截 -->
            /login anon
            <!--静态资源可以直接访问-->
            /css/** anon
            /fonts/** anon
            /js/** anon
            /plugin/** anon
            <!-- user表示身份认证通过或通过记住我认证通过的可以访问 -->
            /** user
        </value>
    </property>
</bean>

如何识别权限规则

​ 我们在web.xml中配置一个全局过滤器,也就是在spring相关配置中是一个spring bean的“shiroFilter“,在这个bean中可以根据访问路径在配置不同的过滤器,其中shiro默认自带一些过滤器。

常见的过滤规则如下:

​ ==我们平时使用就是anno,任何人都可以访问;authc:必须是登录之后才能进行访问,不包括remember me;user:登录用户才可以访问,包含remember me;perms:指定过滤规则,这个一般是扩展使用,不会使用原生的;==其中filterChainDefinitions 就是指定过滤规则的,一般公共配置使用配置文件,例如jss css img这些资源文件是不拦截的,相关业务的url配置到数据库,有过滤器查询数据库进行权限判断。

关于拦截器的优先级

拦截器的运行规则是由上至下,如访问/login,第一个拦截器anon符合,就返回true了,不在往下进行匹配了。

Filter NameClass説明
anonorg.apache.shiro.web.filter.authc.AnonymousFilter任何人都可以访问
authcorg.apache.shiro.web.filter.authc.FormAuthenticationFilter必须是登录之后才能进行访问,不包括remember me
authcBasicorg.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter表示httpBasic认证
logoutorg.apache.shiro.web.filter.authc.LogoutFilter配置对应的url为logout实现登出
permsorg.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter指定过滤规则,这个一般是扩展使用,不会使用原生的
portorg.apache.shiro.web.filter.authz.PortFilter根据端口拦截
restorg.apache.shiro.web.filter.authz.HttpMethodPermissionFilter/admins/user/=rest[user],根据请求的方法,相当于admins/user/=perms[user:method],其中method为post,get,delete等
rolesorg.apache.shiro.web.filter.authz.RolesAuthorizationFilter参数可写多个,多个时必须加上引号,且参数之间用逗号分割,如admins/user/**=roles[“admin,guest”]
sslorg.apache.shiro.web.filter.authz.SslFilter表示安全的URL请求,协议为https
userorg.apache.shiro.web.filter.authc.UserFilter登录用户才可以访问,包含remember me
1.4.2.配合静态资源

导入准备好的相关的静态资源

配合相关的jstl依赖

<dependency>
  <groupId>javax.servlet</groupId>
  <artifactId>jstl</artifactId>
  <version>1.2</version>
</dependency>
<dependency>
  <groupId>javax.servlet.jsp</groupId>
  <artifactId>jsp-api</artifactId>
  <version>2.2</version>
  <scope>provided</scope>
</dependency>

相关的结果返回类

package cn.sr.sys.model;

public class ResultMap {
    private String msg;
    private Integer code;
    private Object data;

    public ResultMap() {
    }

    public ResultMap(String msg, Integer code, Object data) {
        this.msg = msg;
        this.code = code;
        this.data = data;
    }

    public static ResultMap ok(){
        return  new ResultMap("成功",200,null);
    }
    
    public static ResultMap ok(Object data){
        return  new ResultMap("成功",200,data);
    }

    public static ResultMap fail(Object data){
        return  new ResultMap("失败",500,data);
    }

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }

    public Integer getcode() {
        return code;
    }

    public void setcode(Integer code) {
        this.code = code;
    }

    public Object getData() {
        return data;
    }

    public void setData(Object data) {
        this.data = data;
    }
}
1.4.3.相关代码

用户登陆登出控制器

@Controller
public class LoginController {

    /**
     * 登陆跳转页面
     * @return
     */
    @GetMapping("/login")
    public String login(){
        return "login";
    }

    @PostMapping("/login")
    @ResponseBody
    public ResultMap login(String username,String password){
        //获取主体
        Subject subject = SecurityUtils.getSubject();
        //密文
        password= MD5Util.md5(password, "juechuang");
        //令牌验证
        UsernamePasswordToken token=new UsernamePasswordToken(username, password);
        subject.login(token);
        return ResultMap.ok();
    }

    /**
     * 跳转到index页面
     */
    @GetMapping("index")
    public String index(){
        return "index";
    }
    /**
     * 登出
     */
    @GetMapping("/loginout")
    public String loginout(){
        SecurityUtils.getSubject().logout();
        return "redirect:/login";
    }
}

UserController

关于用户的控制器

@Controller
@RequestMapping("/user")
public class UserController {
    //利用通用mapper查询
    @Autowired(required = false)
    UserMapper mapper;

    @RequestMapping("/list")
    public String list(Model model){
        model.addAttribute("list", mapper.selectAll());
        return "user/user_list";
    }
}
1.4.4.权限标签

注意

在使用Shiro标签库前,首先需要在JSP引入shiro标签:

<%@ taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>

常见页面标签

<shiro:authenticated/>	    		登录之后
<shiro:notAuthenticated/>				不在登录状态时(记住我也属于未登录状态)
<shiro:user/>	           				用户在登录之后或RememberMe时
<shiro:guest/>	             		用户在没有登录或RememberMe时
<shiro:hasAnyRoles name="abc,123"/ >	拥有abc或者123角色时
<shiro:hasRole name="abc"/>						拥有角色abc则显示被这个标签所包围的内容
<shiro:lacksRole name="abc"/>					没有角色abc则显示被这个标签所包围的内容	
<shiro:hasPermission name="abc"/>			拥有权限资源abc
<shiro:lacksPermission name="abc"/>		没有abc权限资源
<shiro:principal/>										显示用户身份名称
<shiro:principal property="username"/>   显示用户身份中的属性(默认调用Subject.getPrincipal() 获取)

弊端

如果仅仅只是使用页面标签的shiro控制的话会发现,其实是可以越过这个页面权限的,直接访问后台的数据

比如在没有用户列表权限的情况下,可以直接通过浏览器地址/user/list进行访问后台的用户数据

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kAf4th6F-1667872113271)(img/8.png)]

1.4.5.权限注解

为了解决纯粹页面的权限问题,我们要将资源限制控制到后台的数据处理层级

@RequiresAuthentication:表示当前Subject已经通过login 进行了身份验证;即 Subject. isAuthenticated() 返回 true
@RequiresUser:表示当前 Subject 已经身份验证或者通过记住我登录的。
@RequiresGuest:表示当前Subject没有身份验证或通过记住我登录过,即是游客身份。
@RequiresRoles(value={“admin”, “user”}, logical= Logical.AND):表示当前 Subject 需要角色 admin 和user
@RequiresPermissions (value={“user:a”, “user:b”}, logical= Logical.OR):表示当前 Subject 需要权限 user:a 或user:b

logical属性表示对应关系

配置文件

Shiro 提供了相应的注解用于权限控制,如果使用这些注解就需要使用AOP 的功能来进行判断,如Spring AOP;

Shiro 提供了Spring AOP 集成用于权限注解的解析和验证

在springmvc-config.xml(必须在springmvc的配置文件中)中配置shiro注解支持,可在controller方法中使用shiro注解配置权限

<aop:config />
<!-- 开启shiro注解支持 配置shiro的注解适配器-->
<bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
    <property name="securityManager" ref="securityManager"/>
</bean>

修改对应的UserController

@RequestMapping("/list")
@RequiresRoles({"superadmin"}) //增加访问当前方法必须是拥有相对应的角色
public String list(Model model){
  model.addAttribute("list", mapper.selectAll());
  return "user/user_list";
}

此时如果访问当前页面没有这个对应的角色,那么页面将会报出异常

1.5.关于会话与记住我功能

1.5.1.关于Session

Shiro 提供了完整的企业级会话管理功能,不依赖于底层容器(如web容器tomcat),不管 JavaSE 还是 JavaEE 环境都可以使用,提供了会话管理、会话事件监听、会话存储/持久化、容器无关的集群、失效/过期支持、对Web 的透明支持、SSO 单点登录的支持等特性。

会话相关API

Subject.getSession()即可获取会话;其等价于Subject.getSession(true),即如果当前没有创建 Session 对象会创建一个;Subject.getSession(false),如果当前没有创建 Session 则返回null
session.getId()获取当前会话的唯一标识
session.getHost()获取当前Subject的主机地址
session.getTimeout() & session.setTimeout(毫秒)获取/设置当前Session的过期时间
session.getStartTimestamp() & session.getLastAccessTime()获取会话的启动时间及最后访问时间;如果是 JavaSE 应用需要自己定期调用 session.touch() 去更新最后访问时间;如果是 Web 应用,每次进入 ShiroFilter 都会自动调用 session.touch() 来更新最后访问时间
session.touch() & session.stop()更新会话最后访问时间及销毁会话;当Subject.logout()时会自动调用 stop 方法来销毁会话。如果在web中,调用 HttpSession. invalidate()也会自动调用Shiro Session.stop 方法进行销毁Shiro 的会话。
session.setAttribute(key, val) &session.getAttribute(key) &session.removeAttribute(key)设置/获取/删除会话属性;在整个会话范围内都可以对这些属性进行操作

案例代码

在UserController中添加如下方法,进行测试

@RequestMapping("/session")
@ResponseBody
public String sessionDemo(HttpSession session) {
  session.setAttribute("key", "sessionTest");
  return "session set ok";
}

@RequestMapping("/getSession")
@ResponseBody
public void getSessionDemo() {
  // 利用shiro来获取httpsession中的内容
  System.out.println(SecurityUtils.getSubject().getSession().getAttribute("key").toString());
}

利用原本普通的HttpSession来进行值的存储,同时可以用Shiro中的session接口来进行值的获取,因为Subject是全局的所以可以在任意地方去使用session中的内容,更加方便。

关于会话管理器的一些属性配置

注意在会话管理器配置完成之后要将会话管理器添加到安全管理器的环境中

<!-- 会话管理器 -->
<bean id="sessionManager" class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager">
  <!-- session的失效时长,单位毫秒 -->
  <property name="globalSessionTimeout" value="180000"/>
  <!-- 删除失效的session -->
  <property name="deleteInvalidSessions" value="true"/>       
  <!-- 启用Cookie -->		
  <property name="sessionIdCookieEnabled" value="true"/>
  <!-- 配置Cookie -->
  <property name="sessionIdCookie" ref="sessionIdCookie"/>
  <!-- 设置创建会话工厂 -->
  <property name="sessionFactory" ref="sessionFactory"/>
  <!-- 设置SessionDAO用于会话的CRUD,即DAO(Data Access Object)模式实现 -->
  <property name="sessionDAO" ref="sessionDAO"/>
  <!-- 设置会话监听 -->
  <property name="sessionListeners" ref="sessionListener"/>
  <!-- 设置调度时间间隔,单位毫秒,默认就是1小时 -->
  <property name="sessionValidationInterval" value="3000000"/>
  <!-- 是否开启会话验证器,默认是开启的 -->
  <property name="sessionValidationSchedulerEnabled" value="true"/>
  <!-- 设置会话验证调度器 -->
  <property name="sessionValidationScheduler" ref="sessionValidationScheduler"/>
</bean>
<!-- 添加到安全环境中 -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
  <property name="realm" ref="shiroRealm"/>
  <property name="cacheManager" ref="cacheManager"/>
  <property name="sessionManager" ref="sessionManager"/>
</bean>

1.5.2.Remember Me功能

关于实现原理

1、首先在登录页面选中“记住我”然后登录成功;如果是浏览器登录,一般会把“记住我”的Cookie写到客户端并保存下来。

2、关闭浏览器再重新打开,会发现浏览器还是记住你的。

3、访问一般的网页服务器端还是知道你是谁,且能正常访问。

认证记住我功能二选一

认证 subject.isAuthenticated(): 表示用户进行了身份验证登录的,即使用Subject.login进行了登录

记住我 subject.isRemembered():表示用户是通过“记住我”登录的,此时可能并不是真正的你(如其他人使用你的电脑,或者你的cookie被窃取)在访问的;两者二选一,即subject.isAuthenticated()true,则subject.isRemembered()false;反之一样。

使用场景

访问一般网页:如个人在主页之类的,我们使用user 拦截器即可,user 拦截器只要用户登录(isRemembered() || isAuthenticated())过即可访问成功。

访问特殊网页:如我的订单,提交订单页面,我们使用authc 拦截器即可,authc 拦截器会判断用户是否是通过Subject.login(isAuthenticated()==true)登录的,如果是才放行,否则会跳转到登录页面叫你重新登录

1.5.3.记住我功能实现

页面部分

在页面部分需要有一个关于记住我的标识传递到后台

<div class="form-group">
  <label class="col-md-2 control-label" for="rememberMe"></label>
  <div class="col-md-6">
    <input type="checkbox" name="rememberMe" value="1">记住我
  </div>
</div>

关于登陆的方法

需要修改登录的方法,增加对是否记住我进行判断,如果有携带记住我标识则让对应的Remembered()方法设置为true

@PostMapping("/login")
@ResponseBody
public ResultMap login(String username, String password, String rememberMe){
  //获取主体
  Subject subject = SecurityUtils.getSubject();
  //密文
  password= MD5Util.md5(password, "juechuang");
  //令牌验证
  UsernamePasswordToken token=new UsernamePasswordToken(username, password);
  //判断是否勾选记住我
  if (rememberMe != null) {
    token.setRememberMe(true);
  }

  subject.login(token);
  return ResultMap.ok();
}

配置文件

在shiro相关的配置文件中也要添加和cookie相关的管理配置

<bean id="rememberMeCookie" class="org.apache.shiro.web.servlet.SimpleCookie">
  <!-- cookie的存储时间,为 3600*24*14 -->
  <!--httpOnly 为true, 表示此 cookie 只能在请求时使用, 前面js脚本是不能获取 cookie的信息的, 保证了数据的安全性。-->
  <property name="httpOnly" value="true"></property>
  <property name="maxAge" value="1209600"></property>
  <property name="name" value="rememberMe" ></property>
</bean>
<bean id="rememberMeManager" class="org.apache.shiro.web.mgt.CookieRememberMeManager">
  <property name="cookie" ref="rememberMeCookie"></property>
</bean>

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

🏜写在最后

以上为Shiro权限框架SSM框架的实战案例
以上所有内容只要你用心一步步下来,我相信你能收获到一些内容
希望对你有帮助!任何问题可以关注私信我!

  • 19
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 29
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 29
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

桌子椅子凳子。

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值