SSM整合shiro

5 篇文章 0 订阅

1.完成ssm整合shiro

企业中老项目还在使用ssm框架。

 准备数据库

 数据结构

张三 -user:query user:add user:update user:delete

李四 ---》user:query user:add user:update

王五-----》user:query user:export

搭建ssm的环境

(1)创建一个maven的web工程。

(2)ssm整合到web工程

1.pom依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>org.example</groupId>
  <artifactId>ssm-shiro1029</artifactId>
  <version>1.0-SNAPSHOT</version>
<!--版本和依赖分离-->
  <properties>
    <spring.version>5.2.15.RELEASE</spring.version>
  </properties>

  <dependencies>
    <dependency>
      <groupId>org.apache.shiro</groupId>
      <artifactId>shiro-spring</artifactId>
      <version>1.9.0</version>
    </dependency>
    <!--spring-webmvc-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-webmvc</artifactId>
      <version>${spring.version}</version>
    </dependency>

    <!--mybatis依赖-->
    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis</artifactId>
      <version>3.5.6</version>
    </dependency>

    <!--mybatis和spring整合的依赖-->
    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis-spring</artifactId>
      <version>2.0.6</version>
    </dependency>

    <!--mysql驱动-->
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>8.0.28</version>
    </dependency>

    <!--druid连接池依赖-->
    <dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>druid</artifactId>
      <version>1.2.1</version>
    </dependency>

    <!--lombok依赖-->
    <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
      <version>1.18.24</version>
    </dependency>

    <!--jackson java对象转换为json对象 @ResponseBody-->
    <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-databind</artifactId>
      <version>2.13.2.2</version>
    </dependency>

    <!--servlet-api依赖-->
    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>javax.servlet-api</artifactId>
      <version>4.0.1</version>
    </dependency>

    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-jdbc</artifactId>
      <version>5.2.15.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-tx</artifactId>
      <version>5.2.15.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-aspects</artifactId>
      <version>5.2.15.RELEASE</version>
    </dependency>
    <!--generator-->
    <dependency>
      <groupId>org.mybatis.generator</groupId>
      <artifactId>mybatis-generator-core</artifactId>
      <version>1.4.0</version>
    </dependency>
    <dependency>
      <groupId>com.github.pagehelper</groupId>
      <artifactId>pagehelper</artifactId>
      <version>5.3.0</version>
    </dependency>

    <dependency>
      <groupId>log4j</groupId>
      <artifactId>log4j</artifactId>
      <version>1.2.17</version>
    </dependency>

  </dependencies>

</project>

2.spring配置文件(spring和springmvc配置在一起)

<?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"
       xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd">
    <!-- 包扫描 -->
    <context:component-scan base-package="com.wzh"/>

    <!-- 开启注解 -->
    <mvc:annotation-driven />

    <!-- 静态资源放行 -->
    <mvc:default-servlet-handler/>

    <!--视图解析器-->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="WEB-INF/views/"/>
        <property name="suffix" value=".jsp"/>
    </bean>

    <!-- 启动Shrio的注解 -->
    <bean id="lifecycleBeanPostProcessor"
          class="org.apache.shiro.spring.LifecycleBeanPostProcessor" />
    <bean  class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"
           depends-on="lifecycleBeanPostProcessor" />
    <bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
        <property name="securityManager" ref="securityManager" />
    </bean>
    <!-- 德鲁伊连接池 -->
    <bean id="dataResource" class="com.alibaba.druid.pool.DruidDataSource">
        <!--驱动名称-->
        <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/shiro?serverTimezone=Asia/Shanghai"/>
        <property name="username" value="root"/>
        <property name="password" value="123456"/>
        <!--初始化连接池的个数-->
        <property name="initialSize" value="10"/>
        <!--至少的个数-->
        <property name="minIdle" value="5"/>
        <!--最多的个数-->
        <property name="maxActive" value="10"/>
        <!--最长等待时间单位毫秒-->
        <property name="maxWait" value="2000"/>
    </bean>

    <!--创建事务管理类-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataResource"/>
    </bean>
    <!--开启事务注解-->
    <tx:annotation-driven transaction-manager="transactionManager"/>

    <!-- sqlSessionFactoryBean -->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataResource"/>
        <!-- 配置映射文件 -->
        <property name="mapperLocations" value="classpath:mapper/*.xml"/>
        <!-- 配置分页 -->
        <property name="plugins" >
            <array>
                <bean class="com.github.pagehelper.PageInterceptor"/>
            </array>
        </property>
    </bean>
    <!--dao接口的代理实现类-->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="com.wzh.mapper"/>
    </bean>

    <!--下面是shiro中需要加的-->

    <!--整合shiro的配置内容-->
    <!--1、配置securityManager-->
    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
        <property name="realm" ref="realm"/>
    </bean>
    <!--2、配置自定义的Realm-->
    <bean id="realm" class="com.wzh.realm.MyRealm">
        <property name="credentialsMatcher" ref="credentialsMatcher"/>
    </bean>
    <!--3、创建密码匹配器-->
    <bean id="credentialsMatcher" class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
        <property name="hashAlgorithmName" value="MD5"/>
        <property name="hashIterations" value="1024"/>
    </bean>

    <!--shiro过滤工厂: 设置过滤的规则-->
    <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
        <!-- Shiro的核心安全接口,这个属性是必须的 -->
        <property name="securityManager" ref="securityManager"/>
        <!--设置未登录的跳转路径-->
