Shiro

shiro是一个java的安全框架

官网地址 http://shiro.apache.org/

Shiro综述

graph LR
A1("CacheManager")-->B
A2("Realms")-->B
A3("UserDao")-->C
A4("CredentialsMatcher")-->C
A1-->C
subgraph Shiro
A("Subject(用户)")-->B("SecurityManager(安全管理器)")
B-->C("Realm域")

end

  • Subject:主体,代表了当前 “用户”
  • SecurityManager:安全管理器;即所有与安全有关的操作都会与 SecurityManager 交互;且它管理着所有 Subject;是 Shiro 的核心
  • Realm:域,Shiro 从从 Realm 获取安全数据(如用户、角色、权限),就是说 SecurityManager 要验证用户身份,那么它需要从 Realm 获取相应的用户进行比较以确定用户身份是否合法;也需要从 Realm 得到用户相应的角色 / 权限进行验证用户是否能进行操作;可以把 Realm 看成 DataSource,即安全数据源。

Shiro 不提供维护用户 / 权限,而是通过 Realm 让开发人员自己注入。

参考Shiro提供的JdbcRealm中源码的实现

//获取用户,其会自动绑定到当前线程
Subject subject = SecurityUtils.getSubject();
//构建待认证token
UsernamePasswordToken token = new UsernamePasswordToken("zhang", "123");
//登录,即身份验证
subject.login(token);
//判断是否已经认证
subject.isAuthenticated()
//登出
subject.logout(token);
graph TB
A(Realm)-->B(CachingRealm)
B-->C(AuthenticatingRealm认证)
C-->D(AuthorizingRealm授权)
D-->E(自己实现的Realm)
D-->E1(Shiro提供的JdbcRealm)
E1-->F1(参考内部实现)
E-->F("doGetAuthorizationInfo()")
E-->G("doGetAuthenticationInfo()")
style E fill:#f96

过滤器

认证拦截器

  • anon 匿名拦截器,不需要认证即可访问,如 /static/**=anon,/login=anon
  • authc 需要认证才可以访问,如/**=authc
  • user 用户已经身份验证 / 记住我登录的都可;示例 /**=user
  • logout 退出拦截器,如 /logout=logout

注意authc和user的区别

授权拦截器

  • roles 角色授权拦截器,验证用户是否拥有角色;如:/admin/**=roles[admin]
  • perms 权限授权拦截器,验证用户是否拥有所有权限;/user/**=perms["user:create"]

注解

  • @RequiresPermissions 验证权限
  • @RequiresRoles 验证角色
  • @RequiresUser 验证用户是否登录(包含通过记住我登录的)
  • @RequiresAuthentication 验证是否已认证(不含通过记住我登录的)
  • @RequiresGuest 不需要认证即可访问
//拥有ADMIN角色同时还要有sys:role:info权限
@RequiresRoles(value={"ADMIN")
@RequiresPermissions("sys:role:info")

整合Shiro

1. 配置SecurityManager

注入Realm和CacheManager(选)

 @Bean("securityManager")
public org.apache.shiro.mgt.SecurityManager securityManager(ShrioRealm shrioRealm, PhoneRealm phoneRealm) {
DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
// //注入自定义myRealm
// defaultWebSecurityManager.setRealm(shrioRealm);
//设置多个realm,用户名密码登录realm,手机号短信验证码登录realm
List<Realm> realms = new ArrayList<>();
realms.add(shrioRealm);
realms.add(phoneRealm);
defaultWebSecurityManager.setRealms(realms); return defaultWebSecurityManager;
}

2.实现Realm

注入密码验证器,设置是否启用缓存

/**
*
* 自定义realm
* @author yuxf
* @version 1.0
* @date 2020/12/21 16:10
*/
public class ShrioRealm extends AuthorizingRealm { @Autowired
TestShiroUserService userService; /**
* 获取授权信息
* @param principals
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
//从数据库取角色
Set<String> roles = userService.getRoles();
SimpleAuthorizationInfo simpleAuthorizationInfo=new SimpleAuthorizationInfo();//权限信息
simpleAuthorizationInfo.addRoles(roles);
simpleAuthorizationInfo.addStringPermission("user:create");
return simpleAuthorizationInfo;
} /**
* 获取认证信息
* @param token
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
if(token.getPrincipal()==null)return null;
String userName=token.getPrincipal().toString();
//从数据库查询用户名
String dbUser = userService.loadUserByUserName(userName);
if(dbUser==null||"".equals(dbUser)) throw new UnknownAccountException();
//密码盐
ByteSource salt = ByteSource.Util.bytes(userName);
SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(userName, "123456", salt,getName());
return simpleAuthenticationInfo;
}
} @Bean
public ShrioRealm shrioRealm() {
ShrioRealm shrioRealm = new ShrioRealm();
//设置密码加密规则
shrioRealm.setCredentialsMatcher(hashedCredentialsMatcher());
return shrioRealm;
} /**
* 凭证匹配器
*
* @return
*/
@Bean
public HashedCredentialsMatcher hashedCredentialsMatcher() {
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
hashedCredentialsMatcher.setHashAlgorithmName("md5");//散列算法:这里使用MD5算法;
hashedCredentialsMatcher.setHashIterations(2);//散列的次数,比如散列两次,相当于 md5(md5(""));
return hashedCredentialsMatcher;
}
/**
* 注册时需要生成密码和密码盐存入数据库
* @author yuxf
* @version 1.0
* @date 2020/12/22 17:01
*/
public class PasswordHelper {
private static String algorithmName = "md5";
private static final int hashIterations = 2;
/**
* 获取随机密码盐
* @return
*/
public static String getSalt()
{
RandomNumberGenerator randomNumberGenerator = new SecureRandomNumberGenerator();
String salt = randomNumberGenerator.nextBytes().toHex();
return salt;
} /**
* 生成密码
* @param plainPassword 明文密码
* @param salt 密码盐
* @return
*/
public static String getPassowrd(String plainPassword,String salt)
{
String newPassword = new SimpleHash(algorithmName, plainPassword, salt, hashIterations).toHex();
return newPassword;
}
}

3.配置LifecycleBeanPostProcessor

