shiro 验证 授权配置实例

shiro框架的设计以及接口类之间的关系不在这里学习,下面直接看看shiro的验证、授权代码

一、shiro的验证

1、shiro中用到的jar包  pom.xml

<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>com.test</groupId>
  <artifactId>shiro</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <packaging>jar</packaging>

  <name>shiro</name>
  <url>http://maven.apache.org</url>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>

<!-- 项目所依赖的jar包 -->
	<dependencies>
	<!-- spring springmvc -->
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-core</artifactId>
			<version>4.2.0.RELEASE</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-webmvc</artifactId>
			<version>4.2.0.RELEASE</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-tx</artifactId>
			<version>4.2.0.RELEASE</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-jdbc</artifactId>
			<version>4.2.0.RELEASE</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-test</artifactId>
			<version>4.2.0.RELEASE</version>
		</dependency>
		<dependency>
			<groupId>org.aspectj</groupId>
			<artifactId>aspectjweaver</artifactId>
			<version>1.8.6</version>
		</dependency>
		<!-- mybatis -->
		<dependency>
			<groupId>org.mybatis</groupId>
			<artifactId>mybatis</artifactId>
			<version>3.3.0</version>
		</dependency>
		<!-- mybatis 链接spring包 -->
		<dependency>
			<groupId>org.mybatis</groupId>
			<artifactId>mybatis-spring</artifactId>
			<version>1.2.3</version>
		</dependency>
		<!-- mysql 驱动 -->
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<version>5.1.36</version>
		</dependency>
		<!--druid数据库连接池  -->
		<dependency>
			<groupId>com.alibaba</groupId>
			<artifactId>druid</artifactId>
			<version>1.0.15</version>
		</dependency>
		<dependency>
			<groupId>org.codehaus.jackson</groupId>
			<artifactId>jackson-mapper-asl</artifactId>
			<version>1.9.12</version>
		</dependency>
		<!--文件上传  -->
		<dependency>
			<groupId>commons-fileupload</groupId>
			<artifactId>commons-fileupload</artifactId>
			<version>1.3.1</version>
		</dependency>
		<!-- servlet -->
		<dependency>
			<groupId>javax.servlet</groupId>
			<artifactId>javax.servlet-api</artifactId>
			<version>3.1.0</version>
		</dependency>
		<!--jackson-->
		<dependency>
			<groupId>com.fasterxml.jackson.core</groupId>
			<artifactId>jackson-core</artifactId>
			<version>2.6.1</version>
		</dependency>
		<dependency>
			<groupId>com.fasterxml.jackson.core</groupId>
			<artifactId>jackson-databind</artifactId>
			<version>2.6.1</version>
		</dependency>
		<!--json -->
		<dependency>
			<groupId>net.sf.json-lib</groupId>
			<artifactId>json-lib</artifactId>
			<version>2.4</version>
			<classifier>jdk15</classifier>
		</dependency>

		<!--log -->
		<dependency>
			<groupId>org.slf4j</groupId>
			<artifactId>slf4j-log4j12</artifactId>
			<version>1.7.2</version>
		</dependency>
		<!-- jsp c标签引入 -->
		<dependency>
			<groupId>org.apache.taglibs</groupId>
			<artifactId>taglibs-standard-spec</artifactId>
			<version>1.2.1</version>
		</dependency>
		<dependency>
			<groupId>org.apache.taglibs</groupId>
			<artifactId>taglibs-standard-impl</artifactId>
			<version>1.2.1</version>
		</dependency>

		<!--AES MD5加密编码 -->
		<dependency>
			<groupId>commons-codec</groupId>
			<artifactId>commons-codec</artifactId>
			<version>1.4</version>
		</dependency>
		<!-- httpclient -->
		 <dependency>
            <groupId>commons-logging</groupId>
            <artifactId>commons-logging</artifactId>
            <version>1.1.1</version>
        </dependency>
          <dependency>
            <groupId>commons-httpclient</groupId>
            <artifactId>commons-httpclient</artifactId>
            <version>3.0.1</version>
        </dependency>
		
		<!--测试 -->
		<dependency>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId>
			<version>4.12</version>
			<scope>test</scope>
		</dependency>
		
		<!-- dubbo -->
	<dependency>
	  <groupId>com.alibaba</groupId>
	  <artifactId>dubbo</artifactId>
	  <version>2.5.3</version>
	</dependency>
	<!-- zkclient  -->
	<dependency>
	  <groupId>com.github.sgroschupf</groupId>
	  <artifactId>zkclient</artifactId>
	  <version>0.1</version>
	</dependency>
	<!--  zookeeper -->
	<dependency>
	  <groupId>org.apache.zookeeper</groupId>
	  <artifactId>zookeeper</artifactId>
	  <version>3.4.5</version>
	</dependency>
	<!-- shiro begin -->
	<dependency>
	<groupId>org.apache.shiro</groupId>
	<artifactId>shiro-core</artifactId>
	<version>1.2.3</version>
</dependency>
	<dependency>
		<groupId>org.apache.shiro</groupId>
		<artifactId>shiro-web</artifactId>
		<version>1.2.3</version>
	</dependency>
	<dependency>
		<groupId>org.apache.shiro</groupId>
		<artifactId>shiro-ehcache</artifactId>
		<version>1.2.3</version>
	</dependency>
	<dependency>
		<groupId>org.apache.shiro</groupId>
		<artifactId>shiro-spring</artifactId>
		<version>1.2.3</version>
	</dependency>
	<!-- shiro end -->   
	
	<!--md5-->
	<dependency>  
            <groupId>commons-codec</groupId>  
            <artifactId>commons-codec</artifactId>  
            <version>1.4</version>  
        </dependency>  	
	</dependencies>
	<!-- 构建项目所需要的信息 -->
	<build>
	<plugins>
		<plugin>
			<groupId>org.apache.maven.plugins</groupId>
			<artifactId>maven-war-plugin</artifactId>
			<version>2.1.1</version>
			<configuration>
				<!--指定web.xml的路径 -->
				<webXml>WebRoot\WEB-INF\web.xml</webXml>
				<!--指定jsp、js、css的路劲 -->
				<warSourceDirectory>WebRoot</warSourceDirectory>
			</configuration>
		</plugin>
		<plugin>
			<groupId>org.apache.maven.plugins</groupId>
			<artifactId>maven-compiler-plugin</artifactId>
			<configuration>
				<source>1.7</source>
				<target>1.7</target>
			</configuration>
		</plugin>
	</plugins>
</build>
</project>

2、使用shiro过滤器时的web.xml 配置文件

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" id="WebApp_ID" version="3.0">
  <display-name>shiro</display-name>
  <listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
  </listener>
  <!-- log4j -->
	<listener>
		<listener-class>org.springframework.web.util.Log4jConfigListener</listener-class>
	</listener>
  <context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>  
         classpath:config/springmvc-servlet.xml,  
         classpath:config/ApplicationContext.xml  
       </param-value>
  </context-param>
  <!-- log4j -->
	<context-param>
		<param-name>log4jConfigLocation</param-name>
		<param-value>
		 classpath:log4j.properties
		 </param-value>
	</context-param>
  
  <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>
    <init-param>
      <param-name>forceEncoding</param-name>
      <param-value>true</param-value>
    </init-param>
  </filter>
  <filter-mapping>
    <filter-name>encodingFilter</filter-name>
    <url-pattern>*.do</url-pattern>
  </filter-mapping>
  
  <!-- shiro过滤器 -->
  <filter>    
        <filter-name>shiroFilter</filter-name>    
        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>    
        <init-param>    
            <!-- 该值缺省为false,表示生命周期由SpringApplicationContext管理,设置为true则表示由ServletContainer管理   -->  
            <param-name>targetFilterLifecycle</param-name>    
            <param-value>true</param-value>    
        </init-param>    
    </filter>    
    <filter-mapping>    
        <filter-name>shiroFilter</filter-name>    
        <url-pattern>*.do</url-pattern>    
    </filter-mapping>  
    
  <servlet>
    <servlet-name>springmvc</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
      <param-name>contextConfigLocation</param-name>
      <param-value>classpath:config/springmvc-servlet.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
  </servlet>
  <servlet-mapping>
    <servlet-name>springmvc</servlet-name>
    <url-pattern>*.do</url-pattern>
  </servlet-mapping>
 
  <welcome-file-list>
    <welcome-file>index.html</welcome-file>
    <welcome-file>index.htm</welcome-file>
    <welcome-file>index.jsp</welcome-file>
    <welcome-file>default.html</welcome-file>
  </welcome-file-list>
</web-app>

3、spring 配置文件中添加shiro相关的bean

<?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:tx="http://www.springframework.org/schema/tx"
	xmlns:context="http://www.springframework.org/schema/context"
	xmlns:aop="http://www.springframework.org/schema/aop"
	xmlns:task="http://www.springframework.org/schema/task"
	xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
	xsi:schemaLocation="http://www.springframework.org/schema/beans 
	              http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
				  http://www.springframework.org/schema/context
				  http://www.springframework.org/schema/context/spring-context-3.2.xsd
				  http://www.springframework.org/schema/aop 
                  http://www.springframework.org/schema/aop/spring-aop.xsd		
                  http://www.springframework.org/schema/task
                 http://www.springframework.org/schema/task/spring-task-3.2.xsd
				 http://www.springframework.org/schema/tx 
				 http://www.springframework.org/schema/tx/spring-tx-3.2.xsd
				 http://code.alibabatech.com/schema/dubbo
        http://code.alibabatech.com/schema/dubbo/dubbo.xsd
				 ">
				 
	<!-- shiro begin -->
	<!--自定义拦截器 -->
	<bean id="testFilter" class="com.test.shiro.controller.TestFilter"/>
	<!--重写Realm,验证、授权调用的就是这里的两个方法-->
	<bean id="systemAuthorizingRealm" class="com.test.shiro.controller.SystemAuthorizingRealm">   
        <!-- 密码加密验证方式,这里是md5 -->  
        <property name="credentialsMatcher">    
            <bean class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">    
                <property name="hashAlgorithmName" value="MD5" />  
            </bean>    
        </property>    
    </bean>  
    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">    
        <property name="realm" ref="systemAuthorizingRealm"/>    
    </bean>    
    
    <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">    
        <!-- Shiro的核心安全接口,这个属性是必须的 -->    
        <property name="securityManager" ref="securityManager"/>    
        <!-- 要求登录时的链接(可根据项目的URL进行替换),非必须的属性,默认会自动寻找Web工程根目录下的"/login.jsp"页面 -->    
        <property name="loginUrl" value="/user/login.do"/>    
        <!-- 登录成功后要跳转的连接 -->    
        <property name="successUrl" value="/user/success.do"/>  
        <!-- 用户访问未对其授权的资源时,所显示的连接 -->    
        <!-- 若想更明显的测试此属性可以修改它的值,如unauthor.jsp,然后用[玄玉]登录后访问/admin/listUser.jsp就看见浏览器会显示unauthor.jsp -->    
        <property name="unauthorizedUrl" value="/user/error.do"/>    
          
        <!-- Shiro权限过滤过滤器定义 ,这里是过滤器链-->  
        <property name="filterChainDefinitions">  
            <ref bean="shiroFilterChainDefinitions"/>  
        </property>
        <!--过滤器 -->   
          <property name="filters">  
           <map>    
               <entry key="testfilter" value-ref="testFilter"/>  
           </map> 
        </property>  
    </bean> 
    <!--过滤器链 -->
    <bean name="shiroFilterChainDefinitions" class="java.lang.String">  
        <constructor-arg>  
            <value>  
                /user/login.do=anon  
                /user/info.do=authc
                 /user/info.do= roles[admin] 
                /user/success.do=testfilter
                /dologin=anon  
                /logout=anon 
                /user/info.do=anon 
                /getVerifyCodeImage=anon    
                /admin/channel/** = authc,perms[admin:channel]  
                /admin/content/** = authc,perms[admin:content]  
                /admin/sys/** = authc,perms[admin:sys]  
                /admin/**=authc   
            </value>  
        </constructor-arg>  
    </bean>  
     <!-- 保证实现了Shiro内部lifecycle函数的bean执行 -->    
    <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>    
    <!-- AOP式方法级权限检查,开启Shiro的注解(如@RequiresRoles,@RequiresPermissions),需借助SpringAOP扫描使用Shiro注解的类,并在必要时进行安全逻辑验证 -->    
    <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>  
    <!-- shiro end -->

</beans>


我们在过滤器链中对某些路径的访问设置了登录验证,所以在登录认证没有通过的话,是不会让你访问的。

这里我们对/user/info.do路劲的请求要进行登录验证,而success的访问要通过自定义的过滤器……

4、controller层的校验

package com.test.shiro.controller;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.ExcessiveAttemptsException;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
@RequestMapping(value = "/user")
public class UserController {
	
@RequestMapping(value="/login.do")
public String login(HttpServletRequest request){
	return "login";
}
@RequestMapping(value="/success.do")
public String success(HttpServletRequest request){
	return "successInfo";
}
@RequestMapping(value="/error.do")
public String error(HttpServletRequest request){
	return "error";
}
@RequestMapping(value = "/info.do")
public String info(HttpServletRequest request, HttpServletResponse response) {
		String username = request.getParameter("name");  
        String pwd = request.getParameter("password");  
        UsernamePasswordToken token = new UsernamePasswordToken(username, pwd);   
        Subject currentUser = SecurityUtils.getSubject();
        String  error="";
        try{
        	//验证其实这里调用了自己实现的realm的doGetAuthenticationInfo方法即SystemAuthorizingRealm中的doGetAuthenticationInfo方法
        currentUser.login(token);  
        }catch(UnknownAccountException e){
        	error="用户名密码错误";
        } catch (IncorrectCredentialsException e) {  
            error = "用户名/密码错误";  
        } catch (ExcessiveAttemptsException e) {  
            // TODO: handle exception  
            error = "登录失败多次,账户锁定10分钟";  
        } catch (AuthenticationException e) {  
            // 其他错误,比如锁定,如果想单独处理请单独catch处理  
            error = "其他错误:" + e.getMessage();  
        }  
        System.out.println("验证结束");
        //判断验证是否成功
       boolean authenticated=currentUser.isAuthenticated();
        if(authenticated){
        	return  "success";
        }else{
        	  return "login";  
        }
	}

@RequestMapping(value="/loginout.do")
public String   loginout(){
	  Subject currentUser = SecurityUtils.getSubject();
	  //其实也就是session的删除
	  currentUser.logout();
	  System.out.println("退出");
	  return "login";
}
}

5、重写的验证授权方法

package com.test.shiro.controller;

import org.apache.shiro.SecurityUtils;
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.authc.UsernamePasswordToken;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.session.InvalidSessionException;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.subject.Subject;

import com.test.shiro.bean.User;
import com.test.shiro.utils.MD5;

public class SystemAuthorizingRealm  extends AuthorizingRealm{
private  static final String myname="whd";
private static final  String password="123456";
	//授权回调方法
	@Override
	protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
		//两种方式获取当前用信息
		 String currentUsername = (String)super.getAvailablePrincipal(principals); 
		 String username = (String) principals.getPrimaryPrincipal();
		 SimpleAuthorizationInfo simpleAuthorInfo = new SimpleAuthorizationInfo(); 
		 //按照获取的用户名,从数据中获取用户角色,添加用户角色
		 String role="admin";
		 simpleAuthorInfo.addRole(role);
		 //按照userid从数据库中获取权限
		 String permission="info.do";
		 simpleAuthorInfo.addStringPermission(permission);
		 //将角色 权限赋值在返回
		return simpleAuthorInfo;
	}
   //验证回调方法
	@Override
	protected AuthenticationInfo doGetAuthenticationInfo(
			AuthenticationToken authcToken) throws AuthenticationException {
		// TODO Auto-generated method stub
		 //获取基于用户名和密码的令牌  
        //实际上这个authcToken是从LoginController里面currentUser.login(token)传过来的  
        UsernamePasswordToken token = (UsernamePasswordToken)authcToken; 
        //判断验证码的
        //按照token的用户名从数据库中获取对象
        //MemberModel member = memberService.getMemberByName(token.getUsername());
        User user = new User();
        String name= token.getUsername();
        //这里的密码是加密过的
        char[] password=token.getPassword();
        String npass=String.valueOf(password);
        //注意如果在配置文件中配置了shiro对密码的加密方式那么在这里要用对应的方式加密,比如我在配置文件中使用了md5所以我在这里也使用了md5
        npass=MD5.afterMd5(npass);
        user.setName(name);
        if(user != null){  
        	//这些判断应该是数据库获取的,而这里为了简便直接写死了……
            if(user.getName()==null||!myname.equals(user.getName())){  
                throw new AuthenticationException("msg:该已帐号禁止登录.");  
            }  
            AuthenticationInfo authcInfo = new SimpleAuthenticationInfo(user.getName(),npass, this.getName());  
            //这里没有使用server的session而是使用shiro的session
            this.setSession("currentUser", user.getName());  
            return authcInfo;  
        }  
		return null;
	}
	 //session 中保存数据
	 private void setSession(Object key, Object value){  
	        Session session = getSession();  
	        System.out.println("Session默认超时时间为[" + session.getTimeout() + "]毫秒");  
	        if(null != session){  
	            session.setAttribute(key, value);  
	        }  
	    }  
	//获取session
	    private Session getSession(){  
	        try{  
	            Subject subject = SecurityUtils.getSubject();  
	            Session session = subject.getSession(false);  
	            if (session == null){  
	                session = subject.getSession();  
	            }  
	            if (session != null){  
	                return session;  
	            }  
	        }catch (InvalidSessionException e){  
	              
	        }  
	        return null;  
	    }
}

6、重写的过滤器

package com.test.shiro.controller;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

import org.apache.commons.lang.StringUtils;
import org.apache.shiro.web.filter.authc.AuthenticationFilter;

import com.test.shiro.bean.User;
public class TestFilter extends AuthenticationFilter {
	/**
	 * 这个方法中你可以对指定的路劲进行相应的判断,如果满足要求返回true不满足要求返回false
	 * 如果返回true就会跳转到你访问的路径
	 * 如果不满足则设置跳转地址返回false会返回到指定地址。
	 */
	@Override
	protected boolean onAccessDenied(ServletRequest request, ServletResponse response)
			throws Exception {
		String name=request.getParameter("name");
		String password=request.getParameter("password");
		if(StringUtils.isNotBlank(name)&&StringUtils.isNotBlank(password)){
			User user= new User();
			user.setName(name);
			user.setPassword(password);
			//跳转到访问的地址。
			return true;
		}else{
			// 设置返回页面
			//如果不设置redirectToLogin而直接返回的话他没有返回到loginUrl地址
			//如果设置了redirectToLogin就会返回到loginUrl那个地址
			//重新赋值给重定向,这个赋值的loginUrl会覆盖配合文件中配置的值。
			this.setLoginUrl("error.do");
			this.redirectToLogin(request, response);
			return false;
		}
	}

}