<!--        <property name="loginUrl" value="/login.jsp"/>-->
        <!--设置没有权限的跳转路径-->
<!--        <property name="unauthorizedUrl" value="/unauthorizedUrl.jsp"/>-->
        <!--设置过滤器-->
        <property name="filterChainDefinitions">
            <value>
                /login=anon
                /**=authc
            </value>
        </property>
        <!--未登录 过滤器-->
        <property name="filters">
            <map>
                <entry key="authc">
                    <!--自定义过滤器所在的路径-->
                  <bean class="com.wzh.filter.LoginFilter"/>
                </entry>
            </map>
        </property>
    </bean>


</beans>

3.web.xml配置文件

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">

    <!--shiro过滤器的代理-->
    <filter>
        <filter-name>shiroFilter</filter-name>
        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>shiroFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

    <!-- 注册servlet -->
    <servlet>
        <servlet-name>DispatcherServlet</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>DispatcherServlet</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>

    <!-- 解决中文乱码 注册文字格式拦截器 -->
    <filter>
        <filter-name>encodingFilter</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>
    </filter>
    <filter-mapping>
        <filter-name>encodingFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>


</web-app>

上面是spring和springmvc配置文件在一起的,有的时候spring配置文件和springmvc文件没有在一起,如下

application.xml---spring配置文件

<?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"
       xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd">
    <!-- 包扫描 -->
    <context:component-scan base-package="com.wzh.service"/>
    <!--导入db.properties-->
    <!--引入属性文件-->
    <context:property-placeholder location="classpath:db.properties"/>
    <!--配置数据源-->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="${jdbc.driver}"/>
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>

    <!--SqlSessionFactoryBean-->
    <bean id="sessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource"/>
        <property name="mapperLocations" value="classpath:mapper/*.xml"/>
    </bean>

    <!--为Dao生成实现类-->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="com.wzh.dao"/>
    </bean>

    <!--事务-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <!--开启事务注解-->
    <tx:annotation-driven transaction-manager="transactionManager"/>
</beans>

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: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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd">
    <!-- 包扫描 -->
    <context:component-scan base-package="com.wzh.controller"/>
    <!--静态资源放行-->
    <mvc:default-servlet-handler/>
    <!--注解驱动-->
    <mvc:annotation-driven/>
    <!--视图解析器-->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="WEB-INF/views/"/>
        <property name="suffix" value=".jsp"/>
    </bean>
</beans>

web.xml:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">

  <!--监听器   加载spring配置文件-->
  <context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:application.xml</param-value>
  </context-param>
  <listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
  </listener>
 

  <!-- 前端控制器 加载springmvc配置文件-->
  <servlet>
    <servlet-name>DispatcherServlet</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>DispatcherServlet</servlet-name>
    <url-pattern>/</url-pattern>
  </servlet-mapping>

  <!-- 解决中文乱码 注册文字格式拦截器 -->
  <filter>
    <filter-name>encodingFilter</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>
  </filter>
  <filter-mapping>
    <filter-name>encodingFilter</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>


</web-app>

4.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="com.wzh.mapper.PermissionMapper">

</mapper>

controller  service dao entity文件夹创建  省略,

5.登录页面和成功页面准备好

 

 

整合shiro

        (1)引入shiro的依赖

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

        (2)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"
       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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd">
    <!-- 包扫描 -->
    <context:component-scan base-package="com.wzh.service"/>
    <!--导入db.properties-->
    <!--引入属性文件-->
    <context:property-placeholder location="classpath:db.properties"/>
    <!--配置数据源-->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="${jdbc.driver}"/>
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>

    <!--SqlSessionFactoryBean-->
    <bean id="sessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource"/>
        <property name="mapperLocations" value="classpath:mapper/*.xml"/>
    </bean>

    <!--为Dao生成实现类-->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="com.wzh.dao"/>
    </bean>

    <!--事务-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <!--开启事务注解-->
    <tx:annotation-driven transaction-manager="transactionManager"/>

    <!--spring整合shiro-->

    <!--SecurityManager的管理-->
    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
        <property name="realm" ref="myRealm"/>
    </bean>
    <!--创建Realm对象-->
    <bean id="myRealm" class="com.wzh.realm.MyRealm">
        <property name="credentialsMatcher" ref="credentialsMatcher"/>
    </bean>
    <!--创建密码加密器-->
    <bean id="credentialsMatcher" class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
        <property name="hashAlgorithmName" value="MD5"/>
        <property name="hashIterations" value="1024"/>
    </bean>
    <!--设置shiro的过滤器链
        id:后面要求必须和web.xml中的配置名称相同
    -->
    <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
        <!--设置安全管理对象-->
        <property name="securityManager" ref="securityManager"/>
        <!--如果没有认证 则跳转到登录页面-->
        <property name="loginUrl" value="/toLogin"/>
        <!--如果没有权限,跳转的路径-->
        <property name="unauthorizedUrl" value="/unauthorizedUrl.jsp"/>
        <!--过滤器链-->
        <!--anon:表示允许匿名访问
            authc:需要认证后才可以访问
        -->
        <property name="filterChainDefinitions">
            <value>
                /index.jsp=anon
                /login=anon
                /**=authc
            </value>
        </property>
    </bean>

</beans>

如果jsp文件都在webapp下,并没有在WEB-INF下的views中设置如下:

 

 <!--整合shiro的配置内容-->
    <!--①SecurityManager-->
    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
          <property name="realm" ref="realm"/>
    </bean>
    <!--创建自定义realm类对象-->
    <bean id="realm" class="com.ykq.realm.MyRealm">
           <property name="credentialsMatcher" ref="credentialsMatcher"/>
    </bean>

    <!--创建密码匹配器-->
    <bean id="credentialsMatcher" class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
          <property name="hashAlgorithmName" value="MD5"/>
          <property name="hashIterations" value="1024"/>
    </bean>

    <!--shiro过滤工厂: 设置过滤的规则-->
    <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
         <!--如果没有登录,跳转的路径-->
         <property name="loginUrl" value="/login.jsp"/>
         <!--没有权限,跳转的路径-->
         <property name="unauthorizedUrl" value="/unauthorized.jsp"/>

         <property name="filterChainDefinitions">
               <value>
                    /login=anon
                    /**=authc
               </value>
         </property>
    </bean>

 shiro中内置很多过滤器,而每个过滤都有相应的别名.

         (3) 修改web.xml文件

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">
  <!--shiro过滤器-->
  <filter>
    <!--该名称必须和ShiroFilterFactoryBean的id相同(也就是spring配置文件中的ShiroFilterFactoryBean)-->
    <filter-name>shiroFilter</filter-name>
    <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
  </filter>
  <filter-mapping>
    <filter-name>shiroFilter</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>
  
  <!--监听器   加载spring配置文件-->
  <context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:application.xml</param-value>
  </context-param>
  <listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
  </listener>


  <!-- 前端控制器 加载springmvc配置文件-->
  <servlet>
    <servlet-name>DispatcherServlet</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>DispatcherServlet</servlet-name>
    <url-pattern>/</url-pattern>
  </servlet-mapping>

  <!-- 解决中文乱码 注册文字格式拦截器 -->
  <filter>
    <filter-name>encodingFilter</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>
  </filter>
  <filter-mapping>
    <filter-name>encodingFilter</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>


</web-app>

 (4)controller

package com.wzh.controller;

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

/**
 * @ProjectName: ssm-shiro-1029
 * @Package: com.wzh.controller
 * @ClassName: PageController
 * @Author: 王振华
 * @Description:
 * @Date: 2022/10/29 19:07
 * @Version: 1.0
 */