 /**
* 配置LifecycleBeanPostProcessor 可以自动调用配置在Spring IOC容器中 Shiro Bean的生命周期方法
* @return
*/
@Bean
public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}

4.启动注解

/**
* 配置注解生效
*
* @return
*/
@Bean
public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
defaultAdvisorAutoProxyCreator.setProxyTargetClass(true);
return defaultAdvisorAutoProxyCreator;
} /**
* 配置注解生效
*
* @return
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(@Qualifier("securityManager") org.apache.shiro.mgt.SecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor sourceAdvisor = new AuthorizationAttributeSourceAdvisor();
sourceAdvisor.setSecurityManager(securityManager);
return sourceAdvisor;
}

5.配置ShiroFilter

ssm项目中坑

@Bean("shiroFilterFactoryBean")
public ShiroFilterFactoryBean shiroFilterFactoryBean(@Qualifier("securityManager") org.apache.shiro.mgt.SecurityManager securityManager) {
//shiro对象
ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
bean.setSecurityManager(securityManager);
bean.setLoginUrl("/shiro/login");
bean.setSuccessUrl("/shrio/index");
LinkedHashMap<String, String> linkedHashMap = new LinkedHashMap<String, String>();
//认证顺序是从上往下执行。
linkedHashMap.put("/logout", "logout");//在这儿配置登出地址,不需要专门去写控制器。
linkedHashMap.put("/shiro/phoneLogin", "anon");
linkedHashMap.put("/demo/**", "anon");
linkedHashMap.put("/static/**", "anon");
linkedHashMap.put("/shiro/anon", "anon");
linkedHashMap.put("/**", "user");//需要进行权限验证
bean.setFilterChainDefinitionMap(linkedHashMap);
return bean;
}

SSM项目中web.xml中配置shiroFilter

<!-- shiro过虑器,DelegatingFilterProxy通过代理模式将spring容器中的bean和filter关联起来 -->
<filter>
<filter-name>shiroFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
<!-- 设置true由servlet容器控制filter的生命周期 -->
<init-param>
<param-name>targetFilterLifecycle</param-name>
<param-value>true</param-value>
</init-param>
<!-- 设置spring容器filter的bean id,如果不设置则找与filter-name一致的bean -->
<init-param>
<param-name>targetBeanName</param-name>
<param-value>shiro</param-value>
</init-param>
</filter>

缓存

https://www.cnblogs.com/nuccch/p/8044226.html

思考:为什么Shiro要设计成既可以在Realm,也可以在SecurityManager中设置缓存管理器呢?

加密

https://www.cnblogs.com/cac2020/p/13850318.html

1. 注入HashedCredentialsMatcher实现(推荐)

需要自己编写加密帮助类生成密码和盐值,比较灵活

@Bean
public ShrioRealm shrioRealm() {
ShrioRealm shrioRealm = new ShrioRealm();
//设置密码加密规则
shrioRealm.setCredentialsMatcher(hashedCredentialsMatcher());
return shrioRealm;
}
/**
* 凭证匹配器
*
* @return
*/
@Bean
public HashedCredentialsMatcher hashedCredentialsMatcher() {
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
hashedCredentialsMatcher.setHashAlgorithmName("md5");//散列算法:这里使用MD5算法;
hashedCredentialsMatcher.setHashIterations(2);//散列的次数,比如散列两次,相当于 md5(md5(""));
return hashedCredentialsMatcher;
}

