Apache Shiro的使用

本文将讲述在Spring+SpringMvc项目中使用Shiro来保障系统的安全。关于shiro理论性的东西本文不深入讲解,重点在于在Spring项目中配置和使用shiro.关于理论性的东西,可以看看 张开涛的《跟我学Shiro》。话不多说,先来看看如何配置Shiro。

1 在web.xml中添加一个ShiroFilter

<filter>
		<filter-name>shiroFilter</filter-name>
		<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
		<init-param>
			<param-name>targetFilterLifecycle</param-name>
			<param-value>true</param-value>
		</init-param>
	</filter>
	<filter-mapping>
		<filter-name>shiroFilter</filter-name>
		<url-pattern>/*</url-pattern>
	</filter-mapping>

2 在springMvc.xml (SpringWeb上下文配置文件)中配置 支持Shiro对Controller的方法级AOP安全控制

	<!-- 支持Shiro对Controller的方法级AOP安全控制 begin (即支持shiro的注解对权限和角色的控制) -->
	<bean
		class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"
		depends-on="lifecycleBeanPostProcessor">
		<property name="proxyTargetClass" value="true" />
	</bean>

	<bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor" />


	<bean
		class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
		<property name="exceptionMappings">
			<props>
				<!-- 当用户访问没有权限信息的服务时跳转到相应的页面 -->
				<prop key="org.apache.shiro.authz.UnauthorizedException">error/403</prop>
				
				<!-- 当用户访问因为安全问题抛出异常时跳转到相应的页面 -->
				<prop key="java.lang.Throwable">error/500</prop>
			</props>
		</property>
	</bean>
	<!-- 支持Shiro对Controller的方法级AOP安全控制 end -->
3 新建一个Spring上下文支持Shiro的配置文件  spring-context-shiro.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:aop="http://www.springframework.org/schema/aop" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:context="http://www.springframework.org/schema/context"
	xsi:schemaLocation="
		http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
		http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd 
		http://www.springframework.org/schema/context  http://www.springframework.org/schema/context/spring-context-4.0.xsd"
	default-lazy-init="true">

	<description>Shiro Configuration</description>


	<!-- shiro的安全数据源 用于用户登录认证 以及授权查询 -->
	<bean id="systemAuthorizingRealm" class="com.swx.cn.rbac.realm.SystemAuthorizingRealm">
		<property name="service" ref="realmService" />
	</bean>


	<!-- 为shiro的安全数据源 用于用户登录认证 以及授权查询 通常是从数据库获取数据 -->
	<bean id="realmService" class="com.swx.cn.login.service.impl.RealmService" />


	<!-- Shiro安全认证过滤器 -->
	<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
		<property name="securityManager" ref="securityManager" />
		<!-- 确认用户访问authc拦截器拦截的路径时用户已经登录,否则跳转到登录页面即loginUrl -->
		<property name="loginUrl" value="../../login.jsp" />
		<property name="filterChainDefinitions">
			<ref bean="shiroFilterChainDefinitions" />
		</property>
	</bean>


	<!-- Shiro权限过滤器的定义 定义了 拦截的路径及其对应的 拦截器 -->
	<bean name="shiroFilterChainDefinitions" class="java.lang.String">
		<constructor-arg>
			<value>
				/service/login/loginIn = anon		<!-- 登录路径不需要登录认证拦截 -->
				/service/admin/** = roles[admin] 	<!-- admin模块必须要有admin角色才能访问 -->
				/service/** = authc					<!-- 所有service服务都需要登录认证拦截 -->
			</value>
		</constructor-arg>
	</bean>


	<!-- 添加 Shiro Spring AOP 权限注解的支持 开始 -->
	<bean
		class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
		<property name="securityManager" ref="securityManager" />
	</bean>


	<!-- 安全管理器 -->
	<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
		<property name="realm" ref="systemAuthorizingRealm" />
		<property name="sessionManager" ref="sessionManager" />
		<property name="cacheManager" ref="cacheManager" />
	</bean>


	<!-- 缓存管理器 使用 Ehcache 实现 -->
	<bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
		<property name="cacheManagerConfigFile" value="classpath:ehcache-local.xml" />
	</bean>


	<!-- 会话管理器 -->
	<bean id="sessionManager"
		class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager">
		<property name="globalSessionTimeout" value="1800000" />
		<property name="deleteInvalidSessions" value="true" />
		<property name="sessionValidationSchedulerEnabled" value="true" />
		<property name="sessionDAO" ref="sessionDAO" />
		<property name="sessionIdCookieEnabled" value="true" />
		<property name="sessionIdCookie" ref="sessionIdCookie" />
	</bean>


	<!-- 会话 DAO -->
	<bean id="sessionDAO"
		class="org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO">
		<property name="activeSessionsCacheName" value="activeSessionCache" />
		<property name="sessionIdGenerator" ref="sessionIdGenerator" />
	</bean>


	<!-- 会话 ID 生成器 -->
	<bean id="sessionIdGenerator"
		class="org.apache.shiro.session.mgt.eis.JavaUuidSessionIdGenerator" />


	<!-- 会话 Cookie 模板 -->
	<bean id="sessionIdCookie" class="org.apache.shiro.web.servlet.SimpleCookie">
		<constructor-arg value="sid" />
		<property name="httpOnly" value="true" />
		<property name="maxAge" value="180000" />
	</bean>

</beans>


4 由于spring-context-shiro.xml中需要一个ehcache-local.xml用于配置ecache缓存配置所以需要创建一个ehcache-local.xml。该xml文件定义了不同的缓存 的配置

<?xml version="1.0" encoding="UTF-8"?>
<ehcache updateCheck="false" name="defaultCache">

	<diskStore path="../temp/jeesite/ehcache" />

	<!-- 默认缓存配置. 自动失效:最后一次访问时间间隔300秒失效,若没有访问过自创建时间600秒失效。-->
	<defaultCache maxEntriesLocalHeap="1000" eternal="false" timeToIdleSeconds="300" timeToLiveSeconds="600"
		overflowToDisk="true" statistics="true"/>
	
	<!-- 系统缓存 -->
	<cache name="sysCache" maxEntriesLocalHeap="1000" eternal="true" overflowToDisk="true" statistics="true"/>
	
	<!-- 用户缓存 -->
	<cache name="userCache" maxEntriesLocalHeap="1000" eternal="true" overflowToDisk="true" statistics="true"/>
	
	<!-- 集团缓存 -->
	<cache name="corpCache" maxEntriesLocalHeap="1000" eternal="true" overflowToDisk="true" statistics="true"/>
	
	<!-- 内容管理模块缓存 -->
	<cache name="cmsCache" maxEntriesLocalHeap="1000" eternal="true" overflowToDisk="true" statistics="true"/>
    
	<!-- 工作流模块缓存 -->
	<cache name="actCache" maxEntriesLocalHeap="100" eternal="true" overflowToDisk="true" statistics="true"/>
	
    <!-- 简单页面缓存 -->
    <cache name="pageCachingFilter" maxEntriesLocalHeap="1000" eternal="false" timeToIdleSeconds="120"
    	timeToLiveSeconds="120" overflowToDisk="true" memoryStoreEvictionPolicy="LFU" statistics="true"/>
	
	<!-- 系统活动会话缓存 -->
    <cache name="activeSessionsCache" maxEntriesLocalHeap="10000" eternal="true" overflowToDisk="true"
           diskPersistent="true" diskExpiryThreadIntervalSeconds="600" statistics="true"/>
    	
</ehcache>

5 到此为止,shiro的环境配置我们已经完成了。下面我们在项目中实现shiro

6 由于在4中需要注册一个systemAuthorizingRealm bean .所以创建一个systemAuthorizingRealm类

/**
 * <h3> Shiro 从此类 获取安全数据(如用户、角色、权限)</h3>
 * 创建日期: 2017年10月24日
 * @author 赵松强
 */
public class SystemAuthorizingRealm extends AuthorizingRealm{


	@Autowired
	private IRealmService service;

	/** 授权查询回调函数, 进行鉴权但缓存中无用户的授权信息时调用
	 * @see org.apache.shiro.realm.AuthorizingRealm#doGetAuthorizationInfo(org.apache.shiro.subject.PrincipalCollection)
	 */
	@Override
	protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
		// 获取当前已登录的用户
		Principal principal = (Principal) getAvailablePrincipal(principals);
		IUser user = service.getUserByToken(principal.getUserName(),principal.getOfficeId());
		if(user == null){
			return null;
		}else{


			SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();//用户权限信息
			// 添加用户权限
			info.addStringPermission("user");

			List<IMenu> list = service.getMenuList(user);
			for (IMenu menu : list){
				if (!"".equals(menu.getPermission())){
					// 添加基于Permission的权限信息
					for (String permission : menu.getPermission().split(",")){
						info.addStringPermission(permission);
					}
				}
			}

			List<IRole> rolesList = service.getRoleList(user);
			// 添加用户角色信息
			for (IRole role : rolesList){
				info.addRole(role.getRoleEnName());
			}
			return info;
		}

	}

	/** 用户信息认证回调函数, 登录时调用用于验证用户信息
	 * @see org.apache.shiro.realm.AuthenticatingRealm#doGetAuthenticationInfo(org.apache.shiro.authc.AuthenticationToken)
	 */
	@Override
	protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authcToken) throws AuthenticationException {

		//将默认登录口令转换为带officeid的口令
		UsernamePasswordToken token =(UsernamePasswordToken) authcToken;

		//根据用户名和officeid获取用户信息
		IUser user = service.getUserByToken(token.getUsername(),token.getOfficeid());

		if(user == null){
			return null;
		}

		if(user.loginEnAble()){//用户可以登录
			//返回的信息中包括了一个Principal,项目中可以通过SecurityUtils.getSubject().getPrincipal()获取该信息
			return new SimpleAuthenticationInfo(new Principal(user), 
					user.getPassword(), getName());
		}else{//如果用户已经被禁止登录
			throw new AuthenticationException("msg:该帐号被禁止登录.");
		}
	}


	/**
	 * 	用户信息,可以通过 SecurityUtils.getSubject().getPrincipal()获取该信息
	 */
	public static class Principal implements Serializable {
		private String number;//用户编码(工号、学号等)

		private String userName;//用户名

		private String officeId;//所属组织机构id

		private String office;//所属组织机构


		public Principal(IUser user){
			this.number = user.getNumber();
			this.userName = user.getUserName();
			this.officeId = user.getOfficeId();
			this.office = user.getOffice();
		}



		public String getNumber() {
			return number;
		}

		public void setNumber(String number) {
			this.number = number;
		}

		public String getUserName() {
			return userName;
		}

		public void setUserName(String userName) {
			this.userName = userName;
		}

		public String getOfficeId() {
			return officeId;
		}

		public void setOfficeId(String officeId) {
			this.officeId = officeId;
		}

		public String getOffice() {
			return office;
		}

		public void setOffice(String office) {
			this.office = office;
		}
	}
}
7 在6中引用了一个UsernamePasswordToken登陆扣了类,这个类主要是重写shiro的UsernamePasswordToken增加officeid,用于标识用户所属组织机构id。

