shiro简单入门实例

参考官方文档

什么是Shiro?

Apache Shiro是一个功能强大且灵活的开源安全框架,可以干净地处理身份验证,授权,企业会话管理和加密

Shiro以Shiro开发团队所谓的“应用程序安全性的四个基石”为目标-身份验证,授权,会话管理和密码术:

  • 身份验证:有时称为“登录”,这是证明用户就是他们所说的身份的行为。
  • 授权:访问控制的过程,即确定“谁”有权访问“什么”。
  • 会话管理:即使在非Web或EJB应用程序中,也可以管理用户特定的会话。
  • 密码术:使用密码算法保持数据安全,同时仍然易于使用。

在不同的应用程序环境中,还具有其他功能来支持和加强这些问题,尤其是:

  • Web支持:Shiro的Web支持API可帮助轻松保护Web应用程序。
  • 缓存:缓存是Apache Shiro API的第一层公民,可确保安全操作保持快速有效。
  • 并发性:Apache Shiro的并发功能支持多线程应用程序。
  • 测试:测试支持可帮助您编写单元测试和集成测试,并确保您的代码将按预期进行保护。
  • “运行方式”:一种功能,允许用户采用其他用户的身份(如果允许),有时在管理方案中很有用。
  • “记住我”:在整个会话中记住用户的身份,因此他们仅在必要时登录。

根据官方文档敲一手 http://shiro.apache.org/tutorial.html

shiro的关键组件

image-20200504205738373

官方实例主要是说 这个subject和这个SecurityManager

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
log.info("My First Apache Shiro Application");

Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
SecurityManager securityManager = factory.getInstance();
SecurityUtils.setSecurityManager(securityManager);

// get the currently executing user:
Subject currentUser = SecurityUtils.getSubject();

// Do some stuff with a Session (no need for a web or EJB container!!!)
Session session = currentUser.getSession();
session.setAttribute("someKey", "aValue");
String value = (String) session.getAttribute("someKey");
if (value.equals("aValue")) {
log.info("检索到正确的值! [" + value + "]");
}

// let's login the current user so we can check against roles and permissions:
if (!currentUser.isAuthenticated()) {
UsernamePasswordToken token = new UsernamePasswordToken("lonestarr", "vespa");
token.setRememberMe(true);
try {
currentUser.login(token);
} catch (UnknownAccountException uae) {//用户名不存在
log.info("用户不存在 " + token.getPrincipal());
} catch (IncorrectCredentialsException ice) {//密码不正确
log.info("帐户密码" + token.getPrincipal() + " 不正确!");
} catch (LockedAccountException lae) {//被锁定
log.info("用户名的帐户 " + token.getPrincipal() + " is locked. " +
"请与管理员联系以解除锁定.");
}
// ... catch more exceptions here (maybe custom ones specific to your application?
catch (AuthenticationException ae) {
//unexpected condition? error?
}
}

//say who they are:
//print their identifying principal (in this case, a username):
log.info("User [" + currentUser.getPrincipal() + "] logged in successfully.");//登录成功

//test a role:
if (currentUser.hasRole("schwartz")) {//是否有特定权限
log.info("May the Schwartz be with you!");
} else {
log.info("Hello, mere mortal.");
}

//test a typed permission (not instance-level)
if (currentUser.isPermitted("lightsaber:wield")) {//是否有特定权限
log.info("You may use a lightsaber ring. Use it wisely.");
} else {
log.info("Sorry, lightsaber rings are for schwartz masters only.");
}

//a (very powerful) Instance Level permission:
if (currentUser.isPermitted("winnebago:drive:eagle5")) {
log.info("You are permitted to 'drive' the winnebago with license plate (id) 'eagle5'. " +
"Here are the keys - have fun!");
} else {
log.info("Sorry, you aren't allowed to drive the 'eagle5' winnebago!");
}

//all done - log out!
currentUser.logout();

System.exit(0);

他这里数据源直接使用ini文件的。