加密帮助类

/**
* 注册时需要生成密码和密码盐存入数据库
*
* @author yuxf
* @version 1.0
* @date 2020/12/22 17:01
*/
public class PasswordHelper {
private static String algorithmName = "md5";
private static final int hashIterations = 2; /**
* 获取随机密码盐
*
* @return
*/
public static String getSalt() {
RandomNumberGenerator randomNumberGenerator = new SecureRandomNumberGenerator();
String salt = randomNumberGenerator.nextBytes().toHex();
return salt;
} /**
* 生成密码
*
* @param plainPassword 明文密码
* @param salt 密码盐
* @return
*/
public static String getPassowrd(String plainPassword, String salt) {
String newPassword = new SimpleHash(algorithmName, plainPassword, salt, hashIterations).toHex();
return newPassword;
} }

2. 注入PasswordMatcher实现

  1. Shiro提供的PasswordService 相当于 密码帮助类,可用于生成密码和验证密码
  2. 如果使用公盐(hashService.setGeneratePublicSalt(true)),则必须设置HashFormat为Shiro1CryptFormat或不设置,默认为这个,否则无法保存盐值导致验证失败,密码加密结果如:$shiro1$MD5$3$QvLJZY8JiAJMnK9vRjlG6w==$jbNS0N/3fq2KUXufYwGwWA==,里面包含了加密的方法类型,哈希次数,盐值,加密结果,验证密码时会取出加密密码中的盐值来hash客户端的密码来验证密码是否正确
  3. 盐值保存在密码中,无需额外存储
@Bean
public PhoneRealm phoneRealm() {
PhoneRealm phoneRealm = new PhoneRealm();
//PasswordMatcher
PasswordMatcher passwordMatcher = new PasswordMatcher();
passwordMatcher.setPasswordService(passwordService());
phoneRealm.setCredentialsMatcher(passwordMatcher);
return phoneRealm;
} @Bean
public PasswordService passwordService()
{
DefaultHashService hashService = new DefaultHashService();
hashService.setHashIterations(3);
hashService.setHashAlgorithmName("MD5");
hashService.setGeneratePublicSalt(true);
//设置HashService
DefaultPasswordService passwordService = new DefaultPasswordService();
passwordService.setHashService(hashService);
// passwordService.setHashFormat(new HexFormat());
return passwordService;
}

多身份Realm认证

  1. (推荐)自定义AuthenticationToken并重写Realm的supports方法,来明确Real支持的Token

注意不要继承UsernamePasswordToken

public class PhoneVcodeToken implements AuthenticationToken {
private String phone;
private String vcode;
public PhoneVcodeToken(String phone,String vcode)
{
this.phone=phone;
this.vcode=vcode;
}
@Override
public Object getPrincipal() {
return phone;
} @Override
public Object getCredentials() {
return vcode;
}
}

Realm

