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. LeetCode 023 Merge k Sorted Lists

    题目要求:Merge k Sorted Lists Merge k sorted linked lists and return it as one sorted list. Analyze and ...

  2. 如何自动化你的Excel导入导出(Java)?

    GitHub | 中文 | English | 博客 为什么使用AutoExcel? Excel导入导出在软件开发中非常常见,只要你接触过开发,就一定会遇到.相信很多人会跟我一样选择用Apache P ...

  3. 推荐系统实践 0x0c FM系列

    逻辑回归(LR) 在介绍FM系列之前,我想首先简单介绍一下逻辑回归.通常来说,逻辑回归模型能够综合利用更多的信息,如用户.物品.上下文等多种不同的特征,生成更为全面的结果.另外,逻辑回归将推荐问题看成 ...

  4. mysql中table schema的基本操作

    我们通常对数据库进行的增删插检操作,是针对数据库中的文件.mysql数据库中还有一些表(是view,只能做select操作)记录了现有表的meta data,比如某个column的名字,它的定义是什么 ...

  5. CQUT校园通知网消息爬虫+Server酱微信推送

    上了大三之后发现很多学校的通知都不会发送到班群里面,导致自己会错过很多重要信息,故想写一个爬虫来获取从当前时间之后的新的通知标题,并推送到微信上. PS:推送到微信上这个想法来源是,很多时候都需要将消 ...

  6. LeetCode初级算法之数组:66 加一

    加一 题目地址:https://leetcode-cn.com/problems/plus-one/ 给定一个由整数组成的非空数组所表示的非负整数,在该数的基础上加一.最高位数字存放在数组的首位, 数 ...

  7. TMOOC-1692-分西瓜

    题目 描述 今天是阴历七月初五,首师大附中信息社团队员GDC的生日.GDC正在和SCX.WXY在首师大附中集训.他想给这两位兄弟买点什么庆祝生日,经过调查,GDC发现SCX和WXY都很喜欢吃西瓜,而且 ...

  8. AcWing 195. 骑士精神

    双向BFS (广搜) \(O(8 ^ 7)\) 看到没有双向BFS的题解我就过来了 这道题也可以用双向\(BFS\)来做,时间复杂度与\(IDA*\)不相上下. 双向\(BFS\)的实现有多种: 把初 ...

  9. STL——容器(Set & multiset)的迭代器

    1.set.insert(elem);     //在容器中插入元素. 2.set.begin();         //返回容器中第一个数据的迭代器. 3.set.end();          / ...

  10. 程序员必读的 99 本书籍 & 资源

    作为程序员,始终要保持学习,一直带着纸质书还是很不便的,因此电子书对于我们还是挺需要的.为了方便广大的小伙伴也能方便找到对应的电子书,我花费洪荒之力从各个搜索网站收集了几百本常用的电子书,找到了,我要 ...