Shiro安全框架
1.安全框架相关的知识
1.1目前主流的安全框机
1.SpringSecurity:是Spring家族的一部分,很多项目中会使用spring全家桶,相对与shiro来说,springSecurity更重量级,必须要求spring环境;相对shiro而言,功能更强大
2.shiro:轻量级的,使用很方便,灵活,是apache提供的,在任何框架的
1.2认证
认证 用来确定用户身份(常见用户名和密码组合)
1.3授权
对用户访问系统资源的行为做控制(后台接口访问、敏感数据显示等)
1.4 RBAC(role based access control)基于权限控制
用户关联角色和资源
资源—角色(多对多)
最终通过资源来控制角色的行为
2.了解Shiro
官方网站:http://shiro.apache.org/index.html
2.1Shiro简介
Shiro是java的一个安全(权限)框架。
Shrio可以非常容易开发出足够好的应用,不仅可以用在javaSE环境,也可以用在javaEE环境。
Shiro可以完成:认证、授权、加密、会话管理、与web集成、缓存等。
以下是基本功能点:
2.2Shiro架构(从外部看)*
ApplicationCode(应用程序):主要与subject打交道
Subject(当前角色):当前用户(角色)
ShrioSecurityManager(Shiro安全管理器):Shiro的核心,管理所有的subject
Realm(可拔插的):一种类型的数据
2.3Shiro架构(从内部看)
3.我的shiro框架
<!-- 引入shiro框架-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring-boot-starter</artifactId>
<version>1.6.0</version>
</dependency>
<!-- 引入日志-->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
3.1Shiro配置的三步骤
package com.thh.shirodemo.config;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author shkstart
* @create 2022-07-18-0:01
*/
@Configuration
public class ShiroConfig {
//1.realm 代表系统资源
@Bean
public Realm myRealm(){
return new MyRealm();
}
//2.SecurityManager(做流程控制)
@Bean
public DefaultWebSecurityManager mySecurityManager(Realm myReam){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(myReam);
return securityManager;
}
//3.ShiroFilterFactoryBean 请求过滤器
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager mySecurityManager){
ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();
factoryBean.setSecurityManager(mySecurityManager);
return factoryBean;
}
}
3.2实现登录认证的过程
3.2.1创建myRealm
继承AuthorizingRealm
实现父类doGetAuthenticationInfo认证的方法
配置路径过滤器
package com.thh.shirodemo.config;
import com.thh.shirodemo.bean.User;
import com.thh.shirodemo.service.UserService;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.Resource;
/**
* @author shkstart
* @create 2022-07-18-0:02
*/
public class MyRealm extends AuthorizingRealm {
@Resource
UserService userService;
private Logger logger=LoggerFactory.getLogger(MyRealm.class);
//授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
logger.info("shiro的授权方法");
return null;
}
//认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
logger.info("shiro的认证方法");
//获取当前用户
UsernamePasswordToken userToken=(UsernamePasswordToken) token;
String username=userToken.getUsername();
//获得数据库中的用户来和当前用户进行比对,认证
User user = userService.queryUserByName(username);
//如果没有查到用户 表示没有该用户
if(user==null){
return null;
//后面会抛出UnknownAccountException异常
}
//返回AuthenticationToken,完成认证流程
SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(user,user.getUserPass(),"myRealm");
//shiro帮我们进行密码认证(密码敏感 已经包装好了)
return simpleAuthenticationInfo;
}
}
3.2.2配置路径过滤器
//3.ShiroFilterFactoryBean 请求过滤器
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager mySecurityManager){
ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();
factoryBean.setSecurityManager(mySecurityManager);
//配置路径过滤器
Map<String,String> filterMap=new HashMap<>();
//key是ant路径 value配置shiro的默认过滤器
//Shiro的默认过滤器,配置DefaultFilter中的key
//auth,authc,perms,role
//表示两个资源路径都需要登录才可以访问
filterMap.put("/mobile/**","authc");
filterMap.put("/salary/**","authc");
// //配置登出
// filterMap.put("/common/logout","logout");
factoryBean.setFilterChainDefinitionMap(filterMap);
return factoryBean;
}
3.2.3登录功能
@PostMapping("/login")
public Object login(User user){
//返回的信息
Map<String,String> errorMsg=new HashMap<>();
//获取当前用户
Subject currentUser = SecurityUtils.getSubject();
//判断是否完成认证
if(!currentUser.isAuthenticated()) {
//没认证
UsernamePasswordToken token = new UsernamePasswordToken(user.getUserName(), user.getUserPass());
try {
currentUser.login(token);
//在session中保存登陆的用户
currentUser.getSession().setAttribute("currentUser", currentUser.getPrincipal());
return "login Succeed";
} catch (UnknownAccountException uae) {
//用户不存在
errorMsg.put("errorMsg", "用户不存在");
log.info("There is no user with username of " + token.getPrincipal());
} catch (IncorrectCredentialsException ice) {
//密码不正确
errorMsg.put("errorMsg", "密码不正确");
log.info("Password for account " + token.getPrincipal() + " was incorrect!");
} catch (LockedAccountException lae) {
//账户已锁定
errorMsg.put("errorMsg", "账户已锁定");
log.info("The account for username " + token.getPrincipal() + " is locked. " +
"Please contact your administrator to unlock it.");
}
// ... catch more exceptions here (maybe custom ones specific to your application?
catch (AuthenticationException ae) {
//用户登录失败
errorMsg.put("errorMsg", "用户登录失败");
//unexpected condition? error?
}
}
errorMsg.put("已完成认证","用户为:"+currentUser.getSession().getAttribute("currentUser"));
return errorMsg;
}
3.2.4登出功能
1.手动写接口登出
@RequestMapping("/logout")
public void logout(){
Subject subject = SecurityUtils.getSubject();
//登出
subject.logout();
}
2.配置shiro使用shiro提供的logout过滤器
//配置登出
filterMap.put("/common/logout","logout");
3.3实现授权功能
目标:
1、控制主页上按钮的访问权限
currentUser.getPricipal() 来自于 MyRealm中doGetAuthenticationInfo认证 方法返回的SimpleAuthenticationInfo对象的第一个属性。
//返回AuthenticationToken,完成认证流程
SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(user,user.getUserPass(),"myRealm");
2、控制后台资源路径的访问权限
方法1、在controller中自行判断权限。
Subject currentUser = SecurityUtils.getSubject();
// isPermitted是myRealm中授权部分添加的内容
if(currentUser.isPermitted("mobile")){
return "mobile";
}
return "error";
方法2、使用shiro提供的perms过滤器,集中配置权限信息。
//表示两个资源路径都需要登录才可以访问
filterMap.put("/mobile/**","authc,perms[mobile]");
filterMap.put("/salary/**","authc,perms[salary]");
factoryBean.setUnauthorizedUrl("/common/unauthorized");
错误补充机制:没有权限就会进入ShiroFilterFactoryBean中配置的 UnauthorizedUrl
方法3、使用shiro提供的注解,实现方法级别的权限控制。
@RequiresAuthentication 需要完成用户登录
@RequiresGuest 未登录用户可以访问,登录用就不能访问。
@RequiresPermissions 需要有对应资源权限 @RequiresRoles 需要有对应的角色
@RequiresUser 需要完成用户登录并且完成了记住我功能。
错误补充机制:没有权限就会抛出异常。
区别和方法二的不同
此时捕获异常需要利用springboot中自定义异常
@RestControllerAdvice
public class MyExceptionHandler {
@ExceptionHandler(AuthorizationException.class)
public Object shiroHandler(){
return "请先获取对应的资源,再进行访问!";
}
}
4.密码加密
shiro会获得一个CredentialsMatcher对象,来对密码进行比对。
想要用MD5方式进行加密:
Md5CredentialsMatcher已经过期, 要使用HashedCredentialsMatcher并设定算法名。
HashedCredentialsMatcher :
String hashAlgorithm, 对应Hash接口的实现类。 MD5 算法(迭代2次数) 123456+(盐salt)–>adrian–>jklfi
int hashIterations Hash迭代次数。
boolean hashSalted 已过时,不用设置
boolean storedCredentialsHexEncoded 设置默认的true
在ShiroConfig中设置
//设置加密
HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();
//设置加密算法
matcher.setHashAlgorithmName("MD5");
//设置加密的迭代次数
matcher.setHashIterations(3);
//用该matcher加密我们的realm
myRealm.setCredentialsMatcher(matcher);
在myRealm中的认证方法中加上加密需要的盐
//加入我们需要的添加的盐用来加密
ByteSource salt = ByteSource.Util.bytes("saltAdrian");
//返回AuthenticationToken,完成认证流程
SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(user,user.getUserPass(),salt,"myRealm");
加盐加密: 需要在认证返回的认证信息SimpleAuthenticationInfo中,指定需要加的盐 salt。这样算出来的密文才可以和数据库中的密文进行比对。
解密的方法:
public class PasswordEncoder {
public static String encoder(String password){
//四个参数 分别是 加密方式 密码 盐 迭代次数
SimpleHash simpleHash=new SimpleHash("MD5", ByteSource.Util.bytes(password),ByteSource.Util.bytes("saltAdrian"),3);
return simpleHash.toString();
}
}
5.多Realm认证(Shiro特色)
认证策略:
即一个用户可以通过 手机号或学号或用户名进行登录认证
再写一个用id来判断的realm
package com.thh.shirodemo.config;
import com.thh.shirodemo.bean.User;
import com.thh.shirodemo.mapper.UserMapper;
import com.thh.shirodemo.service.UserService;
import org.apache.log4j.lf5.LF5Appender;
import org.apache.shiro.authc.*;
import org.apache.shiro.realm.AuthenticatingRealm;
import org.apache.shiro.util.ByteSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Configuration;
import javax.annotation.Resource;
import java.util.List;
/**
* @author shkstart
* @create 2022-07-20-22:29
*/
@Configuration
public class UserIdRealm extends AuthenticatingRealm {
@Resource
private UserService userService;
@Resource
private UserMapper userMapper;
private Logger logger= LoggerFactory.getLogger(UserIdRealm.class);
//实现认证方法
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
logger.info("UserIdRealm的认证方法");
//获取当前用户
UsernamePasswordToken userToken=(UsernamePasswordToken) token;
String username=userToken.getUsername();
//获得数据库中的用户来和当前用户进行比对,认证
User user = userService.queryUserById(Integer.parseInt(username));
//如果没有查到用户 表示没有该用户
if(user==null){
return null;
//后面会抛出UnknownAccountException异常
}
List<String> roles = userMapper.getRoles(user.getUserRoles());
user.setUserPerms(roles);
//加入我们需要的添加的盐用来加密
ByteSource salt = ByteSource.Util.bytes("saltAdrian");
//返回AuthenticationToken,完成认证流程
SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(user,user.getUserPass(),salt,"myRealm");
//shiro帮我们进行密码认证(密码敏感 已经包装好了)
return simpleAuthenticationInfo;
}
}
在这个类中
org.apache.shiro.authc.pam.ModularRealmAuthenticator
实现认证策略—>实行不同的认证功能
增加UserIdRealm来实现通过用户id来登录
AuthenticationStrategy接口 有三个实现类
AllSuccessfulStrategy 需要所有Realm认证成功,才能最终认证成功。
AtLeastOneSuccessfulStrategy 至少有一个Realm认证成功,才能最终认证 成功。
//设置认证策略
ModularRealmAuthenticator authenticator=new ModularRealmAuthenticator();
authenticator.setAuthenticationStrategy(new AtLeastOneSuccessfulStrategy());
authenticator.setRealms(Arrays.asList(myRealm,userIdRealm));
securityManager.setAuthenticator(authenticator);
return securityManager;
FirstSuccessfulStrategy 第一个Realm认证成功后即返回认证成功,不再进 行后面的Realm认证。
6.开源代码 欢迎讨论指正
个Realm认证成功,才能最终认证 成功。
//设置认证策略
ModularRealmAuthenticator authenticator=new ModularRealmAuthenticator();
authenticator.setAuthenticationStrategy(new AtLeastOneSuccessfulStrategy());
authenticator.setRealms(Arrays.asList(myRealm,userIdRealm));
securityManager.setAuthenticator(authenticator);
return securityManager;
FirstSuccessfulStrategy 第一个Realm认证成功后即返回认证成功,不再进 行后面的Realm认证。
6.开源代码 欢迎讨论指正
https://gitee.com/adrianwow5/ShiroDemo1