目录
1,前言
2, Shiro的介绍
3,Shiro功能
4,基本使用
4.1 引入一个依赖
4.2 配置两个Bean
4.3 实现两个方法
4.4 权限控制
4.4.1 AdvisorAutoProxyCreator
4.4.2 AuthorizationAttributeSourceAdvisor
5,源码参考
5.1 Gittee
在以往的权限管理中,我们的权限管理通常是有以下几个步骤:
1.创建用户,分配权限。
2.用户登录,权限拦截器拦截请求,识别当前用户登录信息
3.从权限表中判断是否拥有权限
从以上步骤中可以提取到以下三个问题。
1.如何让Shiro拦截请求。
在web开发中,Shiro会提供一个拦截器来对请求进行拦截。
2.Shiro如何判断发起请求用户的身份?
在web开发中,会借助session来判断,如果禁用了session,那么可能需要重写一些方法。
3.如何判断权限?
Shiro使用realm来判断权限。
新建一个基于Springboot的Web项目,引入Shiro依赖。
org.apache.shiro
shiro-spring
1.4.0
新建一个shiroConfig配置类,配置Shiro最为核心的安全管理器SecurityManager。这里准确来说配置了三个Bean,形参中的UserAuthorizingRealm是通过@Component扫描进去的,不添加这个注解直接配置在ShiroConfig中也行。
/**
* 配置安全管理器,Shiro最核心的组件
*
* @param userRealm
* @return
*/
@Bean
public SecurityManager securityManager(UserAuthorizingRealm userRealm) {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(userRealm);
// 取消Cookie中的RememberMe参数
securityManager.setRememberMeManager(null);
// 配置自定义Session管理器
// securityManager.setSessionManager(mySessionManager());
return securityManager;
}
再配置Shiro的过滤器工厂类,将上一步配置的安全管理器注入,并配置相应的过滤规则。
//* authc:配置的url都必须认证通过才可以访问,它是Shiro内置的一个过滤器
//* 对应的实现类 @see org.apache.shiro.web.filter.authc.FormAuthenticationFilter
//
//* anon:也是Shiro内置的,它对应的过滤器里面是空的,什么都没做,可以理解为不拦截
//* 对应的实现类 @see org.apache.shiro.web.filter.authc.AnonymousFilter
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
// 登录页面,无权限时跳转的路径
shiroFilterFactoryBean.setLoginUrl("/login");
// 配置拦截规则
Map filterMap = new LinkedHashMap<>();
// 首页配置放行
filterMap.put("/", "anon");
// 登录页面和登录请求路径需要放行
filterMap.put("/login", "anon");
filterMap.put("/do_login", "anon");
// 其他未配置的所有路径都需要通过验证,否则跳转到登录页
filterMap.put("/**", "authc");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterMap);
return shiroFilterFactoryBean;
}
上面使用LinkedHashMap是为了保持顺序,Filter的配置顺序不能随便打乱,过滤器是按照我们配置的顺序来匹配的。范围大的过滤器要放在后面,/**
这条如果放在前面,那么一来就匹配上了,就不会继续再往后走了。
在上一步的安全管理器配置中,我们通过形参注入了一个UserAuthorizingRealm对象,这个就是认证和授权相关的流程,需要我们自己实现。继承AuthorizingRealm之后,我们需要实现两个抽象方法,一个是认证,一个是授权,这两个方法长得很像,别弄混淆了。
doGetAuthenticationInfo:认证。相当于登录,只有通过登录了,才能进行后面授权的操作。一些只需要登录权限的操作,在登录成功后就可以访问了,比如上一步中配置的authc
过滤器就是只需要登录权限的。
doGetAuthorizationInfo:授权。认证过后,仅仅拥有登录权限,更多细粒度的权限控制,比如菜单权限,按钮权限,甚至方法调用权限等,都可以通过授权轻松实现。在这个方法里,我们可以拿到当前登录的用户,再根据实际业务赋予用户部分或全部权限,当然这里也可以赋予用户某些角色,后面也可以根据角色鉴权。下方的演示代码仅添加了权限,赋予角色可以调用addRoles()
或者setRoles()
方法,传入角色集合。
package com.example.shirodemo.shiro;
import com.example.shirodemo.entity.User;
import com.example.shirodemo.service.LoginService;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.*;
/**
* UserRealm,实现认证和授权
*
* @author woqing
*/
@Component
public class UserAuthorizingRealm extends AuthorizingRealm {
@Autowired
private LoginService loginService;
/**
* 授权验证,获取授权信息
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
User user = (User) principalCollection.getPrimaryPrincipal();
List perms;
// 系统管理员拥有最高权限
if (User.SUPER_ADMIN == user.getId()) {
perms = loginService.getAllPerms();
} else {
perms = loginService.getUserPerms(user.getId());
}
// 权限Set集合
Set permsSet = new HashSet<>();
for (String perm : perms) {
permsSet.addAll(Arrays.asList(perm.trim().split(",")));
}
// 返回权限
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
info.setStringPermissions(permsSet);
return info;
}
/**
* 登录验证,获取身份信息
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
// 获取用户
User user = loginService.getUserByUsername(token.getUsername());
if (user == null) {
throw new UnknownAccountException("账号或密码不正确");
}
// 判断用户是否被锁定
if (user.getStatus() == null || user.getStatus() == 1) {
throw new LockedAccountException("账号已被锁定,请联系管理员");
}
// 验证密码
if (!user.getPassword().equals(new String(token.getPassword()))) {
throw new UnknownAccountException("账号或密码不正确");
}
user.setSessionId(SecurityUtils.getSubject().getSession().getId().toString());
// 设置最后登录时间
user.setLastLoginTime(new Date());
// 此处可以持久化用户的登录信息,这里仅做演示没有连接数据库
return new SimpleAuthenticationInfo(user, user.getPassword(), getName());
}
}
这样配置完成以后,就可以基于URL做复杂的权限控制了,我们可以通过不同的过滤器为URL配置不同的权限。
Shiro提供了很多内置的过滤器,我们最常用的就是第一个和第二个。如果对其效果不满意,我们还可以自定义过滤器实现权限控制。
注解式的权限控制需要配置两个Bean,第一个是AdvisorAutoProxyCreator,代理生成器,需要借助SpringAOP来扫描@RequiresRoles和@RequiresPermissions等注解,生成代理类实现功能增强,从而实现权限控制。需要配合AuthorizationAttributeSourceAdvisor一起使用,否则权限注解无效。
/**
* 代理生成器,需要借助SpringAOP来扫描@RequiresRoles和@RequiresPermissions等注解。
* 生成代理类实现功能增强,从而实现权限控制。
* 需要配合AuthorizationAttributeSourceAdvisor一起使用,否则权限注解无效。
*/
@Bean
public DefaultAdvisorAutoProxyCreator lifecycleBeanPostProcessor() {
DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
// 这里需要设置为True,否则 @RequiresPermissions 注解验证不生效
advisorAutoProxyCreator.setProxyTargetClass(true);
return advisorAutoProxyCreator;
}
上面配置的DefaultAdvisorAutoProxyCreator相当于一个切面,下面这个类就相当于切点了,两个一起才能实现注解权限控制。
/**
* 上面配置的DefaultAdvisorAutoProxyCreator相当于一个切面,下面这个类就相当于切点了,两个一起才能实现注解权限控制。
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
advisor.setSecurityManager(securityManager);
return advisor;
}
配置完上面两个Bean之后我们就可以使用注解来控制权限了,Shiro中的权限注解有很多,我们最常用的其实就两个,@RequiresRoles和@RequiresPermissions,前者是角色验证,后者是权限验证。他们都可以传入两个参数,value是必须的,可以传入一个字符数组,表示一个或多个角色(权限),另一个参数logical有两个值可选,AND和OR,默认为AND,表示这组角色(权限)是必须都有还是仅需要一个就能访问。
4.5 举个栗子:
以上代码表示 i
nfo()
方法需要当前登录用户拥有user:list
或者user:list
权限才能访问。
更多shiro注解解析:shiro的@RequiresPermissions五种注解使用说明
Shiro(一):Shiro的基本使用