好用的安全框架——Shiro
好用的安全框架——Shiro
Shiro帮助我们完成了几大功能
- authentication.认证,确认用户的身份
- authorization, 授权,对用户访问资源的行为做控制
RBAC模型:Role Based Access Control
Springboot中集成Shiro
1.1注入依赖
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring-boot-starter</artifactId>
<version>1.10.0</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
1.2配置三板斧
@Configuration
public class ShiroConfig {
//Realm 代表资源
@Bean
public Realm getRealm(){
return new MyRealm();
}
//SecurityManager 用作流程控制
@Bean
public DefaultWebSecurityManager mySecurityManager(Realm realm){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(realm);
return securityManager;
}
//ShirFilterFactoryBean
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultSecurityManager mySecurityManager){
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(mySecurityManager);
return shiroFilterFactoryBean;
}
}
1.3.1实现登录认证功能
我们定义了一个自己的资源MyRealm,并且继承了AuthorizingRealm,去实现它的两个方法:doGetAuthorizationInfo和doGetAuthenticationInfo
我们在doGetAuthentication 方法里去写认证流程。
@Configuration
public class MyRealm extends AuthorizingRealm{
private Logger logger = LoggerFactory.getLogger(ShiroConfig.class);
@Resource
private UserService userService;
//授权
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
logger.info("enter doGetAuthorizationInfo");
return null;
}
//认证,主要是看你有没有记载我们数据库里,如果在了,信息是否正确
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
logger.info(">>>enter doGetAuthorizationInfo,开始认证");
//获取到当前用户的信息
UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) token;
String username = usernamePasswordToken.getUsername();
//从数据库中拿取比对
UserBean userBean = userService.getUserByUsername(username);
//没有查到就没有这个用户
if(userBean == null){
//抛出UnknownAccountException
return null;
}
//返回AuthenticationToken,完成认证流程,无需接触密码敏感信息,让shiro帮我们做
SimpleAuthenticationInfo simpleAuthenticationInfo =
new SimpleAuthenticationInfo(userBean,
userBean.getUserPass(), "myRealm");
return simpleAuthenticationInfo;
}
}
1.3.2登录认证路径资源过滤
在shiro配置三板斧中的getShiroFilterFactoryBean方法里添加
@Configuration
public class ShiroConfig {
//Realm 代表资源
// @Bean
// public Realm getRealm(){
// return new MyRealm();
// }
//SecurityManager 用作流程控制
@Bean
public DefaultWebSecurityManager mySecurityManager(Realm realm){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(realm);
return securityManager;
}
//ShirFilterFactoryBean
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(DefaultSecurityManager mySecurityManager){
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(mySecurityManager);
//配置路径过滤器
HashMap<String, String> filterMap = new HashMap<>();
//key是ant路径,value是shiro默认配置过滤器的写法。
// 如 anno,auth,authc,perms,role.
filterMap.put("/mobile/**","authc");
filterMap.put("/salary/**","authc");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterMap);
return shiroFilterFactoryBean;
}
}
注意:这里的"authc"取自shiro自定义的过滤策略。这里我们用的是非登录即过滤的策略。
且越宽泛的规则定义放后,越精细的定义放前,因为他是从上到下开始过滤
下面是它默认的策略,可选
注意,这里我们把realm的配置注释掉了,因为,我们在myRealm类里加入@configuration注解了,无需重复配置bean
1.4.controller层login方法
@PostMapping("/login")
public Object login(@RequestBody UserBean userBean){
Map<String,String> msg = new HashMap<>();
Subject currentUser = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken(userBean.getUserName(), userBean.getUserPass());
try {
currentUser.login(token);
msg.put("ok","登录成功");
}catch (UnknownAccountException e){
logger.info("There is no user with username of" + token.getPrincipal());
msg.put("errMsg","用户不存在");
}catch (IncorrectCredentialsException e){
logger.info("Password for account " + token.getPrincipal() + "is wrong");
msg.put("errMsg","密码不正确");
}catch (LockedAccountException e){
logger.info("The account " + token.getPrincipal() + "is locked");
msg.put("errMsg","账户已锁定");
}catch (AuthenticationException e){
logger.info("登录失败",e);
msg.put("errMsg","登录失败");
}
return msg;
}
经过测试发现,两个资源路径salary和mobile需要登录才能访问,否则跳到login.jsp页面
1.5修复登录认证错误访问的情况
当前问题:不登录就跳到login.jsp页面,我们需要自定义一个页面
登出:
@GetMapping("/logout")
public void logout(){
Subject currentUser = SecurityUtils.getSubject();
currentUser.logout();
}
或者在shiroFilterFactoryBean方法里面加上
filterMap.put("/common/logout","logout");
采用它默认的过滤器也可。
实际上它的底层也是调用了这个方法currentUser.logout()
1.6总结
- 入口是在currentUser.login(token)方法
- 在shiroFilterFactoryBean中定义一些资源访问的过滤器策略,来限制资源的访问
- 在MyRealm,也就是资源里,获取到登录信息,且在doGetAuthenticationInfo方法中实现登录的认证,且在认证的时候,无需管密码,shiro会帮我们校验,只需要获取username即可
2.1实现授权
目标:给予用户相应角色以及相应权限。
第一步:
在MyRealm资源类里面实现doGetAuthorizationInfo方法
//授权
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
logger.info("enter doGetAuthorizationInfo");
//获取当前用户,是当前的UserBean
UserBean userBean = (UserBean) principalCollection.getPrimaryPrincipal();
//将你的自己顶一个的permission 和 roles交给 框架取处理。
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
authorizationInfo.setRoles((Set<String>) userBean.getUserRoles());
authorizationInfo.setStringPermissions((Set<String>) userBean.getUserPerms());
return authorizationInfo;
}
注意:这里的userBean之所以能获取,是因为我们在doGetAuthenticationInfo方法中
SimpleAuthenticationInfo simpleAuthenticationInfo =new SimpleAuthenticationInfo(userBean,userBean.getUserPass(), "myRealm");
将userRealm存放了起来
然后我们就将userRealm中的权限,角色交给SimpleAuthorizationInfo,并且返回它的实例交给框架处理。
第二步:
在controller类里面去验证是否有某一个权限,如果有就给他相应资源。
@RestController
@RequestMapping("/mobile")
public class MobileController {
@GetMapping("/query")
public String query(){
Subject currentUser = SecurityUtils.getSubject();
if(currentUser.isPermitted("mobile")){
return "mobile";
}
return "error";
}
}
注意:这里之所以可以用urrentUser.isPermitted()该方法,是因为我们把自定义的MyRealm对象里的角色,权限交给了shiro,所以我们可以使用。
但是这样的硬编码方法会影响到业务逻辑,不太推荐。
2.2注解实现授权
所以我们更常用注解方式。我们用注解来实现方法级别的权限控制
常用的注解有五个
- @RequiresAuthentication 需要完成用户登录
- @RequireGuest 未登录用户可以访问,登录用户不能访问
- @RequirePermissions 需要有对应资源权限
- @RequireRoles 需要有对应角色才能访问
- @RequiresUser 需要用户完成登录并且实现了记住我功能
Demo
@RequiresPermissions("mobile")
@GetMapping("/query")
public String query(){
return "mobile";
}
有mobile权限的才可以访问
转载自:https://juejin.cn/post/7196274316514115621