Spring MVC 安全示例

194 篇文章 3 订阅
19 篇文章 0 订阅

 

Spring MVC 安全示例

 

欢迎使用UserDetailsS​​ervice的Spring安全性示例。在上一篇文章中,我们学习了如何在Web应用程序中使用Spring Security。今天我们将研究如何在Spring MVC项目中集成Spring Security以进行身份​​验证。

 

目录[ 隐藏 ]

 

Spring安全示例

spring安全示例,UserDetailsS​​ervice,spring mvc security,spring UserDetailsS​​ervice

将Spring Security与Spring MVC Framework集成非常简单,因为我们已经有了Spring Beans配置文件。我们所需要的只是创建与弹簧安全认证相关的更改以使其正常工作。今天我们将研究如何使用内存,UserDetailsServiceDAO实现和基于JDBC的身份验证在Spring MVC应用程序中实现身份验证。

首先在Spring Tool Suite中创建一个简单的Spring MVC项目,它将为我们提供基础Spring MVC应用程序来构建我们的Spring安全性示例应用程序。一旦我们完成所有更改,我们的应用程序将如下图所示。

Spring MVC Security,Spring Security Example,UserDetailsS​​ervice

让我们看一下Spring安全性示例项目的每个组件。

 

Spring Security Maven依赖项

我们的最终pom.xml文件如下所示。


<?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/maven-v4_0_0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>com.journaldev.spring</groupId>
	<artifactId>SpringMVCSecurity</artifactId>
	<name>SpringMVCSecurity</name>
	<packaging>war</packaging>
	<version>1.0.0-BUILD-SNAPSHOT</version>
	<properties>
		<java-version>1.6</java-version>
		<org.springframework-version>4.0.2.RELEASE</org.springframework-version>
		<org.aspectj-version>1.7.4</org.aspectj-version>
		<org.slf4j-version>1.7.5</org.slf4j-version>
	</properties>
	<dependencies>
		<!-- Spring -->
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-context</artifactId>
			<version>${org.springframework-version}</version>
			<exclusions>
				<!-- Exclude Commons Logging in favor of SLF4j -->
				<exclusion>
					<groupId>commons-logging</groupId>
					<artifactId>commons-logging</artifactId>
				 </exclusion>
			</exclusions>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-webmvc</artifactId>
			<version>${org.springframework-version}</version>
		</dependency>
		<!-- Spring Security -->
		<dependency>
			<groupId>org.springframework.security</groupId>
			<artifactId>spring-security-config</artifactId>
			<version>3.2.3.RELEASE</version>
		</dependency>
		<dependency>
			<groupId>org.springframework.security</groupId>
			<artifactId>spring-security-web</artifactId>
			<version>3.2.3.RELEASE</version>
		</dependency>
	
		<!-- AspectJ -->
		<dependency>
			<groupId>org.aspectj</groupId>
			<artifactId>aspectjrt</artifactId>
			<version>${org.aspectj-version}</version>
		</dependency>	
		
		<!-- Logging -->
		<dependency>
			<groupId>org.slf4j</groupId>
			<artifactId>slf4j-api</artifactId>
			<version>${org.slf4j-version}</version>
		</dependency>
		<dependency>
			<groupId>org.slf4j</groupId>
			<artifactId>jcl-over-slf4j</artifactId>
			<version>${org.slf4j-version}</version>
			<scope>runtime</scope>
		</dependency>
		<dependency>
			<groupId>org.slf4j</groupId>
			<artifactId>slf4j-log4j12</artifactId>
			<version>${org.slf4j-version}</version>
			<scope>runtime</scope>
		</dependency>
		<dependency>
			<groupId>log4j</groupId>
			<artifactId>log4j</artifactId>
			<version>1.2.15</version>
			<exclusions>
				<exclusion>
					<groupId>javax.mail</groupId>
					<artifactId>mail</artifactId>
				</exclusion>
				<exclusion>
					<groupId>javax.jms</groupId>
					<artifactId>jms</artifactId>
				</exclusion>
				<exclusion>
					<groupId>com.sun.jdmk</groupId>
					<artifactId>jmxtools</artifactId>
				</exclusion>
				<exclusion>
					<groupId>com.sun.jmx</groupId>
					<artifactId>jmxri</artifactId>
				</exclusion>
			</exclusions>
			<scope>runtime</scope>
		</dependency>

		<!-- @Inject -->
		<dependency>
			<groupId>javax.inject</groupId>
			<artifactId>javax.inject</artifactId>
			<version>1</version>
		</dependency>
				
		<!-- Servlet -->
		<dependency>
			<groupId>javax.servlet</groupId>
			<artifactId>servlet-api</artifactId>
			<version>2.5</version>
			<scope>provided</scope>
		</dependency>
		<dependency>
			<groupId>javax.servlet.jsp</groupId>
			<artifactId>jsp-api</artifactId>
			<version>2.1</version>
			<scope>provided</scope>
		</dependency>
		<dependency>
			<groupId>javax.servlet</groupId>
			<artifactId>jstl</artifactId>
			<version>1.2</version>
		</dependency>
	
		<!-- Test -->
		<dependency>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId>
			<version>4.7</version>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-jdbc</artifactId>
			<version>4.0.2.RELEASE</version>
		</dependency>
	</dependencies>
    <build>
        <plugins>
            <plugin>
                <artifactId>maven-eclipse-plugin</artifactId>
                <version>2.9</version>
                <configuration>
                    <additionalProjectnatures>
                        <projectnature>org.springframework.ide.eclipse.core.springnature</projectnature>
                    </additionalProjectnatures>
                    <additionalBuildcommands>
                        <buildcommand>org.springframework.ide.eclipse.core.springbuilder</buildcommand>
                    </additionalBuildcommands>
                    <downloadSources>true</downloadSources>
                    <downloadJavadocs>true</downloadJavadocs>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>2.5.1</version>
                <configuration>
                    <source>1.6</source>
                    <target>1.6</target>
                    <compilerArgument>-Xlint:all</compilerArgument>
                    <showWarnings>true</showWarnings>
                    <showDeprecation>true</showDeprecation>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>exec-maven-plugin</artifactId>
                <version>1.2.1</version>
                <configuration>
                    <mainClass>org.test.int1.Main</mainClass>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

这里已包括spring-security-configspring-security-web为Spring Security依赖。除此之外我们还有spring-jdbc依赖,因为我们也将使用Spring JDBC身份验证。

其余依赖项与Spring MVC,日志记录,AOP等相关。

Spring安全性示例部署描述符

我们的web.xml文件如下所示。


<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">

	<!-- Spring Security Configuration File -->
	<context-param>
		<param-name>contextConfigLocation</param-name>
		<param-value>/WEB-INF/spring/appServlet/spring-security.xml</param-value>
	</context-param>

	<!-- Creates the Spring Container shared by all Servlet and Filters -->
	<listener>
		<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
	</listener>
	<listener>
		<listener-class>org.springframework.security.web.session.HttpSessionEventPublisher</listener-class>
	</listener>
	
	<session-config>
		<session-timeout>15</session-timeout>
	</session-config>

	<!-- Spring Security Filter -->
	<filter>
		<filter-name>springSecurityFilterChain</filter-name>
		<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
	</filter>

	<filter-mapping>
		<filter-name>springSecurityFilterChain</filter-name>
		<url-pattern>/*</url-pattern>
	</filter-mapping>
	
	<!-- Spring MVC - START -->
	<servlet>
		<servlet-name>appServlet</servlet-name>
		<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
		<init-param>
			<param-name>contextConfigLocation</param-name>
			<param-value>
			/WEB-INF/spring/appServlet/servlet-context.xml
			</param-value>
		</init-param>
		<load-on-startup>1</load-on-startup>
	</servlet>

	<servlet-mapping>
		<servlet-name>appServlet</servlet-name>
		<url-pattern>/</url-pattern>
	</servlet-mapping>
	<!-- Spring MVC - END -->

</web-app>

contextConfigLocation是我们提供spring安全bean配置文件名的context参数。它用于ContextLoaderListener在我们的应用程序中配置身份验证。

我们还添加了HttpSessionEventPublisher侦听器,以将会话创建/销毁事件发布到Spring Root WebApplicationContext。

我也设置session-timeout为15分钟,当用户处于非活动状态15分钟时,用于自动超时。

DelegatingFilterProxy 是定义的应用程序过滤器,它用于拦截HTTP请求和执行与身份验证相关的任务。

DispatcherServlet servlet是Spring MVC应用程序的前端控制器。

的UserDetailsS​​ervice

如果我们想使用任何DAO类进行身份验证,我们需要实现UserDetailsService接口。配置DAO后,它将loadUserByUsername()用于验证用户。


package com.journaldev.spring.security.dao;

import java.util.Collection;
import java.util.List;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;

public class AppUserDetailsServiceDAO implements UserDetailsService {

	protected final Log logger = LogFactory.getLog(getClass());
	
	@Override
	public UserDetails loadUserByUsername(final String username)
			throws UsernameNotFoundException {
		
		logger.info("loadUserByUsername username="+username);
		
		if(!username.equals("pankaj")){
			throw new UsernameNotFoundException(username + " not found");
		}
		
		//creating dummy user details, should do JDBC operations
		return new UserDetails() {
			
			private static final long serialVersionUID = 2059202961588104658L;

			@Override
			public boolean isEnabled() {
				return true;
			}
			
			@Override
			public boolean isCredentialsNonExpired() {
				return true;
			}
			
			@Override
			public boolean isAccountNonLocked() {
				return true;
			}
			
			@Override
			public boolean isAccountNonExpired() {
				return true;
			}
			
			@Override
			public String getUsername() {
				return username;
			}
			
			@Override
			public String getPassword() {
				return "pankaj123";
			}
			
			@Override
			public Collection<? extends GrantedAuthority> getAuthorities() {
				List<SimpleGrantedAuthority> auths = new java.util.ArrayList<SimpleGrantedAuthority>();
				auths.add(new SimpleGrantedAuthority("Admin"));
				return auths;
			}
		};
	}

}

请注意,我UserDetails通过使用匿名内部类实现返回实例。理想情况下,我们应该有一个实现类,UserDetails也可以有其他用户数据,如emailID,用户名,地址等。

请注意,唯一可行的组合是用户名为“pankaj”且密码为“pankaj123”。

Spring安全性示例控制器类

这是我们的控制器类,它定义了我们可以访问的两个URI。

 


package com.journaldev.spring.controller;

import java.text.DateFormat;
import java.util.Date;
import java.util.Locale;

import javax.servlet.http.HttpServletRequest;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

@Controller
public class HomeController {
	
	private static final Logger logger = LoggerFactory.getLogger(HomeController.class);
	
	@RequestMapping(value = "/home", method = RequestMethod.GET)
	public String home(Locale locale, Model model) {
		logger.info("Welcome home! The client locale is {}.", locale);
		Date date = new Date();
		DateFormat dateFormat = DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.LONG, locale);
		
		String formattedDate = dateFormat.format(date);
		
		model.addAttribute("serverTime", formattedDate );
		
		return "home";
	}
	
	@RequestMapping(value = "/emp/get/{id}", method = RequestMethod.GET)
	public String getEmployee(Locale locale, Model model,@PathVariable("id") int id) {
		logger.info("Welcome user! Requested Emp ID is: "+id);
		Date date = new Date();
		DateFormat dateFormat = DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.LONG, locale);
		
		String formattedDate = dateFormat.format(date);
		
		model.addAttribute("serverTime", formattedDate );
		model.addAttribute("id", id);
		model.addAttribute("name", "Pankaj");
		
		return "employee";
	}
	
	@RequestMapping(value="/login")
	public String login(HttpServletRequest request, Model model){
		return "login";
	}
	
	@RequestMapping(value="/logout")
	public String logout(){
		return "logout";
	}
	
	@RequestMapping(value="/denied")
	public String denied(){
		return "denied";
	}
}

在我们的示例中,我们仅将身份验证应用于URI“/ emp / get / {id}”。无需任何身份验证即可访问所有其他URI。登录,注销和拒绝URI用于在请求安全URL时发送相应的响应页面。

Spring安全示例Bean配置文件


<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/mvc"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:beans="http://www.springframework.org/schema/beans"
	xmlns:context="http://www.springframework.org/schema/context"
	xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd
		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">

	<!-- DispatcherServlet Context: defines this servlet's request-processing infrastructure -->
	
	<!-- Enables the Spring MVC @Controller programming model -->
	<annotation-driven />

	<!-- Handles HTTP GET requests for /resources/** by efficiently serving up static resources in the ${webappRoot}/resources directory -->
	<resources mapping="/resources/**" location="/resources/" />

	<!-- Resolves views selected for rendering by @Controllers to .jsp resources in the /WEB-INF/views directory -->
	<beans:bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
		<beans:property name="prefix" value="/WEB-INF/views/" />
		<beans:property name="suffix" value=".jsp" />
	</beans:bean>
	
	<context:component-scan base-package="com.journaldev.spring.controller" />
	
</beans:beans>

我们的spring bean配置文件很简单,它只有与Spring MVC应用程序相关的配置。

Spring MVC安全配置

这是我们教程中最重要的部分,让我们来看看我们的文件。我们将逐一了解每个部分。

spring-security.xml


<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/security"
	xmlns:beans="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security.xsd
			http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

	<!-- Configuring RoleVoter bean to use custom access roles, by default roles 
		should be in the form ROLE_{XXX} -->
	<beans:bean id="roleVoter"
		class="org.springframework.security.access.vote.RoleVoter">
		<beans:property name="rolePrefix" value=""></beans:property>
	</beans:bean>

	<beans:bean id="accessDecisionManager"
		class="org.springframework.security.access.vote.AffirmativeBased">
		<beans:constructor-arg name="decisionVoters"
			ref="roleVoter" />
	</beans:bean>

	<http authentication-manager-ref="jdbc-auth"
		access-decision-manager-ref="accessDecisionManager">	
		<intercept-url pattern="/emp/**" access="Admin" />
		<form-login login-page="/login" authentication-failure-url="/denied"
			username-parameter="username" password-parameter="password"
			default-target-url="/home" />
		<logout invalidate-session="true" logout-success-url="/login"
			logout-url="/j_spring_security_logout" />
		<access-denied-handler error-page="/denied"/>
		<session-management invalid-session-url="/login">
			<concurrency-control max-sessions="1"
				expired-url="/login" />
		</session-management>
	</http>

	<authentication-manager id="in-memory-auth">
		<authentication-provider>
			<user-service>
				<user name="pankaj" password="pankaj123" authorities="Admin" />
			</user-service>
		</authentication-provider>
	</authentication-manager>

	<authentication-manager id="dao-auth">
		<authentication-provider user-service-ref="userDetailsService">
		</authentication-provider>
	</authentication-manager>

	<beans:bean id="userDetailsService"
		class="com.journaldev.spring.security.dao.AppUserDetailsServiceDAO" />

	<authentication-manager id="jdbc-auth">
		<authentication-provider>
			<jdbc-user-service data-source-ref="dataSource"
				users-by-username-query="select username,password,enabled from Employees where username = ?"
				authorities-by-username-query="select username,role from Roles where username = ?" />
		</authentication-provider>
	</authentication-manager>

	<!-- MySQL DB DataSource -->
	<beans:bean id="dataSource"
		class="org.springframework.jdbc.datasource.DriverManagerDataSource">

		<beans:property name="driverClassName" value="com.mysql.jdbc.Driver" />
		<beans:property name="url"
			value="jdbc:mysql://localhost:3306/TestDB" />
		<beans:property name="username" value="pankaj" />
		<beans:property name="password" value="pankaj123" />
	</beans:bean>

	<!-- If DataSource is configured in Tomcat Servlet Container -->
	<beans:bean id="dbDataSource"
		class="org.springframework.jndi.JndiObjectFactoryBean">
		<beans:property name="jndiName" value="java:comp/env/jdbc/MyLocalDB" />
	</beans:bean>
</beans:beans>

accessDecisionManagerbean被定义为我们可以拥有自定义角色,默认情况下所有角色都应该以ROLE_开头,并且我们在roleVoterbean属性中覆盖了这个设置rolePrefix

我们可以在spring安全配置中定义多个身份验证管理器。我已经in-memory-auth为内存中的身份验证,dao-authUserDetailsS​​ervice DAO实现和jdbc-authJDBC身份验证定义了。对于JDBC身份验证,我已经为应用程序中定义的DataSource提供了配置,以及我们是否要使用servlet容器中定义的JNDI资源。

http authentication-manager-ref用于定义将用于验证用户的身份验证管理器。目前,它已配置为使用基于JDBC的身份验证。

http access-decision-manager-ref用于指定应该用于授权HTTP请求的AccessDecisionManager实现的ID。

intercept-url用于定义可以访问此页面的用户的URL模式和权限。例如,我们已经定义了URI“/ emp / **”只能由具有“Admin”访问权限的用户访问。

form-login定义登录表单配置,我们可以提供用户名和密码参数名称。authentication-failure-url用于定义身份验证失败页面的URL。如果未指定登录失败URL,Spring Security将自动在/ spring_security_login?login_error创建失败登录URL,并在请求时自动创建登录失败URL。

default-target-url用于定义在成功验证后将重定向到的默认URL,如果用户的先前操作无法恢复。如果用户在没有首先请求触发认证的安全操作的情况下访问登录页面,则通常会发生这种情况。如果未指定,则默认为应用程序的根目录。

logout用于定义注销处理过滤器。这里我们使会话无效并在成功注销后将用户发送到登录页面。logout-url用于定义用于注销操作的URL。

access-denied-handler 如果拒绝用户访问,则定义全局错误页面,因为他无权执行指定的操作。

session-management 将SessionManagementFilter过滤器添加到会话管理的过滤器堆栈。

还有一些其他配置,但我已经包含了我们使用的大多数重要配置。

Spring安全示例查看页面

在部署和测试我们的应用程序之前,让我们快速浏览一下我们的视图页面。

home.jsp


<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<%@ page session="false"%>
<html>
<head>
<title>Home</title>
</head>
<body>
	<h1>Hello world!</h1>

	<P>The time on the server is ${serverTime}.</P>
</body>
</html>

home.jsp返回“/ home”URI,不应该要求任何身份验证。

employee.jsp


<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<%@ page session="false"%>
<html>
<head>
<title>Get Employee Page</title>
</head>
<body>
	<h1>Employee Information</h1>
	<p>
		Employee ID:${id}<br> Employee Name:${name}<br>
	</p>
	<c:if test="${pageContext.request.userPrincipal.name != null}">
	Hi ${pageContext.request.userPrincipal.name}<br>
	
	<c:url var="logoutAction" value="/j_spring_security_logout"></c:url>
	
	<form action="${logoutAction}" method="post">
		<input type="submit" value="Logout" />
	</form>
	</c:if>
</body>
</html>

当我们访问需要身份验证的URI时,将返回此页面。这里我提供了注销选项,以便用户可以注销并终止会话。注销成功后,应按照配置将用户发送回登录页面。

login.jsp


<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>

<html>

<head>
<title>Login Page</title>
</head>
<body>
	<h3>Login with Username and Password</h3>
	<c:url var="loginUrl" value="/j_spring_security_check"></c:url>
	<form action="${loginUrl}" method="POST">
		<table>
			<tr>
				<td>User ID:</td>
				<td><input type='text' name='username' /></td>
			</tr>
			<tr>
				<td>Password:</td>
				<td><input type='password' name='password' /></td>
			</tr>
			<tr>
				<td colspan='2'><input name="submit" type="submit"
					value="Login" /></td>
			</tr>
		</table>
	</form>
</body>
</html>

这里有几点需要注意。第一个是登录URL是“ / j_spring_security_check ”。这是默认的登录处理URL,就像logout-url一样。

另一个重点是用户名和密码的表单参数名称。它们应与弹簧安全配置中配置的相同。

logout.jsp


<html>
<head>
	<title>Logout Page</title>
</head>
<body>
<h2>
	Logout Successful!  
</h2>

</body>
</html>

denied.jsp


<html>
<head>
	<title>Access Denied</title>
</head>
<body>
<h1>
	Access Denied!  
</h1>

</body>
</html>

logout.jsp和denied.jsp页面很简单,但我们可以根据用户的详细信息在这里包含一些信息。

我们的spring安全性示例应用程序已准备好进行测试,请注意,对于JDBC身份验证,我使用的是与之前的Spring安全性示例相同的设置。所以如果你直接降落在这里,你应该检查出来。

 

Spring Security MVC示例测试

只需将应用程序部署在您最喜欢的servlet容器中,我的是Apache Tomcat 7.下图显示了不同URL的不同输出。

这就是使用UserDetailsS​​ervice的Spring安全示例,请从下面的链接下载示例项目并浏览它以了解更多信息。

下载Spring MVC安全项目

 

转载来源:https://www.journaldev.com/2736/spring-security-example-userdetailsservice

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值