【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; }}