最近在做一个期末作品,就是应用ssm+thymeleaf+vue+shiro实现一个具备权限登录,且能实现用户信息增删查改的这么一个我的项目,上面仅仅是实现权限认证和登录。为什么我选shiro,而不选spring Security,是因为我试过,security切实是比拟难,封装的太厉害了,哈哈哈哈,所以果决放弃,抉择shiro进行。

下一篇还实现了增删查改,应用vue,然而没有前后端拆散,博客链接

!github源码连贯,须要请自行下载。

提醒,这个我的项目曾经有了增删查改,跟着上面的博客做,也能做进去页面跳转权限,然而没有增删查改。

以下是学习shiro的一个小Demo:

1.首先是底层数据库:

-- ------------------------------ Table structure for role-- ----------------------------CREATE TABLE `role`  (  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '角色表主键',  `role_name` varchar(32) DEFAULT NULL COMMENT '角色名称',  PRIMARY KEY (`id`));-- ------------------------------ Records of role-- ----------------------------INSERT INTO `role` VALUES (1, 'SUPER_ADMIN');INSERT INTO `role` VALUES (2, 'ADMIN');INSERT INTO `role` VALUES (3, 'USER');-- ------------------------------ Table structure for user-- ----------------------------DROP TABLE IF EXISTS `user`;CREATE TABLE `user`  (  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '用户主键',  `username` varchar(32) NOT NULL COMMENT '用户名',  `password` varchar(32) NOT NULL COMMENT '明码',  `role_id` int(11) DEFAULT NULL COMMENT '与role角色表分割的外键',  PRIMARY KEY (`id`),  CONSTRAINT `user_role_on_role_id` FOREIGN KEY (`role_id`) REFERENCES `role` (`id`));-- ------------------------------ Records of user-- ----------------------------INSERT INTO `user` VALUES (1, 'BWH_Steven', '666666', 1);INSERT INTO `user` VALUES (2, 'admin', '666666', 2);INSERT INTO `user` VALUES (3, 'zhangsan', '666666', 3);-- ------------------------------ Table structure for permission-- ----------------------------CREATE TABLE `permission`  (  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '权限表主键',  `permission_name` varchar(50) NOT NULL COMMENT '权限名',  `role_id` int(11) DEFAULT NULL COMMENT '与role角色表分割的外键',  PRIMARY KEY (`id`),  CONSTRAINT `permission_role_on_role_id` FOREIGN KEY (`role_id`) REFERENCES `role` (`id`));-- ------------------------------ Records of permission-- ----------------------------INSERT INTO `permission` VALUES (1, 'user:*', 1);INSERT INTO `permission` VALUES (2, 'user:*', 2);INSERT INTO `permission` VALUES (3, 'user:queryAll', 3);

2.创立spring boot我的项目,用maven构建

创立实体类(User,Role,Permissions):
User:

