shiro是一个Java安全框架,可以完成:认证、授权、加密、会话管理、与web集成、缓存…
● 易于使用,构建简单
● 功能全面
● 灵活,可以在任何应用程序环境中工作,并且不需要依赖它们
● 高效支持web,可以基于应用程序URL和Web协议创建灵活的安全策略
● 兼容性强,易于和其他框架和应用程序集成
● 社区支持
● shiro需要和spring整合开发,而springsecurity基于spring开发,配合起来更便利
● springsecurity比shiro功能丰富,如安全维护
● shiro配置和使用简单,springsecurity较难
● shiro依赖性低,不需要任何框架和容器,可以独立运行,而springsecurity需要依赖spring容器
● shiro可以工作于任何应用环境,独立于容器
subject:对外API核心,与应用代码直接交互的对象,与subject的所有交互都会委托为securitymanager
securitymanager:安全管理器,所有与安全相关的操作都会与securitymanager交互,管理所有的subject,是shiro的核心,相当于spingmvc的dispatcherservlet的角色
realm:从realm中获取安全数据信息(用户、角色、权限)
shiro内部架构
1.环境搭建
● 引入依赖
<dependencies>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.9.0</version>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
</dependencies>
● ini文件:src/main/resources/shiro.ini
[users]
zhangsan=z3
lisi=l4
概念:
① 身份验证:一般需要提供如身份ID等一些标识信息来表明登录者的身份,如提供email,用户名/密码来证明
② 在shiro中,用户需要提供principals(身份)和credentials(证明)给shiro,从而应用能验证用户身份
③ principals:身份,即主体的标识属性,可以是任何属性,如用户名、邮箱等,唯一即可。一个主体可以有多个principals,但只有一个Primary principals,一般是用户名/邮箱/手机号
④ credentials:证明/凭证,即只有主体知道的安全值,如密码/数字证书等
最常见的principals和credentials组合就是用户名/密码
流程:
① 收集用户身份/凭证,即如用户名/密码
② 调用 Subject.login 进行登录,如果失败将得到相应 的 AuthenticationException异常,根据异常提示用户 错误信息;否则登录成功
③ 创建自定义的 Realm 类,继承 org.apache.shiro.realm.AuthenticatingRealm类,实现 doGetAuthenticationInfo() 方法
实例:
public class ShiroRun {
public static void main(String[] args) {
//1.初始化获取securitymanager
IniSecurityManagerFactory factory = new IniSecurityManagerFactory("classpath:shiro.ini");
SecurityManager securityManager = factory.getInstance();
SecurityUtils.setSecurityManager(securityManager);
//2.获取subject对象
Subject subject = SecurityUtils.getSubject();
//3.创建token对象,web应用用户名密码从页面传递
AuthenticationToken token = new UsernamePasswordToken("zhangsan", "z3");
//4.完成登录
try {
subject.login(token);
System.out.println("login successful");
} catch (UnknownAccountException e) {
e.printStackTrace();
System.out.println("用户不存在");
}catch (IncorrectCredentialsException e) {
e.printStackTrace();
System.out.println("密码错误");
}catch (AuthenticationException e) {
e.printStackTrace();
System.out.println("登录失败");
}
}
}
① 授权,也叫访问控制,即在应用中控制谁访问哪些资源(如访问页面/编辑数据/页面 操作等)。在授权中需了解的几个关键对象:主体(Subject)、资源(Resource)、权限 (Permission)、角色(Role)
② 主体(Subject):访问应用的用户,在 Shiro 中使用 Subject 代表该用户。用户只有授权 后才允许访问相应的资源
③ 资源(Resource):在应用中用户可以访问的 URL,比如访问 JSP 页面、查看/编辑 某些 数据、访问某个业务方法、打印文本等等都是资源。用户只要授权后才能访问
④ 权限(Permission):安全策略中的原子授权单位,通过权限我们可以表示在应用中用户 有没有操作某个资源的权力。即权限表示在应用中用户能不能访问某个资源,如:访问用 户列表页面查看/新增/修改/删除用户数据(即很多时候都是CRUD(增查改删)式权限控 制)等。权限代表了用户有没有操作某个资源的权利,即反映在某个资源上的操作允不允许
⑤ Shiro 支持粗粒度权限(如用户模块的所有权限)和细粒度权限(操作某个用户的权限, 即实例级别的)
⑥ 角色(Role):权限的集合,一般情况下会赋予用户角色而不是权限,即这样用户可以拥有 一组权限,赋予权限时比较方便。典型的如:项目经理、技术总监、CTO、开发工程师等都是角色,不同的角色拥有一组不同的权限
授权方式:
subject.hasRole("admin")
@RequiresRoles("admin")
授权流程:
实例:
1.给shiro.ini增加角色配置
[users]
fate=admin,role1,role2
fuck=123456
2.增加权限配置
[roles]
role1=user:insert,user:select
3.测试代码
public static void main(String[] args) {
//1.初始化获取securitymanager
IniSecurityManagerFactory factory = new IniSecurityManagerFactory("classpath:shiro.ini");
SecurityManager securityManager = factory.getInstance();
SecurityUtils.setSecurityManager(securityManager);
//2.获取subject对象
Subject subject = SecurityUtils.getSubject();
//3.创建token对象,web应用用户名密码从页面传递
AuthenticationToken token = new UsernamePasswordToken("zhangsan", "z3");
//4.完成登录
try {
subject.login(token);
System.out.println("login successful");
//5.判断角色
boolean hasRole = subject.hasRole("role1");
System.out.println("是否拥有role1角色:" + hasRole);
//6.判断权限
boolean permitted = subject.isPermitted("user:insert");
System.out.println("是否拥有此权限:" + permitted);
//也可以用checkPermission方法,但没有返回值,没权限抛AuthenticationException
subject.checkPermission("user:update");
} catch (UnknownAccountException e) {
e.printStackTrace();
System.out.println("用户不存在");
} catch (IncorrectCredentialsException e) {
e.printStackTrace();
System.out.println("密码错误");
} catch (AuthenticationException e) {
e.printStackTrace();
System.out.println("登录失败");
}
}
4.加密
Shiro内嵌很多常用的加密算法,比如MD5加密
public class ShiroMD5 {
public static void main(String[] args) {
//密码明文
String password = "admin";
//使用MD5加密
Md5Hash md5Hash = new Md5Hash(password);
System.out.println("md5加密:" + md5Hash);
//带盐的md5加密,盐就是在密码明文后拼接新字符串,然后再进行加密
Md5Hash md5Hash1 = new Md5Hash(password, "salt");
System.out.println("带盐的md5加密:" + md5Hash1.toHex());
//为了保证安全,避免被破解还可以多次迭代加密,保证数据安全
Md5Hash md5Hash2 = new Md5Hash(password, "salt", 3);
System.out.println("带盐的3次迭代加密:" + md5Hash2);
//使用父类进行加密
SimpleHash simpleHash = new SimpleHash("MD5", password, "salt", 3);
System.out.println("父类带盐的3次加密:" + simpleHash);
}
}
5.自定义登录认证
Shiro 默认的登录认证是不带加密的,如果想要实现加密认证需要自定义登录认证, 自定义Realm
public class MyRealm extends AuthenticatingRealm {
//自定义登录认证方法,shiro的login方法的底层会调用该类的认证方法进行认证
//需要配置自定义的realm生效,应该在ini文件中配置,如果有springboot框架,也可以在Springboot中进行配置
//该方法只是获取进行对比的信息,认证逻辑还是按照shiro的底层认证逻辑来完成
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
//1.获取身份信息
String principal = authenticationToken.getPrincipal().toString();
//2.获取凭证信息
String password = new String((char[]) authenticationToken.getCredentials());
System.out.println("认证的用户信息:" + principal + "---" + password);
//3.获取数据库中存储的用户信息
if(principal.equals("zhangsan")){
//3.1数据库中存储的加盐3次迭代的密码
String pwdInfo = "b073e9301c412431159e7075340c4c66";
//4.创建封装校验逻辑的对象,封装数据返回
AuthenticationInfo info = new SimpleAuthenticationInfo(
authenticationToken.getPrincipal(),
pwdInfo,
ByteSource.Util.bytes("salt"),
authenticationToken.getPrincipal().toString()
);
return info;
}
return null;
}
}
添加配置信息,让shiro知晓你使用的是自定义的Realm
[main]
#在shiro.ini中添加配置信息
md5CredentialsMatcher=org.apache.shiro.authc.credential.Md5CredentialsMatcher
md5CredentialsMatcher.hashIterations=3
myrealm=com.atguigu.shirotest.MyRealm
myrealm.credentialsMatcher=$md5CredentialsMatcher
securityManager.realms=$myrealm
[users]
zhangsan=7174f64b13022acd3c56e2781e098a5f,role1,role2
lisi=l4
[roles]
role1=user:insert,user:select
1.引入依赖:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.1.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring-boot-web-starter</artifactId>
<version>1.9.0</version>
</dependency>
<!--mybatis-plus-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.0.5</version>
</dependency>
<!--mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.46</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
2.配置文件
# mybatis配置
mybatis-plus:
configuration:
# 日志
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
mapper-locations: classpath:mapper/ *.xml
spring:
# 数据库配置
datasource:
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/shirodb?characterEncoding=utf-8&useSSL=false
password: root
username: root
# json格式
jackson:
date-format: yyyy-MM-dd HH:mm:ss
time-zone: GMT+8
shiro:
# 登录接口
loginUrl: /myController/login
3.自定义realm
@Component
public class MyRealm extends AuthorizingRealm{
@Autowired
private UserService userService;
//自定义授权方法
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
return null;
}
//自定义登录认证方法
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
//1.获取用户身份信息
String name = authenticationToken.getPrincipal().toString();
//2.调用业务层获取用户信息(数据库)
User user = userService.getUserInfoByName(name);
//3.非空判断,将数据封装返回
if(user != null){
AuthenticationInfo info = new SimpleAuthenticationInfo(
authenticationToken.getPrincipal(),
user.getPwd(),
ByteSource.Util.bytes("salt"),
authenticationToken.getPrincipal().toString()
);
return info;
}
return null;
}
}
4.配置类
@Slf4j
@Configuration
public class ShiroConfig {
@Autowired
private MyRealm myRealm;
//配置SecurityManager
@Bean
public DefaultWebSecurityManager defaultWebSecurityManager(){
//1.创建defaultWebSecurityManager 对象
DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
//2.创建加密对象,设置相关属性
HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();
//2.1 采用md5加密
matcher.setHashAlgorithmName("md5");
//2.2 迭代加密次数
matcher.setHashIterations(3);
//3.将加密对象存储到myRealm中
myRealm.setCredentialsMatcher(matcher);
//4.将myRealm存入defaultWebSecurityManager 对象
defaultWebSecurityManager.setRealm(myRealm);
//5.返回
return defaultWebSecurityManager;
}
//配置Shiro内置过滤器拦截范围
@Bean
public DefaultShiroFilterChainDefinition shiroFilterChainDefinition(){
DefaultShiroFilterChainDefinition defaultShiroFilterChainDefinition = new DefaultShiroFilterChainDefinition();
// 设置不认证就可以访问的资源
defaultShiroFilterChainDefinition.addPathDefinition("/myController/userLogin","anon");
defaultShiroFilterChainDefinition.addPathDefinition("/login","anon");
// 设置需要进行登录认证的拦截范围
defaultShiroFilterChainDefinition.addPathDefinition("/**","authc");
return defaultShiroFilterChainDefinition;
}
}
5.编写控制类
@Controller
@RequestMapping("myController")
public class MyController{
@GetMapping("userLogin")
@ResponseBoby
public String userLogin(String name,String pwd){
//1.获取subject对象
Subject subject = SecurityUtils.getSubject();
//2.封装请求数据到token
AuthenticationToken token = new UsernamePasswordToken(name,pwd);
//3.调用login方法进行登录认证
try{
subject.login(token);
return "登录成功";
}catch(AuthenticationException e){
e.printStackTrace();
System.out.println("登录失败");
return "登录失败";
}
}