/**
* <h3>重写shiro的UsernamePasswordToken增加officeid,用于标识用户所属组织机构id </h3>
* 创建日期: 2017年10月26日
* @author 赵松强
*/
public class UsernamePasswordToken extends org.apache.shiro.authc.UsernamePasswordToken {

	private static final long serialVersionUID = 1L;
	
	public UsernamePasswordToken(String officeId,String username,String password){
		super.setUsername(username);
		super.setPassword(password.toCharArray());
		this.officeid = officeId;
	}
	
	
	/**
	 * 用户所属组织结构的id
	 */
	private String officeid;


	public String getOfficeid() {
		return officeid;
	}


	public void setOfficeid(String officeid) {
		this.officeid = officeid;
	}


	public static long getSerialversionuid() {
		return serialVersionUID;
	}
	
}
8 目前为止,我们已经为shiro提供了数据源,我们可以真正使用它了  这里我新建了一个Shiro工具类,简单写了两个使用的方法

public class ShiroUtils {

	/**
	 * 用户登录时调用登录方法
	 * @param officeId  组织机构id
	 * @param username	用户名
	 * @param password	密码
	 * @return 登录信息
	 * {@link com.swx.cn.rbac.bean.LoginInfo}
	 */
	public static LoginInfo login(String officeId,String username,String password){
		UsernamePasswordToken token = new UsernamePasswordToken(officeId,username,password);
		Subject subject = SecurityUtils.getSubject();
		try {
			subject.login(token);
			return new LoginInfo("登录成功",true);
			
		} catch (AuthenticationException e) {	//登录失败
			
			String className = e.getClass().getName(), message = "";
			if (IncorrectCredentialsException.class.getName().equals(className)
					|| UnknownAccountException.class.getName().equals(className)){//用户名或密码错误导致的异常
				message = "用户名或密码错误, 请重试.";
			}else if (e.getMessage() != null && e.getMessage().contains("msg:")){//非用户名或密码错误导致的异常
				message = e.getMessage().replace("msg:", "");
			}else{//其他异常
				message = "系统出现点问题,请稍后再试!";
				e.printStackTrace(); // 输出到控制台
			}
			return new LoginInfo(message,false);
			
		}
	}