@Controller
public class PageController {
    @GetMapping("/toLogin")
    public String toLogin(){
        return "login";//经过视图解析器,找到WEB-INF/views下
    }
    @ResponseBody
    @RequestMapping("login")
    public String login(String username, String password) {
        System.out.println(username);
        //获取subject主体对象
        Subject subject = SecurityUtils.getSubject();
        UsernamePasswordToken token = new UsernamePasswordToken(username, password);
        try {
            subject.login(token);
            return "success";
        } catch (Exception e) {
            e.printStackTrace();
            return "fail";
        }
    }
}

(5) myRealm

package com.wzh.realm;


import com.wzh.pojo.entity.User;
import com.wzh.service.UserService;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;

import java.util.List;

/**
 * @ProjectName: maven-shiro1028
 * @Package: com.wzh.realm
 * @ClassName: MyRealm
 * @Author: 王振华
 * @Description:
 * @Date: 2022/10/28 19:07
 * @Version: 1.0
 */
public class MyRealm extends AuthorizingRealm {

    private UserService userService = new UserService();
    //授权时执行该方法
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {

        return null;
    }

    //认证时执行该方法
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        System.out.println("认证方法=====");
        //1.获取登录者的账号
        String username = authenticationToken.getPrincipal().toString();
        //2.根据账号查询数据库
        User user = userService.findByUsername(username);
        if(user!=null){
            //Object principal,账号数据库中
            // Object credentials,密码数据库中
            //ByteSource credentialsSalt 使用的盐
            // String realmName realm的名称随便起
            //密码的比对交于SimpleAuthenticationInfo
            //未来在数据中一列设置为存放盐
            ByteSource salt = ByteSource.Util.bytes(user.getSalt());
            SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user,user.getUserpwd(),salt,this.getName());
            return info;
        }
        return null;
    }
}

