【Java】Shiro整合SSM实战案例

Shiro整合SSM实战案例

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

个人主页:个人主页

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

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

【Java】Shiro整合SSM实战案例_第1张图片

前言

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

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

文章目录

  • Shiro整合SSM实战案例
    • 前言
    • 1.整合SSM环境
      • 1.1.集成Mybatis
        • 1.1.1.mybatis自动生成工具
        • 1.1.2.整合spring配置文件
        • 1.1.3.关于SQL语句
        • 1.1.4.相关代码
      • 1.2.集成Spring
        • 1.2.1.相关依赖
        • 1.2.2.相关配置
      • 1.3.Realm访问数据库
        • 1.3.1.服务逻辑代码
        • 1.3.2.ShiroRealm核心代码
      • 1.4.集成SpringMVC环境
        • 1.4.1.相关配置文件
        • 1.4.2.配合静态资源
        • 1.4.3.相关代码
        • 1.4.4.权限标签
        • 1.4.5.权限注解
      • 1.5.关于会话与记住我功能
        • 1.5.1.关于Session
        • 1.5.2.`Remember Me`功能
        • 1.5.3.记住我功能实现
    • 写在最后


1.整合SSM环境

1.1.集成Mybatis

1.1.1.mybatis自动生成工具

pom依赖


<dependency>
  <groupId>tk.mybatisgroupId>
  <artifactId>mapperartifactId>
  <version>4.0.2version>
dependency>

<dependency>
  <groupId>org.springframeworkgroupId>
  <artifactId>spring-contextartifactId>
  <version>4.3.17.RELEASEversion>
dependency>
<dependency>
  <groupId>org.springframeworkgroupId>
  <artifactId>spring-testartifactId>
  <version>4.3.17.RELEASEversion>
  <scope>testscope>
dependency>
<dependency>
  <groupId>org.springframeworkgroupId>
  <artifactId>spring-aspectsartifactId>
  <version>4.3.17.RELEASEversion>
dependency>

<dependency>
  <groupId>org.springframeworkgroupId>
  <artifactId>spring-jdbcartifactId>
  <version>5.1.1.RELEASEversion>
dependency>

<dependency>
  <groupId>org.mybatisgroupId>
  <artifactId>mybatis-springartifactId>
  <version>1.3.2version>
dependency>

<dependency>
  <groupId>org.mybatisgroupId>
  <artifactId>mybatisartifactId>
  <version>3.4.6version>
dependency>

<dependency>
  <groupId>com.mchangegroupId>
  <artifactId>c3p0artifactId>
  <version>0.9.5.2version>
dependency>
<dependency>
  <groupId>mysqlgroupId>
  <artifactId>mysql-connector-javaartifactId>
  <version>5.1.37version>
  <scope>runtimescope>
dependency>
dependencies>

<build>
  <plugins>
    <plugin>
      <groupId>org.mybatis.generatorgroupId>
      <artifactId>mybatis-generator-maven-pluginartifactId>
      <version>1.3.6version>
      <configuration>
        <configurationFile>${basedir}/src/main/resources/generator/generatorConfig.xmlconfigurationFile>
        <overwrite>trueoverwrite>
        <verbose>trueverbose>
      configuration>
      <dependencies>
        <dependency>
          <groupId>mysqlgroupId>
          <artifactId>mysql-connector-javaartifactId>
          <version>5.1.37version>
          <scope>runtimescope>
        dependency>

        <dependency>
          <groupId>tk.mybatisgroupId>
          <artifactId>mapperartifactId>
          <version>4.0.2version>
        dependency>
      dependencies>
    plugin>
  plugins>
build>

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

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


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"/>
    
    <context id="mysql" targetRuntime="MyBatis3Simple">

        
        <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>
        
        <javaModelGenerator targetPackage="cn.sr.sys.model" targetProject="./src/main/java"/>

        
        <sqlMapGenerator targetPackage="mapper" targetProject="./src/main/resources"/>

        
        <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对应的类


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

    
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        
        <property name="dataSource" ref="dataSource" />
        
        <property name="mapperLocations" value="classpath:mapper/*Mapper.xml" />
    bean>
    
    <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


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">
    
    <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.shirogroupId>
    <artifactId>shiro-springartifactId>
    <version>1.3.2version>
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文件


<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>
    
    <bean id="shiroRealm" class="cn.ssm.common.shiro.ShiroRealm" />
    
    <bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager" />
beans>

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


<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原本配置部分

    
    <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模块的依赖


<dependency>
  <groupId>commons-fileuploadgroupId>
  <artifactId>commons-fileuploadartifactId>
  <version>1.3.1version>
dependency>
<dependency>
  <groupId>org.springframeworkgroupId>
  <artifactId>spring-webmvcartifactId>
  <version>4.3.17.RELEASEversion>
dependency>

<dependency>
  <groupId>org.apache.shirogroupId>
  <artifactId>shiro-webartifactId>
  <version>1.3.2version>
dependency>
<dependency>
  <groupId>com.fasterxml.jackson.coregroupId>
  <artifactId>jackson-coreartifactId>
  <version>2.9.5version>
dependency>

<dependency>
  <groupId>com.fasterxml.jackson.coregroupId>
  <artifactId>jackson-databindartifactId>
  <version>2.9.5version>
dependency>

<dependency>
  <groupId>com.fasterxml.jackson.coregroupId>
  <artifactId>jackson-annotationsartifactId>
  <version>2.9.5version>
dependency>

web.xml

添加shiro过滤器


<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">
    
    <context-param>
        <param-name>contextConfigLocationparam-name>
        <param-value>classpath:spring-config.xmlparam-value>
    context-param>
    
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListenerlistener-class>
    listener>
    <listener>
        <listener-class>org.springframework.web.context.request.RequestContextListenerlistener-class>
    listener>
    
    <filter>
        <filter-name>characterEncodingFilterfilter-name>
        <filter-class>org.springframework.web.filter.CharacterEncodingFilterfilter-class>
        <init-param>
            <param-name>encodingparam-name>
            <param-value>UTF-8param-value>
        init-param>
        <init-param>
            
            <param-name>forceEncodingparam-name>
            <param-value>trueparam-value>
        init-param>
    filter>
    <filter-mapping>
        <filter-name>characterEncodingFilterfilter-name>
        <url-pattern>/*url-pattern>
    filter-mapping>
    
    <servlet>
        <servlet-name>springmvcservlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServletservlet-class>

        
        <init-param>
            <param-name>contextConfigLocationparam-name>
            <param-value>classpath:springmvc-config.xmlparam-value>
        init-param>
    servlet>

    <servlet-mapping>
        <servlet-name>springmvcservlet-name>
        <url-pattern>/url-pattern>
    servlet-mapping>
    
    <filter>
        <filter-name>shiroFilterfilter-name>
        <filter-class>org.springframework.web.filter.DelegatingFilterProxyfilter-class>
        <init-param>
            <param-name>targetFilterLifecycleparam-name>
            <param-value>trueparam-value>
        init-param>
    filter>
    <filter-mapping>
        <filter-name>shiroFilterfilter-name>
        <url-pattern>/*url-pattern>
    filter-mapping>

web-app>

springmvc-config.xml

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


<context:component-scan base-package="cn.springmvc.controller"/>

<mvc:annotation-driven/>

<mvc:default-servlet-handler />









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

<bean id="shiroRealm" class="cn.ssm.common.shiro.ShiroRealm" />

<bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager" />

添加权限的过滤规则


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

1.4.2.配合静态资源

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

配合相关的jstl依赖

<dependency>
  <groupId>javax.servletgroupId>
  <artifactId>jstlartifactId>
  <version>1.2version>
dependency>
<dependency>
  <groupId>javax.servlet.jspgroupId>
  <artifactId>jsp-apiartifactId>
  <version>2.2version>
  <scope>providedscope>
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" %>

常见页面标签

	    		登录之后
				不在登录状态时(记住我也属于未登录状态)
	           				用户在登录之后或RememberMe时
	             		用户在没有登录或RememberMe时
	拥有abc或者123角色时
						拥有角色abc则显示被这个标签所包围的内容
					没有角色abc则显示被这个标签所包围的内容	
			拥有权限资源abc
		没有abc权限资源
										显示用户身份名称
   显示用户身份中的属性(默认调用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 />

<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">
  
  <property name="globalSessionTimeout" value="180000"/>
  
  <property name="deleteInvalidSessions" value="true"/>       
  		
  <property name="sessionIdCookieEnabled" value="true"/>
  
  <property name="sessionIdCookie" ref="sessionIdCookie"/>
  
  <property name="sessionFactory" ref="sessionFactory"/>
  
  <property name="sessionDAO" ref="sessionDAO"/>
  
  <property name="sessionListeners" ref="sessionListener"/>
  
  <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.记住我功能实现

页面部分

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

记住我

关于登陆的方法

需要修改登录的方法,增加对是否记住我进行判断,如果有携带记住我标识则让对应的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">
  
  
  <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>


<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框架的实战案例
以上所有内容只要你用心一步步下来,我相信你能收获到一些内容
希望对你有帮助!任何问题可以关注私信我!

【Java】Shiro整合SSM实战案例_第2张图片

你可能感兴趣的:(Java框架系列,java,mybatis,安全,mysql,spring)