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. JZOJ8月4日提高组反思

    JZOJ8月4日提高组反思 被一堆2018&2019&2020的巨佬暴打 又是愉快的爆0的一天呢 T1 看了看题 没想法 暴力走起 求个质因数呀,二分呀-- 然后就炸了 正解预处理加二 ...

  2. Zookeeper(5)---分布式锁

    基于临时序号节点来实现分布式锁 为什么要用临时节点呢?如果拿到锁的服务宕机了,会话失效ZK自己也会删除掉临时的序号节点,这样也不会阻塞其他服务. 流程: 1.在一个持久节点下面创建临时的序号节点作为锁 ...

  3. centos7中安装redis出现的问题

    重现步骤: 1.解压redis包后 执行make命令.出现一堆东西,其中有gcc:命令未找到. 解决:安装 yum install gcc-c++(需要有网) 2.安装完gcc命令后,再make.出现 ...

  4. Python+moviepy使用manual_tracking和headblur函数10行代码实现视频人脸追踪打马赛克

    ☞ ░ 前往老猿Python博文目录 ░ 一.背景知识 1.1.headblur简介 追踪人脸打马赛克需要使用headblur函数. 调用语法: headblur(clip,fx,fy,r_zone, ...

  5. Python函数中的位置参数

    函数的参数在调用时传递数据时,默认是按参数的位置顺序传值,即形参的顺序与实参的顺序逐一对应,这种参数的使用模式称为位置参数.位置参数是最常用的一种参数使用形式. 使用位置参数传递实参的情况下,要求有缺 ...

  6. JVM 垃圾回收?全面详细安排!

    写在前面: 小伙伴儿们,大家好!今天来学习Java虚拟机相关内容,作为面试必问的知识点,来深入了解一波! 思维导图: image-20201207153125210 1,判断对象是否死亡 我们在进行垃 ...

  7. 团队作业part6--复审与事后分析

    一.Alpha阶段项目复审:https://www.cnblogs.com/3Jax/p/13127401.html 二.事后诸葛亮分析:https://www.cnblogs.com/3Jax/p/ ...

  8. WordCounter项目(基于javase)

    1.  Github项目地址: https://github.com/Flyingwater101/WordCount 1.  PSP表格 PSP2.1 Personal Software Proce ...

  9. 将一个数组转化为需要的格式,来自react官网的商品列表示例

    //原来的格式 const PRODUCTS = [ { category: 'Sporting Goods', price: '$49.99', stocked: true, name: 'Foot ...

  10. P6772 [NOI2020]美食家

    题目大意 给你一个 \(n\) 个点,\(m\) 条边的有向图,每条边有一个权值 \(w_i\) ,每个节点有一个权值 \(a_i\) . 你从节点 \(1\) 出发,每经过一个节点就可以获得该点的权 ...