(6)service

package com.wzh.service;

import com.wzh.dao.UserMapper;

import com.wzh.pojo.entity.User;
import com.wzh.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

/**
 * @ProjectName: ssm-shiro
 * @Package: com.wzh.service.impl
 * @ClassName: UserServiceImpl
 * @Author: 王振华
 * @Description:
 * @Date: 2022/8/4 22:20
 * @Version: 1.0
 */
@Service
public class UserService {
    @Autowired
    private UserMapper userMapper;

    public User findByUsername(String username) {
        if(username!=null&&username!="") {
            User user = userMapper.selectByUsername(username);
            return user;
        }
        return null;
    }
    
}

 (7)userMapper.xml,dao层省略

<?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="com.wzh.dao.UserMapper">

    <select id="selectByUsername" resultType="com.wzh.pojo.entity.User">
        select * from user where username = #{username}
    </select>
</mapper>

1.1. 进入主页后,不同的用户可以看到不同的内容。

<%--
  Created by IntelliJ IDEA.
  User: m1762
  Date: 2022/8/4
  Time: 22:58
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
    <shiro:hasPermission name="user:query">
        <a href="/query">查询用户</a>
    </shiro:hasPermission>
    <shiro:hasPermission name="user:add">
        <a href="/add">添加用户</a>
    </shiro:hasPermission>
    <shiro:hasPermission name="user:delete">
        <a href="/delete">删除用户</a>
    </shiro:hasPermission>
    <shiro:hasPermission name="user:update">
        <a href="/update">修改用户</a>
    </shiro:hasPermission>
    <shiro:hasPermission name="user:export">
        <a href="/export">导出用户</a>
    </shiro:hasPermission>
