Shiro安全框架基础知识点
Shiro基础知识点
Apache Shiro 是ASF旗下的一款开源软件(Shiro发音为“shee-roh”,日语“堡垒(Castle)”的意思),提供了一个强大而灵活的安全框架。可为任何应用提供安全保障— 从命令行应用、移动应用到大型网络及企业应用。
Apache Shiro提供了认证、授权、加密和会话管理功能,将复杂的问题隐藏起来,提供清晰直观的API使开发者可以很轻松地开发自己的程序安全代码。并且在实现此目标时无须依赖第三方的框架、容器或服务,当然也能做到与这些环境的整合,使其在任何环境下都可拿来使用。
Shiro的核心四部分
认证(Authentication):用户身份识别。有时可看作为“登录(login)”,它是用户证明自己是谁的一个行为。
授权(Authorization):访问控制过程,好比决定“认证(who)”可以访问“什么(what)”.
会话管理(SessionManagement):管理用户的会话(sessions),甚至在没有WEB或EJB容器的环境中。管理用户与时间相关的状态。
加密(Cryptography):使用加密算法保护数据更加安全,防止数据被偷窥。
1.Spring整合Shiro
- pom 依赖
1
2
3
4
5
6<!--整合shiro-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring-boot-starter</artifactId>
<version>1.4.1</version>
</dependency>
2. ShiroConfig
- shiro的基本配置
- 1.ShiroFilterFactoryBean #Shiro对外的接口,代表当前“用户”
- 2.DefaultWebSecurityManager #关联realm
- 3.UserRealm #负责登录验证和资源授权
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
70package io.kid1999.operatesystem.config;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* @author kid1999
* @title: Shiro 的 配置
* @date 2019/11/26 21:27
*/
@Configuration
public class ShiroConfig {
// 3. 创建 ShiroFilterFactoryBean
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager securityManager){
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
// 设置安全管理器
shiroFilterFactoryBean.setSecurityManager(securityManager);
/** 添加Shiro的内置过滤器
* 常用:
* anon : 无需认证(登录) 可以访问
* authc :必须认证才可以访问
* user : 如果使用remenberMe功能可以直接访问
* perms: 该资源必须获得相关权限才可访问
* role: 该资源必须获得角色权限才可以访问
*
* 注意: 这个授权拦截链 是按顺序执行的!!!
*/
Map<String,String> filterMap = new LinkedHashMap<>();
filterMap.put("/","anon");
filterMap.put("/login","anon");
filterMap.put("/error","anon");
// filterMap.put("/admin","authc");
// 权限过滤器 也可以设置角色过滤
filterMap.put("/admin","perms[user:admin]");
shiroFilterFactoryBean.setLoginUrl("/login"); // 设置跳转的登录页面
shiroFilterFactoryBean.setUnauthorizedUrl("/noAuth"); // 设置未授权访问页面
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterMap);
return shiroFilterFactoryBean;
}
// 2. 创建DefaultWebSecurityManager
// 所有与安全有关的操作都会与SecurityManager进行交互
@Bean(name = "securityManager")
public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("getRealm") UserRealm userRealm){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
// 关联realm
securityManager.setRealm(getRealm());
return securityManager;
}
// 1. 创建Realm
// Shiro从Realm获取安全数据(用户、角色、权限)
@Bean
public UserRealm getRealm(){
return new UserRealm();
}
}
3. UserRealm
- Shiro从从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
53
54
55
56
57
58
59
60
61
62
63package io.kid1999.operatesystem.config;
import io.kid1999.operatesystem.model.Admin;
import io.kid1999.operatesystem.repository.AdminRepository;
import org.apache.shiro.authc.*;
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.springframework.beans.factory.annotation.Autowired;
/**
* @author kid1999
* @title: UserRealm 自定义 realm 处理 授权和认证
* @date 2019/11/26 21:29
*/
public class UserRealm extends AuthorizingRealm {
@Autowired
private AdminRepository adminRepository;
// 授权执行逻辑
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("授权执行逻辑");
// 给资源进行授权
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
// 添加资源授权字符串
info.addStringPermission("user:admin");
return info;
}
// 认证执行逻辑
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
System.out.println("认证执行逻辑");
// 1. 获取传来验证的token
UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
String name = token.getUsername();
String password = String.valueOf(token.getPassword());
// 2. 通过用户名 取出数据库的数据
Admin user = adminRepository.findByAdminId(name);
// 3. 判断用户名
if (user == null){
return null; // 找不到用户名 报 UnknownAccountException
}
// 4. 判断密码
/**
* arg0: 回传login的数据
* arg1: 数据库的密码
* arg2: realm 的name
*/
return new SimpleAuthenticationInfo("",user.getAdminPwd(),"");
}
}
4.AdminRepository 链接数据库
- Realm 会连接数据源 进行身份验证
- 此处放上我的JPA
1 | package io.kid1999.operatesystem.repository; |
5.登录Controller
- 此处没有使用加密 和 缓存机制
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23/**
* 使用Shiro实现登录
*/
@PostMapping("/login")
String toLogin(String studentId,
String passWord,
Model model){
// 1.获取Subject
Subject subject = SecurityUtils.getSubject();
// 2.封装登录方法
UsernamePasswordToken token = new UsernamePasswordToken(studentId,passWord);
// 3.执行登录方法
try {
subject.login(token); // 调用login方法 -> realm 做验证
return "admin";
}catch (UnknownAccountException e){
model.addAttribute("msg","用户名不存在");
return "login";
}catch (IncorrectCredentialsException e){
model.addAttribute("msg","密码错误");
return "login";
}
}
6.整合redis接管session和缓存
- 再导入两个包
1 | <dependency> |
- 在ShiroConfig 配置到 securityManager
1 | // 2. 创建DefaultWebSecurityManager 管理器 |
7.整合redis接管session和缓存
- 继承FormAuthenticationFilter 重写一些方法
1 | package io.kid19999.backstage.config.Shiro; |
- 在ShiroConfig 注入 自定义拦截器
1 | // 注入 拦截器和认证 |
- 拦截器的实际工作流程:
详见
8.Vue的简单安全认证:
导入vue-cookies 使用cookie保存当前用户的状态
1
2import VueCookies from 'vue-cookies'
Vue.use(VueCookies);使用钩子函数对路由进行权限跳转
1 | router.beforeEach((to, from, next) => { |
- 登录后返回状态 存入cookie中 默认保存一天
1 | this.$axios.post('/login', qs.stringify(data), {headers:{'Content-Type':'application/x-www-form-urlencoded'}}).then(function (response) { |
流程总结:
- 首先 ShiroFilterFactoryBean 查询拦截规则 跳转 login(或者自己访问login)
- 登录验证Controller 调用 subject.login(token)
- Realm接收信息,并且和数据源的数据进行对比 返回 判断结果
- 如果不成功 返回错误,如果成功 去往资源网站。
- ShiroFilterFactoryBean 判断资源访问权限
- 调用 Realm的 授权方法 从数据源获取 用户权限 返回结果
- 如果 不成功 返回错误。成功 允许访问
思考:
前端验证cookie是可见的 role 容易被查看 修改不安全
后端再次验证 session是否存在(超时被抹除|logout被抹除)
每次访问的时候先去过滤器一下是否session存在
参考资料:
Apache Shiro 是ASF旗下的一款开源软件(Shiro发音为“shee-roh”,日语“堡垒(Castle)”的意思),提供了一个强大而灵活的安全框架。可为任何应用提供安全保障— 从命令行应用、移动应用到大型网络及企业应用。
Apache Shiro提供了认证、授权、加密和会话管理功能,将复杂的问题隐藏起来,提供清晰直观的API使开发者可以很轻松地开发自己的程序安全代码。并且在实现此目标时无须依赖第三方的框架、容器或服务,当然也能做到与这些环境的整合,使其在任何环境下都可拿来使用。
Shiro的核心四部分
认证(Authentication):用户身份识别。有时可看作为“登录(login)”,它是用户证明自己是谁的一个行为。
授权(Authorization):访问控制过程,好比决定“认证(who)”可以访问“什么(what)”.
会话管理(SessionManagement):管理用户的会话(sessions),甚至在没有WEB或EJB容器的环境中。管理用户与时间相关的状态。
加密(Cryptography):使用加密算法保护数据更加安全,防止数据被偷窥。
1.Spring整合Shiro
- pom 依赖
1
2
3
4
5
6<!--整合shiro-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring-boot-starter</artifactId>
<version>1.4.1</version>
</dependency>
2. ShiroConfig
- shiro的基本配置
- 1.ShiroFilterFactoryBean #Shiro对外的接口,代表当前“用户”
- 2.DefaultWebSecurityManager #关联realm
- 3.UserRealm #负责登录验证和资源授权
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
70package io.kid1999.operatesystem.config;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* @author kid1999
* @title: Shiro 的 配置
* @date 2019/11/26 21:27
*/
@Configuration
public class ShiroConfig {
// 3. 创建 ShiroFilterFactoryBean
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager securityManager){
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
// 设置安全管理器
shiroFilterFactoryBean.setSecurityManager(securityManager);
/** 添加Shiro的内置过滤器
* 常用:
* anon : 无需认证(登录) 可以访问
* authc :必须认证才可以访问
* user : 如果使用remenberMe功能可以直接访问
* perms: 该资源必须获得相关权限才可访问
* role: 该资源必须获得角色权限才可以访问
*
* 注意: 这个授权拦截链 是按顺序执行的!!!
*/
Map<String,String> filterMap = new LinkedHashMap<>();
filterMap.put("/","anon");
filterMap.put("/login","anon");
filterMap.put("/error","anon");
// filterMap.put("/admin","authc");
// 权限过滤器 也可以设置角色过滤
filterMap.put("/admin","perms[user:admin]");
shiroFilterFactoryBean.setLoginUrl("/login"); // 设置跳转的登录页面
shiroFilterFactoryBean.setUnauthorizedUrl("/noAuth"); // 设置未授权访问页面
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterMap);
return shiroFilterFactoryBean;
}
// 2. 创建DefaultWebSecurityManager
// 所有与安全有关的操作都会与SecurityManager进行交互
@Bean(name = "securityManager")
public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("getRealm") UserRealm userRealm){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
// 关联realm
securityManager.setRealm(getRealm());
return securityManager;
}
// 1. 创建Realm
// Shiro从Realm获取安全数据(用户、角色、权限)
@Bean
public UserRealm getRealm(){
return new UserRealm();
}
}
3. UserRealm
- Shiro从从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
53
54
55
56
57
58
59
60
61
62
63package io.kid1999.operatesystem.config;
import io.kid1999.operatesystem.model.Admin;
import io.kid1999.operatesystem.repository.AdminRepository;
import org.apache.shiro.authc.*;
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.springframework.beans.factory.annotation.Autowired;
/**
* @author kid1999
* @title: UserRealm 自定义 realm 处理 授权和认证
* @date 2019/11/26 21:29
*/
public class UserRealm extends AuthorizingRealm {
@Autowired
private AdminRepository adminRepository;
// 授权执行逻辑
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("授权执行逻辑");
// 给资源进行授权
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
// 添加资源授权字符串
info.addStringPermission("user:admin");
return info;
}
// 认证执行逻辑
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
System.out.println("认证执行逻辑");
// 1. 获取传来验证的token
UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
String name = token.getUsername();
String password = String.valueOf(token.getPassword());
// 2. 通过用户名 取出数据库的数据
Admin user = adminRepository.findByAdminId(name);
// 3. 判断用户名
if (user == null){
return null; // 找不到用户名 报 UnknownAccountException
}
// 4. 判断密码
/**
* arg0: 回传login的数据
* arg1: 数据库的密码
* arg2: realm 的name
*/
return new SimpleAuthenticationInfo("",user.getAdminPwd(),"");
}
}
4.AdminRepository 链接数据库
- Realm 会连接数据源 进行身份验证
- 此处放上我的JPA
1 | package io.kid1999.operatesystem.repository; |
5.登录Controller
- 此处没有使用加密 和 缓存机制
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23/**
* 使用Shiro实现登录
*/
@PostMapping("/login")
String toLogin(String studentId,
String passWord,
Model model){
// 1.获取Subject
Subject subject = SecurityUtils.getSubject();
// 2.封装登录方法
UsernamePasswordToken token = new UsernamePasswordToken(studentId,passWord);
// 3.执行登录方法
try {
subject.login(token); // 调用login方法 -> realm 做验证
return "admin";
}catch (UnknownAccountException e){
model.addAttribute("msg","用户名不存在");
return "login";
}catch (IncorrectCredentialsException e){
model.addAttribute("msg","密码错误");
return "login";
}
}
流程总结:
- 首先 ShiroFilterFactoryBean 查询拦截规则 跳转 login(或者自己访问login)
- 登录验证Controller 调用 subject.login(token)
- Realm接收信息,并且和数据源的数据进行对比 返回 判断结果
- 如果不成功 返回错误,如果成功 去往资源网站。
- ShiroFilterFactoryBean 判断资源访问权限
- 调用 Realm的 授权方法 从数据源获取 用户权限 返回结果
- 如果 不成功 返回错误。成功 允许访问
参考资料: