跳至主要內容

shiro

HeChuangJun约 2204 字大约 7 分钟

shiro权限控制

1. 权限概述

认证:系统提供的用于识别用户身份的功能,通常登录功能就是认证功能-----让系统知道你是谁??
授权:系统授予用户可以访问哪些功能的许可(证书)----让系统知道你能做什么??

常见的权限控制方式
URL拦截权限控制:底层基于拦截器或者过滤器实现
方法注解权限控制:底层基于代理技术实现,为Action创建代理对象,由代理对象进行权限校验

2. shiro的介绍

官网:shiro.apache.org
shiro框架的核心功能:认证、授权、会话管理、加密


shiro框架认证流程
Application Code:应用程序代码,由开发人员负责开发的
Subject:框架提供的接口,代表当前用户对象
SecurityManager:框架提供的接口,代表安全管理器对象
Realm:可以开发人员编写,框架也提供一些,类似于DAO,用于访问权限数据

框架提供的过滤器
1.png
1.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")注解