授权

授权方式

  • 编程式

    1
    2
    3
    4
    5
    6
    Subject subject = SecurityUtils.getSubject();
    if(subject.hasRole("admin")){
    //有权限
    }else{
    //无权限
    }
  • 注解式

    1
    2
    3
    4
    @RequiresRoles("admin")
    public void hello(){
    //有权限
    }
  • 标签式

    1
    2
    3
    4
    5
    JSP/GSP 标签:在JSP/GSP 页面通过相应的标签完成:
    <shiro:hasRole name="admin">
    <!-- 有权限 -->
    </shiro:hasRole>
    注意:Thymeleaf 中使用shiro需要额外集成!

授权实现

首先模拟一下假数据,不操作数据库

在CustomerRealm下有一个doGetAuthorizationInfo方法

这里就负责授权的操作

现在就假设普通用户能看到用户管理 admin可以看到所有的菜单

标签式:
shiro标签引入

1
<%@taglib prefix="shiro" uri="http://shiro.apache.org/tags"%>

在index.jsp添加上shiro标签

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<ul>
<shiro:hasAnyRoles name="user,admin">
<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>

<shiro:hasRole name="admin">
<li><a href="">商品管理</a></li>
<li><a href="">订单管理</a></li>
<li><a href="">物流管理</a></li>
</shiro:hasRole>
</ul>

CustomerRealm中写上对应的授权方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
//获取身份信息
String primaryPrincipal = (String) principalCollection.getPrimaryPrincipal();
System.out.println("调用授权验证:"+primaryPrincipal);
//根据主身份信息获取角色和权限信息
if("xiaochen".equals(primaryPrincipal)){
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();

simpleAuthorizationInfo.addRole("user");

simpleAuthorizationInfo.addStringPermission("user:find:*");
simpleAuthorizationInfo.addStringPermission("user:update:*");

return simpleAuthorizationInfo;
}

return null;
}

现在xiaochen这个账号就是个普通用户 可以看到用户管理 并且对所有文件都具有修改和查找的操作权力 他只能看见修改和查找的功能 看到不添加删除功能

这就完成了通过标签的方式完成了授权的操作,实际开发中一般不会使用这种方式去完成授权操作

代码式:
创建一个OrderController

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package com.ceit.springboot_jsp_shiro.controller;

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.subject.Subject;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
@RequestMapping("order")
public class OrderController {

@RequestMapping("save")
public String save(){
//获取主体对象
Subject subject = SecurityUtils.getSubject();
//代码方式
if (subject.hasRole("admin")) {
System.out.println("保存订单");
}else{
System.out.println("无权访问");
}

//基于权限字符串

return "redirect:/index.jsp";
}
}

这是个简单的演示 如果xiaochen是admin 那就控制台输出保存订单

注解式:
将上面的代码添加上一个注解即可

1
2
@RequestMapping("save")
@RequiresRoles("admin")//用来判断角色

如果要判断多个角色
1
@RequiresRoles(value={"admin","user"})//用来判断角色  同时具有admin和user身份

还有判断权限字符串的注解

1
@RequiresPermissions("user:update:01") //用来判断权限字符串

授权数据持久化

角色信息获取

首先要完成通过数据库授权的操作,自然是先要去设计一下库表了

这里就不讲过程了 直接贴sql文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
/*
Navicat MySQL Data Transfer

Source Server : localhost
Source Server Version : 80020
Source Host : localhost:3306
Source Database : shiro_spring

Target Server Type : MYSQL
Target Server Version : 80020
File Encoding : 65001

Date: 2020-12-07 17:40:29
*/

SET FOREIGN_KEY_CHECKS=0;

