shiro
约 2204 字大约 7 分钟
shiro权限控制
1. 权限概述
认证:系统提供的用于识别用户身份的功能,通常登录功能就是认证功能-----让系统知道你是谁??
授权:系统授予用户可以访问哪些功能的许可(证书)----让系统知道你能做什么??
常见的权限控制方式
URL拦截权限控制:底层基于拦截器或者过滤器实现
方法注解权限控制:底层基于代理技术实现,为Action创建代理对象,由代理对象进行权限校验
2. shiro的介绍
官网:shiro.apache.org
shiro框架的核心功能:认证、授权、会话管理、加密
shiro框架认证流程
Application Code:应用程序代码,由开发人员负责开发的
Subject:框架提供的接口,代表当前用户对象
SecurityManager:框架提供的接口,代表安全管理器对象
Realm:可以开发人员编写,框架也提供一些,类似于DAO,用于访问权限数据
框架提供的过滤器
![1.png](https://290ff162.telegraph-image-eg9.pages.dev/file/a225ee4bba93bf94ffef9.png)
3. shiro的使用
创建权限数据模型
权限表、角色表、用户表、角色权限关系表、用户角色关系表
角色就是权限的集合,引入角色表,是为了方便授权
使用shiro的URL拦截权限控制(基于过滤器实现)
maven项目引入shiro依赖
<!-- 引入shiro框架的依赖 -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-all</artifactId>
<version>1.2.2</version>
</dependency>
在web.xml配置shiro框架进行权限控制在struts2前面
<!-- 配置spring框架提供的用于整合shiro框架的过滤器 -->
<filter>
<filter-name>shiroFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>shiroFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
在spring配置文件中配置bean,id为shiroFilter
<!-- 配置shiro框架的过滤器工厂对象 -->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<!-- 注入安全管理器对象 -->
<property name="securityManager" ref="securityManager"/>
<!-- 注入相关页面访问URL -->
<property name="loginUrl" value="/login.jsp"/>
<property name="successUrl" value="/index.jsp"/>
<property name="unauthorizedUrl" value="/unauthorized.jsp"/>
<!--注入URL拦截规则 -->
<property name="filterChainDefinitions">
<value>
/css/** = anon
/js/** = anon
/images/** = anon
/validatecode.jsp* = anon
/login.jsp = anon
/userAction_login.action = anon
/page_base_staff.action = perms["staff-list"]
/* = authc
</value>
</property>
</bean>
配置安全管理器
<!-- 注册安全管理器对象 -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="realm" ref="bosRealm"/>
</bean>
注册realm并注入给安全管理器
<bean id="bosRealm" class="com.itheima.bos.realm.BOSRealm"></bean>
修改UserAction的login方法,使用shiro提供的方法进行认证
public String login(){
//从Session中获取生成的验证码
String validatecode = (String)
ServletActionContext.getRequest().getSession().getAttribute("key");
//校验验证码是否输入正确
if(StringUtils.isNotBlank(checkcode) && checkcode.equals(validatecode)){
//使用shiro框架提供的方式进行认证操作
Subject subject = SecurityUtils.getSubject();//获得当前用户对象,状态为“未认证”
//创建用户名密码令牌对象
AuthenticationToken token = new UsernamePasswordToken(model.getUsername(),MD5Utils.md5(model.getPassword()));
try{
subject.login(token);
}catch(Exception e){
e.printStackTrace();
return LOGIN;
}
User user = (User) subject.getPrincipal();
ServletActionContext.getRequest().getSession().setAttribute("loginUser", user);
return HOME;
}else{
//输入的验证码错误,设置提示信息,跳转到登录页面
this.addActionError("输入的验证码错误!");
return LOGIN;
}
}
自定义realm,实现AuthorizingRealm接口,并注入给安全管理器
public class BOSRealm extends AuthorizingRealm{
@Autowired
private IUserDao userDao;
//认证方法
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
System.out.println("realm中的认证方法执行了。。。。");
UsernamePasswordToken mytoken = (UsernamePasswordToken)token;
String username = mytoken.getUsername();
//根据用户名查询数据库中的密码
User user = userDao.findUserByUserName(username);
if(user == null){
//用户名不存在
return null;
}
//如果能查询到,再由框架比对数据库中查询到的密码和页面提交的密码是否一致
AuthenticationInfo info = new SimpleAuthenticationInfo(user, user.getPassword(), this.getName());
return info;
}
//授权方法
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
//为用户授权
info.addStringPermission("staff-list");
//TODO 后期需要修改为根据当前登录用户查询数据库,获取实际对应的权限
User user1 = (User) SecurityUtils.getSubject().getPrincipal();
User user2 = (User) principals.getPrimaryPrincipal();
System.out.println(user1 == user2);
return info;
}
}
使用shiro的方法注解方式权限控制(基于代理技术实现)
在spring配置文件中开启shiro注解支持
<!-- 开启shiro框架注解支持 -->
<bean id="defaultAdvisorAutoProxyCreator"
class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator">
<!-- 必须使用cglib方式为Action对象创建代理对象 -->
<property name="proxyTargetClass" value="true"/>
</bean>
<!-- 配置shiro框架提供的切面类,用于创建代理对象 -->
<bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor"/>
在Action的方法上使用shiro注解
@RequestPermission("staff-delete")
//执行这个方法必须有staff-delete权限
public String deleteBatch(){
staffService.deleteBatch(ids);
return LIST;
}
在struts.xml中配置全局异常捕获,
当shiro框架抛出权限不足异常时,跳转到权限不足提示页面
<!-- 全局结果集定义 -->
<global-results>
<result name="login">/login.jsp</result>
<result name="unauthorized">/unauthorized.jsp</result>
</global-results>
<global-exception-mapping>
<exception-mapping result="unauthorized"
exception="org.apache.shiro.authz.unauthorizedException"></exception-mapping>
</global-exception-mapping>
使用shiro提供的页面标签方式权限控制(标签技术实现)
在jsp页面中引入shiro的标签库
<%@ taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>
使用shiro的标签控制页面元素展示
<shiro:hasPermission name="staff-delete">
{
id : 'button-delete',
text : '删除',
iconCls : 'icon-cancel',
handler : doDelete
},
</shiro:hasPermission>
代码级别权限控制(基于代理技术实现)
public String edit(){
//Subject subject = SecurityUtils.getSubject();
//subject.checkPermission("staff-edit");
//显查询数据库,根据id查询原始数据
Staff staff = staffService.findById(model.getId());
//使用页面提交的数据进行覆盖
staff.setName(model.getName());
staff.setTelephone(model.getTelephone());
staff.setHaspda(model.getHaspda());
staff.setStandard(model.getStandard());
staff.setStation(model.getStation());
staffService.update(staff);
return LIST;
}
36. shiro组件,认证,权限怎么做?
- shiro组件
- Subject:主体,代表了当前“用户”,表示要和应用交互的东西.
- SecurityManager: 安全管理器. Shiro中所有的操作都是通过安全管理器操作的.内部会把Subject发出的请求转发到对应的内部组件中完成具体功能
- Realm: 域,shiro从Realm获取安全数据(如用户、角色、权限),就是说SecurityManager要验证用户身份,那么它需要从Realm获取相应的用户进行比较以确定用户身份是否合法;也需要从Realm得到用户相应的角色/权限进行验证用户是否能进行操作(通常我们使用自定义的Realm)
- Shiro认证的流程.(Subject,SecurityManager,Authenticator,Realm做了哪些事情)
- 1.将需要登录的账号和密码封装成UsernamePasswordToken
- 2.然后从SecurityUtils中获取Subject对象,然后调用login方法,把token作为参数传入.
- 3.调用subject.login()之后,会交给SecurityManager进行请求的处理.
- 4.安全管理器SecurityManager会把请求转发给认证器Authenticator
- 5.认证器Authenticator会调用Realm中的方法并把token作为参数传入
- 6.我们需要在Realm中根据用户名从数据库中查询用户信息并封装成认证信息对象SimpleAuthenticationInfo
- 7.如果返回的SimpleAuthenticationInfo对象为空会抛出异常
- 8.认证器Authenticator会拿到传入token中的password和返回认证信息对象SimpleAuthenticationInfo中的password进行比对,如果不一致则抛出异常.
//filter
//1、获取SecurityManager工厂,此处使用Ini配置文件初始化SecurityManager
Factory<org.apache.shiro.mgt.SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
//2、得到SecurityManager实例 并绑定给SecurityUtils
org.apache.shiro.mgt.SecurityManager securityManager = factory.getInstance();
SecurityUtils.setSecurityManager(securityManager);
//3、得到Subject及创建用户名/密码身份验证Token(即用户身份/凭证)
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken("zhang", "123");
try {
//4、登录,即身份验证
subject.login(token);
} catch (AuthenticationException e) {
//5、身份验证失败
}
Assert.assertEquals(true, subject.isAuthenticated()); //断言用户已经登录
//6、退出
subject.logout();
自定义realm
public class CustomAuthorizationRealm extends AuthorizingRealm{
//认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(
AuthenticationToken token) throws AuthenticationException {
//从token中 获取用户身份信息
String username = (String) token.getPrincipal();
//拿username从数据库中查询
//....
//如果查询不到则返回null
//获取从数据库查询出来的用户密码
UsrinfMapper usrinfMapper = (UsrinfMapper)SpringContainer.getBean("usrinfMapper");
Usrinf USRINF = usrinfMapper.loadByLGNNAM(username);
if(USRINF == null){
return null;
}
String password = USRINF.getLGNPWD();//这里使用静态数据模拟。。
//返回认证信息由父类AuthenticatingRealm进行认证
SimpleAuthenticationInfo sinfo = new SimpleAuthenticationInfo(username, password, getName());
return sinfo;
<!-- String username = (String)principals.getPrimaryPrincipal();
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
authorizationInfo.setRoles(userService.findRoles(username));
authorizationInfo.setStringPermissions(userService.findPermissions(username));
return authorizationInfo; -->
}
//授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
authorizationInfo.addRole("role1");
authorizationInfo.addRole("role2");
authorizationInfo.addObjectPermission(new BitPermission("+user1+10"));
authorizationInfo.addObjectPermission(new WildcardPermission("user1:*"));
authorizationInfo.addStringPermission("+user2+10");
authorizationInfo.addStringPermission("user2:*");
return authorizationInfo;
}
}
- 4.Shiro鉴权的流程.(Subject,SecurityManager,Realm做了哪些事情)
- 1.会先判断用户是否已经登录,如果没登录返回false,登录继续后续流程
- 2.然后会通过安全管理器securityManager判断当前登录用户是否有该权限.
- 3.安全管理器securityManager会调用Realm中的方法获取当前用户拥有的角色/权限集合
- 4.然后判断用户的角色/权限集合中是否包含当前判断的权限,如果包含返回true,否则返回false.
- @RequiresRoles("admin")注解