Subject:主体,代表了当前 “用户”,这个用户不一定是一个具体的人,与当前应用交互的任何东西都是 Subject,如网络爬虫,机器人等;即一个抽象概念;所有 Subject 都绑定到 SecurityManager,与 Subject 的所有交互都会委托给 SecurityManager;可以把 Subject 认为是一个门面;SecurityManager 才是实际的执行者;

SecurityManager:安全管理器;即所有与安全有关的操作都会与 SecurityManager 交互;且它管理着所有 Subject;可以看出它是 Shiro 的核心,它负责与后边介绍的其他组件进行交互,如果学习过 SpringMVC,你可以把它看成 DispatcherServlet 前端控制器;

如何接入springboot?

shiro最常用的 除了 SubjectSecurityManager 还有Realm

为了在springboot接入shiro 需要做这么三件事 自定义 Realm、安全管理器 SecurityManager 和 Shiro 过滤器。

参考文章 https://blog.csdn.net/taojin12/article/details/88343990

Realm 是一个域

它是连接 Shiro 和具体应用的桥梁。当需要与安全数据交互时,比如用户账户、访问控制等,Shiro 将会在一个或多个 Realm 中查找。我们可以把 Realm 看作 DataSource,即安全数据源。一般,我们会自己定制 Realm,下文会详细说明。

一般来说接入到我们的web登录之中 ,shiro需要构建的subject对象应该是前端传过来的,所以这里自定义一个Realm

继承AuthorizingRealm 重写这俩方法

doGetAuthenticationInfo() 方法:用来验证当前登录的用户,获取认证信息。
doGetAuthorizationInfo() 方法:为当前登录成功的用户授予权限和分配角色。

创建一个springboot项目 引入依赖

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
<dependency>

<groupId>
org.springframework.boot
</groupId>

<artifactId>
spring-boot-devtools
</artifactId>

<optional>
true
</optional>

<!-- 这个需要为 true 热部署才有效 -->

</dependency>


<!--mybatis-->


<dependency>


<groupId>
org.mybatis.spring.boot
</groupId>


<artifactId>
mybatis-spring-boot-starter
</artifactId>


<version>
1.3.2
</version>


</dependency>




<!-- mysql -->


<dependency>


<groupId>
mysql
</groupId>


<artifactId>
mysql-connector-java
</artifactId>


<version>
5.1.47
</version>


</dependency>




<!--阿里巴巴连接池-->


<dependency>


<groupId>
com.alibaba
</groupId>


<artifactId>
druid
</artifactId>


<version>
1.0.9
</version>


</dependency>

<!--shiro依赖-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.0</version>
</dependency>

创建一个user表 并使用easycode快速生成代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
DROP TABLE IF EXISTS 
`user`
;

CREATE TABLE `user`
(`id` int(11) NOT NULL,
`username` varchar(20) DEFAULT NULL,
`sex` varchar(6) DEFAULT NULL,
`birthday` date DEFAULT NULL,
`address` varchar(20) DEFAULT NULL,
`password` varchar(20) DEFAULT NULL,
PRIMARY KEY (`id`))
ENGINE=InnoDB
DEFAULT CHARSET=utf8;
SET FOREIGN_KEY_CHECKS =1;


自定义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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
package com.xia.zhb.shiro;

import com.xia.zhb.entity.TUser;
import com.xia.zhb.service.TUserService;
import org.apache.shiro.SecurityUtils;
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 javax.annotation.Resource;

public class MyRealm extends AuthorizingRealm {

@Resource
private TUserService tuserService;

@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
// 获取用户名
String username = (String) principalCollection.getPrimaryPrincipal();
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
// 给该用户设置角色,角色信息存在 t_role 表中取
// authorizationInfo.setRoles(userService.getRoles(username));
// 给该用户设置权限,权限信息存在 t_permission 表中取
//authorizationInfo.setStringPermissions(userService.getPermissions(username));
return authorizationInfo;
}

