Spring Boot之Shiro入门

标签: Springboot 分类: Java 创建时间:2020-06-12 07:44:11 更新时间:2024-11-15 10:49:43

这是shiro入门的第二篇文章,第一篇文(Post not found: Spring Boot集成shiro步骤 Spring Boot集成shiro步骤)章使用的还是比较老的shiro-spring依赖,这一篇,使用了shiro-spring-boot-web-starter,写法和思路还是有些差别的。

参考文章:
1.SpringSecurity能否吊打Shiro? 如果开发的项目是Spring这一套,用Spring Security我觉得更合适一些,他们本身就是一套东西,顺畅,可能略微复杂一些,但是学会了就是自己的。如果开发项目比较紧张,Shiro可能更合适,容易上手,也足够用,Spring Security中有的,Shiro也基本都有,没有的部分网上也有大批的解决方案。如果项目没有使用Spring这一套,不用考虑,直接Shiro。同时要考虑团队成员的技术栈,更加熟悉使用哪个,在选型上,也要尽量避免给同行增加不必要的学习成本!

1.引入依赖

org.apache.shiro shiro-spring-boot-web-starter 1.5.3
1
2
3
4
5
6
7
8
9

## 2.提供一个Realm实现
比如我的
``` java
@Bean
public Realm realm() {
DatabaseRealm databaseRealm=new DatabaseRealm();
return databaseRealm;
}
其中DatabaseRealm代码
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
import com.proheng.gis.Entity.UserInfo;
import com.proheng.gis.Reposities.UserInfoRepository;
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.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;


/**
* Created by zjch on 2017/5/16.
*/
public class DatabaseRealm extends AuthorizingRealm {
private final Logger logger = LoggerFactory.getLogger(this.getClass());

@Autowired
private UserInfoRepository userInfoService;

/**
* 认证信息.(身份验证)
* :
* Authentication 是用来验证用户身份
* @param token
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
//获取用户的输入的账号.
String username = (String)token.getPrincipal();
//通过username从数据库中查找 User对象,如果找到,没找到.
//实际项目中,这里可以根据实际情况做缓存,如果不做,Shiro自己也是有时间间隔机制,2分钟内不会重复执行该方法
UserInfo userInfo = userInfoService.findByUsername(username);

if(userInfo == null){
return null;
}
/*
* 获取权限信息:这里没有进行实现,
* 请自行根据UserInfo,Role,Permission进行实现;
* 获取之后可以在前端for循环显示所有链接;
*/
//userInfo.setPermissions(userService.findPermissions(User));
//加密方式;
//交给AuthenticatingRealm使用CredentialsMatcher进行密码匹配,如果觉得人家的不好可以自定义实现
try{
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
userInfo.getUsername(), //用户名
userInfo.getPassword(), //密码
getName() //realm name
);
authenticationInfo.setCredentialsSalt(ByteSource.Util.bytes(userInfo.getCredentialsSalt()));
return authenticationInfo;
}catch (Exception e){
System.out.println("授权失败");
logger.error("doGetAuthenticationInfo",userInfo);
return null;
}

//明文: 若存在,将此用户存放到登录认证info中,无需自己做密码对比,Shiro会为我们进行密码对比校验
// SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
// userInfo.getName(), //用户名
// userInfo.getPassword(), //密码
// getName() //realm name
// );

}

/**
* 此方法调用 hasRole,hasPermission的时候才会进行回调.
*
* 权限信息.(授权):
* 1、如果用户正常退出,缓存自动清空;
* 2、如果用户非正常退出,缓存自动清空;
* 3、如果我们修改了用户的权限,而用户不退出系统,修改的权限无法立即生效。
* (需要手动编程进行实现;放在service进行调用)
* 在权限修改后调用realm中的方法,realm已经由spring管理,所以从spring中获取realm实例,
* 调用clearCached方法;
* :Authorization 是授权访问控制,用于对用户进行的操作授权,证明该用户是否允许进行当前操作,如访问某个链接,某个资源文件等。
* @param principals
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
/*
* 当没有使用缓存的时候,不断刷新页面的话,这个代码会不断执行,
* 当其实没有必要每次都重新设置权限信息,所以我们需要放到缓存中进行管理;
* 当放到缓存中时,这样的话,doGetAuthorizationInfo就只会执行一次了,
* 缓存过期之后会再次执行。
*/
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
//实际项目中,这里可以根据实际情况做缓存,如果不做,Shiro自己也是有时间间隔机制,2分钟内不会重复执行该方法

return authorizationInfo;
}
}

3.定义一个ShiroFilterChainDefinition

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Bean
public ShiroFilterChainDefinition shiroFilterChainDefinition() {
DefaultShiroFilterChainDefinition chainDefinition = new DefaultShiroFilterChainDefinition();

// logged in users with the 'admin' role
chainDefinition.addPathDefinition("/admin/**", "authc, roles[admin]");

// logged in users with the 'document:read' permission
chainDefinition.addPathDefinition("/docs/**", "authc, perms[document:read]");

// all other paths require a logged in user
chainDefinition.addPathDefinition("/**", "authc");
return chainDefinition;
}

简直太简单了有没有?下面的步骤,可以做,也可以不做,都没有关系的,反正上面三步就已经完成了大部分的内容,下面的只是根据需要做一些完善。

4.配置过滤器

配置过滤器,在完成了上面的几个步骤的时候,如果还要进一步的做一些其他的东西,比如设置未登录的跳转页面,在拦截ajax请求的时候,返回json字符串,而不是一个login.jsp页面,就需要配置过滤器了。

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
//配置shrio拦截器
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
// 必须设置SecuritManager
shiroFilterFactoryBean.setSecurityManager(securityManager);
Map<String, Filter> filters = shiroFilterFactoryBean.getFilters();
//配置拦截器,实现无权限返回401,而不是跳转到登录页
// filters.put("authc", new FormLoginFilter());
// 如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面
shiroFilterFactoryBean.setLoginUrl("/");
// 登录成功后要跳转的链接
shiroFilterFactoryBean.setSuccessUrl("/index");
// 未授权界面;
shiroFilterFactoryBean.setUnauthorizedUrl("/403");

// 拦截器
// Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
// // 过滤链定义,从上向下顺序执行,一般将 /**放在最为下边
// // authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问
// filterChainDefinitionMap.put("/**", "authc");
// shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);

return shiroFilterFactoryBean;
}

参考文章:
1.springboot整合shiro使用shiro-spring-boot-web-starter (这个讲了如何加入过滤器链,这里有个坑,就是过滤器的名字必须是shiroFilterFactoryBean,而不能是shiroFilter)
2.使用shiro-spring-boot-web-starter时,报错: required a bean named ‘authorizer’ that could not be found.
3.Shiro踩坑记(一):关于shiro-spring-boot-web-starter自动注解无法注入authorizer的问题 (这个文章倒是将了很多,连如何进行debug的操作都讲到了)

注意
过滤器的名字不能是shiroFilter而必须是shiroFilterFactoryBean,否则启动的时候就会报错

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
  @Bean
public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
// 必须设置SecuritManager
shiroFilterFactoryBean.setSecurityManager(securityManager);
Map<String, Filter> filters = shiroFilterFactoryBean.getFilters();
//配置拦截器,实现无权限返回401,而不是跳转到登录页
// filters.put("authc", new FormLoginFilter());
// 如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面
shiroFilterFactoryBean.setLoginUrl("/");
// 登录成功后要跳转的链接
shiroFilterFactoryBean.setSuccessUrl("/index");
// 未授权界面;
shiroFilterFactoryBean.setUnauthorizedUrl("/403");

// // 拦截器
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
// 过滤链定义,从上向下顺序执行,一般将 /**放在最为下边
// authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问
filterChainDefinitionMap.put("/**", "authc");

shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
参考文章:
1.[Shiro]Shiro整合Spring Boot 自动化配置 (这里提到了继承ShiroWebFilterConfiguration并重写ShiroFilterFactoryBean方法,达到自定义过滤器链的方法)
2.使用shiro-spring-boot-web-starter时,报错: required a bean named ‘authorizer’ that could not be found.

关于x-requested-with的作用以及用法详解

很多的时候,都是在写Shiro的时候,会出现一个判断请求是否是ajax请求的功能,通过自定义Filter,判断如果请求是Ajax请求,就返回一个json字符串,如果不是ajax请求,就直接返回一个页面。大部分的说明都是请求头中是否携带x-requested-with选项作为是否是ajax的请求,但是这个选项只是jquery等前端库自己添加的,并不是标准的请求头,所以有时候通过这个请求头并不能判断这是一个ajax请求,比如原生的XMLHTTPRequest,比如fetch等,都没有这个请求头。所以在判断是否是ajax请求的时候,还是选择其他的方式吧。

小额赞助
本人提供免费与付费咨询服务,感谢您的支持!赞助请发邮件通知,方便公布您的善意!
**光 3.01 元
Sun 3.00 元
bibichuan 3.00 元
微信公众号
广告位
诚心邀请广大金主爸爸洽谈合作
每日一省
isNaN 和 Number.isNaN 函数的区别?

1.函数 isNaN 接收参数后,会尝试将这个参数转换为数值,任何不能被转换为数值的的值都会返回 true,因此非数字值传入也会返回 true ,会影响 NaN 的判断。

2.函数 Number.isNaN 会首先判断传入参数是否为数字,如果是数字再继续判断是否为 NaN ,不会进行数据类型的转换,这种方法对于 NaN 的判断更为准确。

每日二省
为什么0.1+0.2 ! == 0.3,如何让其相等?

一个直接的解决方法就是设置一个误差范围,通常称为“机器精度”。对JavaScript来说,这个值通常为2-52,在ES6中,提供了Number.EPSILON属性,而它的值就是2-52,只要判断0.1+0.2-0.3是否小于Number.EPSILON,如果小于,就可以判断为0.1+0.2 ===0.3。

每日三省
== 操作符的强制类型转换规则?

1.首先会判断两者类型是否**相同,**相同的话就比较两者的大小。

2.类型不相同的话,就会进行类型转换。

3.会先判断是否在对比 null 和 undefined,是的话就会返回 true。

4.判断两者类型是否为 string 和 number,是的话就会将字符串转换为 number。

5.判断其中一方是否为 boolean,是的话就会把 boolean 转为 number 再进行判断。

6.判断其中一方是否为 object 且另一方为 string、number 或者 symbol,是的话就会把 object 转为原始类型再进行判断。

每日英语
Happiness is time precipitation, smile is the lonely sad.
幸福是年华的沉淀,微笑是寂寞的悲伤。