-- ----------------------------
-- Table structure for t_pers
-- ----------------------------
DROP TABLE IF EXISTS `t_pers`;
CREATE TABLE `t_pers` (
`id` int NOT NULL AUTO_INCREMENT,
`name` varchar(80) CHARACTER SET utf8 COLLATE utf8_general_ci 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 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 NOT NULL,
`roleid` int DEFAULT NULL,
`permsid` int 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 NOT NULL AUTO_INCREMENT,
`username` varchar(255) DEFAULT NULL,
`password` varchar(255) DEFAULT NULL,
`salt` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;

-- ----------------------------
-- Table structure for t_user_role
-- ----------------------------
DROP TABLE IF EXISTS `t_user_role`;
CREATE TABLE `t_user_role` (
`id` int NOT NULL,
`userid` int DEFAULT NULL,
`roleid` int DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

t_role表内容:

id name
1 admin
2 user
3 product

t_user_role表内容:

id userid roleid
1 1 1
2 2 2
3 2 3

avatar

接着就是构建实体类Perms 和 Role

Role:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package com.ceit.springboot_jsp_shiro.entity;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;

@Data
@Accessors(chain = true)
@AllArgsConstructor
@NoArgsConstructor
public class Role {
private String id;
private String name;

public String getId() {
return id;
}

public void setId(String id) {
this.id = id;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}
}

Perms:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
package com.ceit.springboot_jsp_shiro.entity;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;

@Data
@Accessors(chain = true)
@AllArgsConstructor
@NoArgsConstructor
public class Perms {
private String id;
private String name;
private String url;

public String getId() {
return id;
}

public void setId(String id) {
this.id = id;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public String getUrl() {
return url;
}

public void setUrl(String url) {
this.url = url;
}
}

User实体类中也应该添加上一个roles

1
2
3
4
5
6
7
8
9
10
//定义角色集合
private List<Role> roles;

public List<Role> getRoles() {
return roles;
}

public void setRoles(List<Role> roles) {
this.roles = roles;
}

UserDAO中开发一个查询方法

1
2
//根据用户名查询所有角色
User findRolesByUserName(String username);

对应的Service一样

1
2
//根据用户名查询所有角色
User findRolesByUserName(String username);

接着在UserDAOMapper.xml中添加select方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<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>

接下来就是业务层的实现了

1
2
3
4
@Override
public User findRolesByUserName(String username) {
return userDAO.findRolesByUserName(username);
}

都做好了 就可以回到我们的自定义Realm了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
    @Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
//获取身份信息
String primaryPrincipal = (String) principalCollection.getPrimaryPrincipal();
System.out.println("调用授权验证:"+primaryPrincipal);
//根据主身份信息获取角色和权限信息
// if("xiaochen".equals(primaryPrincipal)){
// SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
//
// simpleAuthorizationInfo.addRole("user");
//
// simpleAuthorizationInfo.addStringPermission("user:find:*");
// simpleAuthorizationInfo.addStringPermission("user:update:*");
//
// return simpleAuthorizationInfo;
// }
UserService userService = (UserService) ApplicationContextUtils.getBean("userService");
User user = userService.findRolesByUserName(primaryPrincipal);
//授权角色信息
if(!CollectionUtils.isEmpty(user.getRoles())){
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
user.getRoles().forEach(role ->{
simpleAuthorizationInfo.addRole(role.getName());
});
return simpleAuthorizationInfo;
}
return null;
}

这样就完成了从写死数据到连接数据库的授权操作了

权限信息获取

获取权限信息在数据库就比较麻烦了 因为我们既需要查到他的角色信息还要查到他的权限信息

现在需要定义一个权限集合 但权限是绑定角色的 所以就需要在Role实体类中定义一个权限的集合

1
2
//定义权限集合
private List<Perms> perms;

UserDAO中也添加上方法

1
2
//根据角色id查询权限集合的方法
List<Perms> findPermsByRoleId(String id);

Service也加上

1
2
//根据角色id查询权限集合的方法
List<Perms> findPermsByRoleId(String id);

Servicelmpl业务层实现一下

1
2
3
4
@Override
public List<Perms> findPermsByRoleId(String id) {
return userDAO.findPermsByRoleId(id);
}

对应的Mapper添加上select
1
2
3
4
5
6
7
8
9
<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>

这些都做完了 就可以去自定义Realm中添加获取权限信息了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
    @Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
//获取身份信息
String primaryPrincipal = (String) principalCollection.getPrimaryPrincipal();
System.out.println("调用授权验证:"+primaryPrincipal);
//根据主身份信息获取角色和权限信息
// if("xiaochen".equals(primaryPrincipal)){
// SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
//
// simpleAuthorizationInfo.addRole("user");
//
// simpleAuthorizationInfo.addStringPermission("user:find:*");
// simpleAuthorizationInfo.addStringPermission("user:update:*");
//
// return simpleAuthorizationInfo;
// }
UserService userService = (UserService) ApplicationContextUtils.getBean("userService");
User user = userService.findRolesByUserName(primaryPrincipal);
//授权角色信息
if(!CollectionUtils.isEmpty(user.getRoles())){
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
user.getRoles().forEach(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;
}

这样,以数据库的认证授权操作就全部做完了

剩下的就是去优化了 因为现在这样写 每次登录的时候都会去查数据库 所以我们要将授权角色信息和权限信息进行一个缓存 来优化我们的认证和授权