@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
// 根据 Token 获取用户名,如果您不知道该 Token 怎么来的,先可以不管,下文会解释
String username = (String) authenticationToken.getPrincipal();
// 根据用户名从数据库中查询该用户
TUser user = tuserService.queryUsername(username);
if(user != null) {
// 把当前用户存到 Session 中
SecurityUtils.getSubject().getSession().setAttribute("user", user);
// 传入用户名和密码进行身份认证,并返回认证信息
AuthenticationInfo authcInfo = new SimpleAuthenticationInfo(user.getUsername(), user.getPassword(), "myRealm");
return authcInfo;
} else {
return null;
}
}
}

shiro过滤和安全管理器SecurityManager的代码如下

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

private static final Logger logger = LoggerFactory.getLogger(ShiroConfig.class);

/**
* 注入自定义的 Realm
* @return MyRealm
*/
@Bean
public MyRealm myAuthRealm() {
MyRealm myRealm = new MyRealm();
logger.info("====myRealm注册完成=====");
return myRealm;
}
/**
* 注入安全管理器
* @return SecurityManager
*/
@Bean
public SecurityManager securityManager() {
// 将自定义 Realm 加进来
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(myAuthRealm());
logger.info("====securityManager注册完成====");
return securityManager;
}
/**
* 注入 Shiro 过滤器
* @param securityManager 安全管理器
* @return ShiroFilterFactoryBean
*/
@Bean
public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {
// 定义 shiroFactoryBean
ShiroFilterFactoryBean shiroFilterFactoryBean=new ShiroFilterFactoryBean();

// 设置自定义的 securityManager
shiroFilterFactoryBean.setSecurityManager(securityManager);

// 设置默认登录的 URL,身份认证失败会访问该 URL
shiroFilterFactoryBean.setLoginUrl("/login");
// 设置成功之后要跳转的链接
shiroFilterFactoryBean.setSuccessUrl("/success");
// 设置未授权界面,权限认证失败会访问该 URL
shiroFilterFactoryBean.setUnauthorizedUrl("/unauthorized");

// LinkedHashMap 是有序的,进行顺序拦截器配置
Map<String,String> filterChainMap = new LinkedHashMap<>();

// 配置可以匿名访问的地址,可以根据实际情况自己添加,放行一些静态资源等,anon 表示放行
filterChainMap.put("/css/**", "anon");
filterChainMap.put("/imgs/**", "anon");
filterChainMap.put("/js/**", "anon");
filterChainMap.put("/swagger-*/**", "anon");
filterChainMap.put("/swagger-ui.html/**", "anon");
// 登录 URL 放行
filterChainMap.put("/login", "anon");

// 以“/user/admin” 开头的用户需要身份认证,authc 表示要进行身份认证
filterChainMap.put("/user/admin*", "authc");
// “/user/student” 开头的用户需要角色认证,是“admin”才允许
filterChainMap.put("/user/student*/**", "roles[admin]");
// “/user/teacher” 开头的用户需要权限认证,是“user:create”才允许
filterChainMap.put("/user/teacher*/**", "perms[\"user:create\"]");

// 配置 logout 过滤器
filterChainMap.put("/logout", "logout");

// 设置 shiroFilterFactoryBean 的 FilterChainDefinitionMap
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainMap);
logger.info("====shiroFilterFactoryBean注册完成====");
return shiroFilterFactoryBean;
}


}

Shiro 配置一环套一环,遵循从 Reaml 到 SecurityManager 再到 Filter 的过程。在过滤器中,我们需要定义一个 shiroFactoryBean,然后将 SecurityManager 引入其中,需要配置的内容主要有以下几项。

默认登录的 URL:身份认证失败会访问该 URL。
认证成功之后要跳转的 URL。
权限认证失败后要跳转的 URL。
需要拦截或者放行的 URL:这些都放在一个 Map 中。

至此shiro的基本编码完结


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!