【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.DefinitionRealm
securityManager.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标签:
<%@ taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>
【2.2】相关标签
【2.3】案例
【2.3.1】新建项目
拷贝shiro-day01-08web-java新建shiro-day01-09web-jsp-taglib项目
【2.3.2】修改home.jsp
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title></title>
</head>
<body>
<h6>
<a href="${pageContext.request.contextPath}/logout">退出</a>
<shiro:hasRole name="admin">
<a href="${pageContext.request.contextPath}/order-list">列表</a>
</shiro:hasRole>
<shiro:hasPermission name="order:add">
<a href="${pageContext.request.contextPath}/order-add">添加</a>
</shiro:hasPermission>
</h6>
</body>
</html>
【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;
}
}