</body>
</html>

 可以在jsp中获取当前登录者的账号

<h1>欢迎<shiro:principal property="username"/>来到主页</h1>

 上面只是在网页中根据不同用户显示不同的菜单,这种方式只能防君子不能防小人。因为现在依旧可以通过postman访问没有的权限方法  比如张三可以访问到user:export路径

解决办法:

  1. 拦截器---获取请求路径 然后根据你的路径判断当前用户是否具有该权限。

  2. spring整合shiro时提供了一个注解:可以加载相应方法上。

使用注解:

1.springmvc.xml中启动shiro的注解

 <!-- 启动Shrio的注解 -->
    <bean id="lifecycleBeanPostProcessor"
          class="org.apache.shiro.spring.LifecycleBeanPostProcessor" />
    <bean  class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"
           depends-on="lifecycleBeanPostProcessor" />
    <bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
        <property name="securityManager" ref="securityManager" />
    </bean>

(2)使用注解

 

这个太丑了,我们想要跳转一个页面,

我们之前学过全局异常处理:    没有登录会报这个异常

 

2.ssm整合shiro完成前后端分离

所谓前后端完全分离:后端响应的都是json数据,而不再是网页。  

我们需要修改的就是:

1. 登录成功或者失败应该返回json数据
2. 当未登录时返回的也是json数据
3. 访问未授权的资源,也要分会json。

2.1.登录成功或者失败应该返回json数据

修改登录接口

2.2 当未登录时返回的也是json数据

(1)创建一个过滤器,继承登录校验的FormAuthenticationFilter接口。

package com.wzh.filter;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.wzh.utils.CommonResult;
import org.apache.shiro.web.filter.authc.FormAuthenticationFilter;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.PrintWriter;

/**
 * @ProjectName: shiro-ssm0805
 * @Package: com.wzh.filter
 * @ClassName: LoginFilter
 * @Author: 王振华
 * @Description:
 * @Date: 2022/8/5 16:58
 * @Version: 1.0
 */
public class LoginFilter extends FormAuthenticationFilter {
    /**
     * 当没有登录时会经过该方法,如果想让它返回json数据必须重写onAccessDenied这个方法
     * @param request
     * @param response
     * @return
     * @throws Exception
     */
    @Override
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
        response.setContentType("application/json;charset=utf-8");
        PrintWriter writer = response.getWriter();
        CommonResult commonResult = CommonResult.UNLOGIN;
        //jackson中内置对象   将java对象转为json对象
        ObjectMapper objectMapper = new ObjectMapper();
        String json = objectMapper.writeValueAsString(commonResult);
        //响应给客户json数据
        writer.print(json);
        writer.flush();
        writer.close();
        return false;
    }
}

之前默认是在springmvc.xml中配置的跳转页面

(2) 注册我们的过滤器

 2.3 如果没有权限应该返回json数据

3.项目完整代码

项目结构:

Controller层:

 UserController:    用于登录    调用login方法判断是否身份认证  授权

package com.wzh.controller;

import com.wzh.utils.CommonResult;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @ProjectName: ssm-shiro
 * @Package: com.wzh.controller
 * @ClassName: UserController
 * @Author: 王振华
 * @Description:
 * @Date: 2022/8/4 22:07
 * @Version: 1.0
 */
