loading...
SpringBoot整合Shiro
Published in:2022-01-31 | category: Shiro
Words: 7.1k | Reading time: 37min | reading:

1 整体思路

WnQlYd.png

2 配置环境

1.1 创建SpringBoot项目

WnBkfx.png

2.2 引入shiro依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!-- 引入shiro依赖-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring-boot-starter</artifactId>
<version>1.5.3</version>
</dependency>

<!--引入JSP解析依赖-->
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId>
</dependency>
<dependency>
<groupId>jstl</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>

2.3 修改视图

application.properties文件

1
2
3
4
5
6
7
8
# 应用名称
spring.application.name=springboot_jsp_shiro
spring.servlet.content-path=/shiro
# 应用服务 WEB 访问端口
server.port=8888

spring.mvc.view.prefix=/
spring.mvc.view.suffix=.jsp

2.4 修改配置

JSP与IDEA与SpringBoot存在一定的不兼容,修改此配置即可解决。

WawPuF.png

3 简单使用

3.1 创建配置类

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
/**
* 用来整合shiro框架相关的配置类
*/
@Configuration
public class ShiroConfig {

//1.创建shiroFilter //负责拦截所有请求
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager defaultWebSecurityManager){

ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
//给filter设置安全管理器
shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager);

//配置系统受限资源
//配置系统公共资源
Map<String,String> map = new HashMap<String,String>();

map.put("/index.jsp","authc");//authc 请求这个资源需要认证和授权

//默认认证界面路径---当认证不通过时跳转
shiroFilterFactoryBean.setLoginUrl("/login.jsp");
shiroFilterFactoryBean.setFilterChainDefinitionMap(map);

return shiroFilterFactoryBean;
}

//2.创建安全管理器
@Bean
public DefaultWebSecurityManager getDefaultWebSecurityManager(Realm realm){
DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
//给安全管理器设置
defaultWebSecurityManager.setRealm(realm);

return defaultWebSecurityManager;
}

//3.创建自定义realm
@Bean
public Realm getRealm(){
CustomerRealm customerRealm = new CustomerRealm();

return customerRealm;
}

}

3.2 配置自定义Realm

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//自定义realm
public class CustomerRealm extends AuthorizingRealm {

@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
return null;
}

@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
String principal =(String) authenticationToken.getPrincipal();
if("zhangsan".equals(principal)){
return new SimpleAuthenticationInfo(principal,"123456",this.getName());
}
return null;
}

}

3.3 JSP文件

index.jsp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<%@page contentType="text/html;utf-8" pageEncoding="utf-8" isELIgnored="false" %>
<!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>系统主页1.0</h1>
<a href="${pageContext.request.contextPath}/user/logout">退出登录</a>

<ul>
<li><a href="">用户管理</a></li>
<li><a href="">商品管理</a></li>
<li><a href="">订单管理</a></li>
<li><a href="">物流管理</a></li>
</ul>
</body>
</html>

login.jsp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<%@page contentType="text/html;utf-8" pageEncoding="utf-8" isELIgnored="false" %>
<!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>用户登录</h1>

<form action="${pageContext.request.contextPath}/user/login" method="post">
用户名:<input type="text" name="username"><br/>
密 码:<input type="password" name="password"><br/>
<input type="submit" value="登录">

</form>
</body>
</html>

3.4 简单测试

