引言:Shiro是apache旗下一个开源框架,它将软件系统的安全认证相关的功能抽取出来,实现用户身份认证,权限授权、加密、会话管理等功能,组成了一个通用的安全认证框架
Shiro授权实现方式
代码设置的伪数据代码均为xiaozhang用户名,密码123456,角色为user。
shiro有三种授权实现方式,如下:
-
编程式
Subject subject = SecurityUtils.getSubject(); if(subject.hasRole(“admin”)) { //有权限 } else { //无权限 }
-
注解式
@RequiresRoles("admin") public void hello() { //有权限 }
-
标签式
JSP/GSP 标签:在JSP/GSP 页面通过相应的标签完成: <shiro:hasRole name="admin"> <!— 有权限—> </shiro:hasRole> 注意: Thymeleaf 中使用shiro需要额外集成!
标签式授权
0.页面资源授权 index.jsp
shiro模板
—— <%@taglib prefix=“shiro” uri=“http://shiro.apache.org/tags” %>多角色管理
—— <%–具有admin和user才能看见–%>
<shiro:hasAnyRoles name=“admin,user”>单角色管理
—— <%–具有admin用户才能看见–%>
<shiro:hasRole name=“admin”>
<%@page contentType="text/html; UTF-8" pageEncoding="UTF-8" isELIgnored="false" %>
<%@taglib prefix="shiro" uri="http://shiro.apache.org/tags"%>
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<%--受限资源--%>
<h1>系统主页v1.0</h1>
<a href="${pageContext.request.contextPath}/user/logout">退出登录</a>
<ul>
<%--具有admin和user才能看见--%>
<shiro:hasAnyRoles name="admin,user">
<li><a href="">用户管理</a></li>
</shiro:hasAnyRoles>
<%--具有admin用户才能看见--%>
<shiro:hasRole name="admin">
<li><a href="">商品管理</a></li>
<li><a href="">订单管理</a></li>
<li><a href="">物流管理</a></li>
</shiro:hasRole>
</ul>
</body>
</html>
1.自定义Realm的授权处理 CustomerRealm
获取身份信息
String primaryPrincipal = (String) principal.getPrimaryPrincipal();伪数据判断
、根据主身份信息获取角色和权限信息 if (“xiaozhang”.equals(primaryPrincipal)){……伪数据
:如果用户登录的用户名为xiaozhang则是user普通用户在index.jsp看到对应的标签权限
public class CustomerRealm extends AuthorizingRealm {
//授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principal) {
//获取身份信息
String primaryPrincipal = (String) principal.getPrimaryPrincipal();
System.out.println("调用授权验证"+primaryPrincipal);
//伪数据、根据主身份信息获取角色和权限信息
if ("xiaozhang".equals(primaryPrincipal)){
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
//权限为普通用户
simpleAuthorizationInfo.addRole("user");
return simpleAuthorizationInfo;
}
return null;
}
//认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
//伪代码数据
//获取到前台传输的token
String principal = (String) token.getPrincipal();
//在工厂工具类ApplicationContextUtils中获取service对象,默认策略是首字母小写,获取我们的userServiceImpl对象
UserService userService = (UserService) ApplicationContextUtils.getBean("userServiceImpl");
//调用UserService
User user = userService.findByUserName(principal);
// 旧版伪代码
// if ("xiaozhang".equals(principal)){
// //成功返回创建的对象信息
// return new SimpleAuthenticationInfo(principal,"123",this.getName());
// }
//判断
//第一个参数用户名
//第二个参数密码
//第三个参数随机盐
//第四个参数是Realm的名字
if (!ObjectUtils.isEmpty(user)){
return new SimpleAuthenticationInfo(user.getUsername(),user.getPassword(), ByteSource.Util.bytes(user.getSalt()),this.getName());
}
return null;
}
}
- 用户为xiaozhang的普通用户user只能看到用户管理权限
- 参考英文标识
admin管理员
user普通用户
guest游客
权限字符串式授权(标签处)
权限字符串式授权
—— <shiro:hasPermission name=“user:add:*”>
0.页面资源授权index.jsp
<%@page contentType="text/html; UTF-8" pageEncoding="UTF-8" isELIgnored="false" %>
<%@taglib prefix="shiro" uri="http://shiro.apache.org/tags"%>
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<%--受限资源--%>
<h1>系统主页v1.0</h1>
<a href="${pageContext.request.contextPath}/user/logout">退出登录</a>
<ul>
<%--具有admin和user才能看见--%>
<shiro:hasAnyRoles name="admin,user">
<li><a href="">用户管理</a>
<ul>
<shiro:hasPermission name="user:add:*">
<li><a href="">添加</a></li>
</shiro:hasPermission>
<shiro:hasPermission name="user:delete:*">
<li><a href="">删除</a></li>
</shiro:hasPermission>
<shiro:hasPermission name="user:update:*">
<li><a href="">修改</a></li>
</shiro:hasPermission>
<shiro:hasPermission name="user:find:*">
<li><a href="">查询</a></li>
</shiro:hasPermission>
</ul>
</li>
</shiro:hasAnyRoles>
<%--具有admin用户才能看见--%>
<shiro:hasRole name="admin">
<li><a href="">商品管理</a></li>
<li><a href="">订单管理</a></li>
<li><a href="">物流管理</a></li>
</shiro:hasRole>
</ul>
</body>
</html>
1.自定义Realm的授权处理 CustomerRealm
权限字符串式授权
simpleAuthorizationInfo.addStringPermission(“user:find:*”);在index.jsp看到对应的标签权限
public class CustomerRealm extends AuthorizingRealm {
//授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principal) {
//获取身份信息
String primaryPrincipal = (String) principal.getPrimaryPrincipal();
System.out.println("调用授权验证"+primaryPrincipal);
//伪数据、根据主身份信息获取角色和权限信息
if ("xiaozhang".equals(primaryPrincipal)){
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
//权限为普通用户
simpleAuthorizationInfo.addRole("user");
//普通用户只能进行查询和修改权限
simpleAuthorizationInfo.addStringPermission("user:find:*");
simpleAuthorizationInfo.addStringPermission("user:update:*");
return simpleAuthorizationInfo;
}
return null;
}
//认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
//伪代码数据
//获取到前台传输的token
String principal = (String) token.getPrincipal();
//在工厂工具类ApplicationContextUtils中获取service对象,默认策略是首字母小写,获取我们的userServiceImpl对象
UserService userService = (UserService) ApplicationContextUtils.getBean("userServiceImpl");
//调用UserService
User user = userService.findByUserName(principal);
// 旧版伪代码
// if ("xiaozhang".equals(principal)){
// //成功返回创建的对象信息
// return new SimpleAuthenticationInfo(principal,"123",this.getName());
// }
//判断
//第一个参数用户名
//第二个参数密码
//第三个参数随机盐
//第四个参数是Realm的名字
if (!ObjectUtils.isEmpty(user)){
return new SimpleAuthenticationInfo(user.getUsername(),user.getPassword(), ByteSource.Util.bytes(user.getSalt()),this.getName());
}
return null;
}
}
- 用户为xiaozhang的普通用户user只能看到用户管理权限,然后只能操作查询和修改操作
编程式授权(方法体内)
0.新建OrderController
编程式授权
——subject.hasRole(“admin”)判断是否登录用户为admin
- `是admin的话进入http://localhost:8089/shiro/order/save提示保存订单,否则为无权访问``
@Controller
@RequestMapping("order")
public class OrderController {
//save方法
@RequestMapping("save")
public String save(){
Subject subject = SecurityUtils.getSubject();
if (subject.hasRole("admin")){
System.out.println("保存订单");
}else {
System.out.println("无权访问");
}
return "redirect:/index.jsp";
}
}
注解式编程(方法处)
0.使用注解OrderController
@RequiresRoles("admin")
//用来判断角色,只有当admin时候才能进入@RequiresRoles(value = {"admin","user"})
//同时具有admin和user角色才能进入@RequiresPermissions("user:update:01")
//用来判断权限字符串
@Controller
@RequestMapping("order")
public class OrderController {
//save方法
@RequestMapping("save")
@RequiresRoles("admin")//用来判断角色,只有当admin时候才能进入
// @RequiresRoles(value = {"admin","user"}) //同时具有admin和user角色才能进入
public String save(){
Subject subject = SecurityUtils.getSubject();
System.out.println("保存");
if (subject.hasRole("admin")){
System.out.println("保存订单");
}else {
System.out.println("无权访问");
}
return "redirect:/index.jsp";
}
}
- 登录账号xiaozhang为user角色,访问http://localhost:8089/shiro/order/save,注解判断没有权限进入
授权在数据持久化
Shiro授权实现方式部分(上文)实现的都是通过伪代码的方式实现,实际生产过程中,我们是通过数据持久化在数据库中实现的,接下来的教程就教如何实现持久化
设计角色和权限表
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for t_pers
-- ----------------------------
DROP TABLE IF EXISTS `t_pers`;
CREATE TABLE `t_pers` (
`id` int(6) NOT NULL AUTO_INCREMENT,
`name` varchar(80) DEFAULT NULL,
`url` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- ----------------------------
-- Table structure for t_role
-- ----------------------------
DROP TABLE IF EXISTS `t_role`;
CREATE TABLE `t_role` (
`id` int(6) NOT NULL AUTO_INCREMENT,
`name` varchar(60) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- ----------------------------
-- Table structure for t_role_perms
-- ----------------------------
DROP TABLE IF EXISTS `t_role_perms`;
CREATE TABLE `t_role_perms` (
`id` int(6) NOT NULL,
`roleid` int(6) DEFAULT NULL,
`permsid` int(6) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- ----------------------------
-- Table structure for t_user
-- ----------------------------
DROP TABLE IF EXISTS `t_user`;
CREATE TABLE `t_user` (
`id` int(6) NOT NULL AUTO_INCREMENT,
`username` varchar(40) DEFAULT NULL,
`password` varchar(40) DEFAULT NULL,
`salt` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
-- ----------------------------
-- Table structure for t_user_role
-- ----------------------------
DROP TABLE IF EXISTS `t_user_role`;
CREATE TABLE `t_user_role` (
`id` int(6) NOT NULL,
`userid` int(6) DEFAULT NULL,
`roleid` int(6) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
SET FOREIGN_KEY_CHECKS = 1;
授权实现
设计思路
- 建立角色表和权限表
- 登录用户小张角色为admin,拥有index.jsp里面所有权限,登录用户xiaodeng角色,只拥有index.jsp的用户管理的查询功能
- 登录时候创建一个返回信息对象,存放权限字符串,登录用户根据用户名查询角色,根据角色id查询权限
- 在前台渲染
0…创建dao方法UserDAO
-
根据用户名查询所有角色
User findRolesByUserName(String username); -
根据角色id查询权限集合
List findPermsByRoleId(String id);
@Mapper
public interface UserDAO {
//注册保存用户
void save(User user);
//登录验证数据库用户
User findByUserName(String username);
//根据用户名查询所有角色
User findRolesByUserName(String username);
//根据用户id查询权限集合
List<Perms> findPermsByRoleId(String id);
}
1.mapper实现UserDAOMapper
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ryoujou.dao.UserDAO">
<insert id="save" parameterType="User" useGeneratedKeys="true" keyProperty="id">
insert into t_user values(#{id},#{username},#{password},#{salt})
</insert>
<select id="findByUserName" parameterType="String" resultType="User">
select id,username,password,salt from t_user
where username = #{username}
</select>
<resultMap id="userMap" type="User">
<id column="uid" property="id"/>
<result column="username" property="username"/>
<!--角色信息-->
<collection property="roles" javaType="list" ofType="Role">
<id column="id" property="id"/>
<result column="rname" property="name"/>
</collection>
</resultMap>
<select id="findRolesByUserName" parameterType="String" resultMap="userMap">
SELECT u.id uid,u.username,r.id,r.NAME rname
FROM t_user u
LEFT JOIN t_user_role ur
ON u.id=ur.userid
LEFT JOIN t_role r
ON ur.roleid=r.id
WHERE u.username=#{username}
</select>
<select id="findPermsByRoleId" parameterType="String" resultType="Perms">
SELECT p.id,p.NAME,p.url,r.NAME
FROM t_role r
LEFT JOIN t_role_perms rp
ON r.id=rp.roleid
LEFT JOIN t_perms p ON rp.permsid=p.id
WHERE r.id=#{id}
</select>
</mapper>
2.Service接口
根据用户名查询所有角色
User findRolesByUserName(String username);根据角色id查询权限集合
List findPermsByRoleId(String id);
public interface UserService {
//注册用户方法
void register(User user);
// 登录验证数据库用户
User findByUserName(String username);
//根据用户名查询所有角色
User findRolesByUserName(String username);
//根据角色id查询权限集合
List<Perms> findPermsByRoleId(String id);
}
3.Service实现
@Service("userServiceImpl")
@Transactional
public class UserServiceImpl implements UserService {
@Autowired
private UserDAO userDAO;
@Override
public User findByUserName(String username) {
return userDAO.findByUserName(username);
}
@Override
public List<Perms> findPermsByRoleId(String id) {
return userDAO.findPermsByRoleId(id);
}
@Override
public User findRolesByUserName(String username) {
return userDAO.findRolesByUserName(username);
}
@Override
public void register(User user) {
//处理业务调用dao
//1.生成随机盐
String salt = SaltUtils.getSalt(8);
//2.将随机盐保存到数据
user.setSalt(salt);
//3.明文密码进行md5 + salt + hash散列
Md5Hash md5Hash = new Md5Hash(user.getPassword(), salt, 1024);
user.setPassword(md5Hash.toHex());
userDAO.save(user);
}
}
4.修改自定义CustomerRealm
public class CustomerRealm extends AuthorizingRealm {
//授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principal) {
//获取身份信息
String primaryPrincipal = (String) principal.getPrimaryPrincipal();
System.out.println("调用授权验证"+primaryPrincipal);
//根據主身份信息获取角色和权限信息
//在工厂工具类ApplicationContextUtils中获取service对象,默认策略是首字母小写,获取我们的userServiceImpl对象
UserService userService = (UserService) ApplicationContextUtils.getBean("userServiceImpl");
//根據主身份信息获取角色和权限信息实现
User user = userService.findRolesByUserName(primaryPrincipal);
//判断健壮性不为空
if (!CollectionUtils.isEmpty(user.getRoles())){
//创建一个身份权限信息
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
//遍历集合里面的roles
user.getRoles().forEach(role -> {
//把遍历后的role角色添加到创建的身份权限信息对象
simpleAuthorizationInfo.addRole(role.getName());
//权限信息
List<Perms> perms = userService.findPermsByRoleId(role.getId());
if(!CollectionUtils.isEmpty(perms)){
perms.forEach(perm->{
simpleAuthorizationInfo.addStringPermission(perm.getName());
});
}
});
//返回创建的身份权限信息对象
return simpleAuthorizationInfo;
}
return null;
}
//认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
//伪代码数据
//获取到前台传输的token
String principal = (String) token.getPrincipal();
//在工厂工具类ApplicationContextUtils中获取service对象,默认策略是首字母小写,获取我们的userServiceImpl对象
UserService userService = (UserService) ApplicationContextUtils.getBean("userServiceImpl");
//调用UserService
User user = userService.findByUserName(principal);
// 旧版伪代码
// if ("xiaozhang".equals(principal)){
// //成功返回创建的对象信息
// return new SimpleAuthenticationInfo(principal,"123",this.getName());
// }
//判断
//第一个参数用户名
//第二个参数密码
//第三个参数随机盐
//第四个参数是Realm的名字
if (!ObjectUtils.isEmpty(user)){
return new SimpleAuthenticationInfo(user.getUsername(),user.getPassword(), ByteSource.Util.bytes(user.getSalt()),this.getName());
}
return null;
}
}
启动测试结果
—end