本文讲述了基于springmvc+shiro实现安全管理,shiro+freemarker实现权限验证。
首先我们从web.xml开始:
1 <?xml version="1.0" encoding="UTF-8"?> 2 <web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee" 3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 4 xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"> 5 <context-param> 6 <param-name>contextConfigLocation</param-name> 7 <param-value> 8 classpath:resources/tag-context.xml 9 classpath:resources/shiro-context.xml 10 </param-value> 11 </context-param> 12 <!-- log4j --> 13 <context-param> 14 <param-name>log4jConfigLocation</param-name> 15 <param-value>classpath:resources/log4j.properties</param-value> 16 </context-param> 17 <context-param> 18 <param-name>log4jDelay</param-name> 19 <param-value>10000</param-value> 20 </context-param> 21 <!-- Spring --> 22 <listener> 23 <listener-class> 24 org.springframework.web.context.ContextLoaderListener 25 </listener-class> 26 </listener> 27 <listener> 28 <listener-class> 29 com.itrip.rp.listener.InitConfigListener 30 </listener-class> 31 </listener> 32 <!-- log4j --> 33 <listener> 34 <listener-class>org.springframework.web.util.Log4jConfigListener</listener-class> 35 </listener> 36 <!-- 日志记录过滤器 --> 37 <filter> 38 <filter-name>requestLogFilter</filter-name> 39 <filter-class> 40 com.itrip.rp.filter.RequestLogFilter 41 </filter-class> 42 </filter> 43 <filter-mapping> 44 <filter-name>requestLogFilter</filter-name> 45 <url-pattern>/*</url-pattern> 46 </filter-mapping> 47 <!-- 编码过滤 --> 48 <filter> 49 <filter-name>encoding</filter-name> 50 <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class> 51 <init-param> 52 <param-name>encoding</param-name> 53 <param-value>UTF-8</param-value> 54 </init-param> 55 </filter> 56 <filter-mapping> 57 <filter-name>encoding</filter-name> 58 <url-pattern>/*</url-pattern> 59 </filter-mapping> 60 <!-- shiro 安全过滤器 --> 61 <!-- The filter-name matches name of a 'shiroFilter' bean inside applicationContext.xml --> 62 <filter> 63 <filter-name>shiroFilter</filter-name> 64 <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> 65 <async-supported>true</async-supported> 66 <init-param> 67 <param-name>targetFilterLifecycle</param-name> 68 <param-value>true</param-value> 69 </init-param> 70 </filter> 71 <filter-mapping> 72 <filter-name>shiroFilter</filter-name> 73 <url-pattern>/*</url-pattern> 74 </filter-mapping> 75 <!-- SpringMVC --> 76 <servlet> 77 <servlet-name>resourcePlatform</servlet-name> 78 <servlet-class> 79 org.springframework.web.servlet.DispatcherServlet 80 </servlet-class> 81 <init-param> 82 <param-name>contextConfigLocation</param-name> 83 <param-value> 84 classpath:resources/applicationContext-*.xml 85 </param-value> 86 </init-param> 87 <load-on-startup>1</load-on-startup> 88 </servlet> 89 <servlet-mapping> 90 <servlet-name>resourcePlatform</servlet-name> 91 <url-pattern>/*</url-pattern> 92 </servlet-mapping> 93 </web-app>
按照web.xml初始化顺序依次为:context-param--->listener--->filter--->servlet
tag-context.xml中主要配置了权限验证标签,代码如下:
1 <?xml version="1.0" encoding="UTF-8"?> 2 <beans xmlns="http://www.springframework.org/schema/beans" 3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 4 xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd" 5 default-lazy-init="true"> 6 <!--后台权限标签--> 7 <bean id="perm" class="com.itrip.rp.core.permission.PermissionDirective"/> 8 </beans>
权限验证标签实现类是基于freemarker标签的实现方式,具体请看源代码:
1 package com.itrip.rp.core.permission; 2 3 import java.io.IOException; 4 import java.util.Map; 5 6 import org.apache.shiro.SecurityUtils; 7 import org.apache.shiro.subject.Subject; 8 9 import com.itrip.rp.common.Constants; 10 import com.itrip.rp.core.freemarker.DirectiveUtils; 11 12 import freemarker.core.Environment; 13 import freemarker.template.TemplateDirectiveBody; 14 import freemarker.template.TemplateDirectiveModel; 15 import freemarker.template.TemplateException; 16 import freemarker.template.TemplateModel; 17 18 /** 19 * 后台管理员权限许可 20 * 21 * @author Benny 22 */ 23 public class PermissionDirective implements TemplateDirectiveModel { 24 25 /*** 26 * 权限验证 27 */ 28 @SuppressWarnings("unchecked") 29 public void execute(Environment env, Map params, TemplateModel[] loopVars, TemplateDirectiveBody body) throws TemplateException, IOException { 30 String url = DirectiveUtils.getString(Constants.PARAM_URL, params); 31 Subject subject = SecurityUtils.getSubject(); 32 boolean pass = subject.isPermitted(url); 33 if (pass) { 34 body.render(env.getOut()); 35 } 36 } 37 }
Constants.PARAM_URL="url"; //对应的值就是取freemarker标签中的url
标签形式如:
<@perm url="/product/add"></@perm>
Subject subject = SecurityUtils.getSubject(); //这一步是基于shiro获取认证用户对象
boolean pass = subject.isPermitted(url); //这一步就是进行权限验证,权限验证通过返回true,反之返回false
这里还是非常简单的。
接下来让我们看看shiro的具体配置吧,还是先看源代码:
1 <?xml version="1.0" encoding="UTF-8"?> 2 <beans xmlns="http://www.springframework.org/schema/beans" 3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:util="http://www.springframework.org/schema/util" 4 xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd 5 http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-3.0.xsd" 6 default-lazy-init="true"> 7 <!-- Shiro拦截器 --> 8 <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"> 9 <property name="securityManager" ref="securityManager" /> 10 <property name="loginUrl" value="/login" /> 11 <property name="successUrl" value="/index" /> 12 <property name="filters"> 13 <util:map> 14 <entry key="authc" value-ref="authcFilter" /> 15 <entry key="user" value-ref="userFilter" /> 16 <entry key="logout" value-ref="logoutFilter" /> 17 </util:map> 18 </property> 19 <!--authc登陆认证 user用户认证检查 logout退出 filter--> 20 <property name="filterChainDefinitions"> 21 <value> 22 /css/** = anon 23 /img/** = anon 24 /js/** = anon 25 /favicon.ico = anon 26 /login = authc 27 /logout = logout 28 /** = user 29 </value> 30 </property> 31 </bean> 32 <!-- 认证filter --> 33 <bean id="authcFilter" class="com.itrip.rp.core.security.AdminAuthenticationFilter"> 34 <property name="adminLogin" value="/login"/> 35 <property name="adminIndex" value="/index"/> 36 </bean> 37 <!-- 用户检查filter --> 38 <bean id="userFilter" class="com.itrip.rp.core.security.AdminUserFilter"/> 39 <!-- 退出系统filter --> 40 <bean id="logoutFilter" class="com.itrip.rp.core.security.AdminLogoutFilter"> 41 <property name="logoutUrl" value="/login"/> 42 </bean> 43 <!-- 安全管理器 --> 44 <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"> 45 <property name="realm" ref="authorizingRealm" /> 46 <property name="sessionManager" ref="sessionManager"/> 47 <property name="cacheManager" ref="shiroEhcacheManager"/> 48 </bean> 49 <!-- 自定义登陆验证 --> 50 <bean id="authorizingRealm" class="com.itrip.rp.core.security.AdminAuthorizingRealm"> 51 <property name="credentialsMatcher"> 52 <bean class="org.apache.shiro.authc.credential.HashedCredentialsMatcher"> 53 <!-- 密码加密方式 --> 54 <property name="hashAlgorithmName" value="MD5"/> 55 <!-- true means hex encoded, false means base64 encoded --> 56 <property name="storedCredentialsHexEncoded" value="true"/> 57 <!-- 迭代次数 --> 58 <property name="hashIterations" value="1" /> 59 </bean> 60 </property> 61 </bean> 62 <!-- 缓存管理 --> 63 <bean id="shiroEhcacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager"> 64 <property name="cacheManagerConfigFile"> 65 <value>classpath:resources/ehcache-shiro.xml</value> 66 </property> 67 </bean> 68 <!-- 会话Cookie 180000--> 69 <bean id="sessionIdCookie" class="org.apache.shiro.web.servlet.SimpleCookie"> 70 <constructor-arg value="sid"/> 71 <property name="httpOnly" value="true"/> 72 <property name="maxAge" value="180000"/> 73 </bean> 74 <!-- 会话ID生成器 --> 75 <bean id="sessionIdGenerator" class="org.apache.shiro.session.mgt.eis.JavaUuidSessionIdGenerator"/> 76 <!-- 会话DAO --> 77 <bean id="sessionDAO" class="org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO"> 78 <property name="activeSessionsCacheName" value="shiro-activeSessionCache"/> 79 <property name="sessionIdGenerator" ref="sessionIdGenerator"/> 80 </bean> 81 <!-- 会话管理器 --> 82 <bean id="sessionManager" class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager"> 83 <property name="globalSessionTimeout" value="1800000"/> 84 <property name="deleteInvalidSessions" value="true"/> 85 <property name="sessionValidationSchedulerEnabled" value="true"/> 86 <property name="sessionDAO" ref="sessionDAO"/> 87 <property name="sessionIdCookieEnabled" value="true"/> 88 <property name="sessionIdCookie" ref="sessionIdCookie"/> 89 </bean> 90 <!-- Shiro生命周期处理器--> 91 <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor" /> 92 </beans>
shiro缓存配置文件:ehcache-shiro.xml
1 <?xml version="1.0" encoding="UTF-8"?> 2 <ehcache> 3 <diskStore path="java.io.tmpdir/rp-shiro-ehcache"/> 4 <defaultCache 5 maxElementsInMemory="10000" 6 eternal="false" 7 timeToIdleSeconds="120" 8 timeToLiveSeconds="120" 9 overflowToDisk="true" 10 diskSpoolBufferSizeMB="30" 11 maxElementsOnDisk="10000000" 12 diskPersistent="false" 13 diskExpiryThreadIntervalSeconds="120"/> 14 <cache name="shiro-activeSessionCache" 15 maxElementsInMemory="10000" 16 overflowToDisk="true" 17 eternal="true" 18 timeToLiveSeconds="0" 19 timeToIdleSeconds="0" 20 diskPersistent="true" 21 diskExpiryThreadIntervalSeconds="600"/> 22 23 <cache name="org.apache.shiro.realm.text.PropertiesRealm-0-accounts" 24 maxElementsInMemory="1000" 25 eternal="true" 26 overflowToDisk="true"/> 27 </ehcache>
1 /css/** = anon 2 /img/** = anon 3 /js/** = anon 4 /favicon.ico = anon
这里是对静态资源的处理,静态资源不做认证。
重要的是以下三个拦截器,分别实现了用户认证,用户检查及退出系统过程。
1 <property name="filters"> 2 <util:map> 3 <entry key="authc" value-ref="authcFilter" /> 4 <entry key="user" value-ref="userFilter" /> 5 <entry key="logout" value-ref="logoutFilter" /> 6 </util:map> 7 </property>
先看看用户认证authcFilter吧:
1 package com.itrip.rp.core.security; 2 3 import java.util.Date; 4 5 import javax.servlet.ServletRequest; 6 import javax.servlet.ServletResponse; 7 import javax.servlet.http.HttpServletRequest; 8 import javax.servlet.http.HttpServletResponse; 9 10 import org.apache.shiro.authc.AuthenticationToken; 11 import org.apache.shiro.subject.Subject; 12 import org.apache.shiro.web.filter.authc.FormAuthenticationFilter; 13 import org.apache.shiro.web.util.WebUtils; 14 import org.slf4j.Logger; 15 import org.slf4j.LoggerFactory; 16 17 import com.itrip.rp.common.Constants; 18 import com.itrip.rp.entity.beans.UserBaseInfo; 19 import com.itrip.rp.exception.AuthenticationException; 20 import com.itrip.rp.exception.DisabledException; 21 import com.itrip.rp.exception.UsernameNotFoundException; 22 import com.itrip.rp.service.AuthenticationService; 23 import com.itrip.rp.service.LogService; 24 import com.itrip.rp.service.UserService; 25 import com.itrip.rp.session.SessionProvider; 26 import com.itrip.rp.utils.DateFormatUtils; 27 import com.itrip.rp.utils.RequestUtils; 28 import com.itrip.rp.utils.SpringContextUtil; 29 30 /** 31 * 自定义登陆认证filter 32 * 33 * @author Benny 34 */ 35 public class AdminAuthenticationFilter extends FormAuthenticationFilter { 36 37 private Logger logger = LoggerFactory.getLogger("security"); 38 39 /** 40 * 执行登陆操作 41 */ 42 @Override 43 protected boolean executeLogin(ServletRequest request, ServletResponse response) { 44 AuthenticationToken token = createToken(request, response); 45 if (token == null) { 46 String msg = "create AuthenticationToken error"; 47 throw new IllegalStateException(msg); 48 } 49 String username = (String) token.getPrincipal(); 50 UserBaseInfo user = userService.login(username); 51 if (user != null) { 52 if (!user.getStatus()) { 53 // 用户禁用 54 return onLoginFailure(username, token, new DisabledException(), request, response); 55 } 56 } else { 57 // 用户名不存在 58 return onLoginFailure(username, token, new UsernameNotFoundException(), request, response); 59 } 60 try { 61 Subject subject = getSubject(request, response); 62 subject.login(token); 63 return onLoginSuccess(user, token, subject, request, response); 64 } catch (Exception e) { 65 // TODO Auto-generated catch block 66 return onLoginFailure(username, token, new AuthenticationException(), request, response); 67 } 68 } 69 70 /** 71 * 初始化service及登陆跳转 72 */ 73 @Override 74 public boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception { 75 if (userService == null) { 76 userService = (UserService) SpringContextUtil.getBean(UserService.class); 77 } 78 if (logService == null) { 79 logService = (LogService) SpringContextUtil.getBean(LogService.class); 80 } 81 if (authService == null) { 82 authService = (AuthenticationService) SpringContextUtil.getBean(AuthenticationService.class); 83 } 84 if (session == null) { 85 session = (SessionProvider) SpringContextUtil.getBean(SessionProvider.class); 86 } 87 boolean isAllowed = isAccessAllowed(request, response, mappedValue); 88 // 登陆跳转 89 if (isAllowed && isLoginRequest(request, response)) { 90 try { 91 issueSuccessRedirect(request, response); 92 } catch (Exception e) { 93 logger.error("", e); 94 } 95 return false; 96 } 97 return isAllowed || onAccessDenied(request, response, mappedValue); 98 } 99 100 @Override 101 protected void issueSuccessRedirect(ServletRequest request, ServletResponse response) throws Exception { 102 HttpServletRequest req = (HttpServletRequest) request; 103 HttpServletResponse res = (HttpServletResponse) response; 104 String successUrl = getAdminIndex() != null ? getAdminIndex() : super.getSuccessUrl(); 105 WebUtils.redirectToSavedRequest(req, res, successUrl); 106 } 107 108 @Override 109 protected boolean isLoginRequest(ServletRequest req, ServletResponse resp) { 110 String loginUrl = getAdminLogin() != null ? getAdminLogin() : super.getLoginUrl(); 111 return pathsMatch(loginUrl, req); 112 } 113 114 /** 115 * 登陆成功 116 */ 117 private boolean onLoginSuccess(UserBaseInfo user, AuthenticationToken token, Subject subject, ServletRequest request, ServletResponse response) 118 throws Exception { 119 HttpServletRequest req = (HttpServletRequest) request; 120 HttpServletResponse res = (HttpServletResponse) response; 121 // 记录用户登陆信息 122 authService.login(user, RequestUtils.getIpAddr(req), req, res, session); 123 // 将系统当前登陆用户信息放入session 124 session.setAttribute(req, Constants.USERNAME, user.getNickName()); 125 Date lastLogin = authService.findSecond(user.getUserId()); 126 if (lastLogin != null) { 127 session.setAttribute(req, Constants.LAST_LOGIN_TIME, DateFormatUtils.format(lastLogin, "yyyy-MM-dd HH:mm:ss")); 128 } 129 logService.loginSuccess(req, user.getUserId(), "login.log.loginSuccess"); 130 return super.onLoginSuccess(token, subject, request, response); 131 } 132 133 /** 134 * 登陆失败 135 */ 136 private boolean onLoginFailure(String username, AuthenticationToken token, AuthenticationException e, ServletRequest request, 137 ServletResponse response) { 138 HttpServletRequest req = (HttpServletRequest) request; 139 logService.loginFailure(req, "login.log.loginFailure", "userName=" + username); 140 request.setAttribute(Constants.MESSAGE, e.getMessage()); 141 return super.onLoginFailure(token, e, request, response); 142 } 143 144 private UserService userService; 145 private LogService logService; 146 private SessionProvider session; 147 private AuthenticationService authService; 148 149 private String adminIndex; 150 151 private String adminLogin; 152 153 public String getAdminIndex() { 154 return adminIndex; 155 } 156 157 public void setAdminIndex(String adminIndex) { 158 this.adminIndex = adminIndex; 159 } 160 161 public String getAdminLogin() { 162 return adminLogin; 163 } 164 165 public void setAdminLogin(String adminLogin) { 166 this.adminLogin = adminLogin; 167 } 168 }
需要说明的就是service的获取,在filter中获取spring自动注入bean需要通过spring上下文来获取,这里定义实现了获取spring上下文及注入bean的工具类SpringContextUtil.java稍后会详细说明这个类以及配置。
用户登录操作“/login”交由authcFilter处理,登录controller代码很简单:
1 package com.itrip.rp.controller; 2 3 import javax.servlet.http.HttpServletRequest; 4 import javax.servlet.http.HttpServletResponse; 5 6 import org.apache.commons.lang.StringUtils; 7 import org.slf4j.Logger; 8 import org.slf4j.LoggerFactory; 9 import org.springframework.stereotype.Controller; 10 import org.springframework.ui.ModelMap; 11 import org.springframework.web.bind.annotation.RequestMapping; 12 13 import com.itrip.rp.common.Constants; 14 import com.itrip.rp.controller.base.BaseController; 15 16 /** 17 * 系统登陆Controller 18 * 19 * @author Benny 20 * 21 */ 22 @Controller 23 public class LoginController extends BaseController { 24 25 protected static final Logger LOG = LoggerFactory.getLogger("run"); 26 27 /** 28 * 登陆 29 * 30 * @param request 31 * @param response 32 * @param model 33 * @return 34 */ 35 @RequestMapping(value = "/login") 36 public String login(HttpServletRequest request, HttpServletResponse response, ModelMap model) { 37 return "login"; 38 } 39 40 /** 41 * 系统首页 42 * 43 * @param message 44 * @param request 45 * @param response 46 * @param model 47 * @return 48 */ 49 @RequestMapping(value = "/index") 50 public String index(String message, HttpServletRequest request, HttpServletResponse response, ModelMap model) { 51 if (!StringUtils.isBlank(message)) { 52 model.addAttribute(Constants.MESSAGE, message); 53 } 54 return "product/index"; 55 } 56 }
登录页面代码如下,username、password不要写错了:
1 <!doctype html> 2 <body> 3 <form name="jvForm" action="${accessRoot}/login" method="post"> 4 <div class="loginbox round15"> 5 <dl> 6 <dd class="fz24 pt30">User login</dd> 7 <dd> 8 <input type="text" placeholder="Login name" autocomplete="off" name="username"> 9 </dd> 10 <dd> 11 <input type="password" placeholder="Password" autocomplete="off" name="password" title="click enter login"> 12 </dd> 13 <dd> 14 <a href="javascript:document.jvForm.submit();" class="login-bt round3" id="btnLogin">Login</a> 15 </dd> 16 <font color="red" id="errTip">${message!}</font> 17 </dl> 18 </div> 19 </form> 20 </body> 21 </html>
用户认证检查userFilter:
1 package com.itrip.rp.core.security; 2 3 import java.io.IOException; 4 5 import javax.servlet.ServletRequest; 6 import javax.servlet.ServletResponse; 7 import javax.servlet.http.HttpServletRequest; 8 import javax.servlet.http.HttpServletResponse; 9 10 import org.apache.shiro.web.filter.authc.UserFilter; 11 import org.apache.shiro.web.util.WebUtils; 12 13 /** 14 * 用户认证检查filter 15 * 16 * @author Benny 17 */ 18 public class AdminUserFilter extends UserFilter { 19 // 未登陆重定向到登陆页 20 protected void redirectToLogin(ServletRequest req, ServletResponse resp) throws IOException { 21 HttpServletRequest request = (HttpServletRequest) req; 22 HttpServletResponse response = (HttpServletResponse) resp; 23 WebUtils.issueRedirect(request, response, getLoginUrl()); 24 } 25 }
最后是退出系统logoutFilter:
1 package com.itrip.rp.core.security; 2 3 import javax.servlet.ServletRequest; 4 import javax.servlet.ServletResponse; 5 import javax.servlet.http.HttpServletRequest; 6 7 import org.apache.commons.lang.StringUtils; 8 import org.apache.shiro.subject.Subject; 9 import org.apache.shiro.web.filter.authc.LogoutFilter; 10 11 import com.itrip.rp.common.Constants; 12 13 /** 14 * 退出系统 filter 15 * 16 * @author Benny 17 */ 18 public class AdminLogoutFilter extends LogoutFilter { 19 20 @Override 21 protected String getRedirectUrl(ServletRequest req, ServletResponse resp, Subject subject) { 22 HttpServletRequest request = (HttpServletRequest) req; 23 String redirectUrl = request.getParameter(Constants.RETURN_URL); 24 if (StringUtils.isBlank(redirectUrl)) { 25 redirectUrl = getLogoutUrl(); 26 if (StringUtils.isBlank(redirectUrl)) { 27 redirectUrl = getRedirectUrl(); 28 } 29 } 30 return redirectUrl; 31 } 32 33 private String logoutUrl; 34 35 public void setLogoutUrl(String logoutUrl) { 36 this.logoutUrl = logoutUrl; 37 } 38 39 public String getLogoutUrl() { 40 return logoutUrl; 41 } 42 }
接下来让我们看看自定义实现的登录认证及授权Realm吧:
1 package com.itrip.rp.core.security; 2 3 import java.util.HashSet; 4 import java.util.List; 5 import java.util.Set; 6 7 import org.apache.shiro.authc.AuthenticationException; 8 import org.apache.shiro.authc.AuthenticationInfo; 9 import org.apache.shiro.authc.AuthenticationToken; 10 import org.apache.shiro.authc.SimpleAuthenticationInfo; 11 import org.apache.shiro.authc.UsernamePasswordToken; 12 import org.apache.shiro.authz.AuthorizationInfo; 13 import org.apache.shiro.authz.SimpleAuthorizationInfo; 14 import org.apache.shiro.realm.AuthorizingRealm; 15 import org.apache.shiro.subject.PrincipalCollection; 16 import org.apache.shiro.subject.SimplePrincipalCollection; 17 import org.apache.shiro.util.CollectionUtils; 18 19 import com.itrip.rp.entity.beans.UserBaseInfo; 20 import com.itrip.rp.service.UserService; 21 import com.itrip.rp.utils.SpringContextUtil; 22 23 /** 24 * 认证及授权Realm 25 * 26 * @author Benny 27 */ 28 public class AdminAuthorizingRealm extends AuthorizingRealm { 29 30 /** 31 * 登陆认证 32 */ 33 @Override 34 protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authcToken) throws AuthenticationException { 35 UsernamePasswordToken token = (UsernamePasswordToken) authcToken; 36 if (userService == null) { 37 userService = (UserService) SpringContextUtil.getBean(UserService.class); 38 } 39 UserBaseInfo user = userService.login(token.getUsername()); 40 if (user != null) { 41 return new SimpleAuthenticationInfo(user, user.getPassword(), getName()); 42 } else { 43 return null; 44 } 45 } 46 47 /** 48 * 授权 49 */ 50 @Override 51 protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { 52 // TODO Auto-generated method stub 53 UserBaseInfo user = (UserBaseInfo) principals.getPrimaryPrincipal(); 54 SimpleAuthorizationInfo auth = new SimpleAuthorizationInfo(); 55 if (user != null) { 56 if (userService == null) { 57 userService = (UserService) SpringContextUtil.getBean(UserService.class); 58 } 59 List<String> perms = userService.getPerms(user.getUserId()); 60 Set<String> set = new HashSet<String>(perms); 61 if (!CollectionUtils.isEmpty(perms)) { 62 // 权限加入AuthorizationInfo认证对象 63 auth.setStringPermissions(set); 64 } 65 } 66 return auth; 67 } 68 69 /** 70 * 清空用户权限缓存 71 * 72 * @param username 73 */ 74 public void removeUserAuthorizationInfoCache(String username) { 75 SimplePrincipalCollection pc = new SimplePrincipalCollection(); 76 pc.add(username, super.getName()); 77 super.clearCachedAuthorizationInfo(pc); 78 } 79 80 private UserService userService; 81 }
登录认证成功后将用户对象放入AuthenticationInfo中,以便授权过程中直接使用用户对象。
授权操作只有在第一次进行权限验证的时候才会初始化(比较重要),将用户所拥有的所有权限放入AuthorizationInfo对象。
当用户权限发生变化,就需要手动调用removeUserAuthorizationInfoCache方法去清除用户权限缓存。
最后再看看springmvc配置文件:
1 <beans xmlns="http://www.springframework.org/schema/beans" 2 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 3 xmlns:mvc="http://www.springframework.org/schema/mvc" 4 xmlns:aop="http://www.springframework.org/schema/aop" 5 xmlns:jdbc="http://www.springframework.org/schema/jdbc" 6 xmlns:context="http://www.springframework.org/schema/context" 7 xmlns:tx="http://www.springframework.org/schema/tx" 8 xsi:schemaLocation="http://www.springframework.org/schema/beans 9 http://www.springframework.org/schema/beans/spring-beans-3.0.xsd 10 http://www.springframework.org/schema/context 11 http://www.springframework.org/schema/context/spring-context-3.0.xsd 12 http://www.springframework.org/schema/tx 13 http://www.springframework.org/schema/tx/spring-tx-3.0.xsd 14 http://www.springframework.org/schema/jdbc 15 http://www.springframework.org/schema/jdbc/spring-jdbc-3.0.xsd 16 http://www.springframework.org/schema/aop 17 http://www.springframework.org/schema/aop/spring-aop-3.0.xsd 18 http://www.springframework.org/schema/mvc 19 http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd"> 20 21 <!-- 自动依赖注入 --> 22 <context:component-scan base-package="com.itrip.rp" /> 23 24 <!-- 文件上传解析器 id 必须为multipartResolver --> 25 <bean id="multipartResolver" 26 class="org.springframework.web.multipart.commons.CommonsMultipartResolver"> 27 <property name="maxUploadSize" value="10485760" /> 28 </bean> 29 30 <!-- 没有自定义实现拦截器的时候必须声明spring默认配置 --> 31 <!-- <mvc:annotation-driven/> --> 32 <bean class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping"> 33 <property name="interceptors"> 34 <list> 35 <ref bean="adminContextInterceptor"/> 36 </list> 37 </property> 38 </bean> 39 <bean id="adminContextInterceptor" class="com.itrip.rp.interceptor.AdminContextInterceptor"> 40 <property name="excludeUrls"> 41 <list> 42 <value>/login</value> 43 <value>/logout</value> 44 </list> 45 </property> 46 </bean> 47 48 <!-- 静态资源 --> 49 <mvc:resources location="/img/" mapping="/img/**" /> 50 <mvc:resources location="/js/" mapping="/js/**" /> 51 <mvc:resources location="/css/" mapping="/css/**" /> 52 53 <!-- @responsebody标签返回对象格式配置 --> 54 <bean 55 class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter"> 56 <!-- 配置信息转换,将用@responsebody注解的返回值转换为json返回前台,编码为utf-8 --> 57 <property name="messageConverters"> 58 <list> 59 <bean 60 class="org.springframework.http.converter.StringHttpMessageConverter"> 61 <property name="supportedMediaTypes"> 62 <list> 63 <value>text/html;charset=UTF-8</value> 64 </list> 65 </property> 66 </bean> 67 <bean class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter"> 68 <property name="supportedMediaTypes"> 69 <list> 70 <value>application/json;charset=UTF-8</value> 71 </list> 72 </property> 73 </bean> 74 </list> 75 </property> 76 </bean> 77 <!-- 异常处理 --> 78 <bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver"> 79 <property name="exceptionMappings"> 80 <props> 81 <prop key="java.lang.Exception">error/404</prop> 82 <prop key="java.lang.Throwable">error/404</prop> 83 </props> 84 </property> 85 <property name="warnLogCategory" value="WARN" /> 86 <property name="defaultErrorView" value="error/404" /> 87 </bean> 88 89 <!-- 默认视图配置welcome页 --> 90 <mvc:view-controller path="/" view-name="product/index"/> 91 92 <!-- freemarker视图解析器配置 --> 93 <bean id="freemarkerViewResolver" class="org.springframework.web.servlet.view.freemarker.FreeMarkerViewResolver"> 94 <property name="viewClass" value="org.springframework.web.servlet.view.freemarker.FreeMarkerView"/> 95 <!-- 视图名后缀 --> 96 <property name="suffix" value=".html" /> 97 <property name="contentType" value="text/html; charset=UTF-8" /> 98 <!-- request/session==true请求和会话属性都被复制到模板的属性集中,此时spring必须设置为true --> 99 <property name="exposeRequestAttributes" value="false" /> 100 <property name="exposeSessionAttributes" value="false" /> 101 <property name="exposeSpringMacroHelpers" value="true" /> 102 </bean> 103 <bean id="freemarkerConfig" 104 class="org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer"> 105 <!-- 模板路径 --> 106 <property name="templateLoaderPath" value="/view/" /> 107 <property name="freemarkerVariables"> 108 <map> 109 <!--后台管理权限控制 --> 110 <entry key="perm" value-ref="perm" /> 111 </map> 112 </property> 113 <property name="freemarkerSettings"> 114 <props> 115 <prop key="template_update_delay">0</prop> 116 <prop key="defaultEncoding">UTF-8</prop> 117 <prop key="url_escaping_charset">UTF-8</prop> 118 <prop key="locale">zh_CN</prop> 119 <prop key="boolean_format">true,false</prop> 120 <prop key="datetime_format">yyyy-MM-dd HH:mm:ss</prop> 121 <prop key="date_format">yyyy-MM-dd</prop> 122 <prop key="time_format">HH:mm:ss</prop> 123 <prop key="number_format">0.######</prop> 124 <prop key="whitespace_stripping">true</prop> 125 </props> 126 </property> 127 </bean> 128 <!-- session持有者 --> 129 <bean id="sessionProvider" class="com.itrip.rp.session.HttpSessionProvider" /> 130 <!-- spring上下文工具类 --> 131 <bean id="springContextUtil " class="com.itrip.rp.utils.SpringContextUtil" /> 132 <!-- 数据库配置 --> 133 <import resource="classpath:resources/database-context.xml"/> 134 </beans>
这里要重点说明的就是之前提到过的spring上下文工具类:SpringContextUtil.java
1 package com.itrip.rp.utils; 2 3 import org.springframework.beans.BeansException; 4 import org.springframework.beans.factory.NoSuchBeanDefinitionException; 5 import org.springframework.context.ApplicationContext; 6 import org.springframework.context.ApplicationContextAware; 7 8 /** 9 * spring上下文工具类 10 * 11 * @author Benny 12 * 13 */ 14 public class SpringContextUtil implements ApplicationContextAware { 15 16 private static ApplicationContext applicationContext; // Spring应用上下文环境 17 18 public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { 19 SpringContextUtil.applicationContext = applicationContext; 20 } 21 22 public static ApplicationContext getApplicationContext() { 23 return applicationContext; 24 } 25 26 public static Object getBean(String name) throws BeansException { 27 return applicationContext.getBean(name); 28 } 29 30 public static Object getBean(Class<?> requiredType) throws BeansException { 31 return applicationContext.getBean(requiredType); 32 } 33 34 public static Object getBean(String name, Class<?> requiredType) throws BeansException { 35 return applicationContext.getBean(name, requiredType); 36 } 37 38 public static boolean containsBean(String name) { 39 return applicationContext.containsBean(name); 40 } 41 42 public static boolean isSingleton(String name) throws NoSuchBeanDefinitionException { 43 return applicationContext.isSingleton(name); 44 } 45 46 public static Class<?> getType(String name) throws NoSuchBeanDefinitionException { 47 return applicationContext.getType(name); 48 } 49 50 public static String[] getAliases(String name) throws NoSuchBeanDefinitionException { 51 return applicationContext.getAliases(name); 52 } 53 }
至此,springmvc+shiro安全管理+freemarker标签权限验证就完成了。
基于URL权限验证的方式也非常简单,只需要在自定义拦截器中对所有url进行权限校验即可,同样也是使用shiro权限校验机制,跟freemarker标签式的权限校验一致,看看拦截器源代码:
1 package com.itrip.rp.interceptor; 2 3 import javax.servlet.http.HttpServletRequest; 4 import javax.servlet.http.HttpServletResponse; 5 6 import org.apache.shiro.SecurityUtils; 7 import org.apache.shiro.subject.Subject; 8 import org.springframework.web.servlet.handler.HandlerInterceptorAdapter; 9 import org.springframework.web.util.UrlPathHelper; 10 11 /** 12 * URI拦截器 用户权限验证 13 * 14 * @author Benny 15 */ 16 public class AdminContextInterceptor extends HandlerInterceptorAdapter { 17 18 @Override 19 public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { 20 // 获取请求链接 21 String uri = getURI(request); 22 // 排除例外URI,例如:登陆、退出 23 if (exclude(uri)) { 24 return true; 25 } 26 Subject subject = SecurityUtils.getSubject(); 27 boolean pass = subject.isPermitted(uri); 28 if (pass) { 29 return true; 30 } else { 31 // 跳转至异常处理 32 throw new Exception(); 33 } 34 } 35 36 /** 37 * 判断是否例外uri 38 * 39 * @param uri 40 * @return 41 */ 42 private boolean exclude(String uri) { 43 if (excludeUrls != null) { 44 for (String exc : excludeUrls) { 45 // 允许以excludeurl结尾的请求 46 if (uri.endsWith(exc)) { 47 return true; 48 } 49 } 50 } 51 return false; 52 } 53 54 /** 55 * 获取请求URL 56 * 57 * @param request 58 * @author Benny 59 * @return 60 */ 61 private static String getURI(HttpServletRequest request) { 62 UrlPathHelper helper = new UrlPathHelper(); 63 return helper.getOriginatingRequestUri(request); 64 } 65 66 private String[] excludeUrls; 67 68 public void setExcludeUrls(String[] excludeUrls) { 69 this.excludeUrls = excludeUrls; 70 } 71 }
以上实现了shiro安全管理+freemarker标签式的权限控制+系统全局url权限控制,基本满足大部分web项目的权限管理。
到此结束!
使用的jar包以及版本在此说明一下:
shiro相关jar包:
1 <!-- shiro配置start --> 2 <dependency> 3 <groupId>org.apache.shiro</groupId> 4 <artifactId>shiro-web</artifactId> 5 <version>1.2.2</version> 6 </dependency> 7 8 <dependency> 9 <groupId>org.apache.shiro</groupId> 10 <artifactId>shiro-ehcache</artifactId> 11 <version>1.2.2</version> 12 </dependency> 13 14 <dependency> 15 <groupId>org.apache.shiro</groupId> 16 <artifactId>shiro-quartz</artifactId> 17 <version>1.2.2</version> 18 </dependency> 19 <dependency> 20 <groupId>org.apache.shiro</groupId> 21 <artifactId>shiro-spring</artifactId> 22 <version>1.2.2</version> 23 </dependency> 24 <!-- shiro配置end -->
spring使用版本为3.0.5