WnI7xe.png

  • 注意:
    • 默认在配置好shiro环境后默认环境中没有对项目中任何资源进行权限控制,所以现在项目中所有资源都可以通过路径访问
    • 加入权限控制后,访问index,则会自动跳转到login.jsp中
    • “/**”代表拦截项目中一切资源 “authc”代表shiro中的一个filter的别名

4 常见过滤器

注意:shiro提供多个默认的过滤器,我们可以用这些过滤器来配置控制指定url的权限:

配置缩写 对应的过滤器 功能
anon AnonymousFilter 指定url可以匿名访问
authc FormAuthenticationFilter 指定url需要form表单登录,默认会从请求中获取username、password、rememberMe等参数并尝试登录,如果登录不了就会跳转到loginUrl配置的路径。我们也可以用这个过滤器做默认的登录逻辑,但是一般都是我们自己在控制器写登录逻辑的,自己写的话出错返回的信息都可以定制嘛
authcBasic BasicHttpAuthenticationFilter 指定url需要basic登录
logout LogoutFilter 登出过滤器,配置指定的url就可以实现退出功能,非常方便
noSessionCreation NoSessionCreationFilter 禁止创建会话
perms PermissionAuthorizationFilter 需要指定权限才能访问
port PortFilter 需要指定端口才能访问
rest HttpMethodPermissionFilter 将http请求方法转化成相应的动词来构造一个权限字符串
roles RolesAuthorizationFilter 需要指定角色才能访问
ssl SslFilter 需要https请求才能访问
user UserFilter 需要已登录或“记住我”的用户才能访问

5 认证和退出的实现

5.1 login.jsp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<%@page contentType="text/html;utf-8" pageEncoding="utf-8" isELIgnored="false" %>
<!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>登录界面</h1>
<form action="${pageContext.request.contextPath}/user/login" method="post">
用户名:<input type="text" name="username" > <br/>
密码 : <input type="text" name="password"> <br>
<input type="submit" value="登录">
</form>
</body>
</html>

WagYPe.png

5.2 UserController

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
@Controller
@RequestMapping("/user")
public class UserController {

@Autowired
private UserService userService;

/**
* 用来处理身份认证
* @param username
* @param password
* @return
*/
@RequestMapping("login")
public String login(String username , String password){
//获取主体对象
Subject subject = SecurityUtils.getSubject();
try {
subject.login(new UsernamePasswordToken(username, password));
return "index";
}catch (UnknownAccountException e){
e.printStackTrace();
System.out.println("用户名错误!");
}catch (IncorrectCredentialsException e){
e.printStackTrace();
System.out.println("密码错误!!");
}

return "login";
}

/**
* 退出登录
*/
@RequestMapping("logout")
public String logout(){
Subject subject = SecurityUtils.getSubject();
subject.logout();
return "login";
}
}
  • 在认证过程中使用subject.login进行认证

5.3 自定义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
//自定义realm
public class CustomerRealm extends AuthorizingRealm {

@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
return null;
}

@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {

System.out.println("=============");

//从传过来的token获取到的用户名
String principal = (String) token.getPrincipal();
System.out.println("用户名"+principal);

//假设是从数据库获得的 用户名,密码
String password_db="123";
String username_db="zhangsan";

if (username_db.equals(principal)){
// SimpleAuthenticationInfo simpleAuthenticationInfo =
return new SimpleAuthenticationInfo(principal,"123", this.getName());
}

return null;
}

}

5.4 ShiroConfig

主要的Shiro配置类中声明:哪些是需要验证的资源,哪些是公开的资源。

注意:先配置公共资源,后配置需要认证/授权的资源

此时认证功能没有md5和随机盐的认证

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
/**
* 用来整合shiro框架相关的配置类
*/
@Configuration
public class ShiroConfig {

//1.创建shiroFilter //负责拦截所有请求
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager defaultWebSecurityManager){

ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
//给filter设置安全管理器
shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager);

//配置系统受限资源
//配置系统公共资源
Map<String,String> map = new HashMap<String,String>();
map.put("/user/login","anon");//anon 设置为公共资源 放行资源放在下面
map.put("/user/register","anon");//anon 设置为公共资源 放行资源放在下面
map.put("/register.jsp","anon");//anon 设置为公共资源 放行资源放在下面
map.put("/user/getImage","anon");

map.put("/**","authc");//authc 请求这个资源需要认证和授权

//默认认证界面路径---当认证不通过时跳转
shiroFilterFactoryBean.setLoginUrl("/login.jsp");
shiroFilterFactoryBean.setFilterChainDefinitionMap(map);

return shiroFilterFactoryBean;
}

//2.创建安全管理器
@Bean
public DefaultWebSecurityManager getDefaultWebSecurityManager(Realm realm){
DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
//给安全管理器设置
defaultWebSecurityManager.setRealm(realm);

return defaultWebSecurityManager;
}

//3.创建自定义realm
@Bean
public Realm getRealm(){
CustomerRealm customerRealm = new CustomerRealm();
return customerRealm;
}

}

6 MD5、Salt的认证实现

6.1 用户注册和随机盐处理

6.1.1 导入依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<!--mybatis相关依赖-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.2</version>
</dependency>

<!--mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.38</version>
</dependency>


<!--druid-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.19</version>
</dependency>

6.1.2 application.properties

