赞
踩
<dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring-boot-web-starter</artifactId> <version>1.9.0</version> </dependency> <!--mybatis-plus--> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.0.5</version> </dependency> <!--mysql--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.46</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency>
mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl mapper-locations: classpath:mapper/*.xml spring: datasource: type: com.zaxxer.hikari.HikariDataSource driver-class-name: com.mysql.jdbc.Driver url: jdbc:mysql://localhost:3306/shirodb?characterEncoding=utf-8&useSSL=false username: root password: zzybzb jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT+8 shiro: loginUrl: /myController/login
CREATE DATABASE IF NOT EXISTS `shirodb` CHARACTER SET utf8mb4;
USE `shirodb`;
CREATE TABLE `user` (
`id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '编号',
`name` VARCHAR(30) DEFAULT NULL COMMENT '用户名',
`pwd` VARCHAR(50) DEFAULT NULL COMMENT '密码',
`rid` BIGINT(20) DEFAULT NULL COMMENT '角色编号',
PRIMARY KEY (`id`)
) ENGINE=INNODB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 COMMENT='用户表';
public class ShiroMD5 {
public static void main(String[] args) {
String password = "123456";
//为了保证安全,避免被破解还可以多次迭代加密,保证数据安全
Md5Hash md53 = new Md5Hash(password,"salt",3);
System.out.println("md5带盐的3次加密:"+md53.toHex());
}
}
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User implements Serializable {
private Integer id;
private String name;
private String pwd;
private Integer rid;
}
@Mapper
public interface UserMapper extends BaseMapper<User> {
}
public interface UserService {
//用户登录
User getUserInfoByName(String name);
}
@Service public class UserServiceImpl implements UserService { private UserMapper userMapper; @Autowired public UserServiceImpl(UserMapper userMapper) { this.userMapper = userMapper; } //根据姓名查询 @Override public User getUserInfoByName(String name) { QueryWrapper<User> wrapper = new QueryWrapper<>(); wrapper.eq("name",name); User user = userMapper.selectOne(wrapper); return user; } }
@Component public class MyRealm extends AuthorizingRealm { private UserService userService; @Autowired public MyRealm(UserService userService){ this.userService = userService; } @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { return null; } //自定义登录认证方法 @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { //1获取用户身份信息 String username = authenticationToken.getPrincipal().toString(); //2调用业务层获取用户信息(数据库) User user = userService.getUserInfoByName(username); //3非空判断,将数据封装返回 if (user != null){ AuthenticationInfo info = new SimpleAuthenticationInfo( authenticationToken.getPrincipal(), user.getPwd(), ByteSource.Util.bytes("salt"), authenticationToken.getPrincipal().toString() ); return info; } return null; } }
@Configuration public class ShiroConfig { private MyRealm myRealm; @Autowired public ShiroConfig(MyRealm myRealm){ this.myRealm = myRealm; } //配置SecurityManager @Bean public DefaultWebSecurityManager defaultWebSecurityManager(){ //1创建defaultWebSecurityManager 对象 DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager(); //2创建加密对象,设置相关属性 HashedCredentialsMatcher matcher = new HashedCredentialsMatcher(); //2.1采用md5加密 matcher.setHashAlgorithmName("MD5"); //2.2迭代加密次数 matcher.setHashIterations(3); //3将加密对象存储到myRealm中 myRealm.setCredentialsMatcher(matcher); //4将myRealm存入defaultWebSecurityManager 对象 defaultWebSecurityManager.setRealm(myRealm); return defaultWebSecurityManager; } //配置Shiro内置过滤器拦截范围 @Bean public DefaultShiroFilterChainDefinition shiroFilterChainDefinition(){ DefaultShiroFilterChainDefinition definition = new DefaultShiroFilterChainDefinition(); //设置不认证可以访问的资源 definition.addPathDefinition("/my/userLogin","anon"); definition.addPathDefinition("/my/login","anon"); //设置登出过滤器 definition.addPathDefinition("/logout","logout"); //设置需要进行登录认证的拦截范围 definition.addPathDefinition("/**","authc"); //添加存在用户的过滤器(rememberMe) definition.addPathDefinition("/**","user"); return definition; } }
@Controller @RequestMapping("/my") public class MyController { @RequestMapping("/userLogin") @ResponseBody public String login(String username,String password){ //1获取subject对象 Subject subject = SecurityUtils.getSubject(); //2封装请求数据到token AuthenticationToken token = new UsernamePasswordToken(username,password); //3调用login方法进行登录认证 try { subject.login(token); return "登录成功"; } catch (AuthenticationException e) { e.printStackTrace(); return "登录失败"; } } }
dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1>Shiro登录认证</h1> <br> <form action="/my/userLogin"> <div>用户名:<input type="text" name="username" value=""></div> <div>密码:<input type="password" name="password" value=""></div> <div><input type="submit" value="登录"></div> </form> </body> </html>
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org"
xmlns:shiro="http://www.w3.org/1999/xhtml">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>Shiro登录认证后主页面</h1>
<br>
登录用户为:<span th:text="${session.user}"></span>
</body>
//跳转登录页面
@RequestMapping("/login")
public String login(){
return "login";
}
当应用程序配置多个 Realm 时,例如:用户名密码校验、手机号验证码校验等等。
Shiro 的 ModularRealmAuthenticator 会使用内部的 AuthenticationStrategy 组件判断认证是成功还是失败。
AuthenticationStrategy 是一个无状态的组件,它在身份验证尝试中被询问 4 次(这4 次交互所需的任何必要的状态将被作为方法参数):
认证策略的另外一项工作就是聚合所有 Realm 的结果信息封装至一个
AuthenticationInfo 实例中,并将此信息返回,以此作为 Subject 的身份信息。
Shiro 中定义了 3 种认证策略的实现:
AtLeastOneSuccessfulStrategy
只要有一个(或更多)的 Realm 验证成功,那么认证将视为成功。
FirstSuccessfulStrategy
第一个 Realm 验证成功,整体认证将视为成功,且后续 Realm 将被忽略。
AllSuccessfulStrategy
所有 Realm 成功,认证才视为成功。
ModularRealmAuthenticator 内置的认证策略默认实现是
AtLeastOneSuccessfulStrategy 方式。可以通过配置修改策略。
//配置 SecurityManager @Bean public DefaultWebSecurityManager defaultWebSecurityManager(){ //1 创建 defaultWebSecurityManager 对象 DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager(); //2 创建认证对象,并设置认证策略 ModularRealmAuthenticator modularRealmAuthenticator = new ModularRealmAuthenticator(); modularRealmAuthenticator.setAuthenticationStrategy(new AllSuccessfulStrategy()); defaultWebSecurityManager.setAuthenticator(modularRealmAuthenticator); //3 封装 myRealm 集合 List<Realm> list = new ArrayList<>(); list.add(myRealm); list.add(myRealm2); //4 将 myRealm 存入 defaultWebSecurityManager 对象 defaultWebSecurityManager.setRealms(list); //5 返回 return defaultWebSecurityManager; }
Shiro 提供了记住我(RememberMe)的功能,比如访问一些网站时,关闭了浏览器,下次再打开时还是能记住你是谁, 下次访问时无需再登录即可访问。
//cookie属性设置 public SimpleCookie rememberMeCookie(){ SimpleCookie cookie = new SimpleCookie("rememberMe"); //设置跨域 //cookie.setDomain(domain); cookie.setPath("/"); cookie.setHttpOnly(true); cookie.setMaxAge(30*24*60*60); return cookie; } //创建Shiro的cookie管理对象 public CookieRememberMeManager rememberMeManager(){ CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager(); cookieRememberMeManager.setCookie(rememberMeCookie()); cookieRememberMeManager.setCipherKey("1234567890987654".getBytes()); return cookieRememberMeManager; }
defaultWebSecurityManager.setRememberMeManager(rememberMeManager());
添加
//登录认证验证 rememberMe
@GetMapping("userLoginRm")
public String userLogin(HttpSession session) {
session.setAttribute("user","rememberMe");
return "index";
}
修改
<div>记住用户:<input type="checkbox" name="rememberMe" value="true"></div>
用户登录后,配套的有登出操作。直接通过Shiro过滤器即可实现登出。
<a href="/logout">登出</a>
//设置登出过滤器
definition.addPathDefinition("/logout","logout");
用户登录后,需要验证是否具有指定角色指定权限。Shiro也提供了方便的工具进行判断。这个工具就是Realm的doGetAuthorizationInfo方法进行判断。
触发权限判断的有两种方式
通过给接口服务方法添加注解可以实现权限校验,可以加在控制器方法上,也可以加在业务方法上,一般加在控制器方法上。常用注解如下:
@RequiresAuthentication
验证用户是否登录,等同于方法subject.isAuthenticated()
@RequiresUser
验证用户是否被记忆:
登录认证成功subject.isAuthenticated()为true。
登录后被记忆subject.isRemembered()为true。
@RequiresGuest
验证是否是一个guest的请求,是否是游客的请求。
此时subject.getPrincipal()为null。
@RequiresRoles
验证subject是否有相应角色,有角色访问方法,没有则会抛出异常AuthorizationException。
例如:
@RequiresRoles(“aRoleName”)//只有subject有aRoleName角色才能访问方法someMethod()
void someMethod();
@RequiresPermissions ("file:read","wite:aFile.txt")//subject必须同时含有file:read和wite:aFile.txt权限才能访问方法someMethod()
void someMethod();
//登录认证验证角色
@RequiresRoles("admin")
@GetMapping("userLoginRoles")
@ResponseBody
public String userLoginRoles(){
System.out.println("登录认证验证角色");
return "验证角色成功";
}
<a shiro:hasRole="admin" href="/my/userLoginRoles">测试授权-角色验证</a>
自定义授权方法:获取当前登录用户权限信息,返回给 Shiro 用来进行授权对比
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
//自定义添加角色
info.addRole("admin");
CREATE TABLE `role` ( `id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '编号', `name` VARCHAR(30) DEFAULT NULL COMMENT '角色名', `desc` VARCHAR(50) DEFAULT NULL COMMENT '描述', `realname` VARCHAR(20) DEFAULT NULL COMMENT '角色显示名', PRIMARY KEY (`id`) ) ENGINE=INNODB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 COMMENT='角色表'; CREATE TABLE `role_user` ( `id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '编号', `uid` BIGINT(20) DEFAULT NULL COMMENT '用户 id', `rid` BIGINT(20) DEFAULT NULL COMMENT '角色 id', PRIMARY KEY (`id`) ) ENGINE=INNODB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 COMMENT='角色用户映射 表';
自定义数据–角色表
自定义数据–用户角色中间表
根据用户名查询对应角色信息
SELECT NAME FROM role WHERE id IN (SELECT rid FROM role_user WHERE
uid=(SELECT id FROM USER WHERE NAME='zhangsan'));
@Select("SELECT NAME FROM role WHERE id IN (SELECT rid FROM role_user WHERE uid=(SELECT id FROM USER WHERE NAME=#{principal}))")
List<String> getUserRoleInfoMapper(@Param("principal")String principal );
/**
* 根据用户查询角色
* @param principal
* @return
*/
@Override
public List<String> getUserRoleInfo(String principal) {
return userMapper.getUserRoleInfoMapper(principal);
}
@Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { System.out.println("进入自定义授权方法"); //1获取用户身份信息 String principal = principalCollection.getPrimaryPrincipal().toString(); //2调用业务层获取用户的角色信息(数据库) List<String> roles = userService.getUserRoleInfo(principal); System.out.println("当前用户角色信息 = " + roles); //2.5调用业务层获取用户的权限信息(数据库) //3创建对象,封装当前登录用户的角色、权限信息 SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); //自定义添加角色 info.addRoles(roles); //返回信息 return info; }
CREATE TABLE `permissions` (
`id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '编号',
`name` VARCHAR(30) DEFAULT NULL COMMENT '权限名',
`info` VARCHAR(30) DEFAULT NULL COMMENT '权限信息',
`desc` VARCHAR(50) DEFAULT NULL COMMENT '描述',
PRIMARY KEY (`id`)
) ENGINE=INNODB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 COMMENT='权限表';
CREATE TABLE `role_ps` (
`id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '编号',
`rid` BIGINT(20) DEFAULT NULL COMMENT '角色 id',
`pid` BIGINT(20) DEFAULT NULL COMMENT '权限 id',
PRIMARY KEY (`id`)
) ENGINE=INNODB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 COMMENT='角色权限映射表';
自定义数据–权限表
自定义数据–角色权限表
根据角色名查询对应权限信息
SELECT info FROM permissions WHERE id IN (SELECT pid FROM role_ps WHERE rid
IN (SELECT id FROM role WHERE NAME IN('admin','user')));
@Select({
"<script>",
"select info FROM permissions WHERE id IN ",
"(SELECT pid FROM role_ps WHERE rid IN (",
"SELECT id FROM role WHERE NAME IN ",
"<foreach collection='roles' item='name' open='(' separator=',' close=')'>",
"#{name}",
"</foreach>",
"))",
"</script>"
})
List<String> getUserPermissionInfoMapper(@Param("roles")List<String> roles);
/**
* 根据角色查看权限
* @param roles
* @return
*/
@Override
public List<String> getUserPermissionInfo(List<String> roles) {
return userMapper.getUserPermissionInfoMapper(roles);
}
//登录认证验证权限
@RequiresPermissions("user:del")
@GetMapping("userPermissions")
@ResponseBody
public String userLoginPermissions(){
System.out.println("登录认证验证权限");
return "验证权限成功";
}
<a shiro:hasPermission="user:delete" href="/my/userPermissions">测试授权-权限验证</a>
创建认证异常处理类,使用@ControllerAdvice 加@ExceptionHandler 实现特殊异常处理。
package com.bz.shiro.controller; import org.apache.shiro.authz.AuthorizationException; 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; @ControllerAdvice public class PermissionsException { @ResponseBody @ExceptionHandler(UnauthorizedException.class) public String unauthorizedException(Exception e){ return "无权限"; } @ResponseBody @ExceptionHandler(AuthorizationException.class) public String authorizationException(Exception e){ return "权限认证失败"; } }
lisi账号登录
<!--配置Thymeleaf与Shrio的整合依赖-->
<dependency>
<groupId>com.github.theborakompanioni</groupId>
<artifactId>thymeleaf-extras-shiro</artifactId>
<version>2.0.0</version>
</dependency>
@Bean
public ShiroDialect shiroDialect(){
return new ShiroDialect();
}
<shiro:guest></shiro:guest>
<shiro:user></shiro:user>
<shiro:authenticated></shiro:authenticated>
<shiro:notAuthenticated></shiro:notAuthenticated>
<shiro: principal/><shiro:principal property="username"/>
<shiro:lacksPermission name="org:create"></shiro:lacksPermission>
<shiro:hasRole name="admin"></shiro:hasRole>
<shiro:hasAnyRoles name="admin,user"></shiro:hasAnyRoles>
<shiro:lacksRole name="abc"></shiro:lacksRole>
<shiro:hasPermission name="user:create"></shiro:hasPermission>
EhCache是一种广泛使用的开源Java分布式缓存。主要面向通用缓存,Java EE和轻量级容器。可以和大部分Java项目无缝整合,例如:Hibernate中的缓存就是基于EhCache实现的。
EhCache支持内存和磁盘存储,默认存储在内存中,如内存不够时把缓存数据同步到磁盘中。EhCache支持基于Filter的Cache实现,也支持Gzip压缩算法。
EhCache直接在JVM虚拟机中缓存,速度快,效率高;
EhCache缺点是缓存共享麻烦,集群分布式应用使用不方便。
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache</artifactId>
<version>2.6.11</version>
<type>pom</type>
</dependency>
<?xml version="1.0" encoding="UTF-8"?> <ehcache> <!--磁盘的缓存位置--> <diskStore path="java.io.tmpdir/ehcache"/> <!--默认缓存--> <defaultCache maxEntriesLocalHeap="10000" eternal="false" timeToIdleSeconds="120" timeToLiveSeconds="120" maxEntriesLocalDisk="10000000" diskExpiryThreadIntervalSeconds="120" memoryStoreEvictionPolicy="LRU"> <persistence strategy="localTempSwap"/> </defaultCache> <!--helloworld缓存--> <cache name="HelloWorldCache" maxElementsInMemory="1000" eternal="false" timeToIdleSeconds="5" timeToLiveSeconds="5" overflowToDisk="false" memoryStoreEvictionPolicy="LRU"/> <!-- defaultCache:默认缓存策略,当ehcache找不到定义的缓存时,则使用这个缓存策略。只能定义一个。 --> <!-- name:缓存名称。 maxElementsInMemory:缓存最大数目 maxElementsOnDisk:硬盘最大缓存个数。 eternal:对象是否永久有效,一但设置了,timeout将不起作用。 overflowToDisk:是否保存到磁盘,当系统宕机时 timeToIdleSeconds:设置对象在失效前的允许闲置时间(单位:秒)。仅当eternal=false对象不是永久有效时使用,可选属性,默认值是0,也就是可闲置时间无穷大。 timeToLiveSeconds:设置对象在失效前允许存活时间(单位:秒)。最大时间介于创建时间和失效时间之间。仅当eternal=false对象不是永久有效时使用,默认是0.,也就是对象存活时间无穷大。 diskPersistent:是否缓存虚拟机重启期数据 Whether the disk store persists between restarts of the Virtual Machine. The default value is false. diskSpoolBufferSizeMB:这个参数设置DiskStore(磁盘缓存)的缓存区大小。默认是30MB。每个Cache都应该有自己的一个缓冲区。 diskExpiryThreadIntervalSeconds:磁盘失效线程运行时间间隔,默认是120秒。 memoryStoreEvictionPolicy:当达到maxElementsInMemory限制时,Ehcache将会根据指定的策略去清理内存。默认策略是LRU(最近最少使用)。你可以设置为FIFO(先进先出)或是LFU(较少使用)。 clearOnFlush:内存数量最大时是否清除。 memoryStoreEvictionPolicy:可选策略有:LRU(最近最少使用,默认策略)、FIFO(先进先出)、LFU(最少访问次数)。 FIFO,first in first out,这个是大家最熟的,先进先出。 LFU, Less Frequently Used,就是上面例子中使用的策略,直白一点就是讲一直以来最少被使用的。如上面所讲,缓存的元素有一个hit属性,hit值最小的将会被清出缓存。 LRU,Least Recently Used,最近最少使用的,缓存的元素有一个时间戳,当缓存容量满了,而又需要腾出地方来缓存新的元素的时候,那么现有缓存元素中时间戳离当前时间最远的元素将被清出缓存。 --> </ehcache>
import net.sf.ehcache.Cache; import net.sf.ehcache.CacheManager; import net.sf.ehcache.Element; import java.io.InputStream; public class TestEH { public static void main(String[] args) { //获取编译目录下的资源的流对象 InputStream input = TestEH.class.getClassLoader().getResourceAsStream("ehcache.xml"); //获取EhCache的缓存管理对象 CacheManager cacheManager = new CacheManager(input); //获取缓存对象 Cache cache = cacheManager.getCache("HelloWorldCache"); //创建缓存数据 Element element = new Element("name","zhang3"); //存入缓存 cache.put(element); //从缓存中取出数据输出 Element element1 = cache.get("name"); System.out.println("缓存中数据 = " + element1.getObjectValue()); } }
Shiro官方提供了shiro-ehcache,实现了整合EhCache作为Shiro的缓存工具。可以缓存认证执行的Realm方法,减少对数据库的访问,提高认证效率。
<!--Shiro整合EhCache-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-ehcache</artifactId>
<version>1.4.2</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.6</version>
</dependency>
<?xml version="1.0" encoding="UTF-8"?> <ehcache name="ehcache" updateCheck="false"> <!--磁盘的缓存位置--> <diskStore path="java.io.tmpdir"/> <!--默认缓存--> <defaultCache maxEntriesLocalHeap="1000" eternal="false" timeToIdleSeconds="3600" timeToLiveSeconds="3600" overflowToDisk="false"> </defaultCache> <!--登录认证信息缓存:缓存用户角色权限--> <cache name="loginRolePsCache" maxEntriesLocalHeap="2000" eternal="false" timeToIdleSeconds="600" timeToLiveSeconds="0" overflowToDisk="false" statistics="true"/> </ehcache>
//缓存管理器
public EhCacheManager getEhCacheManager(){
EhCacheManager ehCacheManager = new EhCacheManager();
InputStream is =null;
try {
is = ResourceUtils.getInputStreamForPath("classpath:ehcache/ehcache-shiro.xml");
} catch (IOException e) {
e.printStackTrace();
}
CacheManager cacheManager = new CacheManager(is);
ehCacheManager.setCacheManager(cacheManager);
return ehCacheManager;
}
先清除日志,再点击角色认证、权限认证,查看日志,没有查询数据库
会话管理器,负责创建和管理用户的会话(Session)生命周期,它能够在任何环境中在本地管理用户会话,即使没有Web/Servlet/EJB容器,也一样可以保存会话。默认情况下,Shiro会检测当前环境中现有的会话机制(比如Servlet容器)进行适配,如果没有(比如独立应用程序或者非Web环境),它将会使用内置的企业会话管理器来提供相应的会话管理服务,其中还涉及一个名为SessionDAO的对象。SessionDAO负责Session的持久化操作(CRUD),允许Session数据写入到后端持久化数据库。
SessionManager由SecurityManager管理。
Shiro提供了三种实现
Session session = SecurityUtils.getSubject().getSession();
session.setAttribute(“key”,”value”)
说明
Controller 中的 request,在 shiro 过滤器中的 doFilerInternal 方法,被包装成ShiroHttpServletRequest。
SecurityManager 和 SessionManager 会话管理器决定 session 来源于 ServletRequest还是由 Shiro 管理的会话。
无论是通过 request.getSession 或 subject.getSession 获取到 session,操作session,两者都是等价的。
结束!!!
人应尊敬他自己,并应自视能配得上最高尚的东西。——黑格尔
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。