简介
shiro是什么
apache shiro 开源安全框架 , 一个更加健全的RBAC框架 , 干净利落处理身份认证 , 授权 , 企业会话管理 ,加密和缓存等功能 .
shiro和spring security
在shiro之前安全管理框架还有spring security , 它依赖于spring , 更加复杂 , 学习曲线高 , 成本也高 . shiro比较独立 , 并且可以在分布式集群环境中使用 .
shiro结构体系
主要功能:
- Authentication : 认证 , 验证用户是否合法 . 也就是”登陆” ,
- Authorization : 授权 , 登陆成功之后 , 授予谁某些可以授予的权限
- Session Management : 会话管理 , 用户登陆一次就是一次会话 . 用户登录后的用户信息通过会话管理进行管理
- Cryptography : 加密 . 提供了常见的一些加密算法 . 在应用中更方便实现数据安全 , 使用便捷 .
支持的功能:
- Web Support : Web应用程序支持 . shiro可以方便的集成到web应用中.
- Caching : 缓存 , shiro提供了对缓存的支持 . shiro提供对缓存的支持 , 支持多种缓存架构 , 如Redis.
- Concurrency : 多线程 . 支持多线程并发访问.
- Testing : 测试
- “Run As” : 权限传递 , 一个人以另外一个人登陆 . 一个张三 , 一个小张三 , 张三授予权限给小张三 , 小张三就可以登陆
- Remember Me : 记住我 .
shiro架构
- Subject : 实体(用户 , 第三方服务 , cron job计划任务 等), 传递数据到安全管理器 .
- Security Manager : 安全管理器 . Shiro中最重要的核心 . 协调各个组件 , 如管理着认证管理器 , 授权管理器等的工作.
- Authentication : 认证管理器 , 负责验证用户的身份
- Authorization :授权管理器 . 负责为合法的用户指定其权限 . 控制用户可以访问那些资源 .
- Realms : 域 , 和外界的数据库进行交互 . 用户通过Shiro来完成相关安全工作 , Shiro是不会去维护信息的 . 在Shiro中 , 数据的查询和获取工作是通过Realm 从不同的数据源来获取的 . Realm可以获取数据库信息和文本信息等 . Shiro中可以有一个或多个realm
Authentication 用户认证
1 . 认证逻辑
验证用户是否合法登陆
- 用户(Subject )需要提交身份(Principals)和凭证(Credentials)给Security Manager : 安全管理器
- 身份(Principals)能够唯一标识用户(Subject ) , 例如邮箱 , 身份证ID , 因为它独一无二
- 凭证(Credentials)是只被用户(Subject )知道的一个值 , 例如密码 , 数字证书 . 它只被用户(Subject )拥有和知晓.
- 而身份(Principals)和凭证(Credentials)最常见的组合就是: 用户名/密码 , Shiro中通常使用UsernamePasswordToken来制定身份和凭证信息
2 . 在shiro中用户的认证流程
UsernamePasswordToken ->安全管理器(Security Manager) ->认证管理器(Authentication) -> 根据所配置的realm调用策略 -> 根据策略调用realm
授权
-
给身份认证通过的人 , 授予他可以访问某些资源的权限 权限的粒度 , 分为粗粒度和细粒度 .
- 粗粒度 : 对user的增删查改,. 也就是说通常对表的操作 .
- 细粒度 : 对记录的操作 . 如 : 只允许查询id为1的用户工资 .
-
shiro一般管理的是粗粒度的权限 . 比如:菜单 , 按钮 , url . 一般细粒度的权限是通过业务来控制的 .
-
权限表示规则:
- 通配符
资源:操作:实例 ->
user:add 表示用户有添加的权限
user:* 表示user具有用户的所有操作权限
user:delete:100 表示用户对user标识为100的记录有删除权限 - 8421
0001
0010
0100
1000
(增删查改)
- 通配符
代码实现
1 . 依赖(基于springboot2和shiro1.3.2)
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.3.2</version>
</dependency>
2 . 配置类
package com.credi.config;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* @Author: He Xingchi
* Created by Administrator on 2019/10/7.
* 对shiro的一些配置,相对于之前的xml配置。包括:过滤的文件和权限,密码加密的算法,其用注解等相关功能。
*/
@Configuration
public class ShiroConfig {
@Bean(name = "shiroFilter")
public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
// 登录接口
shiroFilterFactoryBean.setLoginUrl("/login");
// 登录失败跳转接口
shiroFilterFactoryBean.setUnauthorizedUrl("/notRole");
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
// <!-- authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问-->
filterChainDefinitionMap.put("/webjars/**", "anon");
filterChainDefinitionMap.put("/login", "anon");
filterChainDefinitionMap.put("/", "anon");
filterChainDefinitionMap.put("/front/**", "anon");
filterChainDefinitionMap.put("/api/**", "anon");
filterChainDefinitionMap.put("/admin/**", "authc");
// filterChainDefinitionMap.put("/user/**", "authc");
filterChainDefinitionMap.put("/user", "authc");
filterChainDefinitionMap.put("/test/**", "authc");
//主要这行代码必须放在所有权限设置的最后,不然会导致所有 url 都被拦截 剩余的都需要认证
filterChainDefinitionMap.put("/**", "authc");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
/*
Security Manager : 安全管理器 .
Shiro中最重要的核心 . 协调各个组件 , 如管理着认证管理器 , 授权管理器等的工作.
*/
@Bean
public SecurityManager securityManager() {
DefaultWebSecurityManager defaultSecurityManager = new DefaultWebSecurityManager();
defaultSecurityManager.setRealm(customRealm());
return defaultSecurityManager;
}
//不加这个注解不生效,具体不详
@Bean
@ConditionalOnMissingBean
public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator defaultAAP = new DefaultAdvisorAutoProxyCreator();
defaultAAP.setProxyTargetClass(true);
return defaultAAP;
}
/**
* 开启shiro aop注解支持.
* 使用代理方式;所以需要开启代码支持;
* @param securityManager
* @return
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager){
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
// 自己定义的授权域
@Bean
public CustomRealm customRealm() {
CustomRealm customRealm = new CustomRealm();
return customRealm;
}
}
3 . 自定义Realm,身份验证和权限验证策略
package com.credi.config;
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 java.util.ArrayList;
import java.util.List;
/**
* @Author: He Xingchi
* Created by Administrator on 2019/10/7.
* 自定义的CustomRealm继承AuthorizingRealm。
* 并且重写父类中的doGetAuthorizationInfo(权限相关)、doGetAuthenticationInfo(身份认证)这两个方法。
*/
public class CustomRealm extends AuthorizingRealm {
/**
* 身份认证
* 这里可以注入userService,为了方便演示,我就写死了帐号了密码
* private UserService userService;
* <p>
* 获取即将需要认证的信息
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
System.out.println("-------身份认证方法--------");
// 获取UsernamePasswordToken中用户输入的用户名
String userName = (String) authenticationToken.getPrincipal();
String userPwd = new String((char[]) authenticationToken.getCredentials());
//根据用户名从数据库获取校验的用户信息
String password = "123";
if (userName == null) {
// 抛出对应的异常
throw new AccountException("用户名不正确");
} else if (!userPwd.equals(password )) {
throw new AccountException("密码不正确");
}
// 从数据库中查询的信息封装到 -> new SimpleAuthenticationInfo(userName, password,getName()) -> shiro框架进行信息的校验
SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(userName, password, getName());// getName() -> realm或自定义realm类名称
// 也可以吧查询到的用户信息存入 new SimpleAuthenticationInf(new User() , password , getName());
// 返回认证信息
return simpleAuthenticationInfo;
}
// 权限认证
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
System.out.println("-------权限认证方法--------");
//身份认证时 , 存入的信息可以被获取
// //获取用户名
// User user = (User)SecurityUtils.getSubject().getPrincipal();
// String username = (String) SecurityUtils.getSubject().getPrincipal();
String username = (String) principals.getPrimaryPrincipal();
//根据身份信息从数据库获取权限数据 == > 模拟
List<String> permissions = new ArrayList<String>();
permissions.add("admin");
permissions.add("user:save");
permissions.add("user:delete");
permissions.add("userInfo:view");
// 将权限信息保存到shiro中的AuthorizationInfo中
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
// list添加到AuthorizationInfo
for(String permission:permissions){
System.out.println(permission);
info.addStringPermission(permission);
}
// set添加到AuthorizationInfo
// Set<String> stringSet = new HashSet<>();
// stringSet.add("user:show");
// stringSet.add("user:admin");
// info.setStringPermissions(stringSet);
return info;
}
}
4 . 用户身份认证(登陆)测试类
package com.credi.controller;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.subject.Subject;
import org.springframework.web.bind.annotation.*;
/**
* @Author: He Xingchi
* Created by Administrator on 2019/10/7.
*/
@RestController
@RequestMapping
public class IndexController {
@RequestMapping(value = "/login", method = RequestMethod.GET)
@ResponseBody
public String defaultLogin() {
return "首页";
}
@RequestMapping(value = "/login", method = RequestMethod.POST)
@ResponseBody
public String login(@RequestParam("username") String username, @RequestParam("password") String password) {
// 从SecurityUtils里边创建一个 subject
Subject subject = SecurityUtils.getSubject();
// 在认证提交前准备 token(令牌)
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
// 执行认证登陆
try {
subject.login(token);
} catch (UnknownAccountException uae) {
return "未知账户";
} catch (IncorrectCredentialsException ice) {
return "密码不正确";
} catch (LockedAccountException lae) {
return "账户已锁定";
} catch (ExcessiveAttemptsException eae) {
return "用户名或密码错误次数过多";
} catch (AuthenticationException ae) {
return "用户名或密码不正确!";
}
if (subject.isAuthenticated()) {
return "登录成功";
} else {
token.clear();
return "登录失败";
}
}
}
5 . 用户权限验证测试类
package com.credi.controller;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.web.bind.annotation.*;
/**
* @Author: He Xingchi
* Created by Administrator on 2019/10/8.
*/
@RestController
@RequestMapping
public class UserController {
@RequestMapping(value = "/user", method = RequestMethod.POST)
@ResponseBody
@RequiresPermissions("userInfo:view") // 测试权限
public String user(@RequestParam("msg") String msg) {
return msg;
}
}
ps : 常用内置权限函数
// 用户认证状态
boolean isAuthenticated = subject.isAuthenticated();
System.out.println("用户认证状态:" + isAuthenticated);
//判断拥有角色:role1
Assert.assertTrue(subject.hasRole("role1"));
//判断拥有角色:role1 and role2
Assert.assertTrue(subject.hasAllRoles(Arrays.asList("role1", "role2")));
//判断拥有角色:role1 and role2 and !role3
boolean[] result = subject.hasRoles(Arrays.asList("role1", "role2", "role3"));
Assert.assertEquals(true, result[0]);
Assert.assertEquals(true, result[1]);
Assert.assertEquals(false, result[2]);
//判断拥有权限:user:create
Assert.assertTrue(subject.isPermitted("user:create"));
//判断拥有权限:user:update and user:delete
Assert.assertTrue(subject.isPermittedAll("user:update", "user:delete"));
//判断没有权限:user:view
Assert.assertFalse(subject.isPermitted("user:view"));