@Data@AllArgsConstructor@NoArgsConstructorpublic class User {    private Integer id;    private String username;    private String password;    //用户对应的角色汇合    private Role role;}

Role:

@Data@AllArgsConstructor@NoArgsConstructorpublic class Role {    private Integer id;    private String roleName;}

Permissions:

@Data@AllArgsConstructor@NoArgsConstructorpublic class Permissions {    private Integer id;    private String permissionName;    private Role role;}

咱们须要晓得三个实体类之间的关系,User与Role一对一,Role与Permissions一对一,当然也能够把它都写成多对多,这就须要去更改数据库文件,和实体类了。

3.在pom.xml增加相干依赖:

上面只给出相干依赖源

<dependency>            <groupId>com.github.theborakompanioni</groupId>            <artifactId>thymeleaf-extras-shiro</artifactId>            <version>2.0.0</version>        </dependency>        <dependency>            <groupId>org.apache.shiro</groupId>            <artifactId>shiro-spring</artifactId>            <version>1.5.3</version>        </dependency>        <dependency>            <groupId>org.springframework.boot</groupId>            <artifactId>spring-boot-starter-thymeleaf</artifactId>        </dependency>        <dependency>            <groupId>org.springframework.boot</groupId>            <artifactId>spring-boot-starter-web</artifactId>        </dependency>        <dependency>            <groupId>org.mybatis.spring.boot</groupId>            <artifactId>mybatis-spring-boot-starter</artifactId>            <version>2.1.3</version>        </dependency>        <dependency>            <groupId>mysql</groupId>            <artifactId>mysql-connector-java</artifactId>        </dependency>        <dependency>            <groupId>org.springframework.boot</groupId>            <artifactId>spring-boot-devtools</artifactId>            <scope>runtime</scope>            <optional>true</optional>        </dependency>        <dependency>            <groupId>org.projectlombok</groupId>            <artifactId>lombok</artifactId>            <optional>true</optional>        </dependency>        <dependency>            <groupId>org.springframework.boot</groupId>            <artifactId>spring-boot-starter-test</artifactId>            <scope>test</scope>            <exclusions>                <exclusion>                    <groupId>org.junit.vintage</groupId>                    <artifactId>junit-vintage-engine</artifactId>                </exclusion>            </exclusions>        </dependency>

4.整合mybatis和springboot:

就只须要创立一个dao层,一个服务层,须要记住要增加注解
(1)mapper配置文件(也能够应用注解模式):

<?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.example.csy.dao.UserMapper">    <select id="queryUserByUsername" resultMap="userRoleMap">        SELECT u.*,r.role_name FROM `user` u, `role` r          WHERE username = #{username} AND u.role_id = r.id;    </select>    <!-- 定义封装 User和 role 的 resultMap -->    <resultMap id="userRoleMap" type="com.example.csy.entity.User">        <id property="id" column="id"/>        <result property="username" column="username"></result>        <result property="password" column="password"></result>        <!-- 配置封装 UserPojo 的内容 -->        <association property="role" javaType="com.example.csy.entity.Role">            <id property="id" column="id"></id>            <result property="roleName" column="role_name"></result>        </association>    </resultMap>    <select id="queryPermissionByUsername" resultMap="permissionRoleMap">        SELECT p.* ,r.role_name FROM `user` u, `role` r, `permission` p          WHERE username = #{username} AND u.role_id = r.id AND p.role_id = r.id;    </select>    <!-- 定义封装 permission 和 role 的 resultMap -->    <resultMap id="permissionRoleMap" type="com.example.csy.entity.Permissions">        <id property="id" column="id"/>        <result property="permissionName" column="permission_name"></result>        <!-- 配置封装 Role 的内容 -->        <association property="role" javaType="com.example.csy.entity.Role">            <id property="id" column="id"></id>            <!--property是实体类中被赋值的参数名,column是数据库的列名-->            <result property="roleName" column="role_name"></result>        </association>    </resultMap></mapper>

(2)DAO层:

@Mapperpublic interface UserMapper {    User queryUserByUsername(@Param("username") String username);    Permissions queryPermissionByUsername(@Param("username") String username);}

(3)service层:

@Servicepublic class UserServiceImpl implements UserService {    @Autowired    private UserMapper userMapper;    @Override    public User queryUserByUsername(String username) {        return userMapper.queryUserByUsername(username);    }    @Override    public Permissions queryPermissionByUsername(String username) {        return userMapper.queryPermissionByUsername(username);    }}

弄到这里,咱们的mybatis+springboot整合也根本完结,所以在测试类里测试一下:

@SpringBootTestclass CsyApplicationTests {    @Autowired    private UserMapper userMapper;    @Test    void contextLoads() {        User admin = userMapper.queryUserByUsername("admin");        System.out.println(admin.toString());        Permissions permission = userMapper.queryPermissionByUsername("admin");        System.out.println(permission.toString());    }}

测试后果:
失去了查问后果

6.整合Thymeleaf进来:

前端页面:
在html页面咱们整合了Thymeleaf,应用了Jquery,semantic,须要导包

index.html代码:
在这里,如果是User就只能拜访A,Admin能拜访A,B,superAdmin能拜访A,B,C

<!DOCTYPE html><html lang="zh_CN"      xmlns:th="http://www.thymeleaf.org"      xmlns="http://www.w3.org/1999/xhtml"      xmlns:layout="http://www.ultraq.net.nz/web/thymeleaf/layout"      xmlns:shiro="http://www.pollix.at/thymeleaf/shiro"><html lang="en"><head>    <meta charset="UTF-8">    <title>信息管理平台-首页</title>    <meta            name="viewport"            content="width=device-width, initial-scale=1, maximum-scale=1"    />    <title>首页</title>    <!--semantic-ui-->    <link            href="https://cdn.bootcss.com/semantic-ui/2.4.1/semantic.min.css"            rel="stylesheet"    />    <!--<link href="css/index.css" rel="stylesheet">-->    <link th:href="@{/css/index.css}" rel="stylesheet">    <!-- <script th:src="@{js/jquery-3.1.1.min.js}"></script> -->    <script src="js/jquery-3.1.1.min.js"></script></head><body><div class="ui container">    <div class="ui secondary menu">        <a class="active item" th:href="@{/index}">            首页        </a>        <a class="active item" th:href="@{/about}">            对于        </a>        <!--登录登记-->        <div class="right menu">            <!--如果未登录-->            <!--<div shiro:authorize="!isAuthenticated()">-->            <div shiro:notAuthenticated="">                <a class="item" th:href="@{/toLoginPage}">                    <i class="address card icon"></i> 登录                </a>            </div>            <!--如果已登录-->            <div shiro:authenticated="">                <a class="item">                    <i class="address card icon"></i>                    用户名:<span shiro:principal></span>                    <!--角色:<span sec:authentication="principal.authorities"></span>-->                </a>            </div>            <div shiro:authenticated="">                <a class="item" th:href="@{/logout}">                    <i class="address card icon"></i> 登记                </a>            </div>        </div>    </div>    <div class="ui stackable three column grid">        <div class="column" shiro:hasAnyRoles="USER,ADMIN,SUPER_ADMIN"><!--有其中任一一个角色课拜访-->            <div class="ui raised segments">                <div class="ui segment">                    <a th:href="@{/levelA/a}">L-A-a</a>                </div>                <div class="ui segment">                    <a th:href="@{/levelA/b}">L-A-b</a>                </div>                <div class="ui segment">                    <a th:href="@{/levelA/c}">L-A-c</a>                </div>            </div>        </div>        <div class="column" shiro:hasAnyRoles="ADMIN,SUPER_ADMIN">            <div class="ui raised segments">                <div class="ui segment">                    <a th:href="@{/levelB/a}">L-B-a</a>                </div>                <div class="ui segment">                    <a th:href="@{/levelB/b}">L-B-b</a>                </div>                <div class="ui segment">                    <a th:href="@{/levelB/c}">L-B-c</a>                </div>            </div>        </div>        <div class="column" shiro:hasRole="SUPER_ADMIN">            <div class="ui raised segments">                <div class="ui segment">                    <a th:href="@{/levelC/a}">L-C-a</a>                </div>                <div class="ui segment">                    <a th:href="@{/levelC/b}">L-C-b</a>                </div>                <div class="ui segment">                    <a th:href="@{/levelC/c}">L-C-c</a>                </div>            </div>        </div>        <!-- <div class="column"></div> -->    </div>    <div class="ui stacked segment">        <div class="ui stackable three column grid">            <div class="column">                <p>                    晚风吹起你鬓间的白发<br/>                    抚平回顾留下的疤<br/>                    你的眼中 明暗交杂 一笑生花<br/>                    暮色遮住你蹒跚的步调<br/>                    走进床头藏起的画<br/>                    画中的你 低着头谈话<br/>                    我仍感叹于世界之大                </p>            </div>            <div class="column">                <p>                    也沉醉于儿时情话<br/>                    不剩虚实 不做挣扎 无谓笑话<br/>                    我终将青春还给了她<br/>                    连同指尖弹出的隆冬<br/>                    心之所动 就随风去了<br/>                    以爱之名 你还违心吗<br/>                    ❤                </p>            </div>            <div class="column">                <img class="ui medium circular image" src="images/001.jpg">            </div>        </div>    </div>    <div class="ui info message">        <div class="header">现实二旬不止</div>        <p>BWH_Steven</p>    </div></div></body></html>

login.html代码:

<!DOCTYPE html><html lang="en" xmlns:th="http://www.thymeleaf.org"      xmlns="http://www.w3.org/1999/xhtml"      xmlns:layout="http://www.ultraq.net.nz/web/thymeleaf/layout"><head>    <meta charset="UTF-8">    <title>用户管理系统-登录</title>    <!-- <script th:src="@{js/jquery-3.1.1.min.js}"></script> -->    <script src="js/jquery-3.1.1.min.js"></script>    <link            href="https://cdn.bootcss.com/semantic-ui/2.4.1/semantic.min.css"            rel="stylesheet"    /></head><body><h1>用户管理系统-登录</h1><div class="ui container" style="margin-top: 180px;">    <div style="text-align: center; margin-bottom: 20px;">        <h1 class="header">            登录        </h1>    </div>    <div class="ui three column stackable grid login-div">        <div class="column"></div>        <div class="column">            <form id="login" class="ui fluid form segment" th:action="@{/login}" method="post">                <div class="field">                    <label class="">用户名</label>                    <div class="ui left icon input">                        <input type="text" name="username" placeholder=""/>                        <i class="user icon"></i>                        <div class="ui corner label">                            <i class="icon asterisk"></i>                        </div>                    </div>                </div>                <div class="field">                    <label class="">明码</label>                    <div class="ui left icon input">                        <input type="password" name="password" placeholder=""/>                        <i class="lock icon"></i>                        <div class="ui corner label">                            <i class="icon asterisk"></i>                        </div>                    </div>                </div>                <div class="inline field">                    <div class="ui checkbox">                        <input type="checkbox" name="terms"/>                        <label>记住明码</label>                    </div>                </div>                <div class="inline field">                    <input type="submit" class="ui blue submit button">                </div>            </form>        </div>        <div class="column"></div>    </div></div></body></html>

success.html:

<!DOCTYPE html><html lang="en"><head>    <meta charset="UTF-8">    <title>用户管理系统-胜利</title></head><body><h2>登录胜利</h2><a href="/index">返回主页</a></body></html>

7.将shiro整合到我的项目里:

(1)自定义Realm:

咱们须要自定义,认证和受权:

public class UserRealm extends AuthorizingRealm {    @Autowired    private UserMapper userMapper;    /**     * @MethodName doGetAuthorizationInfo 受权操作     * @Description 权限配置类     * @Param [principalCollection]     * @Return AuthorizationInfo     */    @Override    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {        // 获取用户名信息        String username = (String) principalCollection.getPrimaryPrincipal();        // 创立一个简略受权验证信息        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();        // 给这个用户设置从 role 表获取到的角色信息        authorizationInfo.addRole(userMapper.queryUserByUsername(username).getRole().getRoleName());        //给这个用户设置从 permission 表获取的权限信息        authorizationInfo.addStringPermission(userMapper.queryPermissionByUsername(username).getPermissionName());        return authorizationInfo;    }    /**     * @MethodName doGetAuthenticationInfo 身份验证     * @Description 认证配置类     * @Param [authenticationToken]     * @Return AuthenticationInfo     * @Author WangShiLin     */    @Override    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {        // 依据在承受前台数据创立的 Token 获取用户名        String username = (String) authenticationToken.getPrincipal();//        UsernamePasswordToken userToken = (UsernamePasswordToken) authenticationToken;//        System.out.println(userToken.getPrincipal());//        System.out.println(userToken.getUsername());//        System.out.println(userToken.getPassword());        // 通过用户名查问相干的用户信息(实体)        User user = userMapper.queryUserByUsername(username);        if (user != null) {            // 存入 Session,可选            SecurityUtils.getSubject().getSession().setAttribute("user", user);            // 明码认证的工作,Shiro 来做            AuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(user.getUsername(), user.getPassword(), "userRealm");            return authenticationInfo;        } else {            // 返回 null 即会抛异样            return null;        }    }}

(2)写配置类shiroConfig:

@Configurationpublic class ShiroConfig {    //将本人的验证形式退出容器    @Bean    public UserRealm myShiroRealm() {        return new UserRealm();    }    /**     * 配置平安管理器 SecurityManager     *     * @return     */    @Bean    public DefaultWebSecurityManager securityManager() {        // 将自定义 Realm 加进来        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();        // 关联 Realm        securityManager.setRealm(myShiroRealm());        return securityManager;    }    /**     * 配置 Shiro 过滤器     *     * @param securityManager     * @return     */    @Bean    public ShiroFilterFactoryBean shiroFilter(DefaultWebSecurityManager securityManager) {        // 定义 shiroFactoryBean        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();        // 关联 securityManager        shiroFilterFactoryBean.setSecurityManager(securityManager);        // 自定义登录页面,如果登录的时候,就会执行这个申请,即跳转到登录页        shiroFilterFactoryBean.setLoginUrl("toLoginPage");        // 指定胜利页面        shiroFilterFactoryBean.setSuccessUrl("/success");        // 指定未受权界面        shiroFilterFactoryBean.setUnauthorizedUrl("/unauthorized");        // 设置自定义 filter        Map<String, Filter> filterMap = new LinkedHashMap<>();        filterMap.put("anyRoleFilter", new MyRolesAuthorizationFilter());        shiroFilterFactoryBean.setFilters(filterMap);        // LinkedHashMap 是有序的,进行程序拦截器配置        Map<String, String> filterChainMap = new LinkedHashMap<>();        // 配置能够匿名拜访的地址,能够依据理论状况本人增加,放行一些动态资源等,anon 示意放行        filterChainMap.put("/css/**", "anon");        filterChainMap.put("/img/**", "anon");        filterChainMap.put("/js/**", "anon");        // 指定页面放行,例如登录页面容许所有人登录        filterChainMap.put("/toLoginPage", "anon");        // 以“/user/admin” 结尾的用户须要身份认证,authc 示意要进行身份认证        filterChainMap.put("/user/admin/**", "authc");        // 页面 -用户须要角色认证        filterChainMap.put("/levelA/**", "anyRoleFilter[USER,ADMIN,SUPER_ADMIN]");        filterChainMap.put("/levelB/**", "anyRoleFilter[ADMIN,SUPER_ADMIN]");        filterChainMap.put("/levelC/**", "anyRoleFilter[SUPER_ADMIN]");//        filterChainMap.put("/levelA/**", "roles[USER]");//        filterChainMap.put("/levelB/**", "roles[ADMIN]");//        filterChainMap.put("/levelC/**", "roles[SUPER_ADMIN]");        // /user/admin/ 下的所有申请都要通过权限认证,只有权限为 user:[*] 的能够拜访,也能够具体设置到 user:xxx        filterChainMap.put("/user/admin/**", "perms[user:*]");        // 配置登记过滤器        filterChainMap.put("/logout", "logout");        // 将Map 存入过滤器        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainMap);        return shiroFilterFactoryBean;    }    /**     * 整合 thymeleaf     * @return     */    @Bean(name = "shiroDialect")    public ShiroDialect shiroDialect(){        return new ShiroDialect();    }

首先咱们将自定义的Realm办法,依赖注入进来到容器

//将本人的验证形式退出容器    @Bean    public UserRealm myShiroRealm() {        return new UserRealm();    }

而后是:SecurityManager配置平安管理器

/**     * 配置平安管理器 SecurityManager     *     * @return     */    @Bean    public DefaultWebSecurityManager securityManager() {        // 将自定义 Realm 加进来        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();        // 关联 Realm        securityManager.setRealm(myShiroRealm());        return securityManager;    }

最初就是自定义的过滤器,管制那些页面须要什么样的角色能力拜访,哪些资源须要谁能力拜访,并且setSecurityManager,返回一个ShiroFilterFactoryBean。

重点说一下拦挡放行(Map)这块:通过 map 键值对的模式存储,key 存储 URL ,value 存储对应的一些权限或者角色等等,其实 key 这块还是很好了解的,例如 :/css/、/user/admin/ 别离代表 css 文件夹下的所有文件,以及申请门路前缀为 /user/admin/ URL,而对应的 value 就有肯定的标准了。

要害:
anon:无需认证,即可拜访,也就是游客也能够拜访
authc:必须认证,能力拜访,也就是例如须要登录后
roles[xxx] :比方领有某种角色身份能力拜访 ,注:xxx为角色参数
perms[xxx]:必须领有对某个申请、资源的相干权限能力拜访,注:xxx为权限参数

(3)自定义一个角色认证过滤器MyRolesAuthorizationFilter:

因为咱们的角色,只需用有一个角色就能拜访到映射页面,shiro默认是hasAllRoles,也就是说,咱们要满足所有的身份能力拜访,所以须要咱们自定义一个hasAnyRoles,任选其一角色即可。

public class MyRolesAuthorizationFilter extends AuthorizationFilter {    @SuppressWarnings({"unchecked"})    public boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws IOException {        Subject subject = getSubject(request, response);        String[] rolesArray = (String[]) mappedValue;        if (rolesArray == null || rolesArray.length == 0) {            //no roles specified, so nothing to check - allow access.            return false;        }        List<String> roles = CollectionUtils.asList(rolesArray);        boolean[] hasRoles = subject.hasRoles(roles);        for (boolean hasRole : hasRoles) {            if (hasRole) {                return true;            }        }        return false;    }}

(4)最初就是controller

controller是springMvc的前端控制器,接管什么申请,并且返回对应指定的页面(映射)。
首先咱们先将所以页面的映射写好,

PageController:

@Controllerpublic class PageController {    @RequestMapping({"/", "index"})    public String index() {        return "index";    }    @RequestMapping("about")    public String toAboutPage() {        return "redirect:http://www.ideal-20.cn";    }    @RequestMapping("/toLoginPage")    public String toLoginPage() {        return "views/login";    }    @RequestMapping("/levelA/{name}")    public String toLevelAPage(@PathVariable("name") String name) {        return "views/L-A/" + name;    }    @RequestMapping("/levelB/{name}")    public String toLevelBPage(@PathVariable("name") String name) {        return "views/L-B/" + name;    }    @RequestMapping("/levelC/{name}")    public String toLevelCPage(@PathVariable("name") String name) {        return "views/L-C/" + name;    }    @RequestMapping("/unauthorized")    public String toUnauthorizedPage() {        return "views/unauthorized";    }    @RequestMapping("/success")    public String toSuccessPage() {        return "views/success";    }}

UserController:
下面那两个映射,只是测试,次要是那个login办法,他能够依据咱们前台输出的数据,并创立一个token,如果该token能被认证,即返回胜利页面,否则就失败。

@Controllerpublic class UserController {    @RequestMapping("/user/queryAll")    @ResponseBody    public String queryAll() {        return "这是 user/queryAll 办法";    }    @RequestMapping("/user/admin/add")    @ResponseBody    public String adminAdd() {        return "这是 user/adminAdd 办法";    }    @RequestMapping("/login")    public String login(String username, String password, HttpServletRequest request) {        // 因为是依据name参数获取的,我这里封装了一下        User user = new User();        user.setUsername(username);        user.setPassword(password);        // 创立出一个 Token 内容实质基于前台的用户名和明码(不肯定正确)        UsernamePasswordToken token = new UsernamePasswordToken(username, password);        // 获取 subject 认证主体(这里也就是当初登录的用户)        Subject subject = SecurityUtils.getSubject();        try{            // 认证开始,这里会跳转到自定义的 UserRealm 中            subject.login(token);            // 能够存储到 session 中            request.getSession().setAttribute("user", user);            return "views/success";        }catch(Exception e){            // 捕捉异样            e.printStackTrace();            request.getSession().setAttribute("user", user);            request.setAttribute("errorMsg", "兄弟,用户名或明码谬误");            return "views/login";        }    }}

8.最终成果:

首先是http://localhost:8080/index

登录界面:

表单提交后,就返回值到UserController那个Login办法,认证:

这样咱们就登录胜利了,并且是superAdmin的权限,能够查看A,B,C

而用户张三,只能看见A

到此结束,本博客借鉴:博客,须要源码的请查看此博客。