1
2
3
4
5
6
7
8
spring.datasource.type= com.mysql.cj.jdbc.MysqlDataSource
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url= jdbc:mysql://localhost:3306/shiro?characterEncoding=UTF-8
spring.datasource.username=root
spring.datasource.password=root

mybatis.type-aliases-package=com.kenyang.springboot_jsp_shiro_entity
mybatis.mapper-locations=classpath:mapper/*.xml

6.1.3 创建数据库

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- 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;

SET FOREIGN_KEY_CHECKS = 1;

6.1.4 创建entity

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Data
@NoArgsConstructor
@AllArgsConstructor
@Accessors(chain = true)
public class User {

private int id;

private String username;

private String password;

private String salt;
}

6.1.5 创建DAO接口

1
2
3
4
5
6
@Mapper
public interface UserDao {

void save(User user);

}

6.1.6 开发mapper配置文件

注意:mapper文件的位置要在application.properties配置的目录下面

注意:mapper文件的命名与Dao接口保持一致

1
2
3
4
5
6
7
8
9
10
11
12
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.lut.dao.UserDao">

<insert id="save" parameterType="User" useGeneratedKeys="true" keyProperty="id">
insert into user values(#{id},#{username},#{password},#{salt})
</insert>

</mapper>

6.1.7 开发service接口

1
2
3
4
5
6
7
8
public interface UserService {

/**
* 注册用户方法
* @param user
*/
void register(User user);
}

6.1.8 创建salt工具类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class SaltUtil {

/**
* 生成salt的静态方法
* @param n
* @return
*/
public static String getSalt(int n){
char[] chars = "ABCDEFGHJKLMNOPQRSTUVWXYZabcdefghjklmnopqrstuvwxyz0123456789!@#$%^&*()".toCharArray();
StringBuilder sb = new StringBuilder();
for ( int i = 0 ; i < n ; i++){
char aChar = chars[new Random().nextInt(chars.length)];
sb.append(aChar);
}

return sb.toString();
}
}

6.1.9 开发service实现类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Service
@Transactional
public class UserServiceImpl implements UserService {

@Autowired(required = false)
private UserDao userDao;


@Override
public void register(User user) {
//处理业务调用
//1.生成随机盐
String salt = SaltUtil.getSalt(8);
System.out.println(salt);
//2.将随机盐保存到数据库
user.setSalt(salt);
//3.明文密码进行md5 + salt +hash散列
Md5Hash md5Hash = new Md5Hash(user.getPassword(), salt, 1024);
user.setPassword(md5Hash.toHex());

userDao.save(user);
}
}

6.1.10 开发Controller

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Controller
@RequestMapping("/user")
public class UserController {
/**
* 用户认证
*/
@RequestMapping("/regist")
public String register(User user){
try {
userService.register(user);
return "login";
}catch(Exception e){
e.printStackTrace();
return "regist";
}
}
}

6.1.11 设置公共资源

在ShiroConfig中添加

1
2
map.put("/user/register","anon");//anon 设置为公共资源 
map.put("/register.jsp","anon");//anon 设置为公共资源

6.1.12 测试

添加成功

Wab8OO.png

6.2 开发数据库认证

6.2.1 开发DAO

1
2
3
4
5
6
7
@Mapper
public interface UserDAO {

void save(User user);
//根据身份信息认证的方法
User findByUserName(String username);
}

6.2.2 开发mapper配置文件

1
2
3
4
<select id="findByUserName" parameterType="String" resultType="User">
select id,username,password,salt from user
where username = #{username}
</select>

6.2.3 开发Service接口

1
2
3
4
5
6
public interface UserService{
//注册用户方法
void register(User user);
//根据用户名查询业务的方法
User findByUserName(String username);
}

6.2.4 开发Service实现类

1
2
3
4
5
6
7
8
9
10
@Service("userService")
@Transactional
public class UserServiceImpl implements UserService {
@Autowired
private UserDAO userDAO;
@Override
public User findByUserName(String username) {
return userDAO.findByUserName(username);
}
}

6.2.5 开发工厂工具类

在工厂中获取bean对象的工具类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Component
public class ApplicationContextUtils implements ApplicationContextAware {

private static ApplicationContext context;

@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.context = applicationContext;
}

//根据bean名字获取工厂中指定bean 对象
public static Object getBean(String beanName){
System.out.println("beanName"+beanName);
Object object=context.getBean(beanName);
System.out.println("object"+object);
return context.getBean(beanName);
}
}