public class PhoneRealm extends AuthorizingRealm {
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
return null;
} @Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
String userName = token.getPrincipal().toString();
if (userName.equals("admin")) {
//123456a
return new SimpleAuthenticationInfo(userName, "$shiro1$MD5$3$j8X4VX1f6T6zGiGEFIW5yA==$ipG89XmDquh++g5xXmV1dQ==", getName());
} else {
//123456
return new SimpleAuthenticationInfo(userName, "$shiro1$MD5$3$QvLJZY8JiAJMnK9vRjlG6w==$jbNS0N/3fq2KUXufYwGwWA==", getName());
}
} @Override
public boolean supports(AuthenticationToken token) {
return token instanceof PhoneVcodeToken;
}
}
  1. 自定义AuthenticationToken并加入类型参数,重写ModularRealmAuthenticator 在doAuthenticate()方法中根据类型来选择Realm
/**
* @author chenzhi
* @Description: 自定义当使用多realm时管理器
* @Date:Created: in 13:41 2018/8/13
* @Modified by:
*/
public class MyModularRealmAuthenticator extends ModularRealmAuthenticator { @Override
protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken) {
//先判断Realm是否为空
assertRealmsConfigured();
//强转为自定义的Token
MyUsernamePasswordToken myUsernamePasswordToken = (MyUsernamePasswordToken) authenticationToken;
//拿到登录类型
String loginType = myUsernamePasswordToken.getLoginType();
//拿到所有Realm集合
Collection<Realm> realms = getRealms();
List<Realm> myrealms = new ArrayList<>();
//遍历每个realm 根据loginType将对应的Reaml加入到myrealms
for (Realm realm : realms) {
//拿到Realm的类名 ,所以在定义Realm时,类名要唯一标识并且包含枚举中loginType某一个Type
//注意:一个Realm的类名不能包含有两个不同的loginType
if (realm.getName().contains(loginType)) {
myrealms.add(realm);
}
}
//判断是单Reaml还是多Realm
if (myrealms.size() == 1) {
return doSingleRealmAuthentication(myrealms.iterator().next(), myUsernamePasswordToken);
} else {
return doMultiRealmAuthentication(myrealms, myUsernamePasswordToken);
}
}
}

认证流程

token=new UsernamePasswordToken(userName,password)
graph TB
subgraph Suject
A1("Subject")--"subject = SecurityUtils.getSubject();"-->C1("token")
B1(token)-->C1("subject.login(token)")
end
subgraph SecurityManager
A2("securityManager.login(token)")
B2("onSuccessfulLogin(token, info, loggedIn)")
end

subgraph ModularRealmAuthenticator
A3("authenticate(token)")
end

subgraph Realm
A4("getAuthenticationInfo(token)")--获取认证信息-->B4("doGetAuthenticationInfo(token)")
B4--传入认证信息并验证密码-->C4("assertCredentialsMatch(token,info)")
end

subgraph CredentialsMatcher
A5("doCredentialsMatch(token,info)")

end

C1-->A2
A2--this.authenticator-->A3
A3-->B2
A3--"this.getRealms()"-->A4
C4--"getCredentialsMatcher()"-->A5