ok到这里一个简单的shiro验证就好了,简单总结一下:

1、添加shiro 需要的以及shiro-spring的jar包

2、web.xml中配置shiro这个过滤器

3、spring的配置文件中配置shiro相关bean

4、通过上面的配置我们发现我们只需要自己动手写两个类即Relm这个类的实现和自定义过滤器的实现,这个程序中也就是SystemAuthorizingRealm和TestFilter这两个类

5、过滤器链filterChainDefinitions的配置是从上到下的顺序执行的。

6、过滤器,我们可以按照自己的需求定义过滤器来配置在过滤器链中比如testfilter过滤器一样

7、可以按照自己需求定义很多过滤器配置在过滤器链中,过滤器的配置如下:

  <property name="filters">  
           <map>    
               <entry key="testfilter" value-ref="testFilter"/>  
           </map> 
        </property> 
8、loginUrl这个属性在配置文件中有注释,简单说一下就是如果某个路劲是需要验证的而访问的时候验证的没有通过则会跳转到这个页面。

9、successUrl这个属性是在验证通过后会自动跳转到这个页面


二、shiro的授权

shiro的授权也是在Realm的实现类中调用方法进行授权的,而一个文件的访问权限的配置也是在过滤器链中进行配置的。具体的见配置文件

但是授权这个方法不回调,在网上大家也都有同样的问题,对这个问题网上找了一篇文章,这篇文章很类似哦:

1、http://www.javaweb1024.com/java/JavaWebzhongji/2016/06/06/966.html


shiro验证代码下载:点击打开链接

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值