Apache Shiro的使用

来源:互联网 发布:linux cp命令 目录 编辑:程序博客网 时间:2024/05/13 02:50

本文将讲述在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的注解对权限和角色的控制) --><beanclass="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"depends-on="lifecycleBeanPostProcessor"><property name="proxyTargetClass" value="true" /></bean><bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor" /><beanclass="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.xsdhttp://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 权限注解的支持 开始 --><beanclass="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{@Autowiredprivate IRealmService service;/** 授权查询回调函数, 进行鉴权但缓存中无用户的授权信息时调用 * @see org.apache.shiro.realm.AuthorizingRealm#doGetAuthorizationInfo(org.apache.shiro.subject.PrincipalCollection) */@Overrideprotected 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) */@Overrideprotected 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;//所属组织机构idprivate 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接口提供的方法在任何地方进行 权限验证



原创粉丝点击