• springmvc+shiro+freemarker实现的安全及权限管理


    本文讲述了基于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

  • 相关阅读:
    STM32 IIC双机通信—— HAL库硬件IIC版
    利用 ST-LINK Utility软件下载程序
    STM32CubeMx的使用分享
    STM32 GPIO重映射(转)
    IIC 原理讲解
    STM32 软件模拟 IIC 代码,标准库、HAL库可用
    STM32 抢占优先级和响应优先级
    浅谈C中的malloc和free
    C语言-cout<<"123"<<"45"<<endl;
    VC6-Win7下VC++6.0打开多个工程的设置
  • 原文地址:https://www.cnblogs.com/jpfss/p/8310152.html
Copyright © 2020-2023  润新知