	/**判断用户是否拥有指定的角色标识
	 * @param roleName 角色标识
	 * @return
	 */
	public static boolean hasRole(String roleName){
		Subject subject = SecurityUtils.getSubject();
		return subject.hasRole(roleName);
	}
	
	/**
	 * 获取Shiro的session (Shiro自己实现的session机制,注意不是HttpSession)
	 * @return  Shiro session
	 */
	public static Session getSession(){
		Subject subject = SecurityUtils.getSubject();
		return subject.getSession(true);
	}

}
其实这个类的主要功能都是来自于  SecurityUtils.getSubject() 这个方法返回的 Subject对象。我们可以看看org.apache.shiro.subject.Subject 接口为我们提供了那些使用的方法

public interface Subject {

    Object getPrincipal();

    PrincipalCollection getPrincipals();

    boolean isPermitted(String permission);

    boolean isPermitted(Permission permission);

    boolean[] isPermitted(String... permissions);

    boolean[] isPermitted(List<Permission> permissions);

    boolean isPermittedAll(String... permissions);

    boolean isPermittedAll(Collection<Permission> permissions);

    void checkPermission(String permission) throws AuthorizationException;

    void checkPermission(Permission permission) throws AuthorizationException;

    void checkPermissions(String... permissions) throws AuthorizationException;

    void checkPermissions(Collection<Permission> permissions) throws AuthorizationException;

    boolean hasRole(String roleIdentifier);

    boolean[] hasRoles(List<String> roleIdentifiers);

    boolean hasAllRoles(Collection<String> roleIdentifiers);

    void checkRole(String roleIdentifier) throws AuthorizationException;

    void checkRoles(Collection<String> roleIdentifiers) throws AuthorizationException;

    void checkRoles(String... roleIdentifiers) throws AuthorizationException;

    void login(AuthenticationToken token) throws AuthenticationException;

    boolean isAuthenticated();

    boolean isRemembered();

    Session getSession();

    Session getSession(boolean create);

    void logout();

    <V> V execute(Callable<V> callable) throws ExecutionException;


    void execute(Runnable runnable);


    <V> Callable<V> associateWith(Callable<V> callable);

    Runnable associateWith(Runnable runnable);

    void runAs(PrincipalCollection principals) throws NullPointerException, IllegalStateException;

    boolean isRunAs();

    PrincipalCollection getPreviousPrincipals();

    PrincipalCollection releaseRunAs();

}
使用上面这个接口提供的功能我们基本上能满足所有我们需要shiro 的功能。

9 完成工具类后我们可以在登录方法中调用工具类中的登录方法了

@Controller
@RequestMapping("/login")
public class LoginController {
	
	@RequestMapping("loginIn")
	public String login(HttpServletRequest request, HttpServletResponse response, Model model){
		
		LoginInfo flag = ShiroUtils.login("370000", "郑进", "11111111");
		
		return "login";
	}
}
LoginInfo类里面只是简单封装了下登录的结果
public class LoginInfo {
	/**
	 * 登录信息 主要是承载失败信息
	 */
	private String message;
	
	/**
	 * 登录结果 true:登录成功 false:登录失败
	 */
	private boolean result;
//省略setter getter
}



Spring+SpringMVC环境下使用Shiro已经搭建好了,在系统中可以在控制器的方法上使用

@RequiresPermissions("user:view:123") //标识用户必须要有  user:view:123 才能访问此方法

@RequiresRoles("ceshi") //标识用户必须要有  ceshi 角色才能访问此方法

当然也可以通过 Subject接口提供的方法在任何地方进行 权限验证



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值