【7】测试
【7.1】启动



点击apply然后点击OK

【7.2】登录过滤
访问http://localhost:8080/platform/home的时候,会被


【7.3】角色过滤
使用“admin”用户登录,密码:123

根据SecurityServiceImpl我们可以知道使用admin账号

登录成功之后:

此时点击“列表”,因为当前admin用户是有admin角色

所有可以正常访问

点击“添加”,因为当前admin用户是没有order:add的资源

所以回401

【7.4】资源过滤
点击“退出”

使用“jay”用户登录,密码为123

点击“添加”

因为SecurityServiceImpl中为jay用户添加如下的资源

点击“添加”之后正常访问

点击“列表”之后,因为“jay”用户满意“admin”角色,所以访问受限

4、web项目授权
前面我们学习了基于ini文件配置方式来完成授权,下面我们来看下其他2种方式的授权
【1】基于代码
【1.1】登录相关

【1.2】角色相关

【1.3】资源相关

【1.4】案例
【1.4.1】创建项目
拷贝shiro-day01-07web新建shiro-day01-08web-java

【1.4.2】修改shiro.ini
#声明自定义的realm,且为安全管理器指定realms[main]definitionRealm=com.itheima.shiro.realm.DefinitionRealmsecurityManager.realms=$definitionRealm#用户退出后跳转指定JSP页面logout.redirectUrl=/login.jsp#若没有登录,则被authc过滤器重定向到login.jsp页面authc.loginUrl = /login.jsp[urls]/login=anon#发送/home请求需要先登录#/home= authc#发送/order/list请求需要先登录#/order-list = roles[admin]#提交代码需要order:add权限#/order-add = perms["order:add"]#更新代码需要order:del权限#/order-del = perms["order:del"]#发送退出请求则用退出过滤器/logout = logout【1.4.3】登录相关
修改HomeServlet的doPost方法
package com.itheima.shiro.web;import org.apache.shiro.SecurityUtils;import org.apache.shiro.subject.Subject;import javax.servlet.ServletException;import javax.servlet.annotation.WebServlet;import javax.servlet.http.HttpServlet;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.IOException;/** * @Description:系统home页面 */@WebServlet(urlPatterns = "/home")public class HomeServlet extends HttpServlet {    @Override    protected void doGet(HttpServletRequest req, HttpServletResponse resp)            throws ServletException, IOException {        doPost(req, resp);    }    @Override    protected void doPost(HttpServletRequest req, HttpServletResponse resp)            throws ServletException, IOException {        //通过subjectd对象去判断是否登录        Subject subject = SecurityUtils.getSubject();        boolean flag  = subject.isAuthenticated();        if (flag){            resp.sendRedirect("home.jsp");        }else {            req.getRequestDispatcher("/login").forward(req, resp);        }    }}访问http://localhost:8080/platform/home 进行debug

此时我们通过subject.isAuthenticated()判断是否登录,如果登录则重定向到home.jsp,如果没有登录则转发到/login对应的servlet
【1.4.4】角色相关
修改OrderListServlet的doPost方法,判断是否有admin角色,如果有则转发order-list.jsp,没有则转发/login
package com.itheima.shiro.web;import org.apache.shiro.SecurityUtils;import org.apache.shiro.subject.Subject;import javax.servlet.ServletException;import javax.servlet.annotation.WebServlet;import javax.servlet.http.HttpServlet;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.IOException;/** * @Description:订单列表 */@WebServlet(urlPatterns = "/order-list")public class OrderListServlet extends HttpServlet {    @Override    protected void doGet(HttpServletRequest req, HttpServletResponse resp)            throws ServletException, IOException {        doPost(req, resp);    }    @Override    protected void doPost(HttpServletRequest req, HttpServletResponse resp)            throws ServletException, IOException {        Subject subject = SecurityUtils.getSubject();        //判断当前角色        boolean flag = subject.hasRole("admin");        if (flag){            req.getRequestDispatcher("order-list.jsp").forward(req, resp);        }else {            req.getRequestDispatcher("/login").forward(req, resp);        }    }}访问http://localhost:8080/platform/order-list

因为此时我未登录,也就是说当前没有admin角色,这是通过subject.hasRole("admin")返回未false
【1.4.5】资源相关
修改OrderAddServlet
package com.itheima.shiro.web;import org.apache.shiro.SecurityUtils;import org.apache.shiro.subject.Subject;import javax.servlet.ServletException;import javax.servlet.annotation.WebServlet;import javax.servlet.http.HttpServlet;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.IOException;/** * @Description:添加页码 */@WebServlet(urlPatterns = "/order-add")public class OrderAddServlet extends HttpServlet {    @Override    protected void doGet(HttpServletRequest req, HttpServletResponse resp)            throws ServletException, IOException {        doPost(req, resp);    }    @Override    protected void doPost(HttpServletRequest req, HttpServletResponse resp)            throws ServletException, IOException {        Subject subject = SecurityUtils.getSubject();        //判断是否有对应资源        boolean flag = subject.isPermitted("order:add");        if (flag){            req.getRequestDispatcher("order-add.jsp").forward(req, resp);        }else {            req.getRequestDispatcher("/login").forward(req, resp);        }    }}访问http://localhost:8080/platform/order-add

因为此时我未登录,也就是说当前没有order:add资源,通过 subject.isPermitted("order:add")返回未false
【2】基于Jsp标签
【2.1】使用方式
Shiro提供了一套JSP标签库来实现页面级的授权控制, 在使用Shiro标签库前,首先需要在JSP引入shiro标签:
【2.2】相关标签

【2.3】案例
【2.3.1】新建项目
拷贝shiro-day01-08web-java新建shiro-day01-09web-jsp-taglib项目

【2.3.2】修改home.jsp
     退出 列表 添加 
【2.3.3】测试
访问http://localhost:8080/platform/login
使用admin/123登录

这个时候我们只能看见“列表”,看不见“添加”,点击“退出”
使用jay/123登录

这个时候我们只能看见“添加”,看不见“列表”,点击“退出”
需要注意的是,这里只是页面是否显示内容,不能防止盗链的发生
第五章 Springboot集成Shiro
1、技术栈
主框架:springboot
响应层:springMVC
持久层:mybatis
事务控制:jta
前端技术:easyui
2、数据库设计
【1】数据库图解

sh_user:用户表,一个用户可以有多个角色
sh_role:角色表,一个角色可以有多个资源
sh_resource:资源表
sh_user_role:用户角色中间表
sh_role_resource:角色资源中间表
【2】数据库脚本
sh_user
CREATE TABLE `sh_user` (  `ID` varchar(36) NOT NULL COMMENT '主键',  `LOGIN_NAME` varchar(36) DEFAULT NULL COMMENT '登录名称',  `REAL_NAME` varchar(36) DEFAULT NULL COMMENT '真实姓名',  `NICK_NAME` varchar(36) DEFAULT NULL COMMENT '昵称',  `PASS_WORD` varchar(150) DEFAULT NULL COMMENT '密码',  `SALT` varchar(36) DEFAULT NULL COMMENT '加密因子',  `SEX` int(11) DEFAULT NULL COMMENT '性别',  `ZIPCODE` varchar(36) DEFAULT NULL COMMENT '邮箱',  `ADDRESS` varchar(36) DEFAULT NULL COMMENT '地址',  `TEL` varchar(36) DEFAULT NULL COMMENT '固定电话',  `MOBIL` varchar(36) DEFAULT NULL COMMENT '电话',  `EMAIL` varchar(36) DEFAULT NULL COMMENT '邮箱',  `DUTIES` varchar(36) DEFAULT NULL COMMENT '职务',  `SORT_NO` int(11) DEFAULT NULL COMMENT '排序',  `ENABLE_FLAG` varchar(18) DEFAULT NULL COMMENT '是否有效',  PRIMARY KEY (`ID`)) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=COMPACT COMMENT='用户表';sh_role
CREATE TABLE `sh_role` (  `ID` varchar(36) NOT NULL COMMENT '主键',  `ROLE_NAME` varchar(36) DEFAULT NULL COMMENT '角色名称',  `LABEL` varchar(36) DEFAULT NULL COMMENT '角色标识',  `DESCRIPTION` varchar(200) DEFAULT NULL COMMENT '角色描述',  `SORT_NO` int(36) DEFAULT NULL COMMENT '排序',  `ENABLE_FLAG` varchar(18) DEFAULT NULL COMMENT '是否有效',  PRIMARY KEY (`ID`)) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=COMPACT COMMENT='用户角色表';sh_resource
CREATE TABLE `sh_resource` (  `ID` varchar(36) NOT NULL COMMENT '主键',  `PARENT_ID` varchar(36) DEFAULT NULL COMMENT '父资源',  `RESOURCE_NAME` varchar(36) DEFAULT NULL COMMENT '资源名称',  `REQUEST_PATH` varchar(200) DEFAULT NULL COMMENT '资源路径',  `LABEL` varchar(200) DEFAULT NULL COMMENT '资源标签',  `ICON` varchar(20) DEFAULT NULL COMMENT '图标',  `IS_LEAF` varchar(18) DEFAULT NULL COMMENT '是否叶子节点',  `RESOURCE_TYPE` varchar(36) DEFAULT NULL COMMENT '资源类型',  `SORT_NO` int(11) DEFAULT NULL COMMENT '排序',  `DESCRIPTION` varchar(200) DEFAULT NULL COMMENT '描述',  `SYSTEM_CODE` varchar(36) DEFAULT NULL COMMENT '系统code',  `IS_SYSTEM_ROOT` varchar(18) DEFAULT NULL COMMENT '是否根节点',  `ENABLE_FLAG` varchar(18) DEFAULT NULL COMMENT '是否有效',  PRIMARY KEY (`ID`)) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=COMPACT COMMENT='资源表';sh_role_resource
CREATE TABLE `sh_role_resource` (  `ID` varchar(36) NOT NULL,  `ENABLE_FLAG` varchar(18) DEFAULT NULL,  `ROLE_ID` varchar(36) DEFAULT NULL,  `RESOURCE_ID` varchar(36) DEFAULT NULL,  PRIMARY KEY (`ID`)) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=COMPACT COMMENT='角色资源表';sh_user_role
CREATE TABLE `sh_user_role` (  `ID` varchar(36) NOT NULL,  `ENABLE_FLAG` varchar(18) DEFAULT NULL,  `USER_ID` varchar(36) DEFAULT NULL,  `ROLE_ID` varchar(36) DEFAULT NULL,  PRIMARY KEY (`ID`)) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=COMPACT COMMENT='用户角色表';3、项目骨架

4、ShiroDbRealm定义
【1】图解

【2】原理分析
(1)、ShiroDbRealmImpl继承ShiroDbRealm向上继承AuthorizingRealm,ShiroDbRealmImpl实例化时会创建密码匹配器HashedCredentialsMatcher实例,HashedCredentialsMatcher指定hash次数与方式,交于AuthenticatingRealm
(2)、调用login方法后,最终调用doGetAuthenticationInfo(AuthenticationToken authcToken)方法,拿到SimpleToken的对象,调用UserBridgeService的查找用户方法,把ShiroUser对象、密码和salt交于SimpleAuthenticationInfo去认证
(3)、访问需要鉴权时,调用doGetAuthorizationInfo(PrincipalCollection principals)方法,然后调用UserBridgeService的授权验证
【3】核心类代码
【3.1】ShiroDbRealm
package com.itheima.shiro.core;import org.apache.shiro.authc.AuthenticationInfo;import org.apache.shiro.authc.AuthenticationToken;import org.apache.shiro.authz.AuthorizationInfo;import org.apache.shiro.realm.AuthorizingRealm;import org.apache.shiro.subject.PrincipalCollection;import javax.annotation.PostConstruct;/** * * @Description shiro自定义realm */public abstract class ShiroDbRealm extends AuthorizingRealm {    /**     * @Description 认证     * @param authcToken token对象     * @return      */    public abstract AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authcToken) ;    /**     * @Description 鉴权     * @param principals 令牌     * @return     */    public abstract AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals);    /**     * @Description 密码匹配器     */    @PostConstruct    public abstract void initCredentialsMatcher() ;}【3.2】ShiroDbRealmImpl
package com.itheima.shiro.core.impl;import com.itheima.shiro.constant.SuperConstant;import com.itheima.shiro.core.base.ShiroUser;import com.itheima.shiro.core.base.SimpleToken;import com.itheima.shiro.core.ShiroDbRealm;import com.itheima.shiro.core.bridge.UserBridgeService;import com.itheima.shiro.pojo.User;import com.itheima.shiro.utils.BeanConv;import com.itheima.shiro.utils.DigestsUtil;import com.itheima.shiro.utils.EmptyUtil;import org.apache.shiro.authc.AuthenticationInfo;import org.apache.shiro.authc.AuthenticationToken;import org.apache.shiro.authc.SimpleAuthenticationInfo;import org.apache.shiro.authc.UnknownAccountException;import org.apache.shiro.authc.credential.HashedCredentialsMatcher;import org.apache.shiro.authz.AuthorizationInfo;import org.apache.shiro.subject.PrincipalCollection;import org.apache.shiro.util.ByteSource;import org.springframework.beans.factory.annotation.Autowired;/** * @Description:自定义shiro的实现 */public class ShiroDbRealmImpl extends ShiroDbRealm {    @Autowired    private UserBridgeService userBridgeService;    /**     * @Description 认证方法     * @param authcToken 校验传入令牌     * @return AuthenticationInfo     */    @Override    public AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authcToken) {        SimpleToken token = (SimpleToken)authcToken;        User user  = userBridgeService.findUserByLoginName(token.getUsername());        if(EmptyUtil.isNullOrEmpty(user)){            throw new UnknownAccountException("账号不存在");        }        ShiroUser shiroUser = BeanConv.toBean(user, ShiroUser.class);        shiroUser.setResourceIds(userBridgeService.findResourcesIdsList(user.getId()));        String salt = user.getSalt();        String password = user.getPassWord();        return new SimpleAuthenticationInfo(shiroUser, password, ByteSource.Util.bytes(salt), getName());    }    /**     * @Description 授权方法     * @param principals SimpleAuthenticationInfo对象第一个参数     * @return     */    @Override    public AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {        ShiroUser shiroUser = (ShiroUser) principals.getPrimaryPrincipal();        return userBridgeService.getAuthorizationInfo(shiroUser);    }    /**     * @Description 加密方式     */    @Override    public void initCredentialsMatcher() {        HashedCredentialsMatcher matcher = new HashedCredentialsMatcher(SuperConstant.HASH_ALGORITHM);        matcher.setHashIterations(SuperConstant.HASH_INTERATIONS);        setCredentialsMatcher(matcher);    }}【3.3】SimpleToken
package com.itheima.shiro.core.base;import org.apache.shiro.authc.UsernamePasswordToken;/** * @Description 自定义tooken */public class SimpleToken extends UsernamePasswordToken {    /** serialVersionUID */    private static final long serialVersionUID = -4849823851197352099L;    private String tokenType;    private String quickPassword;    /**     * Constructor for SimpleToken     * @param tokenType     */    public SimpleToken(String tokenType, String username,String password) {        super(username,password);        this.tokenType = tokenType;    }    public SimpleToken(String tokenType, String username,String password,String quickPassword) {        super(username,password);        this.tokenType = tokenType;        this.quickPassword = quickPassword;    }    public String getTokenType() {        return tokenType;    }    public void setTokenType(String tokenType) {        this.tokenType = tokenType;    }    public String getQuickPassword() {        return quickPassword;    }    public void setQuickPassword(String quickPassword) {        this.quickPassword = quickPassword;    }}