Spring Security前后分离校验token的实现方法
前言
之前采取项目中嵌套html
页面,实现基本的登录校验
、权限校验
、登出操作
、记住我
等功能试下。
但是,现在的开发基本都是前后分离
样式,后端并不需要配置登录页
的操作。
如何才能做到前后分离
,同时也能支持登录
和token
校验呢,本篇博客详细说明。
token配置
本次token
生成采取jwt
的方式。
引入JWT依赖文件
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.10.3</version>
</dependency>
配置token管理器类
自定一个Token生成和从token中解析用户名的一个类,并交给Spring管理。
package security.config;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.Claim;
import com.auth0.jwt.interfaces.DecodedJWT;
import org.springframework.stereotype.Component;
import java.util.Calendar;
import java.util.HashMap;
@Component
public class TokenJwtManager {
// 设置token时间
private int tokenEcpiration = 24*60*60*1000; // 毫秒 24h
// 编码密钥
private String tokenSignKey = "123456";
// 1、根据用户名生成token
public String createToken(String userName){
Calendar calendar = Calendar.getInstance();
calendar.add(Calendar.SECOND, tokenEcpiration);
String userName1 = JWT.create()
.withHeader(new HashMap<>())
.withClaim("userName", userName)
.withExpiresAt(calendar.getTime()) // 过期时间
.sign(Algorithm.HMAC256(tokenSignKey));// 签名
return userName1;
}
// 2、根据token得到用户名信息
public String getUserName(String token){
JWTVerifier build = JWT.require(Algorithm.HMAC256(tokenSignKey)).build();
DecodedJWT verify = build.verify(token);
Claim userName = verify.getClaim("userName");
return userName.asString();
public static void main(String[] args) {
String ss = new TokenJwtManager().createToken("1111111");
System.out.println(ss);
System.out.println(new TokenJwtManager().getUserName(ss));
}
security 配置
配置未登录处理类
package security.config.handler;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.HashMap;
import java.util.Map;
@Component
@Slf4j
public class MyUnAuthEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
log.info("======= commence ===");
// 返回请求端
Map<String,Object> resultMap = new HashMap<>();
// 保存数据
resultMap.put("code","10000");
resultMap.put("msg","当前账户未登录");
resultMap.put("data",new HashMap<>());
// 设置返回消息类型
httpServletResponse.setHeader("Content-type", "text/html;charset=UTF-8");
httpServletResponse.setCharacterEncoding("utf-8");
httpServletResponse.setContentType("application/json;charset=UTF-8");
// 返回给请求端
PrintWriter writer = httpServletResponse.getWriter();
writer.write(resultMap.toString());
writer.close();
}
}
配置无权限处理类
package security.config.handler;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Component;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.HashMap;
import java.util.Map;
@Component // 交给spring管理
public class MyAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {
Map<String,Object> resultMap = new HashMap<>();
// 保存数据
resultMap.put("code","403");
resultMap.put("msg","无权访问");
resultMap.put("data",null);
// 设置返回消息类型
httpServletResponse.setHeader("Content-type", "text/html;charset=UTF-8");
httpServletResponse.setCharacterEncoding("utf-8");
httpServletResponse.setContentType("application/json;charset=UTF-8");
// 返回给请求端
PrintWriter writer = httpServletResponse.getWriter();
writer.write(resultMap.toString());
writer.flush();
writer.close();
}
}
配置登出操作处理类
package security.config.handler;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.logout.LogoutHandler;
import org.springframework.stereotype.Component;
import security.config.TokenJwtManager;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.HashMap;
import java.util.Map;
@Component
@Slf4j
public class MyLogoutHandler implements LogoutHandler {
@Autowired
private TokenJwtManager tokenJwtManager;
// public MyLogoutHandler(TokenJwtManager tokenJwtManager) {
// this.tokenJwtManager = tokenJwtManager;
// }
@Override
public void logout(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) {
// 1、从header中获取token
String token = httpServletRequest.getHeader("token");
log.info("token信息为 {}",token);
String userName = tokenJwtManager.getUserName(token);
log.info("从token获取userName信息为 {}",token);
// redis 移除登录信息等逻辑
// xxxxx
// 2、返回请求端
Map<String,Object> resultMap = new HashMap<>();
// 保存数据
resultMap.put("code","200");
resultMap.put("msg",userName+"登录成功");
resultMap.put("data",new HashMap<>());
// 设置返回消息类型
httpServletResponse.setHeader("Content-type", "text/html;charset=UTF-8");
httpServletResponse.setCharacterEncoding("utf-8");
httpServletResponse.setContentType("application/json;charset=UTF-8");
// 返回给请求端
PrintWriter writer = null;
try {
writer = httpServletResponse.getWriter();
writer.write(resultMap.toString());
writer.flush();
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
配置token认证过滤器
package security.filter;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.stereotype.Component;
import security.config.TokenJwtManager;
import security.vo.SecurityUser;
import security.vo.User;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
// 这里交给spring管理会报错
@Slf4j
public class TokenLoginFilter extends UsernamePasswordAuthenticationFilter {
private TokenJwtManager tokenJwtManager;
private AuthenticationManager authenticationManager;
public TokenLoginFilter(TokenJwtManager tokenJwtManager, AuthenticationManager authenticationManager) {
this.tokenJwtManager = tokenJwtManager;
this.authenticationManager = authenticationManager;
this.setPostOnly(false); // 关闭登录只允许 post
// 设置登陆路径,并且post请求
this.setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher("/user/login","POST"));
}
// 1、获取登录页传递来的账户和密码信息
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response){
log.info("==== attemptAuthentication ======");
String userName = request.getParameter("userName");
String pwd = request.getParameter("passWord");
log.info("userName:{},pwd:{}",userName,pwd);
return authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(userName,
pwd,new ArrayList<>()));
// 2、认证成功调用
@Autowired
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult)
throws IOException, ServletException {
log.info("==== successfulAuthentication ======");
// 认证成功之后,获取认证后的用户基本信息
SecurityUser securityUser = (SecurityUser) authResult.getPrincipal();
// 根据用户名生成对应的token
String token = tokenJwtManager.createToken(securityUser.getUsername());
// token信息存于redis、数据库、缓存等
// 返回成功
Map<String,Object> resultMap = new HashMap<>();
// 保存数据
resultMap.put("code","200");
resultMap.put("msg","登录成功");
resultMap.put("data",token);
// 设置返回消息类型
response.setHeader("Content-type", "text/html;charset=UTF-8");
response.setCharacterEncoding("utf-8");
response.setContentType("application/json;charset=UTF-8");
// 返回给请求端
PrintWriter writer = response.getWriter();
writer.write(resultMap.toString());
writer.flush();
writer.close();
// 3、认证失败调用的方法
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed)
log.info("==== unsuccessfulAuthentication ======");
resultMap.put("code","500");
resultMap.put("msg","登录验证失败");
resultMap.put("data",new HashMap<>());
}
配置token权限校验过滤器
package security.filter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
import org.springframework.stereotype.Component;
import security.config.TokenJwtManager;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
@Slf4j
//@Component // 交给 spring 会报错
public class TokenAuthFilter extends BasicAuthenticationFilter {
private TokenJwtManager tokenJwtManager;
public TokenAuthFilter(AuthenticationManager authenticationManager, TokenJwtManager tokenJwtManager) {
super(authenticationManager);
this.tokenJwtManager = tokenJwtManager;
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
log.info("==== doFilterInternal ========== token校验");
//获取当前认证成功用户权限信息
UsernamePasswordAuthenticationToken authRequest = getAuthentication(request);
if(authRequest != null){
// 有权限,则放入权限上下文中
SecurityContextHolder.getContext().setAuthentication(authRequest);
}
// 执行下一个 filter 过滤器链
chain.doFilter(request,response);
private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request) {
log.info("==== getAuthentication =====");
//从header获取token
String token = request.getHeader("token");
log.info("token:{}",token);
if(token != null) {
//从token获取用户名
String username = tokenJwtManager.getUserName(token);
log.info("解析token获取userName为:{}",username);
// 数据库获取权限信息
// 本次模拟
List<String> permissionValueList = Arrays.asList("admin","select");
Collection<GrantedAuthority> authority = new ArrayList<>();
for(String permissionValue : permissionValueList) {
SimpleGrantedAuthority auth = new SimpleGrantedAuthority(permissionValue);
authority.add(auth);
}
return new UsernamePasswordAuthenticationToken(username,token,authority);
return null;
}
自定义加密类
package security.config;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;
import security.utils.Md5Utils;
@Component // bean
@Slf4j
public class DefaultPwdEndoder implements PasswordEncoder {
@Override
public String encode(CharSequence charSequence) {
log.info("==== encode ====");
log.info("charSequence 为 {}",charSequence);
log.info("charSequence md5为 {}",Md5Utils.md5(charSequence.toString()));
return Md5Utils.md5(charSequence.toString());
}
* 进行密码比对
* @param charSequence 不加密
* @param encodePwd 加密
public boolean matches(CharSequence charSequence, String encodePwd) {
log.info("==== matches ====");
log.info("charSequence:{}",charSequence);
log.info("charSequenceMd5:{}",Md5Utils.md5(charSequence.toString()));
log.info("encodePwd:{}",encodePwd);
return encodePwd.equalsIgnoreCase(Md5Utils.md5(charSequence.toString()));
}
配置UserDetailService
package security.service;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
import security.mapper.UserMapper;
import security.vo.SecurityUser;
import security.vo.User;
import java.util.Arrays;
import java.util.List;
@Service("userDetailsService")
@Slf4j
public class UserDetailService implements UserDetailsService {
// 注入Usermapper
@Autowired
private UserMapper userMapper;
@Override
public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {
log.info("====== loadUserByUsername ======");
// 通过username查询数据库获取用户信息
QueryWrapper<User> userQueryWrapper = new QueryWrapper<>();
userQueryWrapper.eq("username",userName);
User user = userMapper.selectOne(userQueryWrapper);
// 判断用户是否存在
if(user == null){
throw new UsernameNotFoundException("账户信息不存在!");
}
// 存在对应的用户信息,则将其封装,丢给security自己去解析
log.info("user:{}",user);
// 权限暂时不查数据库
List<String> admin = Arrays.asList("ROLE_user,ROLE_admin,admin");
// 将数据封装给 SecurityUser ,因为 SecurityUser 是 UserDetails 的子类
SecurityUser securityUser = new SecurityUser();
securityUser.setPermissionValueList(admin);
securityUser.setUser(user);
log.info("securityUser:{}",securityUser.toString());
return securityUser;
}
}
配置数据库User对象映射类
package security.vo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User implements Serializable {
private static final long serialVersionUID = -5461108964440966122L;
private Integer id;
private String username;
private String password;
private Integer enabled;
private Integer locked;
}
配置UserDetailService使用的SecurityUser类
package security.vo;
import lombok.Data;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.util.StringUtils;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
@Data
public class SecurityUser implements UserDetails {
// 登录用户的基本信息
private User user;
//当前权限
private List<String> permissionValueList;
public SecurityUser() {
}
public SecurityUser(User user) {
if (user != null) {
this.user = user;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
Collection<GrantedAuthority> authorities = new ArrayList<>();
permissionValueList.forEach(permission ->{
if(!StringUtils.isEmpty(permission)){
SimpleGrantedAuthority authority = new SimpleGrantedAuthority(permission);
authorities.add(authority);
}
});
return authorities;
public String getPassword() {
return user.getPassword();
public String getUsername() {
return user.getUsername();
public boolean isAccountNonExpired() {
return true;
public boolean isAccountNonLocked() {
public boolean isCredentialsNonExpired() {
public boolean isEnabled() {
}
配置mybatis-plus
首先,需要配置application.properties
数据库连接源。
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.url=jdbc:mysql://106.55.137.66:3306/security?useSSL=false&useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
server.port=80
其次,需要配置Mapper类,查询数据库获取基本数据信息。
package security.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.springframework.stereotype.Repository;
import security.vo.User;
@Repository
public interface UserMapper extends BaseMapper<User> {
}
配置security配置类
package security.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import security.config.handler.*;
import security.filter.TokenAuthFilter;
import security.filter.TokenLoginFilter;
import security.service.UserDetailService;
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true) // 方法增加权限
public class MyTokenSecurityConfig extends WebSecurityConfigurerAdapter {
// 将 UserDetailService 注入,使其去查询数据库
@Autowired
private UserDetailService userDetailsService;
// token 生成器
@Autowired
private TokenJwtManager tokenManager;
// 自定义密码加密解密
@Autowired
private DefaultPwdEndoder defaultPwdEndoder;
// 未登录handler
@Autowired
private MyUnAuthEntryPoint myUnAuthEntryPoint;
// 无权限
@Autowired
private MyAccessDeniedHandler myAccessDeniedHandler;
// 登出handler处理
@Autowired
private MyLogoutHandler myLogoutHandler;
// 登录失败
@Autowired
private LoginFailedHandler loginFailedHandler;
// 登录成功
@Autowired
private LoginSuccessHandler loginSuccessHandler;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// 设置 userDetailsService 和 密码解析
auth.userDetailsService(userDetailsService).passwordEncoder(defaultPwdEndoder);
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.exceptionHandling()
.authenticationEntryPoint(myUnAuthEntryPoint) // 未登录 handler
.accessDeniedHandler(myAccessDeniedHandler) // 无权限
.and().csrf().disable() // 关闭 csrf 跨域请求
.formLogin()
.loginProcessingUrl("/user/login") // 设定登录请求接口
.usernameParameter("userName")
.passwordParameter("passWord")
//.successHandler(loginSuccessHandler) // 因为有了 TokenLoginFilter 配置过滤器,此处配置没用
//.failureHandler(loginFailedHandler) // 因为有了 TokenLoginFilter 配置过滤器,此处配置没用
.permitAll()
.and()
.authorizeRequests() // 请求设置
.antMatchers("/test").permitAll() // 配置不需要认证的接口
.anyRequest().authenticated() // 任何请求都需要认证
.and()
.logout() // logout设定
.logoutUrl("/logouts") //退出请求 /logouts 未定义,交给自定义handler实现功能
.addLogoutHandler(myLogoutHandler) // 登出 myLogoutHandler 处理
.and()
.addFilter(new TokenLoginFilter(tokenManager,authenticationManager())) // 认证交给 自定义 TokenLoginFilter 实现
.addFilter(new TokenAuthFilter(authenticationManager(),tokenManager))
.httpBasic();
}
@Override
public void configure(WebSecurity web) throws Exception {
//web.ignoring().antMatchers("/test","/user/login");
web.ignoring().antMatchers(HttpMethod.OPTIONS, "
public class Md5Utils {
public static String md5(String str) {
try {
MessageDigest md = MessageDigest.getInstance("MD5");
md.update(str.getBytes());
byte b[] = md.digest();
str = byteToStr(b);
} catch (Exception e) {
e.printStackTrace();
}
return str;
}
public static String byteToStr(byte[] b){
int i;
StringBuffer buf = new StringBuffer("");
for (int offset = 0; offset < b.length; offset++) {
i = b[offset];
//System.out.println(i);
if (i < 0)
i += 256;
if (i < 16)
buf.append("0");
buf.append(Integer.toHexString(i));
return buf.toString();
public static String SHA256(final String strText)
{
return SHA(strText, "SHA-256");
public static String SHA1(final String strText)
return SHA(strText, "SHA-1");
* 传入文本内容,返回 SHA-512 串
public static String SHA512(final String strText)
return SHA(strText, "SHA-512");
* 字符串 SHA 加密
private static String SHA(final String strText, final String strType)
// 返回值
String strResult = null;
// 是否是有效字符串
if (strText != null && strText.length() > 0)
{
try
{
// SHA 加密开始
MessageDigest messageDigest = MessageDigest.getInstance(strType);
// 传入要加密的字符串
messageDigest.update(strText.getBytes("utf-8"));
// 得到 byte 类型的结果
byte byteBuffer[] = messageDigest.digest();
strResult = byteToStr(byteBuffer);
}
catch (NoSuchAlgorithmException e)
e.printStackTrace();
}catch (UnsupportedEncodingException e) {
// TODO Auto-generated catch block
return strResult;
public static String base64(String str){
String baseStr = null;
Base64.Encoder encoder = Base64.getEncoder();
byte[] textByte;
textByte = str.getBytes("UTF-8");
baseStr = encoder.encodeToString(textByte);
} catch (UnsupportedEncodingException e) {
return baseStr;
public static void main(String[] args) {
String password = "bunana1";
System.out.println(md5(password));
//String base64 = base64(sha512);
//System.out.println(base64);
//String pwd1 = md5(base64);
//System.out.println(pwd1);
}
测试
测试采取ApiPost 工具
,让测试更接近前后分离。
首先测试登录
Post
localhost/user/login
账号密码有一个不对时。
正确的账号密码
测试存在权限的接口
localhost/user/test1
测试不存在权限的接口
localhost/user/test2
测试登出
localhost/logouts
测试不需要权限的接口
localhost/test
数据库sql脚本
CREATE TABLE `user` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, -- 主键
`username` varchar(255) DEFAULT NULL, -- 用户名
`password` varchar(255) DEFAULT NULL, -- 用户密码
`enabled` tinyint(1) DEFAULT '1', -- 是否启用 1-启用 0-未启用
`locked` tinyint(1) DEFAULT '0', -- 是否被锁 1-已锁 0-未锁
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
数据为:
insert INTO user(username,password,enabled,locked) VALUES("xiangjiaoSS","1babad058e03c5296a94a5a8d7d6dd8a",1,0); -- bunana 的md5 值
insert INTO user(username,password,enabled,locked) VALUES("xiangjiaoSS2","0b13310f8db2dc22e7ddd0cdc5f0a61a",1,0); -- bunana1 的md5 值
insert INTO user(username,password,enabled,locked) VALUES("xiangjiaoSS3","b3fbcd9c9d97e47f263a19a0e01efc7d",1,0); -- bunana2 的md5 值
代码下载
springboot-security-10-qianhou
gitee 代码下载地址
到此这篇关于Spring Security前后分离校验token的文章就介绍到这了,更多相关Spring Security校验token内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!
免责声明:
① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。
② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341