在阅读之前建议先去官网把一些基本概念看了
http://shiro.apache.org/get-started.html
简介:为什么要学Shiro
权限框架
Springboot2.x/SpringMVC + Maven + jdk8 + IDEA/Eclipse
Shiro
在公司中实际的使用,包括明白里面的核心原理简介:什么是权限控制,初学JavaWeb
时处理流程
javaweb
时怎么做
Filter
接口中有一个doFilter
方法,自己编写好业务Filter
,并配置对哪个web资源进行拦截后Filter
,则会执行doFilter()
方法,然后判断是否有权限进行访问对应的资源/api/user/info?id=1
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws Exception {
HttpServletRequest httpRequest=(HttpServletRequest)request;
HttpServletResponse httpResponse=(HttpServletResponse)response;
HttpSession session=httpRequest.getSession();
if(session.getAttribute("username")!=null){
chain.doFilter(request, response);
} else {
httpResponse.sendRedirect(httpRequest.getContextPath()+"/login.jsp");
}
}
ACL: Access
Control List
访问控制列表
RBAC: Role Based Access Control
Apache Shiro、spring Security
BAT企业 ACL,一般是对报表系统,阿里的ODPS
总结:不能过于复杂,规则过多,维护性和性能会下降, 更多分类 ABAC、PBAC等
简介:介绍主流的权限框架 Apache Shiro、spring Security
什么是 spring Security
:官网基础介绍
Spring Security
是一个能够为基于Spring
的企业应用系统提供声明式的安全访问控制解决方案的安全框架。它提供了一组可以在Spring
应用上下文中配置的Bean
,充分利用了Spring
IoC
,DI
(控制反转Inversion of Control ,DI:Dependency Injection
依赖注入)和AOP
(面向切面编程)功能,为应用系统提供声明式的安全访问控制功能,减少了为企业系统安全控制编写大量重复代码的工作。
一句话:Spring Security
的前身是Acegi Security
,是Spring
项目组中用来提供安全认证服务的框架
什么是 Apache Shiro
:官网基础介绍
Apache Shiro
是一个强大且易用的Java安全框架,执行身份验证、授权、密码和会话管理。使用Shiro
的易于理解的API,您可以快速、轻松地获得任何应用程序,从最小的移动应用程序到最大的网络和企业应用程序。
一句话:Shiro
是一个强大易用的Java安全框架,提供了认证、授权、加密和会话管理等功能
两个优缺点,应该怎么选择
Apache Shiro
比Spring Security
, 前者使用更简单
Shiro
功能强大、 简单、灵活, 不跟任何的框架或者容器绑定,可以独立运行
Spring Security
对Spring
体系支持比较好,脱离Spring
体系则很难开发
SpringSecutiry
支持Oauth
鉴权 https://spring.io/projects/spring-security-oauth,Shiro
需要自己实现
…
总结:两个框架没有谁超过谁,大体功能一致,新手一般先推荐Shiro
,学习会容易点
Apache Shiro
官网 http://shiro.apache.org/introduction.htmlAuthentication
,身份认证,一般就是登录Authorization
,给用户分配角色或者访问某些资源的权限Session Management
, 用户的会话管理员,多数情况下是web session
Cryptography
, 数据加解密,比如密码加解密等简介:讲解用户访问整合Shrio
的系统,权限控制的运行流程和Shiro
常见名称讲解
重点:记住下面这七个概念
Subject
subject
)由主体(principal
)和凭证(credential
)构成,也就是用户名和密码。SecurityManager
Subject
的认证和授权都要在安全管理器下进行Authenticator
Subject
的认证Realm
Shiro
和安全数据的连接器,好比jdbc
连接数据库; 通过realm
获取认证授权相关信息Authorizer
Subject
的授权, 控制subject拥有的角色或者权限Cryptography
Shiro
的包含易于使用和理解的数据加解密方法,简化了很多复杂的apiCache Manager
更多资料导航:http://shiro.apache.org/reference.html
简介:使用SpringBoot2.x
整合Shiro
权限认证
Maven + Jdk8 + Springboot 2.X + IDEA
(Eclipse也可以)步骤极少
SpringBoot
项目 <dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>druidartifactId>
<version>1.2.1version>
dependency>
<dependency>
<groupId>org.apache.shirogroupId>
<artifactId>shiro-springartifactId>
<version>1.4.0version>
dependency>
<dependency>
<groupId>org.apache.shirogroupId>
<artifactId>shiro-springartifactId>
<version>1.4.0version>
dependency>
简介:Shrio
的认证和授权实操
认证:用户身份识别,俗称为用户“登录”
在测试包下创建测试类
package com.xdclass;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.mgt.DefaultSecurityManager;
import org.apache.shiro.realm.SimpleAccountRealm;
import org.apache.shiro.subject.Subject;
import org.junit.Before;
import org.junit.Test;
/**
* @author : wly
* @version : 1.0
* @date : 2021/11/20 10:14
* @description:
*/
public class QuickStartTest {
private DefaultSecurityManager securityManager = new DefaultSecurityManager();
private SimpleAccountRealm simpleAccountRealm = new SimpleAccountRealm();
@Before
public void init() {
//初始化数据源,相当于往数据库存两个用户
simpleAccountRealm.addAccount("user","1234");
simpleAccountRealm.addAccount("jack","123");
//构建环境,把realm添加到shiro环境里
securityManager.setRealm(simpleAccountRealm);
}
@Test
public void testAuthentication() {
//设置securityManager
SecurityUtils.setSecurityManager(securityManager);
//获取用户主体
Subject subject = SecurityUtils.getSubject();
//用户输入的用户名和密码
UsernamePasswordToken token = new UsernamePasswordToken("user","1234");
//用户登录
subject.login(token);
System.out.println("身份认证结果:"+subject.isAuthenticated());
}
}
选中login
,ctrl+alt+B
可以看到login
方法的实现,可以看到调用的是
Subject subject = securityManager.login(this, token);
这个login再往深了看就是实现身份认证。
更细节的认证流程参考以下博客,看了直接懂
https://blog.csdn.net/yanluandai1985/article/details/79171389
简介:讲解Shiro
的授权实操和常用Api 梳理
创建新的测试类 QuickStartTest2
package com.xdclass;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.mgt.DefaultSecurityManager;
import org.apache.shiro.realm.SimpleAccountRealm;
import org.apache.shiro.subject.Subject;
import org.junit.Before;
import org.junit.Test;
/**
* @author : wly
* @version : 1.0
* @date : 2021/11/20 10:14
* @description:
*/
public class QuickStartTest2 {
private DefaultSecurityManager securityManager = new DefaultSecurityManager();
private SimpleAccountRealm simpleAccountRealm = new SimpleAccountRealm();
@Before
public void init() {
//初始化数据源
simpleAccountRealm.addAccount("user","1234","root","admin");
simpleAccountRealm.addAccount("jack","123","user");
//构建环境
securityManager.setRealm(simpleAccountRealm);
}
@Test
public void testAuthentication() {
SecurityUtils.setSecurityManager(securityManager);
//用户主题(由principal(主体,即用户名)和credential(凭证,即密码)构成)
Subject subject = SecurityUtils.getSubject();
//模拟用户输入的用户名和密码
UsernamePasswordToken token = new UsernamePasswordToken("user","1234");
//用户登录
subject.login(token);
System.out.println("身份认证结果:"+subject.isAuthenticated());
System.out.println("主体是:"+subject.getPrincipal());
System.out.println("是否有root角色"+subject.hasRole("root"));
//用户退出登录
subject.logout();
System.out.println("身份认证结果:"+subject.isAuthenticated());
}
}
subject.hasRole("root")
ctrl+alb+b点进去,可以看到调用的是DelegatingSubject
类的抽象方法hasRole
这个hasRole
仍然是抽象的,再次进入hasRole
,可以看到调用的AuthorizingSecurityManager
类的hasRole
方法依然是抽象的
在这个方法里又调用了hasRole
方法,再次使用ctrl+alt+b
,可以看到有三个实现方法
选那个呢,选ModularRealmAuthorizer
类的,进去之后发现这个hasRole
不是抽象的了,说明就是最终的实现方法了。
怎么确定这个方法是属于ModularRealmAuthorizer
类呢,可以在DelegatingSubject
类的hasRole
那里return
哪行打个断点进行调试,开始dubug
按键F7F8F7F8F8
hasRole()
有返回值,有这个角色返回true
,没有返回false
.
checkRole()
没有返回值。也是用来判断有没有该角色。咋一看好像功能重复,其实它起到了断言的作用,如果用户没有该角色会抛出AuthorizationException
,直接不往下走了,而使用hasRole()
要达到没有某种角色就抛出异常停止运行需要额外的代码。
简介:讲解shiro
默认自带的realm
和常见使用方法
有默认实现和自定义继承的realm
两个概念
principal
: 主体的标示,可以有多个,但是需要具有唯一性,常见的有用户名,手机号,邮箱等credential
:凭证, 一般就是密码principal + credential
就账号 + 密码开发中,往往是自定义realm
, 即继承AuthorizingRealm
为什么这么做,因为AuthorizingRealm
支持授权,AuthenticatingRelam
支持认证,这不就功能够了吗?
所以,一般在真实的项目中,我们不会直接实现Realm
接口,也不会直接继承最底层的功能贼复杂的IniRealm
。我们一般的情况就是直接继承AuthorizingRealm
,能够继承到认证与授权功能。它需要强制重写两个方法:doGetAuthorizationInfo
和 doGetAuthenticationInfo
建议先看下ini配置,一看就懂的那种。IniRealm
是Realm
的最底层实现类,它从后缀为ini或者其他的文件中读取配置,读取程序员设置的用户和角色等相关信息。但是不用设置SecurityManager
,因为读取文件后就会有一个默认的。
ini配置文档:http://shiro.apache.org/configuration.html#Configuration-INIConfiguration
先看下上面这个。
下面开始实际案例
首先在test目录建立一个resources目录(要mark directory as test resources root,不然无法新建文件),里面建一个shiro.ini文件。
# 格式 name=password,role1,role2,..roleN
[users]
# user 'root' with password 'secret' and the 'admin' role,
jack = 456, user
# user 'guest' with the password 'guest' and the 'guest' role
xdclass = 123, root,admin
# 格式 role=permission1,permission2...permissionN 也可以用通配符
# 下面配置user的权限为所有video:find,video:buy,如果需要配置video全部操作crud 则 user = video:*
[roles]
user = video:find,video:buy
# 'admin' role has all permissions, indicated by the wildcard '*'
admin = *
这份文件设置了两个用户jack
密码是456,拥有user角色;xdclass
密码是123,拥有root
和admin
角色。video:find,video:buy
表示角色user
拥有这两个权限,具体命名去看上面的文档。
admin = *
表示角色admin拥有所有权限
创建测试类QuickStartTest5_2
package com.xdclass;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.config.IniSecurityManagerFactory;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.subject.Subject;
import org.junit.Test;
/**
* @author : wly
* @version : 1.0
* @date : 2021/11/21 11:58
* @description:
*/
public class QuickStartTest5_2 {
@Test
public void testAuthentication() {
//创建SecurityManager工厂,通过ini配置文件读取
IniSecurityManagerFactory factory = new IniSecurityManagerFactory("classpath:shiro.ini");
//通过SecurityManager工厂获取SecurityManager实例
SecurityManager securityManager = factory.getInstance();
//通过SecurityUtils工具类将SecurityManager设置到运行环境中
SecurityUtils.setSecurityManager(securityManager);
//通过SecurityUtils工具类获取主题
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken("jack","456");
subject.login(token);
System.out.println("身份认证结果:"+subject.isAuthenticated());
String username = subject.getPrincipal().toString();
System.out.println("主体是:"+subject.getPrincipal());
System.out.println("用户 "+username+" 是否有root角色:"+subject.hasRole("root"));
System.out.println("用户 "+username+" 是否有user角色:"+subject.hasRole("user"));
System.out.println("用户 "+username+" 是否有video:find 权限:"+subject.isPermitted("video:find"));
System.out.println("用户 "+username+" 是否有video:buy 权限:"+subject.isPermitted("video:buy"));
System.out.println("用户 "+username+" 是否有video:find,video:buy 两个权限:"+subject.isPermittedAll("video:find","video:buy"));
//用户退出登录
subject.logout();
System.out.println("身份认证结果:"+subject.isAuthenticated());
}
@Test
public void testAuthentication2() {
//创建SecurityManager工厂,通过ini配置文件读取
IniSecurityManagerFactory factory = new IniSecurityManagerFactory("classpath:shiro.ini");
//通过SecurityManager工厂获取SecurityManager实例
SecurityManager securityManager = factory.getInstance();
//通过SecurityUtils工具类将SecurityManager设置到运行环境中
SecurityUtils.setSecurityManager(securityManager);
//通过SecurityUtils工具类获取主题
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken("xdclass","123");
subject.login(token);
System.out.println("身份认证结果:"+subject.isAuthenticated());
String username = subject.getPrincipal().toString();
System.out.println("主体是:"+subject.getPrincipal());
System.out.println("用户 "+username+" 是否有admin角色:"+subject.hasRole("admin"));
//在ini设置了admin=* 表示角色admin有所有权限
System.out.println("用户 "+username+" 是否有video:find,video:buy 两个权限:"+subject.isPermittedAll("video:find","video:buy"));
//用户退出登录
subject.logout();
System.out.println("身份认证结果:"+subject.isAuthenticated());
}
}
第一个测试方法测试用户jack
,第二个测试用户xdclass
创建一个数据库,导入以下sql文件
DROP TABLE IF EXISTS `roles_permissions`;
CREATE TABLE `roles_permissions` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`role_name` varchar(100) DEFAULT NULL,
`permission` varchar(100) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `idx_roles_permissions` (`role_name`,`permission`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
LOCK TABLES `roles_permissions` WRITE;
/*!40000 ALTER TABLE `roles_permissions` DISABLE KEYS */;
INSERT INTO `roles_permissions` (`id`, `role_name`, `permission`)
VALUES
(4,'admin','video:*'),
(3,'role1','video:buy'),
(2,'role1','video:find'),
(5,'role2','*'),
(1,'root','*');
/*!40000 ALTER TABLE `roles_permissions` ENABLE KEYS */;
UNLOCK TABLES;
DROP TABLE IF EXISTS `user_roles`;
CREATE TABLE `user_roles` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`username` varchar(100) DEFAULT NULL,
`role_name` varchar(100) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `idx_user_roles` (`username`,`role_name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
LOCK TABLES `user_roles` WRITE;
/*!40000 ALTER TABLE `user_roles` DISABLE KEYS */;
INSERT INTO `user_roles` (`id`, `username`, `role_name`)
VALUES
(1,'jack','role1'),
(2,'jack','role2'),
(4,'xdclass','admin'),
(3,'xdclass','root');
/*!40000 ALTER TABLE `user_roles` ENABLE KEYS */;
UNLOCK TABLES;
DROP TABLE IF EXISTS `users`;
CREATE TABLE `users` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`username` varchar(100) DEFAULT NULL,
`password` varchar(100) DEFAULT NULL,
`password_salt` varchar(100) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `idx_users_username` (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
LOCK TABLES `users` WRITE;
/*!40000 ALTER TABLE `users` DISABLE KEYS */;
INSERT INTO `users` (`id`, `username`, `password`, `password_salt`)
VALUES
(1,'jack','123',NULL),
(2,'xdclass','456',NULL);
/*!40000 ALTER TABLE `users` ENABLE KEYS */;
UNLOCK TABLES;
在test->resources目录下创建jdbcrealm.ini文件
#注意 文件格式必须为ini,编码为ANSI
#声明Realm,指定realm类型
jdbcRealm=org.apache.shiro.realm.jdbc.JdbcRealm
#配置数据源
#dataSource=com.mchange.v2.c3p0.ComboPooledDataSource
dataSource=com.alibaba.druid.pool.DruidDataSource
# mysql-connector-java 5 用的驱动url是com.mysql.jdbc.Driver,mysql-connector-java6以后用的是com.mysql.cj.jdbc.Driver
dataSource.driverClassName=com.mysql.cj.jdbc.Driver
#避免安全警告
dataSource.url=jdbc:mysql://localhost:3306/xdclass_shiro?characterEncoding=UTF-8&serverTimezone=UTC&useSSL=false
dataSource.username=root
dataSource.password=root
#指定数据源
jdbcRealm.dataSource=$dataSource
#开启查找权限, 默认是false,不会去查找角色对应的权限,坑!!!!!
jdbcRealm.permissionsLookupEnabled=true
#指定SecurityManager的Realms实现,设置realms,可以有多个,用逗号隔开
securityManager.realms=$jdbcRealm
新建测试类QuickStartTest5_3
package com.xdclass;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.config.IniSecurityManagerFactory;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.subject.Subject;
import org.junit.Test;
/**
* @author : wly
* @version : 1.0
* @date : 2021/11/21 11:58
* @description:
*/
public class QuickStartTest5_3 {
@Test
public void testAuthentication() {
//创建SecurityManager工厂,通过ini配置文件读取
IniSecurityManagerFactory factory = new IniSecurityManagerFactory("classpath:jdbcrealm.ini");
//通过SecurityManager工厂获取SecurityManager实例
SecurityManager securityManager = factory.getInstance();
//通过SecurityUtils工具类将SecurityManager设置到运行环境中
SecurityUtils.setSecurityManager(securityManager);
//通过SecurityUtils工具类获取主题
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken("jack","123");
subject.login(token);
System.out.println("身份认证结果:"+subject.isAuthenticated());
System.out.println( "是否有role1角色:"+subject.hasRole("role1"));
System.out.println( "是否有role2角色:"+subject.hasRole("role2"));
System.out.println( "是否有video:find 权限:"+subject.isPermitted("video:find"));
System.out.println(" 是否有video:buy 权限:"+subject.isPermitted("video:buy"));
}
}
试着把ini
里的jdbcRealm.permissionsLookupEnabled
设置为false
,发现jack没有对应的权限。
简介:自定义Realm
实战基础
步骤:
AuthorizingRealm
->AuthenticatingRealm
->CachingRealm
->Realm
doGetAuthorizationInfo
doGetAuthenticationInfo
方法:
doGetAuthenticationInfo
doGetAuthorizationInfo
对象介绍
UsernamePasswordToken
: 对应就是 shiro
的token
中有Principal
和Credential
UsernamePasswordToken
-》HostAuthenticationToken
-》AuthenticationToken
SimpleAuthorizationInfo
:代表用户角色权限信息
SimpleAuthenticationInfo
:代表该用户的认证信息
继承AuthorizingRealm
的自定义realm类
package com.xdclass;
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.realm.SimpleAccountRealm;
import org.apache.shiro.subject.PrincipalCollection;
import java.util.*;
/**
* @author : wly
* @version : 1.0
* @date : 2021/11/21 21:24
* @description: 自定义realm
*/
public class CustomRealm extends AuthorizingRealm {
//模拟数据库的用户数据(用户名密码)
private final Map<String,String> userInfoMap = new HashMap<>();
{
userInfoMap.put("jack", "123");
userInfoMap.put("xdclass", "456");
}
//模拟数据库的角色数据
private final Map<String, Set<String>> userHasRolesMap = new HashMap<>();
{
Set<String> set1 = new HashSet<>();
set1.add("role1");
set1.add("role2");
Set<String> set2 = new HashSet<>();
set2.add("root");
set2.add("admin");
userHasRolesMap.put("jack",set1);
userHasRolesMap.put("xdclass",set2);
}
//模拟数据库的权限数据
private final Map<String, Set<String>> roleHasPermissionsMap = new HashMap<>();
{
Set<String> set3 = new HashSet<>();
set3.add("video:look");
set3.add("video:buy");
Set<String> set4 = new HashSet<>();
set4.add("video:delete");
set4.add("video:query");
Set<String> set5 = new HashSet<>();
set5.add("video:delete");
set5.add("video:query");
set5.add("video:look");
set5.add("video:buy");
roleHasPermissionsMap.put("role1",set3);
roleHasPermissionsMap.put("role2",set4);
roleHasPermissionsMap.put("admin",set5);
}
/**
* 权限校验时调用
* @param principals
* @return org.apache.shiro.authz.AuthorizationInfo
**/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
System.out.println("权限doGetAuthorizationInfo");
//获取用户名
String name = (String) principals.getPrimaryPrincipal();
//通过用户名查询用户有哪些角色
Set<String> userHasRoles = getRolesByUserNameFromDB(name);
//通过用户名查询有哪些权限
Set<String> userHasPermissions = getPermissionsByUserNameFromDB(name);
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
simpleAuthorizationInfo.setRoles(userHasRoles);
simpleAuthorizationInfo.setStringPermissions(userHasPermissions);
return simpleAuthorizationInfo;
}
/**
* 用户登录时调用,用于身份认证
* @param token 包含用户输入的用户名和密码等信息
* @return org.apache.shiro.authc.AuthenticationInfo
**/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
System.out.println("认证doGetAuthenticationInfo");
//获取用户输入的用户名
String name = token.getPrincipal().toString();
//用户输入的用户名,模拟在数据查询有没有该用户对应的密码,没有则认证不通过,有则认证通过
String password = getPassWordByUserNameFromDB(name);
if (password == null || "".equals(password)) {
return null;
}
SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(name,password,this.getName());
//认证通过则返回该该用户的认证信息,包含用户名密码和realm的名称
return simpleAuthenticationInfo;
}
private String getPassWordByUserNameFromDB(String name) {
return userInfoMap.get(name);
}
/**
* //模拟从数据库通过用户名查询有哪些权限
* @param name
* @return java.util.Set
**/
private Set<String> getPermissionsByUserNameFromDB(String name) {
Set<String> userHasPermissions = new HashSet<>();
//获取用户拥有的角色,遍历每个角色拥有的权限
Set<String> roles = userHasRolesMap.get(name);
// Set permissions = new HashSet<>();
for (String role : roles) {
//当某个角色暂时没有赋予权限时会报空指针,所以需要跳出本次循环
if (roleHasPermissionsMap.get(role) == null)
continue;
Set<String> permissions = roleHasPermissionsMap.get(role);
userHasPermissions.addAll(permissions);
}
return userHasPermissions;
}
/**
* //模拟从数据库通过用户名查询用户有哪些角色
* @param name
* @return java.util.Set
**/
private Set<String> getRolesByUserNameFromDB(String name) {
return userHasRolesMap.get(name);
}
}
测试类
package com.xdclass;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.config.IniSecurityManagerFactory;
import org.apache.shiro.mgt.DefaultSecurityManager;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.subject.Subject;
import org.junit.Before;
import org.junit.Test;
/**
* @author : wly
* @version : 1.0
* @date : 2021/11/21 11:58
* @description: 测试自定义realm
*/
public class QuickStartTest5_4 {
private DefaultSecurityManager securityManager = new DefaultSecurityManager();
private CustomRealm customRealm = new CustomRealm();
@Before
public void init() {
//设置realm
securityManager.setRealm(customRealm);
//构建环境
SecurityUtils.setSecurityManager(securityManager);
}
//测试认证
@Test
public void testAuthentication() {
//获取主体
Subject subject = SecurityUtils.getSubject();
//用户输入用户名和密码
UsernamePasswordToken token = new UsernamePasswordToken("jack","123");
subject.login(token);
System.out.println("用户主体是:"+subject.getPrincipal());
System.out.println("身份认证结果是:"+subject.isAuthenticated());
}
@Test
public void testAuthorization() {
//获取主体
Subject subject = SecurityUtils.getSubject();
//用户输入用户名和密码
UsernamePasswordToken token = new UsernamePasswordToken("jack","123");
subject.login(token);
System.out.println("用户主体是:"+subject.getPrincipal());
System.out.println("身份认证结果是:"+subject.isAuthenticated());
String name = subject.getPrincipal().toString();
//权限校验
//checkRole通过就继续往下走,没通过就抛出异常
subject.checkRole("role1");
System.out.println(name+" 是否拥有拥有角色role1:"+subject.hasRole("role1"));
//isPermitted只有当值为true才会返回值,为false不会返回值
System.out.println(name+" 是否拥有拥有权限video:delete" +subject.isPermitted("video:delete"));
subject.logout();
Subject subject2 = SecurityUtils.getSubject();
UsernamePasswordToken token1 = new UsernamePasswordToken("xdclass","456");
subject2.login(token1);
String name2 = subject2.getPrincipal().toString();
System.out.println(name2+" 是否拥有拥有角色admin:"+subject2.hasRole("admin"));
System.out.println(name2+" 是否拥有拥有权限video:look "
+subject2.isPermitted("video:look"));
}
}
简介:Shiro
认证和授权流程的源码解读,和断点测试
认证流程解读:
subject.login(usernamePasswordToken);
DelegatingSubject->login()
DefaultSecurityManager->login()
AuthenticatingSecurityManager->authenticate()
AbstractAuthenticator->authenticate()
ModularRealmAuthenticator->doAuthenticate()
ModularRealmAuthenticator->doSingleRealmAuthentication()
AuthenticatingRealm->getAuthenticationInfo()
//补充:密码验证方法 AuthenticatingRealm-> assertCredentialsMatch()
授权流程解读:
subject.checkRole("admin")
DelegatingSubject->checkRole()
AuthorizingSecurityManager->checkRole()
ModularRealmAuthorizer->checkRole()
AuthorizingRealm->hasRole()
AuthorizingRealm->doGetAuthorizationInfo()
简介:讲解shir
o内置的过滤器讲解
核心过滤器类:DefaultFilter
, 配置哪个路径对应哪个拦截器进行处理
authc
:org.apache.shiro.web.filter.authc.FormAuthenticationFilter
user
:org.apache.shiro.web.filter.authc.UserFilter
anon
:org.apache.shiro.web.filter.authc.AnonymousFilter
roles
:org.apache.shiro.web.filter.authz.RolesAuthorizationFilter
roles["admin,user"]
,当有多个参数时必须每个参数都通过才算通过perms
:org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter
authcBasic
:org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter
httpBasic
身份验证拦截器。logout
:org.apache.shiro.web.filter.authc.LogoutFilter
shiroFilterFactoryBean.setLoginUrl();
设置的 urlport
:org.apache.shiro.web.filter.authz.PortFilter
ssl
:org.apache.shiro.web.filter.authz.SslFilter
ssl
拦截器,只有请求协议是https
才能通过。简介:Filter配置路径
/admin/video /user /pub
路径通配符支持 ?、*、**,注意通配符匹配不 包括目录分隔符“/”
心 可以匹配所有,不加*可以进行前缀匹配,但多个冒号就需要多个 * 来匹配
URL权限采取第一次匹配优先的方式
?
: 匹配一个字符,如 /user? , 匹配 /user3,但不匹配/user/;
*
: 匹配零个或多个字符串,如 /add* ,匹配 /addtest,但不匹配 /user/1
**
: 匹配路径中的零个或多个路径,如 /user/** 将匹 配 /user/xxx 或 /user/xxx/yyy例子 /user/**=filter1 /user/add=filter2
请求 /user/add 命中的是filter1拦截器
简介: 数据安全核心知识,介绍常见的处理办法,Shiro
里的 CredentialsMatcher
使用
为啥要加解密
什么是散列算法
什么是salt(盐) 667788——》aabbcc
Shiro
里面 CredentialsMatcher
,用来验证密码是否正确,
源码:AuthenticatingRealm -> assertCredentialsMatch()
一般会自定义验证规则
@Bean
public HashedCredentialsMatcher hashedCredentialsMatcher(){
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
//散列算法,使用MD5算法;
hashedCredentialsMatcher.setHashAlgorithmName("md5");
//散列的次数,比如散列两次,相当于 md5(md5("xxx"));
hashedCredentialsMatcher.setHashIterations(2);
return hashedCredentialsMatcher;
}
简介:权限角色控制 @RequiresRoles
, @RequiresPermissions
等注解的使用和编程式控制
配置文件的方式
使用ShiroConfig
注解方式
@RequiresRoles(value={"admin", "editor"}, logical= Logical.AND)
@RequiresPermissions (value={"user:add", "user:del"}, logical= Logical.OR)
@RequiresAuthentication
Subject.isAuthenticated()
返回true@RequiresUser
编程方式
Subject subject = SecurityUtils.getSubject();
//基于角色判断
if(subject.hasRole(“admin”)) {
//有角色,有权限
} else {
//无角色,无权限
}
//或者权限判断
if(subject.isPermitted("/user/add")){
//有权限
}else{
//无权限
}
subject.hasRole("xxx");
subject.isPermitted("xxx");
subject. isPermittedAll("xxxxx","yyyy");
subject.checkRole("xxx");
// 无返回值,可以认为内部使用断言的方式简介:缓存的作用和Shiro
的缓存模块
什么是shiro
缓存
shiro
中提供了对认证信息和授权信息的缓存。
AuthenticatingRealm
及 AuthorizingRealm
分别提供了对AuthenticationInfo
和 AuthorizationInfo
信息的缓存。
简介:Shiro Session
模块作用和SessionManager
什么是会话session
session
识别到哪个用户,和javaweb
中的session
类似什么是会话管理器SessionManager
会话管理器管理所有subject
的所有操作,是shiro的核心组件
核心方法:
//开启一个session
Session start(SessionContext context);
//指定Key获取session
Session getSession(SessionKey key)
shiro
中的会话管理器有多个实现
SessionDao
会话存储/持久化
SessionDAO
AbstractSessionDAO
CachingSessionDAO
EnterpriseCacheSessionDAO
MemorySessionDAO
核心方法
//创建
Serializable create(Session session);
//获取
Session readSession(Serializable sessionId) throws UnknownSessionException;
//更新
void update(Session session)
//删除,会话过期时会调用
void delete(Session session);
//获取活跃的session
Collection<Session> getActiveSessions();
会话存储有多个实现
附属资料:
RememberMe
1、 Cookie 写到客户端并 保存
2、 通过调用subject.login()前,设置 token.setRememberMe(true);
3、 关闭浏览器再重新打开;会发现浏览器还是记住你的
4、 注意点:
- subject.isAuthenticated() 表示用户进行了身份验证登录的,即Subject.login 进行了登录
- subject.isRemembered() 表示用户是通过RememberMe登录的
- subject.isAuthenticated()==true,则 subject.isRemembered()==false, 两个互斥
- 总结:特殊页面或者API调用才需要authc进行验证拦截,该拦截器会判断用户是否是通过 subject.login()登录,安全性更高,其他非核心接口或者页面则通过user拦截器处理即可
简介:介绍Apache Shiro整合SpringBoot2.x综合实战和技术栈
简介:设计案例实战数据库 用户-角色-权限 及关联表
DROP TABLE IF EXISTS `permission`;
CREATE TABLE `permission` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(128) DEFAULT NULL COMMENT '名称',
`url` varchar(128) DEFAULT NULL COMMENT '接口路径',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
LOCK TABLES `permission` WRITE;
/*!40000 ALTER TABLE `permission` DISABLE KEYS */;
INSERT INTO `permission` (`id`, `name`, `url`)
VALUES
(1,'video_update','/api/video/update'),
(2,'video_delete','/api/video/delete'),
(3,'video_add','/api/video/add'),
(4,'order_list','/api/order/list'),
(5,'user_list','/api/user/list');
/*!40000 ALTER TABLE `permission` ENABLE KEYS */;
UNLOCK TABLES;
DROP TABLE IF EXISTS `role`;
CREATE TABLE `role` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(128) DEFAULT NULL COMMENT '名称',
`description` varchar(64) DEFAULT NULL COMMENT '描述',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
LOCK TABLES `role` WRITE;
/*!40000 ALTER TABLE `role` DISABLE KEYS */;
INSERT INTO `role` (`id`, `name`, `description`)
VALUES
(1,'admin','普通管理员'),
(2,'root','超级管理员'),
(3,'editor','审核人员');
/*!40000 ALTER TABLE `role` ENABLE KEYS */;
UNLOCK TABLES;
DROP TABLE IF EXISTS `role_permission`;
CREATE TABLE `role_permission` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`role_id` int(11) DEFAULT NULL,
`permission_id` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
LOCK TABLES `role_permission` WRITE;
/*!40000 ALTER TABLE `role_permission` DISABLE KEYS */;
INSERT INTO `role_permission` (`id`, `role_id`, `permission_id`)
VALUES
(1,3,1),
(2,3,2),
(3,3,3),
(4,2,1),
(5,2,2),
(6,2,3),
(7,2,4);
/*!40000 ALTER TABLE `role_permission` ENABLE KEYS */;
UNLOCK TABLES;
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`username` varchar(128) DEFAULT NULL COMMENT '用户名',
`password` varchar(256) DEFAULT NULL COMMENT '密码',
`create_time` datetime DEFAULT NULL,
`salt` varchar(128) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
LOCK TABLES `user` WRITE;
/*!40000 ALTER TABLE `user` DISABLE KEYS */;
INSERT INTO `user` (`id`, `username`, `password`, `create_time`, `salt`)
VALUES
(1,'二当家小D','123456',NULL,NULL),
(2,'大当家','123456789',NULL,NULL),
(3,'jack','123',NULL,NULL);
/*!40000 ALTER TABLE `user` ENABLE KEYS */;
UNLOCK TABLES;
DROP TABLE IF EXISTS `user_role`;
CREATE TABLE `user_role` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`role_id` int(11) DEFAULT NULL,
`user_id` int(11) DEFAULT NULL,
`remarks` varchar(64) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
LOCK TABLES `user_role` WRITE;
/*!40000 ALTER TABLE `user_role` DISABLE KEYS */;
INSERT INTO `user_role` (`id`, `role_id`, `user_id`, `remarks`)
VALUES
(1,3,1,'二当家小D是editor'),
(2,1,3,'jack是admin'),
(3,2,3,'jack是root'),
(4,3,3,'jack是editor'),
(5,1,2,'大当家是admin');
/*!40000 ALTER TABLE `user_role` ENABLE KEYS */;
UNLOCK TABLES;
简介:使用springboot+mybatis+shiro
搭建项目基础框架
创建项目rbac_shiro
,添加wb,mysql,mybatis,druid,shiro依赖
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.mybatis.spring.bootgroupId>
<artifactId>mybatis-spring-boot-starterartifactId>
<version>2.2.0version>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>druidartifactId>
<version>1.2.1version>
dependency>
<dependency>
<groupId>org.apache.shirogroupId>
<artifactId>shiro-springartifactId>
<version>1.4.0version>
dependency>
dependencies>
简介:开发用户-角色-权限 相关Service和Dao层
#============数据库相关配置================
spring.datasource.driver-class-name= com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/xdclass_rbac_shiro?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=UTC
spring.datasource.username =root
spring.datasource.password =root
#使用阿里巴巴druid数据源,默认使用自带的
#spring.datasource.type =com.alibaba.druid.pool.DruidDataSource
#开启控制台打印sql
mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
# mybatis 下划线转驼峰配置,两者都可以
#mybatis.configuration.mapUnderscoreToCamelCase=true
mybatis.configuration.map-underscore-to-camel-case=true
简介:开发用户-角色-权限 多对多关联查询SQL
第一步 查询用户对应的角色映射关系
select * from user u
left join user_role ur on u.id=ur.user_id
where u.id=3
第二步 查询用户对应的角色信息
select * from user u
left join user_role ur on u.id=ur.user_id
left join role r on ur.role_id = r.id
where u.id=3
第三步 查询角色和权限的关系
select * from user u
left join user_role ur on u.id=ur.user_id
left join role r on ur.role_id = r.id
left join role_permission rp on r.id=rp.role_id
where u.id=1
第四步 查询角色对应的权限信息(某个用户具备的角色和权限集合)
select * from user u
left join user_role ur on u.id=ur.user_id
left join role r on ur.role_id = r.id
left join role_permission rp on r.id=rp.role_id
left join permission p on rp.permission_id=p.id
where u.id=1
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
/**
* 根据用户id查询用户简单信息
*
* @param id
* @return net.xdclass.rbac_shiro.domain.User
**/
@RequestMapping("/findByUserId")
public User getUserByUserId(@RequestParam("userId") int id) {
return userService.findUserSimpleInfoById(id);
}
/**
* 根据用户名查询用户简单信息
*
* @param username
* @return net.xdclass.rbac_shiro.domain.User
**/
@RequestMapping("/findByUsername")
public User getUserByUsername(@RequestParam("username") String username) {
return userService.findUserSimpleInfoByUsername(username);
}
/**
* 根据用户名和密码查询用户简单信息
*
* @param username
* @param password
* @return net.xdclass.rbac_shiro.domain.User
**/
@RequestMapping("/find")
public User getUserByUsernameAndPassword(@RequestParam("username") String username, @RequestParam("password") String password) {
return userService.findByUsernameAndPassword(username, password);
}
/**
* 根据用户id查询用户角色(包含角色的权限)
*
* @param id
* @return net.xdclass.rbac_shiro.domain.User
**/
@RequestMapping("/findRoleListByUserId")
public List<Role> getUserInfoByUserId(@RequestParam("userId") int id) {
return userService.findRoleListByUserId(id);
}
/**
* 根据用户名查询用户角色(包含角色的权限)
*
* @param username
* @return net.xdclass.rbac_shiro.domain.User
**/
@RequestMapping("/findRoleListByUsername")
public List<Role> getUserInfoByUsername(@RequestParam("username") String username) {
return userService.findRoleListByUsername(username);
}
/**
* 根据用户ID查询用户全部信息(包括角色和角色有的权限)
* @param userId
* @return net.xdclass.rbac_shiro.domain.User
**/
@RequestMapping("/findUserAllInfoByUserId")
public User findUserAllInfoByUserId(@RequestParam("userId") int userId){
return userService.findUserAllInfoByUserId(userId);
}
/**
* 根据用户名查询用户全部信息(包括角色和角色有的权限)
* @param username
* @return net.xdclass.rbac_shiro.domain.User
**/
@RequestMapping("/findUserAllInfoByUsername")
public User findUserAllInfoByUsername(@RequestParam("username") String username){
return userService.findUserAllInfoByUsername(username);
}
}
RoleController.java
@RestController
@RequestMapping("/role")
public class RoleController {
@Autowired
private RoleService roleService;
/** 根据角色Id查询权限
* @param roleId
* @return java.util.List
**/
@RequestMapping("/findPermissionById")
public List<Permission> getPermission(@RequestParam("roleId") int roleId){
return roleService.findPermissionListByRoleId(roleId);
}
/** 根据角色名查询权限
* @param roleName
* @return java.util.List
**/
@RequestMapping("/findPermissionByName")
public List<Permission> getPermission(@RequestParam("roleName") String roleName){
return roleService.findPermissionListByRoleName(roleName);
}
}
dao
层代码如下
UserMapper.java
public interface UserMapper {
/**
* 根据用户id查询用户简单信息
*/
@Select("select * from user where id=#{id} ")
User findById(@Param("id") int id);
/**
* 根据用户名查询用户简单信息
*/
@Select("select * from user where username =#{username} ")
User findByUsername(@Param("username") String username);
/**
* 根据用户名和密码查询用户简单信息
*/
@Select("select * from user where username =#{username} and password=#{pwd} ")
User findByUsernameAndPassword(@Param("username") String name, @Param("pwd") String password);
/**
* 根据用户ID查询用户拥有的角色(包括角色有的权限)
*/
@Select("Select r.id id,r.name name,r.description description from user_role ur left join role r on ur.role_id=r.id where ur.user_id=#{userId} ")
@Results(value = {
@Result(id = true, property = "id", column = "id"),
@Result(property = "name", column = "name"),
@Result(property = "description", column = "description"),
@Result(property = "permissionList", column = "id", many = @Many(
select = "net.xdclass.rbac_shiro.dao.RoleMapper.findPermissionListByRoleId",
fetchType = FetchType.DEFAULT))})
List<Role> findRoleListByUserId(@Param("userId") int userId);
}
RoleMapper.java
public interface RoleMapper {
/**
* 根据角色id查询权限
*/
@Select("select p.id,p.name,p.url from role_permission rp left join permission p on rp.permission_id=p.id where rp.role_id=#{roleId} ")
List<Permission> findPermissionListByRoleId(@Param("roleId") int roleId);
/**
* 根据角色名查询权限
*/
@Select("select p.id,p.name,p.url from role r left join role_permission rp on r.id=rp.role_id left join" +
" permission p on rp.permission_id=p.id where r.name=#{roleName} ")
List<Permission> findPermissionListByRoleName(@Param("roleName") String roleName);
}
domain
层代码如下(只写属性)
User.java
private int id;
private String username;
private String password;
private Date createTime;
private String salt;
private List<Role> roleList;
Role.java
private int id;
private String name;
private String description;
private List<Permission> permissionList;
UserRole.java
private int id;
private int userId;
private int roleId;
Permission.java
private int id;
private String name;
private String url;
RolePermission.java
private int id;
private int roleId;
private int permissionId;
service
及impl层层代码如下
Uservice.java
public interface UserService {
/**
* 根据用户id查询用户简单信息
*/
User findUserSimpleInfoById(int id);
/**
* 根据用户名查询用户简单信息
*/
User findUserSimpleInfoByUsername(String name);
/**
* 根据用户名和密码查询用户简单信息
*/
User findByUsernameAndPassword(String name, String password);
/**
* 根据用户ID查询用户拥有的角色(包括角色拥有的权限)
*/
List<Role> findRoleListByUserId(int userId);
/**
* 根据用户名查询用户拥有的角色(包括角色拥有的权限)
*/
List<Role> findRoleListByUsername(String name);
/**
* 根据用户ID查询用户全部信息(包括角色和角色有的权限)
*/
User findUserAllInfoByUserId(int userId);
/**
* 根据用户名查询用户全部信息(包括角色和角色有的权限)
*/
User findUserAllInfoByUsername(String name);
}
UserServiceImpl.java
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserMapper userMapper;
/**
* 根据用户id查询用户简单信息
*/
@Override
public User findUserSimpleInfoById(int id) {
return userMapper.findById(id);
}
/**
* 根据用户名查询用户简单信息
*/
@Override
public User findUserSimpleInfoByUsername(String name) {
return userMapper.findByUsername(name);
}
/**
* 根据用户名和密码查询用户简单信息
*/
@Override
public User findByUsernameAndPassword(String name, String password) {
return userMapper.findByUsernameAndPassword(name, password);
}
/**
* 根据用户ID查询用户拥有的角色(包括角色拥有的权限)
*/
@Override
public List<Role> findRoleListByUserId( int userId){
return userMapper.findRoleListByUserId(userId);
}
/**
* 根据用户名查询用户拥有的角色(包括角色拥有的权限)
*/
@Override
public List<Role> findRoleListByUsername(String name){
int id = userMapper.findByUsername(name).getId();
return userMapper.findRoleListByUserId(id);
}
/**
* 根据用户ID查询用户全部信息(包括角色和角色有的权限)
*/
@Override
public User findUserAllInfoByUserId( int userId){
User user = userMapper.findById(userId);
List<Role> roleList = userMapper.findRoleListByUserId(userId);
user.setRoleList(roleList);
return user;
}
/**
* 根据用户名查询用户全部信息(包括角色和角色有的权限)
*/
@Override
public User findUserAllInfoByUsername(String name){
User user = userMapper.findByUsername(name);
List<Role> roleList = userMapper.findRoleListByUserId(user.getId());
user.setRoleList(roleList);
return user;
}
}
RoleService.java
public interface RoleService {
/**
* 根据角色id查询权限
*/
List<Permission> findPermissionListByRoleId(int roleId);
/**
* 根据角色名查询权限
*/
List<Permission> findPermissionListByRoleName( String roleName);
}
RoleServiceImpl.java
@Service
public class RoleServiceImpl implements RoleService {
@Autowired
private RoleMapper roleMapper;
/**
* 根据角色id查询权限
*/
@Override
public List<Permission> findPermissionListByRoleId(int roleId) {
return roleMapper.findPermissionListByRoleId(roleId);
}
/**
* 根据角色名查询权限
*/
@Override
public List<Permission> findPermissionListByRoleName(String roleName) {
return roleMapper.findPermissionListByRoleName(roleName);
}
}
简介:自定义CustomRealm
开发实战
AuthorizingRealm
doGetAuthorizationInfo
doGetAuthenticationInfo
config
包建一个CustomRealm
类
package net.xdclass.rbac_shiro.config;
import net.xdclass.rbac_shiro.domain.Permission;
import net.xdclass.rbac_shiro.domain.Role;
import net.xdclass.rbac_shiro.domain.User;
import net.xdclass.rbac_shiro.service.UserService;
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;
import java.util.ArrayList;
import java.util.List;
/**
* @author : wly
* @version : 1.0
* @date : 2021/11/24 12:10
* @description: 自定义realm
*/
public class CustomRealm extends AuthorizingRealm {
@Autowired
private UserService userService;
/**
* 授权,权限校验时使用
* @param principals
* @return org.apache.shiro.authz.AuthorizationInfo
**/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
System.out.println("授权doGetAuthorizationInfo");
//获取用户名
String username = (String) principals.getPrimaryPrincipal();
List<String> stringRoleList = new ArrayList<>();
List<String> stringPermissionList = new ArrayList<>();
//通过用户名查询用户拥有的角色
User user = userService.findUserAllInfoByUsername(username);
//获取用户角色列表
List<Role> roleList = user.getRoleList();
for (Role role : roleList) {
stringRoleList.add(role.getName());
List<Permission> permissionList = role.getPermissionList();
for (Permission permission : permissionList) {
if (permission != null) {
stringPermissionList.add(permission.getName());
}
}
}
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
simpleAuthorizationInfo.addRoles(stringRoleList);
simpleAuthorizationInfo.addStringPermissions(stringPermissionList);
return simpleAuthorizationInfo;
}
/**
* 身份认证,登录时调用
* @param token 含有用户输入的用户名和密码
* @return org.apache.shiro.authc.AuthenticationInfo
**/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
System.out.println("认证doGetAuthenticationInfo");
//从token中获取用户名
String username = (String) token.getPrincipal();
//从数据库中取密码
User user = userService.findUserAllInfoByUsername(username);
String password = user.getPassword();
if (password == null || "".equals(password)){
// return null;
//如果根据用户输入的用户名,去数据库中没有查询到相关的密码
throw new UnknownAccountException();
}
/**
* 返回一个从数据库中查出来的的认证信息。用户名为username,密码为从数据库查出对应用户名的password。
* 封装成当前返回值
* 接下来shiro框架做的事情就很简单了。
* 它会拿你的输入的token与当前返回的SimpleAuthenticationInfo对比一下
* 看看是不是一样,如果用户的帐号密码(即token)与数据库中查出来的数据一样,那么本次登录成功
* 否则就是你密码输入错误*/
return new SimpleAuthenticationInfo(username,password,this.getName());
}
}
简介:讲解ShiroFilterFactoryBean配置实战
shiro大体上的配置就是一个配置类里面有5个bean(如果不是前后端分离则不用设置sessionManager)
1.ShiroFilterFactoryBean
2.SecurityManager
3.CustomSessionManager
4.CustomRealm
5.hashedCredentialsMatcher
也就是说在项目中使用shiro,主要是写好这五个bean。
ShiroFilterFactoryBean
->设置SecurityManager
SecurityManager
->设置CustomSessionManager
和CustomRealm
CustomRealm
->设置hashedCredentialsMatcher
CustomRealm
是realm
的自定义实现,CustomSessionManager
是SessionManager
的自定义实现SessionManager
DefaultSessionManager
: 默认实现,常用于javaseServletContainerSessionManager
: web环境DefaultWebSessionManager
:常用于自定义实现首先在config
下创建SessionManager
的自定义实现类CustomSessionManager
,继承自DefaultWebSessionManager
,具体实现下节写
在config
包创建配置类ShiroConfig
,这个类就是shiro的关键
ShiroConfig
代码如下
package net.xdclass.rbac_shiro.config;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.session.mgt.SessionManager;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* @author : wly
* @version : 1.0
* @date : 2021/11/24 18:01
* @description: realm和sessionManager都需要自定义实现
* shiro配置
* shiroFilterFactoryBean里面-》设置
* SecurityManager-里面》设置
* CustomSessionManager和
* CustomRealm里面-》设置
* hashedCredentialsMatcher
*/
@Configuration
public class ShiroConfig {
/**
* spring容器中注入securityManager
* securityManager里面设置CustomSessionManager和CustomRealm
* @param
* @return org.apache.shiro.mgt.SecurityManager
**/
@Bean
public SecurityManager securityManager() {
//web环境下用SecurityManager的web环境下实现类
DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
//设置sessionManager,非前后端分离下不用设置
defaultWebSecurityManager.setSessionManager(sessionManager());
//设置realm,推荐放到后面(某些情况下可能不生效)
defaultWebSecurityManager.setRealm(customRealm());
return defaultWebSecurityManager;
}
/**
* spring容器中注入自定义realm
* @param
* @return net.xdclass.rbac_shiro.config.CustomRealm
**/
@Bean
public CustomRealm customRealm() {
CustomRealm customRealm = new CustomRealm();
//设置hashedCredentialsMatcher
customRealm.setCredentialsMatcher(hashedCredentialsMatcher());
return customRealm;
}
/**
* spring容器中注入HashedCredentialsMatcher,密码加解密规则
* @param
* @return org.apache.shiro.authc.credential.HashedCredentialsMatcher
**/
@Bean
public HashedCredentialsMatcher hashedCredentialsMatcher() {
HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
//设置散列算法
credentialsMatcher.setHashAlgorithmName("md5");
//设置hash次数
credentialsMatcher.setHashIterations(2);
return credentialsMatcher;
}
/**
* spring容器中注入自定义sessionManager
* @param
* @return org.apache.shiro.session.mgt.SessionManager
**/
@Bean
public SessionManager sessionManager() {
return new CustomSessionManager();
}
/**
* filter工厂,设置过滤条件和跳转条件
* @param securityManager
* @return org.apache.shiro.spring.web.ShiroFilterFactoryBean
**/
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
//将securityManager设置到环境中(必须设置)
shiroFilterFactoryBean.setSecurityManager(securityManager);
//需要登录的接口,如果访问某个接口,需要登录却没有登录,则调用此接口(非前后端分离则跳转页面)
//如果不设置值,默认会自动寻找Web工程根目录下的"/login.jsp"页面 或 "/login" 映射
shiroFilterFactoryBean.setLoginUrl("/pub/need_login");
//登录成功,跳转url,如果前后端分离,则没这个调用
shiroFilterFactoryBean.setSuccessUrl("");
//没有权限,未授权会调用此方法,先验证登录-》再验证是否有权限
shiroFilterFactoryBean.setUnauthorizedUrl("/pub/not_permit");
//拦截路径,坑一:部分路径无法进行拦截,时有时无,因为有的人用hashMap
//hashMap无序,配置的过滤器需要按照顺序进行拦截,所以用LinkHashedMap
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
//value来源于DefaultFilter
//退出过滤器
filterChainDefinitionMap.put("/logout","logout");
//匿名可以访问,也就是游客模式
filterChainDefinitionMap.put("/pub/**","anon");
//登录用户才可以访问(即通过认证才能访问/autc开头的路径)
filterChainDefinitionMap.put("/authc/**","authc");
//管理员角色才可以访问
filterChainDefinitionMap.put("/admin/**","roles[admin]");
//默认是同时具有admin和root角色才可以访问(可以自定义过滤器实现只有其中任意一个角色就可以访问,具体请看8.1)
//filterChainDefinitionMap.put("/admin/**","roles[admin,root]");
//有编辑权限才可以访问
filterChainDefinitionMap.put("/video/update","perms[video_update]");
//坑二:过滤链是顺序执行,从上而下,一般/** 放到最后面,如果用hashMap,有可能将/**放到第一个
//那么变成了任何路径都得要"authc"filter
filterChainDefinitionMap.put("/**","authc");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
}
简介:讲解前后端分离情况下自定义SessionManager
自定义sessionManager
的实现,在config
包下建CustomSessionManager
类,并修改ShiroConfig
类的sessionManager()
方法
修改后如下:
@Bean
public SessionManager sessionManager() {
CustomSessionManager customSessionManager = new CustomSessionManager();
//设置session的过期时间,单位毫秒
customSessionManager.setGlobalSessionTimeout(30000);
return customSessionManager;
}
CustomSessionManager
类代码如下:
package net.xdclass.rbac_shiro.config;
import org.apache.shiro.web.servlet.ShiroHttpServletRequest;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.apache.shiro.web.util.WebUtils;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.Serializable;
/**
* @author : wly
* @version : 1.0
* @date : 2021/11/24 21:26
* @description:
* 传统结构项目中,shiro从cookie中读取sessionId以此来维持会话,
* 在前后端分离的项目中(也可在移动APP项目使用),非前后端分离就不用设置sessionManager,因为sessionId可以从cookie中取
* 我们选择在ajax的请求头中传递sessionId,因此需要重写shiro获取sessionId的方式。
*/
public class CustomSessionManager extends DefaultWebSessionManager {
//自定义请求头,key 为"token",value为会话的sessionId
private static final String AUTHORIZATION = "token";
public CustomSessionManager() {
super();
}
@Override
protected Serializable getSessionId(ServletRequest request, ServletResponse response) {
//获取请求头的sessionId
String sessionId = WebUtils.toHttp(request).getHeader(AUTHORIZATION);
if (sessionId != null) {
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE,
ShiroHttpServletRequest.COOKIE_SESSION_ID_SOURCE);
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, sessionId);
//标记sessionId有效
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE);
return sessionId;
} else {
//如果没有携带id参数则按照父类的方式在cookie进行获取
return super.getSessionId(request, response);
}
}
}
**简介: 讲解使用filterChainDefinitionMap
控制api
访问角色权限 **
localhost:8080/pub/login
localhost:8080/authc/video/play_record
localhost:8080/admin/video/order
localhost:8080/video/update
创建6个类
创建JsonData
类用于结果集,创建UserQuery
用于包装用户名和密码的实体类,
PubController
OrderController
AdminController
OrderController
具体哪些路径被设置了什么过滤器,参考上面的ShiroConfig类
PubController
用于测试anon
过滤器,即匿名过滤器任何人都可以访问包括游客,测试路径有:
localhost:8080/pub/need_login
此路径一是用于访问需要登录的接口时会调用,二可以直接调用因为是匿名过滤器
localhost:8080/pub/not_permit
此路径一是用于访问需要某些角色或者权限的接口时会调用,二可以直接调用因为是匿名过滤器
localhost:8080/pub/index
首页
localhost:8080/pub/login
登录接口
OrderController
用于测试authc过滤器,只有登录用户可以访问
AdminController
用于测试roles过滤器,只有具有某些角色的用户才可以访问
OrderController
用于测试perms过滤器,只有具有某些权限的用户才可以访问
6个类的代码如下
JsonData.java
package net.xdclass.rbac_shiro.domain;
import java.io.Serializable;
/**
* @author : wly
* @version : 1.0
* @date : 2021/11/25 17:59
* @description: 结果集
*/
public class JsonData implements Serializable {
private static final long serialVersionUID = 1L;
private Integer code; // 状态码 0 表示成功,1表示处理中,-1表示失败
private Object data; // 数据
private String msg;// 描述
public JsonData() {
}
public JsonData(Integer code, Object data, String msg) {
this.code = code;
this.data = data;
this.msg = msg;
}
// 成功,传入数据
public static JsonData buildSuccess() {
return new JsonData(0, null, null);
}
// 成功,传入数据
public static JsonData buildSuccess(Object data) {
return new JsonData(0, data, null);
}
// 失败,传入描述信息
public static JsonData buildError(String msg) {
return new JsonData(-1, null, msg);
}
// 失败,传入描述信息,状态码
public static JsonData buildError(String msg, Integer code) {
return new JsonData(code, null, msg);
}
// 成功,传入数据,及描述信息
public static JsonData buildSuccess(Object data, String msg) {
return new JsonData(0, data, msg);
}
// 成功,传入数据,及状态码
public static JsonData buildSuccess(Object data, int code) {
return new JsonData(code, data, null);
}
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
@Override
public String toString() {
return "JsonData [code=" + code + ", data=" + data + ", msg=" + msg
+ "]";
}
}
UserQuery.java
package net.xdclass.rbac_shiro.domain;
/**
* @author : wly
* @version : 1.0
* @date : 2021/11/25 21:01
* @description:
*/
public class UserQuery {
private String name;
private String password;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
PubController,java
package net.xdclass.rbac_shiro.controller;
import net.xdclass.rbac_shiro.domain.JsonData;
import net.xdclass.rbac_shiro.domain.UserQuery;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @author : wly
* @version : 1.0
* @date : 2021/11/25 18:01
* @description: 测试anon过滤器,以/pub开头的路径任何人都能访问包括游客
*/
@RestController
@RequestMapping("/pub")
public class PubController {
//当游客访问需要登录的接口时会调用下面方法
@RequestMapping("/need_login")
public JsonData needLogin() {
return JsonData.buildSuccess("温馨提示:请使用对应的账号登录", -2);
}
//当用户没有权限访问某些接口时而去访问会调用下面方法
@RequestMapping("/not_permit")
public JsonData notPermit() {
return JsonData.buildSuccess("温馨提示:拒绝访问,没权限", -3);
}
//首页
@RequestMapping("/index")
public JsonData index() {
List<String> videoList = new ArrayList<>();
videoList.add("Mysql零基础入门到实战 数据库教程");
videoList.add("Redis高并发高可用集群百万级秒杀实战");
videoList.add("Zookeeper+Dubbo视频教程 微服务分布式教程");
videoList.add("2019年新版本RocketMQ4.X教程消息队列教程");
videoList.add("微服务SpringCloud+Docker入门到高级实战");
return JsonData.buildSuccess(videoList);
}
/**
* 登录接口
*
* @param userQuery 前端传过来的用户名和密码 json
* @param request
* @param response
* @return net.xdclass.rbac_shiro.domain.JsonData
**/
@PostMapping("/login")
public JsonData login(@RequestBody UserQuery userQuery, HttpServletRequest request, HttpServletResponse response) {
Map<String, Object> info = new HashMap<>();
Subject subject = SecurityUtils.getSubject();
try {
UsernamePasswordToken token = new UsernamePasswordToken(userQuery.getName(), userQuery.getPassword());
subject.login(token);
info.put("msg", "登录成功");
info.put("session_id", subject.getSession().getId());
return JsonData.buildSuccess(info);
} catch (Exception e) {
e.printStackTrace();
return JsonData.buildError("账号或密码错误");
}
}
}
OrderController.java
package net.xdclass.rbac_shiro.controller;
import net.xdclass.rbac_shiro.domain.JsonData;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.Map;
/**
* @author : wly
* @version : 1.0
* @date : 2021/11/25 21:43
* @description: 测试authc过滤器,用户播放记录,当用户登录后可以正常访问以/authc开头的接口,未登录则提示需要登录
*/
@RestController
@RequestMapping("/authc")
public class OrderController {
@RequestMapping("/video/play_record")
public JsonData findMyPlayRecord() {
Map<String, String> recordMap = new HashMap<>();
recordMap.put("SpringBoot入门到高级实战", "第8章第1集");
recordMap.put("Cloud微服务入门到高级实战", "第4章第10集");
recordMap.put("分布式缓存Redis", "第10章第3集");
return JsonData.buildSuccess(recordMap);
}
}
AdminController.java
package net.xdclass.rbac_shiro.controller;
import net.xdclass.rbac_shiro.domain.JsonData;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.Map;
/**
* @author : wly
* @version : 1.0
* @date : 2021/11/25 22:08
* @description: 测试roles过滤器,具有admin角色的用户才能访问下面的接口(以/admin开头的接口)
*/
@RestController
@RequestMapping("/admin")
public class AdminController {
@RequestMapping("/video/order")
public JsonData findOrder() {
Map<String, String> recordMap = new HashMap<>();
recordMap.put("SpringBoot入门到高级实战", "300元");
recordMap.put("Cloud微服务入门到高级实战", "877元");
recordMap.put("分布式缓存Redis", "990元");
return JsonData.buildSuccess(recordMap);
}
}
OrderController.java
package net.xdclass.rbac_shiro.controller;
import net.xdclass.rbac_shiro.domain.JsonData;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author : wly
* @version : 1.0
* @date : 2021/11/26 11:54
* @description: 测试perms过滤器,具有perms[video_update]权限的用户才能访问/video/update接口
*/
@RestController
@RequestMapping("/video")
public class VideoController {
@RequestMapping("/update")
public JsonData videoUpdate() {
return JsonData.buildSuccess("更新视频成功");
}
}
使用postman
进行测试,在测试之前需要将ShiroConfig
类中的设置加密注释掉,因为数据库存的密码没有加密,以后加密。
接着启动项目,目前还没有吧登录,
1.直接在路径中输入localhost:8080/authc/video/play_record
可以看到需要登录
2.我们进行登录(注意前面ShiroConfig类设置了session的有效期为20秒)
如下图所示
接着把session_id的值复制,访问上一个接口,要往header里添加key-value,注意时间只有2秒这个sessionid,或者可以去ShiroConfig类里sessionManager
方法修改sessionid
有效期。
3.如下图所示,添加名为token值为刚刚登录获取的session_id
4.然后测试需要admin
角色的路径
去数据库查看,可以看到用户jack具有admin角色
依然是先登录,用户名和密码为jack 123
然后如下图所示,同样需要用到token
接着测试perms过滤器,ShiroConfig类中写了filterChainDefinitionMap.put("/video/update","perms[video_update]");
也就是说访问/video/update路径需要具有video_update权限
测试方法依旧,先登录,拿着token去访问路径
如下图所示:
简介:加密处理
取消掉ShiroConfig
类的customRealm
的注释
依次将数据库中用户的密码放到下面的测试类运行,将得到的加密密码依次替换数据库的明文密码(记的保存明文密码),然后在postman进行测试登录(请求体里还是写明文密码如123)
@Test
public void testMD5(){
//加密算法
String algoName = "md5";
String password = "123";
//算法名,明文密码,盐,md5次数
Object object = new SimpleHash(algoName,password,null,2);
System.out.println(object);
}
简介:权限控制综合案例 自定义Shiro Filter
过滤器
背景知识:
我们的需求:
因为在ShiroConfig
类的shiroFactoryBean
方法如果设置以下代码
filterChainDefinitionMap.put("/admin/**","roles[admin,root]");
表示如果要访问/admin开头的路径要同时具备admin和root角色
我们如果需要达到这种需求:访问/admin路径只需要具备admin或者root角色
那就需要自定义过滤器
既然要自定义先看它的默认实现
点开RolesAuthorizationFilter
类,可以看到实现roles过滤器的方法
可以看到最后用的是hasAllRoles
方法
我们自定义过滤器同样继承AuthorizationFilter
// mappedValue -》roles[admin,root]
public class CustomRolesOrAuthorizationFilter extends AuthorizationFilter {
@Override
@SuppressWarnings({"unchecked"})
protected boolean isAccessAllowed(ServletRequest req, ServletResponse resp, Object mappedValue) throws Exception {
Subject subject = getSubject(req, resp);
//获取当前访问路径所需要的角色集合
String[] rolesArray = (String[]) mappedValue;
//没有角色限制,有权限访问
if (rolesArray == null || rolesArray.length == 0) {
return true;
}
//当前subject是rolesArray中的任何一个,则有权限访问
for (int i = 0; i < rolesArray.length; i++) {
if (subject.hasRole(rolesArray[i])) {
return true;
}
}
return false;
}
}
然后修改ShiroConfig
类的shiroFactoryBean
方法修改后的代码如下修改的地方如红线所示
Filter类是javax.servlet.Filter;
简介:自定义Shiro Filter过滤器验证
上节自定义了过滤器roleOrFilter
,现在进行测试看能不能达到需求
登录(post)
localhost:8080/pub/login
管理员查看后台信息(get)
localhost:8080/admin/video/order
简介:讲解使用Redis
整合CacheManager
使用原因?
步骤
<dependency>
<groupId>org.crazycakegroupId>
<artifactId>shiro-redisartifactId>
<version>3.1.0version>
dependency>
在ShiroConfig
类中添加以下方法
/** 配置redisManager
* @param
* @return org.crazycake.shiro.RedisManager
**/
public RedisManager getRedisManager() {
RedisManager redisManager = new RedisManager();
redisManager.setHost("localhost");
redisManager.setPort(6379);
return redisManager;
}
/**
* 配置具体cache实现类
* @param
* @return org.crazycake.shiro.RedisCacheManager
**/
public RedisCacheManager cacheManager() {
RedisCacheManager redisCacheManager = new RedisCacheManager();
redisCacheManager.setRedisManager(getRedisManager());
return redisCacheManager;
}
然后在securityManager
方法里设置自定义cacheManager
安装redis
(如何不会使用redis,则参考网上的博客文章)
启动redis服务端和项目
然后进行登录并访问以下接口localhost:8080/admin/video/order
可以看到报了个错
错误如下
class java.lang.String must has getter for field: authCacheKey or id\nWe need a field to identify this Cache Object in Redis. So you need to defined an id field which you can get unique id to identify this principal. For example, if you use UserInfo as Principal class, the id field maybe userId, userName, email, etc. For example, getUserId(), getUserName(), getEmail(), etc.\nDefault value is authCacheKey or id, that means your principal object has a method called \"getAuthCacheKey()\" or \"getId()\""
因为用了redis
,而redis
是key-value
类型,而我们没有指定key
,所以需要进行改造
改造原有的逻辑,修改缓存的唯一key
doGetAuthorizationInfo 方法
原有:
String username = (String)principals.getPrimaryPrincipal();
User user = userService.findUserAllInfoByUsername(username);
改为
User newUser = (User)principals.getPrimaryPrincipal();
User user = userService.findUserAllInfoByUsername(newUser.getUsername());
doGetAuthenticationInfo方法
原有:
return new SimpleAuthenticationInfo(username, user.getPassword(), this.getClass().getName());
改为
return new SimpleAuthenticationInfo(user, user.getPassword(), this.getClass().getName());
这样redis
就会将user
的id
作为它的key
再次进行登录并访问刚刚的接口两次,可以看到控制台只打印一次输出,说明redis
起作用了
启动redis
客户端,输入keys *
可以看到key
在cacheManager
方法里添加以下代码
//设置缓存过期时间,单位是秒,20s,
redisCacheManager.setExpire(20);
简介:使用Redis
整合SessionManager
,管理Session
会话
为啥session
也要持久化?
怎么持久化?
在shiroConfig
类中添加自定义sessionDao
/**
* 自定义sessionDao
* @param
* @return org.apache.shiro.session.mgt.eis.SessionDAO
**/
public SessionDAO sessionDAO() {
RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
redisSessionDAO.setRedisManager(getRedisManager());
return redisSessionDAO;
}
在sessionManager
里添加代码来配置session
持久化
然后启动redis服务端和项目,进行登录
会提示没有序列化,然后User,Role,Permission类都实现Serializable
接口
注意点:
DO对象需要实现序列化接口 Serializable
logout接口和以前一样调用,请求logout后会删除redis里面的对应的key,即删除对应的token
在实现序列化启动redis服务端和项目,进行登录,然后访问admin开头的接口,然后重启项目,再次访问admin开头的接口发现可以访问的到,说明session持久化成功
简介:讲解ShiroConfig常用bean 介绍
在一些博客搜索springboot整合shiro经常会看到下面代码
在ShiroConfig
类将下面三个bean加进去
LifecycleBeanPostProcessor
/**
* 管理shiro一些bean的生命周期 即bean初始化 与销毁
* @param
* @return org.apache.shiro.spring.LifecycleBeanPostProcessor
**/
@Bean
public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}
AuthorizationAttributeSourceAdvisor
/**
* 加入注解的使用,不加入这个AOP注解不生效(shiro的注解 例如 @RequiresGuest)
* @param
* @return org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor
**/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor() {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager());
return authorizationAttributeSourceAdvisor;
}
DefaultAdvisorAutoProxyCreator
Advistor
(通知器), 将符合条件的Advisor
应用到切入点的Bean
中,需要在LifecycleBeanPostProcessor
创建后才可以创建/**
* 用来扫描上下文寻找所有的Advistor(通知器), 将符合条件的Advisor应用到切入点的Bean中,
* 需要在LifecycleBeanPostProcessor创建后才可以创建
* @param
* @return org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator
**/
@Bean
@DependsOn("lifecycleBeanPostProcessor")
public DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator(){
DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator=new DefaultAdvisorAutoProxyCreator();
defaultAdvisorAutoProxyCreator.setUsePrefix(true);
return defaultAdvisorAutoProxyCreator;
}
}
**简介:介绍单体应用到分布式应用下的鉴权方式 **
电商项目:商品服务,支付服务,用户服务
session
UUID
JWT
Oauth2.0
简介:基于原先项目,实现自定义sessionid
import java.io.Serializable;
import java.util.UUID;
/**
* @author : wly
* @version : 1.0
* @date : 2021/11/28 20:43
* @description: 自定义sessionid生成器
*/
public class CustomSeessionIdGenerator implements SessionIdGenerator {
@Override
public Serializable generateId(Session session) {
return "xdclass"+UUID.randomUUID().toString().replace("-","");
}
}
然后在ShiroConfig
类的sessionDAO
方法里设置自定义sessionid
生成器
未设置自定义sessionid
生成器时sessionid
长这样,
37ab859d-5e8c-492c-b949-e7e1a4f5c4bc
下面验证多节点下sessionid
是否有效,也就是分布式sessionid
首先如下图所示允许项目并行运行
接着在application.properties
里设置server.port为8080,8081,8082,设置一次启动一次项目
然后用8080端口进行登录,8081,8082端口查询数据,可以看到已经成功,说明sessionid
在分布式环境也可以使用成功
没有100%可靠的算法,暴力破解,穷举
建议:微服务里面,特别是对C端用户的应用,不要做过于复杂的权限校验,特别是影响性能这块