@RestController
public class UserController {
    @RequestMapping("login")
    public CommonResult login(String username,String password){
        System.out.println(username);
        //获取subject主体对象
        Subject subject = SecurityUtils.getSubject();
        UsernamePasswordToken token = new UsernamePasswordToken(username, password);
        try{
            subject.login(token);
            System.out.println("是否身份认证:"+subject.isAuthenticated());
            System.out.println("是否授权:"+subject.isPermitted("查询"));
            System.out.println("是否授权:"+subject.isPermitted("添加"));
            System.out.println("是否授权:"+subject.isPermitted("修改"));
            System.out.println("是否授权:"+subject.isPermitted("删除"));
            System.out.println("是否授权:"+subject.isPermitted("导出"));
            System.out.println("是否授权:"+subject.hasRole("超级管理员"));
            System.out.println("是否授权:"+subject.hasRole("管理员"));
            System.out.println("是否授权:"+subject.hasRole("用户"));


            return CommonResult.LOGIN_SUCCESS;
        }catch (Exception e){
            e.printStackTrace();
            return CommonResult.LOGIN_ERROR;
        }
    }
}

PermissionController:      用于登录之后查看是否授权

package com.wzh.controller;

import com.wzh.utils.CommonResult;
import org.apache.shiro.authz.annotation.Logical;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @ProjectName: shiro-ssm0805
 * @Package: com.wzh.controller
 * @ClassName: PermissionController
 * @Author: 王振华
 * @Description:
 * @Date: 2022/8/5 9:57
 * @Version: 1.0
 */
@RestController
public class PermissionController {

    @GetMapping("/query")
    //使用shiro注解
    @RequiresPermissions(value = {"user:query","user:aaa"},logical = Logical.OR)
    public String query(){
        return "query";
    }
    @RequestMapping("/add")
    @RequiresPermissions(value = {"user:add"})
    public String add(){
        return "add";
    }
    @RequestMapping("/delete")
    @RequiresPermissions(value = {"user:delete"})
    public String delete(){
        return "delete";
    }
    @RequestMapping("/update")
    @RequiresPermissions(value = {"user:update"})
    public String update(){
        return "update";
    }
    @RequestMapping("/export")
    @RequiresPermissions(value = {"user:export"})
    public String export(){
        return "export";
    }
}

service层:

UserService:

package com.wzh.service;

import com.wzh.entity.User;

import java.util.List;

/**
 * @ProjectName: ssm-shiro
 * @Package: com.wzh.service
 * @ClassName: UserService
 * @Author: 王振华
 * @Description:
 * @Date: 2022/8/4 22:19
 * @Version: 1.0
 */
public interface UserService {
    User findByUsername(String username);

}

PermissionService:

package com.wzh.service;

import java.util.List;

/**
 * @ProjectName: ssm-shiro
 * @Package: com.wzh.service
 * @ClassName: PermissionService
 * @Author: 王振华
 * @Description:
 * @Date: 2022/8/4 22:27
 * @Version: 1.0
 */
public interface PermissionService {


    List<String> findPermissionById(Integer userid);
}

RoleService:

package com.wzh.service;

import java.util.List;

/**
 * @ProjectName: ssm-shiro
 * @Package: com.wzh.service
 * @ClassName: RoleService
 * @Author: 王振华
 * @Description:
 * @Date: 2022/8/4 22:27
 * @Version: 1.0
 */
public interface RoleService {

    List<String> findRolesById(Integer userid);
}

UserServiceImpl:

package com.wzh.service.impl;

import com.wzh.entity.User;
import com.wzh.mapper.UserMapper;
import com.wzh.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

/**
 * @ProjectName: ssm-shiro
 * @Package: com.wzh.service.impl
 * @ClassName: UserServiceImpl
 * @Author: 王振华
 * @Description:
 * @Date: 2022/8/4 22:20
 * @Version: 1.0
 */
@Service
public class UserServiceImpl implements UserService {
    @Autowired
    private UserMapper userMapper;

    @Override
    public User findByUsername(String username) {
        if(username!=null&&username!="") {
            User user = userMapper.selectByUsername(username);
            return user;
        }
        return null;
    }


}

PermissionServiceImpl:

package com.wzh.service.impl;

import com.wzh.mapper.PermissionMapper;
import com.wzh.service.PermissionService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;

/**
 * @ProjectName: ssm-shiro
 * @Package: com.wzh.service.impl
 * @ClassName: PermissionServiceImpl
 * @Author: 王振华
 * @Description:
 * @Date: 2022/8/4 22:28
 * @Version: 1.0
 */
@Service
public class PermissionServiceImpl implements PermissionService {
    @Autowired
    private PermissionMapper permissionMapper;


    @Override
    public List<String> findPermissionById(Integer userid) {
        List<String> list = permissionMapper.selectByUserId(userid);
        return list;
    }
}

RoleServiceImpl:

package com.wzh.service.impl;

import com.wzh.mapper.RoleMapper;
import com.wzh.service.RoleService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

/**
 * @ProjectName: ssm-shiro
 * @Package: com.wzh.service.impl
 * @ClassName: RoleServiceImpl
 * @Author: 王振华
 * @Description:
 * @Date: 2022/8/4 22:28
 * @Version: 1.0
 */
@Service
public class RoleServiceImpl implements RoleService {
    @Autowired
    private RoleMapper roleMapper;
    @Override
    public List<String> findRolesById(Integer userid) {
        List<String> list = roleMapper.selectByUserId(userid);
        return list;
    }
}

mapper层:

UserMapper:

package com.wzh.mapper;

import com.wzh.entity.User;

import java.util.List;

/**
 * @ProjectName: ssm-shiro
 * @Package: com.wzh.mapper
 * @ClassName: UserMapper
 * @Author: 王振华
 * @Description:
 * @Date: 2022/8/4 22:21
 * @Version: 1.0
 */
public interface UserMapper {
    User selectByUsername(String username);


}

PermissionMapper:

package com.wzh.mapper;

import java.util.List;

/**

@ProjectName: ssm-shiro
@Package: com.wzh.mapper
@ClassName: PermissionMapper
@Author: 王振华
@Description: 
@Date: 2022/8/4 22:21
@Version: 1.0
*/
public interface PermissionMapper {
    List<String> selectByUserId(Integer userid);
}

RoleMapper:

package com.wzh.mapper;

import java.util.List;

/**
 * @ProjectName: ssm-shiro
 * @Package: com.wzh.mapper
 * @ClassName: RoleMapper
 * @Author: 王振华
 * @Description:
 * @Date: 2022/8/4 22:21
 * @Version: 1.0
 */

public interface RoleMapper {
    List<String> selectByUserId(Integer userid);
}

entity层:

User:

package com.wzh.entity;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import javax.management.relation.Role;

/**
 * @ProjectName: ssm-shiro
 * @Package: com.wzh.entity
 * @ClassName: User
 * @Author: 王振华
 * @Description:
 * @Date: 2022/8/4 22:19
 * @Version: 1.0
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
    private Integer userid;

    private String username;

    private String userpwd;

    private String sex;

    private String address;

    private String salt;
    private Permission permission;
    private Role role;
}

Permission:

package com.wzh.entity;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * @ProjectName: ssm-shiro
 * @Package: com.wzh.entity
 * @ClassName: Permission
 * @Author: 王振华
 * @Description:
 * @Date: 2022/8/4 22:22
 * @Version: 1.0
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Permission {
    private Integer perid;

    private String pername;

    private String percode;
}

Role:

package com.wzh.entity;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * @ProjectName: ssm-shiro
 * @Package: com.wzh.entity
 * @ClassName: Role
 * @Author: 王振华
 * @Description:
 * @Date: 2022/8/4 22:22
 * @Version: 1.0
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Role {
    private Integer roleid;

    private String rolename;
}

filter:

LoginFilter:   用于未登录返回json数据

package com.wzh.filter;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.wzh.utils.CommonResult;
import org.apache.shiro.web.filter.authc.FormAuthenticationFilter;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.PrintWriter;

/**
 * @ProjectName: shiro-ssm0805
 * @Package: com.wzh.filter
 * @ClassName: LoginFilter
 * @Author: 王振华
 * @Description:
 * @Date: 2022/8/5 16:58
 * @Version: 1.0
 */