Shiro认证详解的更多相关文章

  1. 转:JAVAWEB开发之权限管理(二)——shiro入门详解以及使用方法、shiro认证与shiro授权

    原文地址:JAVAWEB开发之权限管理(二)——shiro入门详解以及使用方法.shiro认证与shiro授权 以下是部分内容,具体见原文. shiro介绍 什么是shiro shiro是Apache ...

  2. MySQL权限授权认证详解

    MySQL权限授权认证详解 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.MySQL权限系统介绍1>.权限系统的作用是授予来自某个主机的某个用户可以查询.插入.修改.删除 ...

  3. JWT(Json web token)认证详解

    JWT(Json web token)认证详解 什么是JWT Json web token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准((RFC 7519).该to ...

  4. OAuth2.0认证详解

    目录 什么是OAuth协议 OAuth2.0是为了解决什么问题? OAuth2.0成员和授权基本流程 OAuth2.0成员 OAuth2.0基本流程 什么是OAuth协议 OAuth 协议为用户资源的 ...

  5. OAuth 2.0 授权认证详解

    一.认识 OAuth 2.0 1.1 OAuth 2.0 应用场景 OAuth 2.0 标准目前被广泛应用在第三方登录场景中,以下是虚拟出来的角色,阐述 OAuth2 能帮我们干什么,引用阮一峰这篇理 ...

  6. Shiro学习详解

    1.Shiro基本架构 一.什么是Shiro Apache Shiro是一个强大易用的Java安全框架,提供了认证.授权.加密和会话管理等功能: 认证 - 用户身份识别,常被称为用户“登录”: 授权 ...

  7. shiro学习详解(开篇)

    一.前言 要开始接触公司另外一个项目了,RX和我说了下整个项目框架的结构,其中提到权限的控制是通过shiro来处理的,对我而言又是一个全新的知识点,于是今天花了一点时间去学习shiro的使用,看了好几 ...

  8. Shrio认证详解+自定义Realm

    Authentication(身份认证)是Shiro权限控制的第一步,用来告诉系统你就是你. 在提交认证的时候,我们需要给系统提交两个信息: Principals:是一个表示用户的唯一属性,可以是用户 ...

  9. shiro过滤器详解分析

    (原) shiro最核心的2个操作,一个是登录的实现,一就是过滤器了.登录有时间再补录说明,这里分析下shiro过滤器怎样玩的. 1.目标 这里会按如下顺序逐一看其实原理,并尽量找出其出处. 先看一下 ...

随机推荐

  1. CentOS下搭建文件共享服务

    nfs部署以及优化 Server端配置 安装rpm服务包 yum install -y nfs-utils 创建数据挂载点 mkdir -p /data 编辑exports文件 vi /etc/exp ...

  2. 基于ARM64的Qemu/KVM学习环境搭建

    作者:pengdonglin137@163.com 在没有aarch64架构的开发板的情况下,可以使用Qemu来模拟一个支持KVM的AArch64位的host,然后再在其上运行一个开启KVM加速的Qe ...

  3. Spring Boot 2.x 多数据源配置之 JPA 篇

    场景假设:现有电商业务,商品和库存分别放在不同的库 配置数据库连接 app: datasource: first: driver-class-name: com.mysql.cj.jdbc.Drive ...

  4. 2016 ACM/ICPC ECNA Regional I.Waif Until Dark(最大流)

    这是一道ECNA的16年题,问有n个小朋友,m个玩具,不同孩子有不同喜好的玩具,每个玩具可能属于一个类别,同一类别的玩具最多只能用一定次数,问最大匹配 这个就很裸的二分图,掏出dinic板子,首先最后 ...

  5. 转:【Python3网络爬虫开发实战】 requests基本用法

    1. 准备工作 在开始之前,请确保已经正确安装好了requests库.如果没有安装,可以参考1.2.1节安装. 2. 实例引入 urllib库中的urlopen()方法实际上是以GET方式请求网页,而 ...

  6. 第10.1节 Python的模块及模块导入

    一. 什么是模块 Python中的模块即单个的Python代码文件,为什么称为模块呢?这是因为在Python中,每个独立的Python文件都可以作为被其他代码导入的模块使用,导入的模块有自己的名字空间 ...

  7. 自动化运维工具之Puppet master/agent模型、站点清单和puppet多环境设定

    前文我们了解了puppe中模块的使用,回顾请参考https://www.cnblogs.com/qiuhom-1874/p/14086315.html:今天我来了解下puppet的master/age ...

  8. centos 7系统,解决python3.x 安装后使用yum不能安装的问题(错误:urlgrabber-ext-down | KeyboardInterrupt)

    1.在安装python3.xx版本后,通过yum去安装软件会出现问题,目前我遇到的有2种问题 比如显示:urlgrabber-ext-down Downloading packages: File & ...

  9. MySQL存储引擎:MyISAM和InnoDB的区别

    MyISAM和InnoDB的区别 定义 InnoDB:MySQL默认的事务型引擎,也是最重要和使用最广泛的存储引擎.它被设计成为大量的短期事务,短期事务大部分情况下是正常提交的,很少被回滚.InnoD ...

  10. mvvm和mvc区别?

    mvc和mvvm其实区别并不大.都是一种设计思想.主要就是mvc中Controller演变成mvvm中的viewModel. mvvm主要解决了mvc中大量的DOM 操作使页面渲染性能降低,加载速度变 ...