一、引子
最近项目需要添加权限拦截,经讨论决定采用spring security4.2.2!废话少说直接上干货!
spring security 4.2.2文档:http://docs.spring.io/spring-security/site/docs/4.2.2.RELEASE/reference/htmlsingle/#el-access-web
spring security 3 中文2文档:http://www.mossle.com/docs/auth/html/index.html
二、pom.xml配置
需要在pom.xml里配置spring security的依赖,可以通过http://mvnrepository.com/search?q=spring+securitye查询不同版本需要的spring的版本支持。
1 <!--spring security --> 2 <dependency> 3 <groupId>org.springframework.security</groupId> 4 <artifactId>spring-security-core</artifactId> 5 <version>4.2.2.RELEASE</version> 6 </dependency> 7 8 <dependency> 9 <groupId>org.springframework.security</groupId> 10 <artifactId>spring-security-web</artifactId> 11 <version>4.2.2.RELEASE</version> 12 </dependency> 13 14 <dependency> 15 <groupId>org.springframework.security</groupId> 16 <artifactId>spring-security-config</artifactId> 17 <version>4.2.2.RELEASE</version> 18 </dependency> 19 <dependency> 20 <groupId>org.springframework.security</groupId> 21 <artifactId>spring-security-taglibs</artifactId> 22 <version>4.2.2.RELEASE</version> 23 </dependency>
三、web.xml配置
需要在web.xml中配置Spring Security控制权限过滤器,
1 <filter> 2 <filter-name>springSecurityFilterChain</filter-name> 3 <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> 4 </filter> 5 <filter-mapping> 6 <filter-name>springSecurityFilterChain</filter-name> 7 <url-pattern>/*</url-pattern> 8 </filter-mapping>
四、spring-security.xml配置
下面最主要的就是spring-security.xml的配置了
1 <?xml version="1.0" encoding="UTF-8"?> 2 <beans:beans xmlns:beans="http://www.springframework.org/schema/beans" 3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 4 xmlns="http://www.springframework.org/schema/security" 5 xsi:schemaLocation="http://www.springframework.org/schema/beans 6 http://www.springframework.org/schema/beans/spring-beans.xsd 7 http://www.springframework.org/schema/security 8 http://www.springframework.org/schema/security/spring-security.xsd" > 9 10 <!-- 打印调试信息,仅在开发环境中使用 --> 11 <!-- <debug/> --> 12 13 <!-- 不需要被拦截的请求 --> 14 <http pattern="/loginPage" security="none"/> 15 <http pattern="/scripts/**" security="none"/> 16 17 <!-- 18 登录页面可以使用第一种: 19 <http pattern="/login.jsp" security="none"></http> 20 <form-login login-page="/login.jsp" authentication-failure-url="/login.jsp?error=true" ...... 21 这种方式直接访问.jsp 22 也可以使用Controller来控制,两种方式登录页面login.jsp的位置不一样!!! 23 下面是第二种方式: 24 <http pattern="/login" security="none"></http> 25 <form-login login-page="/login" login-processing-url="/login" ...... 26 第一个配置告诉spring security,类似于/login的url请求不做过滤处理,而第二个配置信息又告诉spring security url为/login的post请求登录请求处理。正是这种冲突导致了405错误的发生。 27 既然知道了错误原因,那么只要避免冲突就能解决这个问题:使登录页的请求和登录处理的请求不一致,然后只配置登录页的请求不做拦截处理. 28 我采用的方法是使用默认的登录URL /login,修改登录页面跳转url为/loginPage。请自行修改代码和配置信息 29 这样是不是就大工告成了呢?很遗憾,当你启动程序时,输出用户名和密码,不管正确与否,都会有404 Not Found等着你,这又是为什么呢? 30 登录请求404错误 31 首先,建议你看一下spring security自动生成的登录页源码,你会发现有如下代码 32 <input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}" /> 33 对于什么是csrf,请自行参考网上的资料。 34 spring security默认情况下csrf protection是开启的,由于我们的登录页没有配置csrf的相关信息,因此spring security内置的过滤器将此链接置为无效链接 35 解决办法就是配置csrf protection为不可用状态,在配置文件中增加 36 <csrf disabled="true"/> 37 --> 38 39 <!-- 40 1、<http auto-config="true">,他可以自动配置login form,BSIC 认证和logout URL 和logout services,如果没有特殊表明,这个的默认值是false。想要自己配置则设置为"true" 41 2、Spring Security采用的是一种就近原则,就是说当用户访问的url资源满足多个intercepter-url时,系统将使用第一个符合条件的intercept-url进行权限控制 42 --> 43 <http auto-config="true" use-expressions="true"> 44 <!-- 45 另一种权限表达式: 46 <http use-expressions="false"> 47 <intercept-url pattern='/**' access='ROLE_USER' /> 48 --> 49 <!-- 禁用CSRF保护,默认是启用 --> 50 <csrf disabled="true"/> 51 52 <anonymous enabled="false"/> 53 54 <!-- 基于角色认证(必须拥有ROLE_XXX角色才能访问所有/**/XXX/**资源) --> 55 <!-- 确保对功能URL访问都需要权限 --> 56 <intercept-url pattern="/**/add/**" access="hasRole('ADD')"/> 57 <intercept-url pattern="/**/update/**" access="hasRole('UPDATE')"/> 58 <intercept-url pattern="/**/delete/**" access="hasRole('DELETE')"/> 59 <intercept-url pattern="/**/download/**" access="hasRole('DOWNLOAD')"/> 60 <intercept-url pattern="/**/access/**" access="hasRole('ADMIN')"/> 61 62 <!-- Ensures that any request to our application requires the user to be authenticated --> 63 <intercept-url pattern="/**" access="authenticated"/> 64 65 66 <!-- 67 实现免登陆验证,默认有效时间是两周,启用rememberMe之后的两周内,用户都可以直接跳过系统,直接进入系统。 68 实际上,Spring Security中的rememberMe是依赖cookie实现的,当用户在登录时选择使用rememberMe,系统就会在登录成功后将为用户生成一个唯一标识,并将这个标识保存进cookie中 69 Spring Security生成的cookie名称是SPRING_SECURITY_REMEMBER_ME_COOKIE,它的内容是一串加密的字符串, 70 当用户再次访问系统时,Spring Security将从这个cookie读取用户信息,并加以验证。如果可以证实cookie有效,就会自动将用户登录到系统中,并为用户授予对应的权限。 71 --> 72 73 <!-- 74 <remember-me remember-me-parameter="remember-me" 75 data-source-ref="dataSource"/> 76 spring security还提供了remember me的另一种相对更安全的实现机制 :在客户端的cookie中,仅保存一个无意义的加密串(与用户名、密码等敏感数据无关),然后在db中保存该加密串-用户信息的对应关系,自动登录时,用cookie中的加密串,到db中验证,如果通过,自动登录才算通过。会自动在你的数据库里创建一个表:PERSISTENT_LOGINS。 77 如果不加data-source-ref="dataSource",会将上述信息放在内存中! 78 作者的项目有些特殊要求,所有采用了下面的方式:登录后要在myAuthenticationSuccessHandler做特殊的处理! 79 --> 80 <remember-me authentication-success-handler-ref="myAuthenticationSuccessHandler"/> 81 82 <!-- 83 login-page : 表示用户登陆时显示我们自定义的登录页面 84 authentication-failure-url : 登录认证失败转向的url,当用户输入的登录名和密码不正确时,系统将再次跳转到登录页面,并添加一个error=true参数作为登陆失败的标示,这个标识是我们自定义的。 85 default-target-url : 登录认证成功转向的地址 86 --> 87 <form-login 88 login-page="/loginPage" 89 authentication-failure-url="/loginPage?error=true" 90 authentication-success-handler-ref="myAuthenticationSuccessHandler" 91 /> 92 93 <!-- 登出后,返回到登陆页面 --> 94 <!-- <logout logout-success-url="/loginPage" logout-url="/logout"/> 95 delete-cookies="JSESSIONID":退出删除JSESSIONID 96 --> 97 <logout /> 98 <!-- 99 控制同步session的过滤器 100 如果concurrency-control标签配置了error-if-maximum-exceeded="true",max-sessions="1",那么第二次登录时,是登录不了的。 101 如果error-if-maximum-exceeded="false",那么第二次是能够登录到系统的, 102 但是第一个登录的账号再次发起请求时,会跳转到expired-url配置的url中, 103 如果没有配置expired-url,则显示: 104 This session has been expired (possibly due to multiple concurrent logins being attempted as the same user). 105 翻译过来意思就是说:这个会话已经过期(可能由于多个并发登录尝试相同的用户) 106 --> 107 108 <session-management invalid-session-url="/loginPage" > 109 <concurrency-control max-sessions="1" error-if-maximum-exceeded="false" expired-url="/loginPage"/> 110 </session-management> 111 112 <!-- 指定自己的权限验证过滤器,首先走自己的的过滤器 myFilter,如果被拦截就报没有权限; 113 如果通过会走spring security自带的拦截器,即上面配置的权限配置! 114 --> 115 <custom-filter before="FILTER_SECURITY_INTERCEPTOR" ref="myFilter"/> 116 </http> 117 118 <!-- 权限认证Spring日志监听器 --> 119 <beans:bean class="org.springframework.security.authentication.event.LoggerListener"/> 120 <beans:bean class="org.springframework.security.access.event.LoggerListener"/> 121 122 <!-- 123 一个自定义的filter,必须包含authenticationManager,accessDecisionManager,securityMetadataSource三个属性, 124 我们的所有控制将在这三个类中实现,解释详见具体配置。 125 --> 126 <beans:bean id="myFilter" class="com.tcbd.common.interceptor.MyFilterSecurityInterceptor" > 127 <beans:property name="authenticationManager" ref="authenticationManager" /> 128 <beans:property name="accessDecisionManager" ref="myAccessDecisionManagerBean" /> 129 <beans:property name="securityMetadataSource" ref="securityMetadataSource" /> 130 </beans:bean> 131 132 <!-- 验证配置,实现用户认证的入口,主要实现UserDetailsService接口即可 --> 133 <authentication-manager alias="authenticationManager" > 134 <authentication-provider ref="daoAuthenticationProvider" > 135 </authentication-provider> 136 </authentication-manager> 137 138 <beans:bean id="daoAuthenticationProvider" class="org.springframework.security.authentication.dao.DaoAuthenticationProvider"> 139 <beans:property name="userDetailsService" ref="myUserDetailService" /> 140 <beans:property name="passwordEncoder" ref="passwordEncoder" /> 141 </beans:bean> 142 <!-- spring推荐的单向加密算法 --> 143 <beans:bean id="passwordEncoder" class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder"/> 144 145 <!-- 在这个类中,读入用户的密码,角色信息,是否锁定,账号是否过期等属性信息 --> 146 <beans:bean id="myUserDetailService" class="com.tcbd.common.interceptor.MyUserDetailService" /> 147 148 149 <!-- 访问决策器,决定某个用户具有的角色,是否有足够的权限去访问某个资源 --> 150 <beans:bean id="myAccessDecisionManagerBean" class="com.tcbd.common.interceptor.MyAccessDecisionManager" ></beans:bean> 151 152 <!-- 资源源数据定义,即定义某一资源可以被哪些角色访问 --> 153 <beans:bean id="securityMetadataSource" class="com.tcbd.common.interceptor.MyFilterSecurityMetadataSource" /> 154 155 <beans:bean id="myAuthenticationSuccessHandler" class="com.tcbd.common.interceptor.MyAuthenticationSuccessHandler"/> 156 157 </beans:beans>
spring security为我们提供了三种通配符。
通配符:?
示例:/admin/g?t.jsp
匹配任意一个字符,/admin/g?t.jsp可以匹配/admin/get.jsp和/admin/got.jsp或是/admin/gxt.do。不能匹配/admin/xxx.jsp。
通配符:*
示例:/admin/*.jsp
匹配任意多个字符,但不能跨越目录。/*/index.jsp可以匹配/admin/index.jsp和/user/index.jsp,但是不能匹配/index.jsp和/user/test/index.jsp。
通配符:**
示例:/**/index.jsp
可以匹配任意多个字符,可以跨越目录,可以匹配/index.jsp,/admin/index.jsp,/user/admin/index.jsp和/a/b/c/d/index.jsp
五、自己的拦截器
1,读入用户的密码,角色信息,是否锁定,账号是否过期等属性信息 :MyUserDetailService
1 import java.util.ArrayList; 2 import java.util.Collection; 3 4 import javax.annotation.Resource; 5 6 import org.apache.commons.lang.StringUtils; 7 import org.apache.log4j.Logger; 8 import org.springframework.dao.DataAccessException; 9 import org.springframework.security.core.GrantedAuthority; 10 import org.springframework.security.core.authority.SimpleGrantedAuthority; 11 import org.springframework.security.core.userdetails.User; 12 import org.springframework.security.core.userdetails.UserDetails; 13 import org.springframework.security.core.userdetails.UserDetailsService; 14 import org.springframework.security.core.userdetails.UsernameNotFoundException; 15 16 /** 17 * 从数据库中读入用户的密码,角色信息,是否锁定,账号是否过期等 18 * 19 */ 20 public class MyUserDetailService implements UserDetailsService { 21 @Resource 22 private UserService userService; 23 24 /** 25 * 数据库交互获取用户拥有的权限角色,并设置权限 26 */ 27 @Override 28 public UserDetails loadUserByUsername(String userCode) throws UsernameNotFoundException, DataAccessException { 29 // 根据登录用户名获取用户信息 30 Users user = new Users(); 31 user.setUserCode(username); 32 user = userService.selectByModel(user); 33 if (null != user) { 34 // 存放权限 35 Collection<GrantedAuthority> auths = new ArrayList<GrantedAuthority>(); 36 String action = user.getRole(); 37 if (StringUtils.isNotBlank(action)) { 38 String[] roleaCtion = action.split(","); 39 for (int i = 0; i < roleaCtion.length; i++) { 40 SimpleGrantedAuthority auth = new SimpleGrantedAuthority(roleaCtion[i]); 41 auths.add(auth); 42 } 43 } 44 //spring security自带的User对象 45 User userDetails = new User(username, user.getPassword(), true, true, true, true, auths); 46 return userDetails; 47 } 48 return null; 49 } 50 }
2,定义某一资源可以被哪些角色访问:MyFilterSecurityMetadataSource
1 import java.util.ArrayList; 2 import java.util.Collection; 3 import java.util.List; 4 import java.util.Map; 5 6 import javax.servlet.http.HttpServletRequest; 7 8 import org.apache.log4j.Logger; 9 import org.springframework.security.access.ConfigAttribute; 10 import org.springframework.security.access.SecurityConfig; 11 import org.springframework.security.web.FilterInvocation; 12 import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource; 13 14 public class MyFilterSecurityMetadataSource implements FilterInvocationSecurityMetadataSource { 15 private static Logger logger = Logger.getLogger(MyFilterSecurityMetadataSource.class); 16 17 public List<ConfigAttribute> getAttributes(Object object) { 18 FilterInvocation fi = (FilterInvocation) object; 19 HttpServletRequest request = fi.getRequest(); 20 String requestUrl = fi.getRequest().getRequestURI(); 21 List<ConfigAttribute> attributes = new ArrayList<ConfigAttribute>(); 22 // 所有URL对应的角色,应用启动就存放到静态资源里,得到的结果是:不同的URL下,包含的多个角色 23 Map<String, String> resRoles = Constant.URL_ROLES; 24 25 for (Map.Entry<String, String> ent : resRoles.entrySet()) { 26 String url = ent.getKey(); 27 String roles = ent.getValue(); 28 //根据业务写自己的匹配逻辑 29 if(requestUrl.startsWith(url)){ 30 attributes.addAll(SecurityConfig.createListFromCommaDelimitedString(roles)); 31 } 32 } 33 logger.debug("【"+request.getRequestURI()+"】 roles: "+attributes); 34 return attributes; 35 } 36 37 38 public Collection<ConfigAttribute> getAllConfigAttributes() { 39 return null; 40 } 41 42 public boolean supports(Class<?> clazz) { 43 return FilterInvocation.class.isAssignableFrom(clazz); 44 } 45 }
3,访问决策器,决定某个用户具有的角色,是否有足够的权限去访问某个资源:MyAccessDecisionManager
1 import java.util.Collection; 2 import java.util.Iterator; 3 4 import org.apache.commons.logging.Log; 5 import org.apache.commons.logging.LogFactory; 6 import org.springframework.security.access.AccessDecisionManager; 7 import org.springframework.security.access.AccessDeniedException; 8 import org.springframework.security.access.ConfigAttribute; 9 import org.springframework.security.access.SecurityConfig; 10 import org.springframework.security.authentication.InsufficientAuthenticationException; 11 import org.springframework.security.core.Authentication; 12 import org.springframework.security.core.GrantedAuthority; 13 14 /** 15 * 在这种方法中,需要与configAttributes比较验证 16 * 1、一个对象是一个URL,一个过滤器被这个URL找到权限配置,并通过这里 17 * 2、如果没有匹配相应的认证,AccessDeniedException 18 * 19 */ 20 public class MyAccessDecisionManager implements AccessDecisionManager { 21 22 private static final Log logger = LogFactory.getLog(MyAccessDecisionManager.class); 23 24 /** 25 * 在这个类中,最重要的是decide方法,如果不存在对该资源的定义,直接放行; 否则,如果找到正确的角色,即认为拥有权限,并放行,否则throw 26 * new AccessDeniedException("no right");这样,就会进入上面提到的/accessDenied.jsp页面。 27 * @param authentication :当前用户所且有的角色 28 * @param object :当前请求的URL 29 * @param configAttributes :当前URL所且有的角色 30 */ 31 @Override 32 public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes) 33 throws AccessDeniedException, InsufficientAuthenticationException { 34 // 资源所需的角色列表,如果角色列表为空,则放行!继续下一个拦截器。 35 if (configAttributes == null) { 36 return; 37 } 38 // 即将访问的资源URL,如 : /admin.jsp 39 logger.info("URL :"+object); 40 // 遍历所需的角色集合 41 Iterator<ConfigAttribute> ite = configAttributes.iterator(); 42 while (ite.hasNext()) { 43 ConfigAttribute ca = ite.next(); 44 // 该资源所需要的角色 45 String needRole = ((SecurityConfig) ca).getAttribute(); 46 // authentication.getAuthorities()获取用户所拥有的角色列表,如:OLE_DEFULT 47 for (GrantedAuthority grantedAuthority : authentication.getAuthorities()) { 48 // 将资源所需要的角色与用户拥有的角色比较 49 if (needRole.equals(grantedAuthority.getAuthority())) { 50 // grantedAuthority is user's role. 51 // 角色相同,直接放行 52 return; 53 } 54 } 55 } 56 // 否则,提示没有权限访问该资源 57 throw new AccessDeniedException("no right"); 58 } 59 60 @Override 61 public boolean supports(ConfigAttribute attribute) { 62 return true; 63 } 64 65 @Override 66 public boolean supports(Class<?> clazz) { 67 return true; 68 } 69 70 }
4,拦截器核心,MyFilterSecurityInterceptor
1 import java.io.IOException; 2 3 import javax.servlet.Filter; 4 import javax.servlet.FilterChain; 5 import javax.servlet.FilterConfig; 6 import javax.servlet.ServletException; 7 import javax.servlet.ServletRequest; 8 import javax.servlet.ServletResponse; 9 10 import org.springframework.security.access.SecurityMetadataSource; 11 import org.springframework.security.access.intercept.AbstractSecurityInterceptor; 12 import org.springframework.security.access.intercept.InterceptorStatusToken; 13 import org.springframework.security.web.FilterInvocation; 14 import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource; 15 16 /** 17 * 18 */ 19 public class MyFilterSecurityInterceptor extends AbstractSecurityInterceptor 20 implements Filter { 21 22 private FilterInvocationSecurityMetadataSource securityMetadataSource; 23 24 /* get、set方法 */ 25 public FilterInvocationSecurityMetadataSource getSecurityMetadataSource() { 26 return securityMetadataSource; 27 } 28 29 public void setSecurityMetadataSource( 30 FilterInvocationSecurityMetadataSource securityMetadataSource) { 31 this.securityMetadataSource = securityMetadataSource; 32 } 33 34 @Override 35 public Class<? extends Object> getSecureObjectClass() { 36 return FilterInvocation.class; 37 } 38 39 @Override 40 public SecurityMetadataSource obtainSecurityMetadataSource() { 41 return this.securityMetadataSource; 42 } 43 44 @Override 45 public void doFilter(ServletRequest request, ServletResponse response, 46 FilterChain chain) throws IOException, ServletException { 47 48 FilterInvocation fi = new FilterInvocation(request, response, chain); 49 invoke(fi); 50 } 51 52 public void invoke(FilterInvocation fi) throws IOException, 53 ServletException { 54 /** 55 * 最核心的代码就是@link InterceptorStatusToken token = super.beforeInvocation(fi); 56 * 它会调用我们定义的MyInvocationSecurityMetadataSource.getAttributes方法和MyAccessDecisionManager.decide方法 57 * 这一句,即在执行doFilter之前,进行权限的检查,而具体的实现已经交给@link MyAccessDecisionManager 了 58 */ 59 InterceptorStatusToken token = super.beforeInvocation(fi); 60 try { 61 //继续走下一个拦截器,也就是org.springframework.security.web.access.intercept.FilterSecurityInterceptor 62 fi.getChain().doFilter(fi.getRequest(), fi.getResponse()); 63 } finally { 64 super.afterInvocation(token, null); 65 } 66 } 67 68 @Override 69 public void destroy() { 70 71 } 72 73 @Override 74 public void init(FilterConfig arg0) throws ServletException { 75 76 } 77 78 }
5,登录页面(需要自己改一些东西)
1 <%@ page language="java" contentType="text/html; charset=UTF-8" 2 pageEncoding="UTF-8"%> 3 <%@ taglib prefix='fmt' uri="http://java.sun.com/jsp/jstl/fmt" %> 4 <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> 5 <%@ taglib prefix="sec" uri="http://www.springframework.org/security/tags"%> 6 <!DOCTYPE html> 7 <html lang="zh-CN"> 8 <head> 9 <meta charset="utf-8"> 10 <meta http-equiv="X-UA-Compatible" content="IE=edge"> 11 <meta name="viewport" content="width=device-width, initial-scale=1"> 12 <!-- 上述3个meta标签*必须*放在最前面,任何其他内容都*必须*跟随其后! --> 13 <meta name="description" content=""> 14 <meta name="author" content=""> 15 <link rel="icon" href="/favicon.ico"> 16 17 <title><fmt:message key="application.title"></fmt:message></title> 18 19 <!-- Bootstrap core CSS --> 20 <link href="/scripts/plugins/bootstrap/css/bootstrap.min.css" rel="stylesheet"> 21 22 <!-- HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries --> 23 <!-- Custom styles for this template --> 24 25 <link href="/scripts/apps/css/signin.css" rel="stylesheet"> 26 27 <style type="text/css"> 28 .tip { 29 font-size: 10px; 30 color: red; 31 text-align: center; 32 } 33 34 </style> 35 </head> 36 37 <body> 38 <div class="container"> 39 <c:url var="loginUrl" value="/login" /> 40 <form action="${loginUrl}" id="login_form" class="form-signin" method="post"> 41 <div class="form-signin-heading text-center"> 42 <h2 ><fmt:message key="application.title"></fmt:message></h2> 43 </div> 44 <div id="tip" class="tip"></div> 45 <c:if test="${param.error != null}"> 46 <span style="color:red">用户名或密码有误</span> 47 </c:if> 48 49 <label for="inputEmail" class="sr-only">登录帐号</label> 50 <input type="text" id="inputUserCode" name="username" class="form-control" placeholder="登录帐号" autofocus> 51 <label for="inputPassword" class="sr-only">登录密码</label> 52 <input type="password" id="inputPassword" name="password" class="form-control" placeholder="登录密码"> 53 <div class="input-group input-sm"> 54 <div class="checkbox"> 55 <label><input type="checkbox" id="rememberme" name="remember-me" value="true"> 下次自动登录</label> 56 </div> 57 </div> 58 <button id="login_button" class="btn btn-lg btn-primary btn-block" type="submit">登录</button> 59 </form> 60 61 </div> 62 63 <script src="/scripts/plugins/jQuery/jquery-2.2.3.min.js"></script> 64 </body> 65 </html>
在JSP中相应的操作按钮上加上如下标签,控制显示:
<%@ taglib prefix="sec" uri="http://www.springframework.org/security/tags"%> 该标签一定要加上!!!
<sec:authorize access="hasRole('ADD')">
<a href="/XXX/add">增加</a>
</sec:authorize>
数据库设计:user表里有roleId,role表,resource-role表,resource表