public class LoginFilter extends FormAuthenticationFilter {
    /**
     * 当没有登录时会经过该方法,如果想让它返回json数据必须重写onAccessDenied这个方法
     * @param request
     * @param response
     * @return
     * @throws Exception
     */
    @Override
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
        response.setContentType("application/json;charset=utf-8");
        PrintWriter writer = response.getWriter();
        CommonResult commonResult = CommonResult.UNLOGIN;
        //jackson中内置对象   将java对象转为json对象
        ObjectMapper objectMapper = new ObjectMapper();
        String json = objectMapper.writeValueAsString(commonResult);
        //响应给客户json数据
        writer.print(json);
        writer.flush();
        writer.close();
        return false;
    }
}

handler:  全局异常处理类   用户没有权限返回json数据给前端

MyException:

package com.wzh.handler;

import com.wzh.utils.CommonResult;
import org.apache.shiro.authz.UnauthorizedException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;

/**
 * @ProjectName: shiro-ssm0805
 * @Package: com.wzh.handler
 * @ClassName: MyException
 * @Author: 王振华
 * @Description:
 * @Date: 2022/8/5 16:42
 * @Version: 1.0
 */
@ControllerAdvice   //异常处理类
public class MyException {
    //当发生该异常时触发该方法
    @ExceptionHandler(value = UnauthorizedException.class)
    @ResponseBody
    public CommonResult Unauth(UnauthorizedException e){
        e.printStackTrace();
        return CommonResult.UNAUTHORIZED;
    }
}

realm:   自定义的认证授权规则

MyRealm:

package com.wzh.realm;


import com.wzh.entity.User;
import com.wzh.service.PermissionService;
import com.wzh.service.RoleService;
import com.wzh.service.UserService;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;
import org.springframework.beans.factory.annotation.Autowired;

import java.util.List;

/**
 * @ProjectName: shiro
 * @Package: com.wzh.demo02
 * @ClassName: MyRealm
 * @Author: 王振华
 * @Description:
 * @Date: 2022/8/4 19:44
 * @Version: 1.0
 */
public class MyRealm extends AuthorizingRealm {
    @Autowired
    private UserService userService;

    @Autowired
    private PermissionService permissionService;

    @Autowired
    private RoleService roleService;

    @Override
    //该方法用于完成认证的功能
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        //1.根据token获取账号
        String username = (String) authenticationToken.getPrincipal();
        /**
         * 以前登陆的逻辑是  把用户和密码全部发到数据库  去匹配
         * 在shrio里面是先根据用户名把用户对象查询出来,再来做密码匹配
         */

        //2.根据账号查询用户信息
        User user = userService.findByUsername(username);
        //表示该用户名在数据库中存在
        if(user!=null){
            /**
             * 参数说明
             * 参数1:可以传到任意对象
             * 参数2:从数据库里面查询出来的密码
             * 参数3:盐
             * 参数4:当前类名
             */
            ByteSource credentialsSalt = ByteSource.Util.bytes(user.getSalt());
            SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user, user.getUserpwd(),credentialsSalt,this.getName());
            return info;
        }
        //用户不存在  shiro会抛 UnknowAccountException
        return null;
    }



    //授权
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        User user = (User) principalCollection.getPrimaryPrincipal();
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        //根据账号查找该用户具有哪些权限
        List<String> list = permissionService.findPermissionById(user.getUserid());
        if(list!=null&&list.size()>0){
            info.addStringPermissions(list);
        }
        List<String> roles = roleService.findRolesById(user.getUserid());
        if(roles!=null&&roles.size()>0){
            info.addRoles(roles);
        }
        return info;

    }


}

util:

CommonResult:

package com.wzh.utils;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * @ProjectName: shiro-ssm0805
 * @Package: com.wzh.utils
 * @ClassName: CommonResult
 * @Author: 王振华
 * @Description:
 * @Date: 2022/8/5 11:02
 * @Version: 1.0
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class CommonResult {
    public static final CommonResult UNLOGIN = new CommonResult(403,"未登录",null);
    public static final CommonResult UNAUTHORIZED = new CommonResult(405,"未授权",null);
    public static final CommonResult LOGIN_SUCCESS = new CommonResult(200,"登录成功",null);
    public static final CommonResult LOGIN_ERROR = new CommonResult(-1,"登录失败",null);
    private Integer code;

    private String msg;

    private Object data;

}

  • 4
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值