6.2.6 修改自定义realm

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class CustormerRealm extends AuthorizingRealm {
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
return null;
}

@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
//根据身份信息
String principal =(String) authenticationToken.getPrincipal();
//在工厂中获取service对象
UserService userServiceImpl =(UserService) ApplicationContextUtils.getBean("userServiceImpl");
User user = userServiceImpl.findUserByUserName(principal);
if(!ObjectUtils.isEmpty(user)){
return new SimpleAuthenticationInfo(user.getUsername(),user.getPassword(), ByteSource.Util.bytes(user.getSalt()),this.getName());
}

return null;
}
}

6.2.7 修改ShiroConfig中Realm

使用凭证匹配器以及hash散列

以及在 getShiroFilterFactoryBean 中添加公共资源

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
@Configuration
public class ShiroConfig {

/**
* 1.创建shiroFilter 负责拦截所有请求
*/
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager defaultWebSecurityManager){
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();

//给filter设置安全管理器
shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager);

//配置系统受限资源
//配置系统公共资源
Map<String,String> map = new HashMap<>();
//anon 设置为公共资源
map.put("/user/login","anon");
map.put("/user/*","anon");
map.put("/regist.jsp","anon");
//authc 请求这个资源需要认证和授权
map.put("/**","authc");
shiroFilterFactoryBean.setFilterChainDefinitionMap(map);

//默认认证界面路径
shiroFilterFactoryBean.setLoginUrl("/login.jsp");
return shiroFilterFactoryBean;
}

/**
* 创建安全管理器
* @param realm
* @return
*/
@Bean
public DefaultWebSecurityManager getDefaultWebSecurityManager( Realm realm){
DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
//给安全管理器设置
defaultWebSecurityManager.setRealm(realm);
return defaultWebSecurityManager;
}


/**
* 创建自定义Realm
* @return
*/
@Bean
public Realm getRealm(){
CustormerRealm custormerRealm = new CustormerRealm();
// 修改凭证校验匹配器
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
//设置加密算法为Md5
hashedCredentialsMatcher.setHashAlgorithmName("MD5");
//设置散列次数
hashedCredentialsMatcher.setHashIterations(1024);
custormerRealm.setCredentialsMatcher(hashedCredentialsMatcher);
return custormerRealm;
}
}

7 授权实现

7.1 没有数据库

7.1.1 页面资源授权

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
<%@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>系统主页1.0</h1>
<a href="${pageContext.request.contextPath}/user/logout">退出登录</a>

<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>
</body>
</html>

7.1.2 代码方式授权

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@RequestMapping("save")
public String save(){
System.out.println("进入方法");

//基于角色
//获取主体对象
Subject subject = SecurityUtils.getSubject();
//代码方式
if (subject.hasRole("admin")) {
System.out.println("保存订单!");
}else{
System.out.println("无权访问!");
}
//基于权限字符串
//....
return "redirect:/index.jsp";
}

7.1.3 方法调用授权

  • @RequiresRoles 用来基于角色进行授权
  • @RequiresPermissions 用来基于权限进行授权
1
2
3
4
5
6
7
8
9
10
11
12
13
@Controller
@RequestMapping("order")
public class OrderController {

@RequiresRoles(value={"admin","user"})//用来判断角色 同时具有 admin user
@RequiresPermissions("user:update:01") //用来判断权限字符串
@RequestMapping("save")
public String save(){
System.out.println("进入方法");
return "redirect:/index.jsp";
}

}

7.1.4 自定义Realm

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

simpleAuthorizationInfo.addRole("admin");
simpleAuthorizationInfo.addStringPermission("user:find:*");
simpleAuthorizationInfo.addStringPermission("user:update:*");
return simpleAuthorizationInfo;
}
return null;
}

7.2 连接数据库

7.2.1 授权数据持久化

WWHxBD.png

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
DROP TABLE IF EXISTS `pers`;
CREATE TABLE `pers` (
`id` int(11) NOT NULL,
`name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`url` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;

-- ----------------------------
-- Table structure for role
-- ----------------------------
DROP TABLE IF EXISTS `role`;
CREATE TABLE `role` (
`id` int(6) NOT NULL,
`name` varchar(60) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;

-- ----------------------------
-- Table structure for role_perms
-- ----------------------------
DROP TABLE IF EXISTS `role_perms`;
CREATE TABLE `role_perms` (
`id` int(11) NOT NULL,
`roleid` int(11) NULL DEFAULT NULL,
`permsid` int(11) NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;

-- ----------------------------
-- Table structure for user
-- ----------------------------
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(40) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`password` varchar(40) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`salt` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 9 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;

-- ----------------------------
-- Table structure for user_role
-- ----------------------------
DROP TABLE IF EXISTS `user_role`;
CREATE TABLE `user_role` (
`id` int(11) NOT NULL,
`userid` int(11) NULL DEFAULT NULL,
`roleid` int(11) NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;

SET FOREIGN_KEY_CHECKS = 1;

WWL1mT.png

7.2.2 创建实体类

  • User

    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
    import lombok.experimental.Accessors;

    import java.util.List;

    /**
    * TODO
    *
    * @author YoungKai
    * @date 2021/7/16 18:22
    */
    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    @Accessors(chain = true)
    public class User {

    private int id;

    private String username;

    private String password;

    private String salt;

    //定义角色集合
    private List<Role> roles;
    }

  • Role

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    @Data
    @Accessors(chain = true)
    @AllArgsConstructor
    @NoArgsConstructor
    public class Role {

    private String id;

    private String name;

    //定义权限集合
    private List<Perms> perms;
    }
  • Perms

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    @Data
    @Accessors(chain = true)
    @AllArgsConstructor
    @NoArgsConstructor
    public class Perms {

    private String id;

    private String name;

    private String url;
    }

7.2.3 创建dao方法

1
2
3
User findRolesByUserName(String username);

List<Perms> findPermsByRoleId(String id);

7.2.4 mapper实现

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
   <resultMap id="userMap" type="com.kenyang.springboot_jsp_shiro.entity.User">
<id column="uid" property="id"></id>
<result column="username" property="username"></result>
<collection property="roles" javaType="list" ofType="com.kenyang.springboot_jsp_shiro.entity.Role">
<id column="id" property="id"></id>
<id column="rname" property="name"></id>
</collection>
</resultMap>
<select id="findRolesByUserName" parameterType="string" resultMap="userMap">
select u.id uid , u.username , r.id , r.name rname
from user u
left join user_role ur
on u.id = ur.userid
left join role r
on ur.roleid = r.id
where u.username = #{username}
</select>

<select id="findPermsByRoleId" parameterType="String" resultType="com.kenyang.springboot_jsp_shiro.entity.Perms">
SELECT p.id,p.NAME,p.url,r.NAME
FROM role r
LEFT JOIN role_perms rp
ON r.id=rp.roleid
LEFT JOIN perms p
ON rp.permsid=p.id
WHERE r.id=#{id}
</select>

7.2.5 Service接口

1
2
3
4
5
6
7
8
9
10
11
/**
* 根据用户名查询所有角色
*/
User findRolesByUserName(String username);

/**
* 根据角色id查询权限集合
* @param id
* @return
*/
List<Perms> findPermsByRoleId(String id);

7.2.6 Service实现

1
2
3
4
5
6
7
8
9
@Override
public User findRolesByUserName(String username) {
return userDao.findRolesByUserName(username);
}

@Override
public List<Perms> findPermsByRoleId(String id) {
return userDao.findPermsByRoleId(id);
}

7.2.7 修改自定义Realm

注意:如果你创建了一个用户,并为这个用户授予了一个角色,但这个角色并未关联任何的 授权字符串,那么调用数据库获得的结果是 List<Perms> perms=[null],此时 perms已经被初始化,里面只有一个属性null,使用判空的方法无法判别,此时继续遍历会报出空指针异常,此时应当添加判断条件 perms.get(0)!=null

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
72
73
74
75
76
77
78
79
80
81
82
package com.kenyang.springboot_jsp_shiro.shiro.realms;

import com.kenyang.springboot_jsp_shiro.entity.Perms;
import com.kenyang.springboot_jsp_shiro.entity.Role;
import com.kenyang.springboot_jsp_shiro.entity.User;
import com.kenyang.springboot_jsp_shiro.service.UserService;
import com.kenyang.springboot_jsp_shiro.utils.ApplicationContextUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;
import org.apache.shiro.util.CollectionUtils;
import org.springframework.util.ObjectUtils;

import java.util.List;

/**
* 自定义Realm
*
* @author YoungKai
* @date 2021/7/15 17:51
*/
public class CustormerRealm extends AuthorizingRealm {
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
//获取身份信息
String primaryPrincipal = (String)principalCollection.getPrimaryPrincipal();

//根据主身份信息获取角色 和 权限信息
UserService userServiceImpl =(UserService) ApplicationContextUtils.getBean("userServiceImpl");
List<Role> roles = userServiceImpl.findRolesByUserName(primaryPrincipal).getRoles();

//授权角色信息
if(!CollectionUtils.isEmpty(roles)){
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
roles.forEach( role -> {
//添加角色信息
simpleAuthorizationInfo.addRole(role.getName());

//从数据库中获取权限信息
List<Perms> perms = userServiceImpl.findPermsByRoleId(role.getId());

if( !CollectionUtils.isEmpty(perms) && perms.get(0) != null) {
perms.forEach(perm -> {
String permName = perm.getName();
System.out.println(permName);
//添加权限信息
simpleAuthorizationInfo.addStringPermission(permName);
});
}
});
return simpleAuthorizationInfo;
}
return null;
}

@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {

//根据身份信息
String principal =(String) authenticationToken.getPrincipal();

//在工厂中获取service对象
UserService userServiceImpl =(UserService) ApplicationContextUtils.getBean("userServiceImpl");

//根据身份信息查询
User user = userServiceImpl.findUserByUserName(principal);

//用户不为空
if(!ObjectUtils.isEmpty(user)){
//返回数据库信息
return new SimpleAuthenticationInfo(user.getUsername(),user.getPassword(), ByteSource.Util.bytes(user.getSalt()),this.getName());
}

return null;
}
}

7.2.8 向数据库中添加信息

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
-- ----------------------------
-- Records of user
-- ----------------------------
INSERT INTO `shiro`.`user`(`id`, `username`, `password`, `salt`) VALUES (7, 'lisi', '52ce991ec216ff838a02074100579f18', '3wPVu)XJ');
INSERT INTO `shiro`.`user`(`id`, `username`, `password`, `salt`) VALUES (8, 'xiaohei', '4434a95b34b690e1c0aac6413abc8113', '(QH#xW0Z');


-- ----------------------------
-- Records of role
-- ----------------------------
INSERT INTO `role` VALUES (1, 'admin');
INSERT INTO `role` VALUES (2, 'user');
INSERT INTO `role` VALUES (3, 'product');

-- ----------------------------
-- Records of user_role
-- ----------------------------
INSERT INTO `user_role` VALUES (1, 7, 1);
INSERT INTO `user_role` VALUES (2, 8, 2);
INSERT INTO `user_role` VALUES (3, 8, 3);

-- ----------------------------
-- Records of perms
-- ----------------------------
INSERT INTO `perms` VALUES (1, 'user:*:*', NULL);
INSERT INTO `perms` VALUES (2, 'order:*:*', NULL);
INSERT INTO `perms` VALUES (3, 'user:add:*', NULL);
INSERT INTO `perms` VALUES (4, 'order:add:*', NULL);

-- ----------------------------
-- Records of role_perms
-- ----------------------------
INSERT INTO `role_perms` VALUES (1, 1, 1);
INSERT INTO `role_perms` VALUES (2, 1, 2);
INSERT INTO `role_perms` VALUES (3, 2, 3);
INSERT INTO `role_perms` VALUES (4, 3, 2);

7.2.9 Index文件

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
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
<%@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>系统主页1.0</h1>
<a href="${pageContext.request.contextPath}/user/logout">退出登录</a>

<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="product">
<ul>
<li><a href="">商品管理</a>
<shiro:hasPermission name="order:add:*">
<li><a href="">添加</a></li>
</shiro:hasPermission>
<shiro:hasPermission name="order:delete:*">
<li><a href="">删除</a></li>
</shiro:hasPermission>
<shiro:hasPermission name="order:update:*">
<li><a href="">修改</a></li>
</shiro:hasPermission>
<shiro:hasPermission name="order:find:*">
<li><a href="">查询</a></li>
</shiro:hasPermission>
</ul>
<li><a href="">订单管理</a></li>
<li><a href="">物流管理</a></li>
</shiro:hasRole>
</ul>
</body>
</html><%@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>系统主页1.0</h1>
<a href="${pageContext.request.contextPath}/user/logout">退出登录</a>

<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="product">
<ul>
<li><a href="">商品管理</a>
<shiro:hasPermission name="order:add:*">
<li><a href="">添加</a></li>
</shiro:hasPermission>
<shiro:hasPermission name="order:delete:*">
<li><a href="">删除</a></li>
</shiro:hasPermission>
<shiro:hasPermission name="order:update:*">
<li><a href="">修改</a></li>
</shiro:hasPermission>
<shiro:hasPermission name="order:find:*">
<li><a href="">查询</a></li>
</shiro:hasPermission>
</ul>
<li><a href="">订单管理</a></li>
<li><a href="">物流管理</a></li>
</shiro:hasRole>
</ul>
</body>
</html>

7.2.10 启动测试

Wf0DsK.png

WfBKTe.png

8 使用CacheManager

8.1 Cache作用

  • Cache 缓存:计算机内存中一段数据

  • 作用:用来减轻DB的访问压力,从而提高系统得查询效率

  • 流程:

    Wfsvh4.png

8.2 使用shiro中默认EhCache实现缓存

8.2.1 引入依赖

1
2
3
4
5
6
<!--引入shiro和ehcache-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-ehcache</artifactId>
<version>1.5.3</version>
</dependency>

8.2.2 开启缓存

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* 创建自定义Realm
* @return
*/
@Bean
public Realm getRealm(){
CustormerRealm custormerRealm = new CustormerRealm();
// 修改凭证校验匹配器
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
//设置加密算法为Md5
hashedCredentialsMatcher.setHashAlgorithmName("MD5");
//设置散列次数
hashedCredentialsMatcher.setHashIterations(1024);
custormerRealm.setCredentialsMatcher(hashedCredentialsMatcher);

//开启缓存管理器
custormerRealm.setCachingEnabled(true);
custormerRealm.setAuthenticationCachingEnabled(true);
custormerRealm.setAuthorizationCachingEnabled(true);
custormerRealm.setCacheManager(new EhCacheManager());
return custormerRealm;
}

8.2.3 启动测试

  • 注意:如果控制台没有任何sql展示说明缓存已经开启

9 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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
<shiro:guest>
游客访问 <a href = "login.jsp"></a>
</shiro:guest>

<!--user 标签:用户已经通过认证\记住我 登录后显示响应的内容-->
<shiro:user>
欢迎[<shiro:principal/>]登录 <a href = "logout">退出</a>
</shiro:user>

<!--authenticated标签:用户身份验证通过,即 Subjec.login 登录成功 不是记住我登录的-->
<shiro:authenticted>
用户[<shiro:principal/>] 已身份验证通过
</shiro:authenticted>

<!--notAuthenticated标签:用户未进行身份验证,即没有调用Subject.login进行登录,包括"记住我"也属于未进行身份验证-->
<shiro:notAuthenticated>
未身份验证(包括"记住我")
</shiro:notAuthenticated>


<!--principal 标签:显示用户身份信息,默认调用
Subjec.getPrincipal()获取,即Primary Principal-->
<shiro:principal property = "username"/>

<!--hasRole标签:如果当前Subject有角色将显示body体内的内容-->
<shiro:hashRole name = "admin">
用户[<shiro:principal/>]拥有角色admin
</shiro:hashRole>

<!--hasAnyRoles标签:如果Subject有任意一个角色(或的关系)将显示body体里的内容-->
<shiro:hasAnyRoles name = "admin,user">
用户[<shiro:pricipal/>]拥有角色admin 或者 user
</shiro:hasAnyRoles>

<!--lacksRole:如果当前 Subjec没有角色将显示body体内的内容-->
<shiro:lacksRole name = "admin">
用户[<shiro:pricipal/>]没有角色admin
</shiro:lacksRole>

<!--hashPermission:如果当前Subject有权限将显示body体内容-->
<shiro:hashPermission name = "user:create">
用户[<shiro:pricipal/>] 拥有权限user:create
</shiro:hashPermission>

<!--lacksPermission:如果当前Subject没有权限将显示body体内容 -->
<shiro:lacksPermission name = "org:create">
用户[<shiro:pricipal/>] 没有权限org:create
</shiro:lacksPermission>

Prev:
InitBinder注解
Next:
Shiro身份验证
catalog
catalog