先发几张图片看下大致的样子,美工不专业,有点渣,大家找自己想了解的就好。
实现功能:
1、当没有登录的时候访问数据库有的资源,直接跳转到登录页面
2、用户登录后,在url栏直接访问没有权限的资源,跳转到403页面。
3、控制用户重复登录和次数
4、用户和角色都允许分配权限
先看下web.xml
<?xml version="1.0" encoding="UTF-8"?> <web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"> <session-config> <session-timeout>100</session-timeout> </session-config> <listener> <listener-class> org.springframework.security.web.session.HttpSessionEventPublisher </listener-class> </listener> <!-- 编码统一最好放最上面,最先加载,防止乱码--> <!-- Spring编码转换过滤器,将请求信息的编码统一转换为UTF-8,避免中文乱码 --> <filter> <filter-name>encodingFilter</filter-name> <filter-class> org.springframework.web.filter.CharacterEncodingFilter </filter-class> <init-param> <param-name>encoding</param-name> <param-value>UTF-8</param-value> </init-param> <init-param> <param-name>forceEncoding</param-name> <param-value>true</param-value> </init-param> </filter> <filter-mapping> <filter-name>encodingFilter</filter-name> <url-pattern>*.html</url-pattern> </filter-mapping> <!-- 然后接着是SpringSecurity必须的filter 优先配置,让SpringSecurity先加载,防止SpringSecurity拦截失效--> <filter> <filter-name>springSecurityFilterChain</filter-name> <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> </filter> <filter-mapping> <filter-name>springSecurityFilterChain</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <!--配置文件加载 --> <context-param> <param-name>contextConfigLocation</param-name> <param-value> WEB-INF/classes/applicationContext.xml, WEB-INF/classes/config.xml, WEB-INF/spring-security.xml </param-value> </context-param> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <!--默认所对应的配置文件是WEB-INF下的{servlet-name}-servlet.xml,这里便是:demoweb-servlet.xml--> <servlet> <servlet-name>demoweb</servlet-name> <servlet-class> org.springframework.web.servlet.DispatcherServlet </servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>demoweb</servlet-name> <url-pattern>*.html</url-pattern> </servlet-mapping> <welcome-file-list> <welcome-file>index.jsp</welcome-file> </welcome-file-list> </web-app>
spring-mvc的配置文件demoweb-servlet.xml
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:p="http://www.springframework.org/schema/p" xmlns:mvc="http://www.springframework.org/schema/mvc" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd"> <context:component-scan base-package="com.demo.web.app" /> <!-- 修改@ResponseBody返回中文乱码问题 --> <mvc:annotation-driven> <mvc:message-converters> <bean class="org.springframework.http.converter.StringHttpMessageConverter"> <property name="supportedMediaTypes"> <list> <value>text/plain;charset=UTF-8</value> <value>text/html;charset=UTF-8</value> </list> </property> </bean> </mvc:message-converters> </mvc:annotation-driven> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" p:order="2" p:prefix="/WEB-INF/jsp/" p:suffix=".jsp" /> <!-- FreeMarker配置 --> <bean class="org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer" p:templateLoaderPath="/WEB-INF/ftl" p:defaultEncoding="UTF-8"> <property name="freemarkerSettings"> <props> <prop key="classic_compatible">true</prop> </props> </property> </bean> <bean class="org.springframework.web.servlet.view.freemarker.FreeMarkerViewResolver" p:order="1" p:suffix=".ftl" p:contentType="text/html; charset=utf-8" /> <!-- SpringMVC上传文件时,需要配置MultipartResolver处理器 --> <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"> <property name="defaultEncoding" value="UTF-8"/> <!-- 指定所上传文件的总大小不能超过200KB。注意maxUploadSize属性的限制不是针对单个文件,而是所有文件的容量之和 --> <property name="maxUploadSize" value="200000"/> </bean> <!-- SpringMVC在超出上传文件限制时,会抛出org.springframework.web.multipart.MaxUploadSizeExceededException --> <!-- 该异常是SpringMVC在检查上传的文件信息时抛出来的,而且此时还没有进入到Controller方法中 --> <bean id="exceptionResolver" class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver"> <property name="exceptionMappings"> <props> <!-- 遇到MaxUploadSizeExceededException异常时,自动跳转到/WEB-INF/jsp/error_fileupload.jsp页面 --> <prop key="org.springframework.web.multipart.MaxUploadSizeExceededException">error_fileupload</prop> </props> </property> </bean> <mvc:view-controller path="/" view-name="/index" /> </beans>
以上这两文件了解spring-mvc的应该都没什么问题
最重要的spring-security.xml文件
<?xml version="1.0" encoding="UTF-8"?> <beans:beans xmlns="http://www.springframework.org/schema/security" xmlns:beans="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.0.xsd"> <http auto-config="false" entry-point-ref="authenticationProcessingFilterEntryPoint" access-denied-page="/403.jsp"><!-- 当访问被拒绝时,会转到403.jsp --> <!--filters="none" 该路径下不用过滤 --> <intercept-url pattern="/themes/**" filters="none" /> <intercept-url pattern="/loginhtml.html" filters="none" /> <!-- session没用户时访问资源跳转的默认指定登陆页面,出错后跳转页面,成功页面 --> <!-- <form-login login-page="/login.jsp" authentication-failure-url="/loginhtml.html?error=true" default-target-url="/login/login.html" /> --> <!-- 退出销毁session, 退出系统转向url ,响应退出系统的url, 默认:/j_spring_security_logout--> <logout invalidate-session="true" logout-success-url="/loginhtml.html" logout-url="/j_spring_security_logout"/> <!-- 默认为false,此值表示:用户第二次登录时,前一次的登录信息都被清空 。 true系统拒绝登陆,后面试登陆次数 --> <session-management > <concurrency-control error-if-maximum-exceeded="true" max-sessions="1"/> </session-management> <http-basic /> <!--position表示替换掉Spring Security原来默认的登陆验证Filter。--> <custom-filter ref="loginFilter" position="FORM_LOGIN_FILTER" /> <!-- 增加一个filter,这点与Acegi是不一样的,不能修改默认的filter了,这个filter位于FILTER_SECURITY_INTERCEPTOR之前 --> <custom-filter before="FILTER_SECURITY_INTERCEPTOR" ref="myFilter" /> </http> <beans:bean id="loginFilter" class="com.demo.web.app.security.filter.MyUsernamePasswordAuthenticationFilter"> <!-- 处理登录的action --> <beans:property name="filterProcessesUrl" value="/j_spring_security_check"></beans:property> <!-- 验证成功后的处理--> <beans:property name="authenticationSuccessHandler" ref="loginLogAuthenticationSuccessHandler"></beans:property> <!-- 验证失败后的处理--> <beans:property name="authenticationFailureHandler" ref="simpleUrlAuthenticationFailureHandler"></beans:property> <beans:property name="authenticationManager" ref="authenticationManager"></beans:property> </beans:bean> <!-- 未登录的切入点 --> <beans:bean id="authenticationProcessingFilterEntryPoint" class="org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint"> <beans:property name="loginFormUrl" value="/loginhtml.html"></beans:property> </beans:bean> <beans:bean id="loginLogAuthenticationSuccessHandler" class="org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler"> <beans:property name="defaultTargetUrl" value="/member/index.html"></beans:property> </beans:bean> <beans:bean id="simpleUrlAuthenticationFailureHandler" class="org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler"> <!-- 可以配置相应的跳转方式。属性forwardToDestination为true采用forward false为sendRedirect --> <beans:property name="defaultFailureUrl" value="/loginhtml.html"></beans:property> </beans:bean> <!-- 一个自定义的filter,必须包含authenticationManager,accessDecisionManager,securityMetadataSource三个属性, 我们的所有控制将在这三个类中实现,解释详见具体配置 --> <beans:bean id="myFilter" class="com.demo.web.app.security.filter.MySecurityFilter"> <beans:property name="authenticationManager" ref="authenticationManager" /> <beans:property name="accessDecisionManager" ref="myAccessDecisionManagerBean" /> <beans:property name="securityMetadataSource" ref="securityMetadataSource" /> </beans:bean> <!-- 认证管理器,实现用户认证的入口,主要实现UserDetailsService接口即可 --> <authentication-manager alias="authenticationManager"> <authentication-provider user-service-ref="myUserDetailService"> </authentication-provider> </authentication-manager> <beans:bean id="myUserDetailService" class="com.demo.web.app.security.MyUserDetailServiceImpl" /> <!-- 访问决策器,决定某个用户具有的角色,是否有足够的权限去访问某个资源 --> <beans:bean id="myAccessDecisionManagerBean" class="com.demo.web.app.security.MyAccessDecisionManager"> </beans:bean> <!-- 资源源数据定义,即定义某一资源可以被哪些角色访问 --> <beans:bean id="securityMetadataSource" class="com.demo.web.app.security.MySecurityMetadataSource" > <beans:constructor-arg name="resourcesService" ref="resourcesService"></beans:constructor-arg> </beans:bean> <beans:bean id="resourcesService" class="com.demo.web.app.service.resources.ResourcesService"></beans:bean> </beans:beans>
下面把spring-secuity关键的说一下
<!-- 未登录的切入点 --> <beans:bean id="authenticationProcessingFilterEntryPoint" class="org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint"> <beans:property name="loginFormUrl" value="/loginhtml.html"></beans:property> </beans:bean>
也就是没登录的时候访问资源跳转到登录页,注意:要在http标签上加
auto-config="false" entry-point-ref="authenticationProcessingFilterEntryPoint" access-denied-page="/403.jsp"
登录:
<beans:bean id="loginFilter" class="com.demo.web.app.security.filter.MyUsernamePasswordAuthenticationFilter"> <!-- 处理登录的action --> <beans:property name="filterProcessesUrl" value="/j_spring_security_check"></beans:property> <!-- 验证成功后的处理--> <beans:property name="authenticationSuccessHandler" ref="loginLogAuthenticationSuccessHandler"></beans:property> <!-- 验证失败后的处理--> <beans:property name="authenticationFailureHandler" ref="simpleUrlAuthenticationFailureHandler"></beans:property> <beans:property name="authenticationManager" ref="authenticationManager"></beans:property> </beans:bean>
login.jsp的action为/j_spring_security_check,重新写了一个MyUsernamePasswordAuthenticationFilter
package com.demo.web.app.security.filter; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.authentication.AuthenticationServiceException; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import app.pojo.TUser; import com.demo.web.app.service.user.UserService; public class MyUsernamePasswordAuthenticationFilter extends UsernamePasswordAuthenticationFilter{ private Log log = LogFactory.getLog(MyUsernamePasswordAuthenticationFilter.class); public static final String VALIDATE_CODE = "validateCode"; public static final String USERNAME = "username"; public static final String PASSWORD = "password"; @Autowired private UserService userService; @Override public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException { log.info("登录ing。。。。。。。。。。。"); if (!request.getMethod().equals("POST")) { throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod()); } String username = obtainUsername(request); String password = obtainPassword(request); TUser user = null; try { user = userService.getUserByUsername(username.trim()); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } if(user==null){ throw new AuthenticationServiceException("用户名错误!"); }else{ if(!user.getPassword().equals(password)){ throw new AuthenticationServiceException("密码错误!"); } } //UsernamePasswordAuthenticationToken实现 Authentication UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password); // 允许子类设置详细属性 setDetails(request, authRequest); // 运行UserDetailsService的loadUserByUsername 再次封装Authentication return super.attemptAuthentication(request, response); } @Override protected String obtainPassword(HttpServletRequest request) { Object obj = request.getParameter(PASSWORD); return null == obj ? "" : obj.toString(); } @Override protected String obtainUsername(HttpServletRequest request) { Object obj = request.getParameter(USERNAME); return null == obj ? "" : obj.toString(); } }
这里主要的就是异常的抛出,可以在login.jsp通过${SPRING_SECURITY_LAST_EXCEPTION.message}取得,具体就不谈了。
权限控制的Filter,3个属性
<beans:bean id="myFilter" class="com.demo.web.app.security.filter.MySecurityFilter"> <beans:property name="authenticationManager" ref="authenticationManager" /> <beans:property name="accessDecisionManager" ref="myAccessDecisionManagerBean" /> <beans:property name="securityMetadataSource" ref="securityMetadataSource" /> </beans:bean>
MySecurityFilter.java
package com.demo.web.app.security.filter; import java.io.IOException; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.security.access.SecurityMetadataSource; import org.springframework.security.access.intercept.AbstractSecurityInterceptor; import org.springframework.security.access.intercept.InterceptorStatusToken; import org.springframework.security.web.FilterInvocation; import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource; public class MySecurityFilter extends AbstractSecurityInterceptor implements Filter{ private Log log = LogFactory.getLog(MySecurityFilter.class); //与applicationContext-security.xml里的myFilter的属性securityMetadataSource对应, //其他的两个组件,已经在AbstractSecurityInterceptor定义 private FilterInvocationSecurityMetadataSource securityMetadataSource; @Override public Class<? extends Object> getSecureObjectClass() { // TODO Auto-generated method stub //下面的MyAccessDecisionManager的supports方面必须放回true,否则会提醒类型错误 return FilterInvocation.class; } @Override public SecurityMetadataSource obtainSecurityMetadataSource() { // TODO Auto-generated method stub return this.securityMetadataSource; } public void destroy() { // TODO Auto-generated method stub } public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { // TODO Auto-generated method stub FilterInvocation fi = new FilterInvocation(request, response, chain); invoke(fi); } public void init(FilterConfig filterConfig) throws ServletException { // TODO Auto-generated method stub } private void invoke(FilterInvocation fi) throws IOException, ServletException { // object为FilterInvocation对象 //super.beforeInvocation(fi);源码 //1.获取请求资源的权限 //执行Collection<ConfigAttribute> attributes = SecurityMetadataSource.getAttributes(object); //2.是否拥有权限 //this.accessDecisionManager.decide(authenticated, object, attributes); log.info("------------MyFilterSecurityInterceptor.doFilter()-----------开始拦截器了...."); InterceptorStatusToken token = super.beforeInvocation(fi); try { // Collection<ConfigAttribute> attributes = SecurityMetadataSource.getAttributes(object); // this.accessDecisionManager.decide(authenticated, object, attributes); fi.getChain().doFilter(fi.getRequest(), fi.getResponse()); } catch (Exception e) { e.printStackTrace(); }finally { super.afterInvocation(token, null); } log.info("------------MyFilterSecurityInterceptor.doFilter()-----------拦截器该方法结束了...."); } public FilterInvocationSecurityMetadataSource getSecurityMetadataSource() { return securityMetadataSource; } public void setSecurityMetadataSource( FilterInvocationSecurityMetadataSource securityMetadataSource) { this.securityMetadataSource = securityMetadataSource; } }
MyUserDetailServiceImpl.java
package com.demo.web.app.security; import java.util.ArrayList; import java.util.Collection; import java.util.List; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.dao.DataAccessException; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.GrantedAuthorityImpl; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import app.pojo.Application; import app.pojo.Button; import app.pojo.Menu; import app.pojo.Privilege; import app.pojo.TUser; import com.demo.web.app.common.util.CommonUtil; import com.demo.web.app.security.user.UserSession; import com.demo.web.app.service.resources.ResourcesService; import com.demo.web.app.service.user.UserService; public class MyUserDetailServiceImpl implements UserDetailsService{ private Log log = LogFactory.getLog(MyUserDetailServiceImpl.class); @Autowired private UserService userService; @Autowired private ResourcesService resourcesService; public UserDetails loadUserByUsername(String username)throws UsernameNotFoundException, DataAccessException { log.info("获取用户信息保存到全局缓存securityContextHolder,usernmae="+username); TUser user = null; user =userService.getUserByUsername(username); if(user==null){ throw new UsernameNotFoundException(username); } Collection<GrantedAuthority> grantedAuths = obtionGrantedAuthorities(user); boolean enables = true; boolean accountNonExpired = true; boolean credentialsNonExpired = true; boolean accountNonLocked = true; UserSession userSession = new UserSession(user.getUsername(), user.getPassword(), enables, accountNonExpired, credentialsNonExpired, accountNonLocked, grantedAuths); userSession.setId(user.getId()); return userSession; } //取得用户的权限 private List<GrantedAuthority> obtionGrantedAuthorities(TUser user) { List<GrantedAuthority> authSet = new ArrayList<GrantedAuthority>(); // authSet.add(new GrantedAuthorityImpl("ROLE_USER")); //用户基本权限 List<Privilege> ps = new ArrayList<Privilege>(); List<Privilege> psU = getPrivilegeByMasterId("user", user.getId()); ps.addAll(psU); //先加载用户的权限,再去找相应角色的权限,都没有返回null List<String> roleIds = getRoleIdsByUserId(user.getId()); for(String roleId : roleIds) { List<Privilege> psR = getPrivilegeByMasterId("role",roleId); ps.addAll(psR); } packAuthority(ps, authSet); return authSet; } /** * 加载用户拥有资源 * @param ps * @param authSet */ public void packAuthority(List<Privilege> ps,List<GrantedAuthority> authSet){ for(Privilege p : ps){ authSet.add(new GrantedAuthorityImpl(p.getAccessCode())); } /* for(Privilege p : ps){ if(p.getPrivilegeAccess().equals("application")){ Application application = getApplication(p.getPrivilegeAccessValue()); log.info("加载application"); authSet.add(new GrantedAuthorityImpl(application.getApplicationCode())); //application for(Privilege pmenu : ps){ if(pmenu.getPrivilegeAccess().equals("menu")){ Menu menu = getMenu(pmenu.getPrivilegeAccessValue()); log.info("加载menu"); if(menu.getApplicationId().equals(application.getApplicationId())){ authSet.add(new GrantedAuthorityImpl(menu.getMenuCode())); //menu for(Privilege pbtn : ps){ if(pbtn.getPrivilegeAccess().equals("button")){ Button btn = getButton(pbtn.getPrivilegeAccessValue()); log.info("加载button"); if(btn.getMenuId().equals(menu.getMenuId())){ authSet.add(new GrantedAuthorityImpl(btn.getBtnCode())); //button } } } } } } } } */ } private Button getButton(String buttonId) { Button button = resourcesService.getButtonById(buttonId); return button; } private Menu getMenu(String menuId) { Menu menu = resourcesService.getMenuById(menuId); return menu; } private List<Privilege> getPrivilegeByMasterId(String master,String id) { List<Privilege> list = resourcesService.getPrivilegeByMasterId(master, id); return list; } private Application getApplication(String applicationId) { Application application = resourcesService.getApplicationById(applicationId); return application; } private List<String> getRoleIdsByUserId(String id) { List<String> roleIds = resourcesService.getRoleIdsByUserId(id); return roleIds; } @Override public boolean equals(Object obj) { System.out.println(obj); return super.equals(obj); } @Override public int hashCode() { System.out.println(MyUserDetailServiceImpl.this.hashCode()); return super.hashCode(); } }
MySecurityMetadataSource.java
package com.demo.web.app.security; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.security.access.ConfigAttribute; import org.springframework.security.access.SecurityConfig; import org.springframework.security.web.FilterInvocation; import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource; import com.demo.web.app.service.resources.ResourcesService; import app.pojo.Application; import app.pojo.BaseResources; import app.pojo.Button; import app.pojo.Menu; public class MySecurityMetadataSource implements FilterInvocationSecurityMetadataSource{ private ResourcesService resourcesService; private Log log = LogFactory.getLog(MySecurityMetadataSource.class); public MySecurityMetadataSource() { } //spring调用 public MySecurityMetadataSource(ResourcesService resourcesService) { this.resourcesService = resourcesService; loadResourceDefine(); } /* 保存资源和权限的对应关系 key-资源url value-权限 */ private static Map<String, Collection<ConfigAttribute>> resourceMap = null; public Collection<ConfigAttribute> getAllConfigAttributes() { // TODO Auto-generated method stub return null; } //返回所请求资源所需要的权限 //返回null是不执行下面MyAccessDecisionManager中的decide方法 出现null的情况:访问的资源在数据库中没有定义 public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException { String requestUrl = ((FilterInvocation) object).getRequestUrl(); log.info("MySecurityMetadataSource:getAttributes()---------------请求地址为:"+requestUrl); if(resourceMap==null){ loadResourceDefine(); } Iterator<String> it = resourceMap.keySet().iterator(); while(it.hasNext()){ String _url = it.next(); if(requestUrl.indexOf("?")!=-1){ requestUrl = requestUrl.substring(0, requestUrl.indexOf("?")); } if(_url.equals(requestUrl)) return resourceMap.get(_url); } log.info("请求的资源:"+requestUrl+" 在数据库中没有定义!"); return null; // return resourceMap.get(requestUrl); } //加载数据库资源,封装成Map<String, Collection<ConfigAttribute>> private void loadResourceDefine(){ log.info("MySecurityMetadataSource.loadResourcesDefine()--------------开始加载资源列表数据--------"); if(resourceMap == null) { resourceMap = new HashMap<String, Collection<ConfigAttribute>>(); List<BaseResources> baseResources = resourcesService.getBaseResources(); List<Application> applications = resourcesService.getApplication(); List<Menu> menus = resourcesService.getMenu(); List<Button> buttons = resourcesService.getButton(); for(BaseResources base : baseResources){ Collection<ConfigAttribute> configAttributes = new ArrayList<ConfigAttribute>(); if(base.getBaseResourcesUrl()!=null && !base.getBaseResourcesUrl().equals("")){ ConfigAttribute configAttribute = new SecurityConfig(base.getBaseResourcesCode()); configAttributes.add(configAttribute); resourceMap.put(base.getBaseResourcesUrl(), configAttributes); } } for(Application application : applications){ Collection<ConfigAttribute> configAttributes = new ArrayList<ConfigAttribute>(); if(application.getApplicationUrl()!=null && !application.getApplicationUrl().equals("")){ ConfigAttribute configAttribute = new SecurityConfig(application.getApplicationCode()); configAttributes.add(configAttribute); resourceMap.put(application.getApplicationUrl(), configAttributes); } } for (Menu menu : menus) { Collection<ConfigAttribute> configAttributes = new ArrayList<ConfigAttribute>(); //以menuCode封装为Spring的security Object if(menu.getMenuUrl()!=null && !menu.getMenuUrl().equals("")){ ConfigAttribute configAttribute = new SecurityConfig(menu.getMenuCode()); configAttributes.add(configAttribute); resourceMap.put(menu.getMenuUrl(), configAttributes); } } for(Button button : buttons){ Collection<ConfigAttribute> configAttributes = new ArrayList<ConfigAttribute>(); if(button.getBtnUrl()!=null && !button.getBtnUrl().equals("")){ ConfigAttribute configAttribute = new SecurityConfig(button.getBtnCode()); configAttributes.add(configAttribute); resourceMap.put(button.getBtnUrl(), configAttributes); } } } } public boolean supports(Class<?> arg0) { // TODO Auto-generated method stub // System.out.println("MySecurityMetadataSource.supports()---------------------"); return true; } }
MyAccessDecisionManager.java
package com.demo.web.app.security; import java.util.Collection; import java.util.Iterator; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.security.access.AccessDecisionManager; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.access.ConfigAttribute; import org.springframework.security.authentication.InsufficientAuthenticationException; import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; public class MyAccessDecisionManager implements AccessDecisionManager{ private Log log = LogFactory.getLog(MyAccessDecisionManager.class); public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException { log.info("MyAccessDescisionManager.decide()------------------验证用户是否具有一定的权限--------"); if(configAttributes==null){ return; } //所请求的资源拥有的权限(一个资源对多个权限) Iterator<ConfigAttribute> iterator = configAttributes.iterator(); while(iterator.hasNext()) { ConfigAttribute configAttribute = iterator.next(); //访问所请求资源所需要的权限 String needPermission = configAttribute.getAttribute(); log.info("请求资源所需要的权限~~~~~~~~~~~~~~~~~~needPermission is " + needPermission); //用户所拥有的权限authentication for(GrantedAuthority ga : authentication.getAuthorities()) { if(needPermission.equals(ga.getAuthority())) { return; } } } //没有权限 throw new AccessDeniedException(" 没有权限访问! "); } /** * 启动时候被AbstractSecurityInterceptor调用,决定AccessDecisionManager是否可以执行传递ConfigAttribute。 */ public boolean supports(ConfigAttribute configAttribute) { // TODO Auto-generated method stub log.info("MyAccessDescisionManager.supports()------------角色名:"+configAttribute.getAttribute()); return true; } /** * 被安全拦截器实现调用,包含安全拦截器将显示的AccessDecisionManager支持安全对象的类型 */ public boolean supports(Class<?> arg0) { // TODO Auto-generated method stub // System.out.println("MyAccessDescisionManager.supports()--------------------------------"); return true; } }
服务器启动时先执行MySecurityMetadataSource的loadResourceDefine()加载数据库资源,
登录时会调用authenticationManager的authentication-provider,
MyUserDetailServiceImpl的loadUserByUsername把用户信息和用户权限加载进来放到全局变量securityContextHolder中
本人实验这个好像也就在登录的时候执行下,这个session过期的问题还不清楚,感觉还是首次登录后计时,不是最后操作后计时。我也自己写了userSession,继承他的user
package com.demo.web.app.security.user; import java.util.Collection; import java.util.List; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.userdetails.User; import app.pojo.Application; import app.pojo.Button; import app.pojo.Menu; /** * spring security中存的 用户基本信息 * 即session中存储的用户信息 * @author Administrator * */ public class UserSession extends User{ private String id; private List<Application> applications; private List<Menu> menus; private List<Button> buttons; public UserSession(String username, String password, boolean enabled, boolean accountNonExpired, boolean credentialsNonExpired, boolean accountNonLocked, Collection<? extends GrantedAuthority> authorities) { super(username, password, enabled, accountNonExpired, credentialsNonExpired,accountNonLocked, authorities); } public String getId() { return id; } public void setId(String id) { this.id = id; } public List<Application> getApplications() { return applications; } public void setApplications(List<Application> applications) { this.applications = applications; } public List<Menu> getMenus() { return menus; } public void setMenus(List<Menu> menus) { this.menus = menus; } public List<Button> getButtons() { return buttons; } public void setButtons(List<Button> buttons) { this.buttons = buttons; } @Override public boolean equals(Object rhs) { // TODO Auto-generated method stub return super.equals(rhs); } @Override public int hashCode() { // TODO Auto-generated method stub return super.hashCode(); } }
浏览器访问时MySecurityFilter拦截执行InterceptorStatusToken token = super.beforeInvocation(fi);
就会调用MySecurityMetadataSource 的getAttributes(Object object)方法,返回所请求资源所需要的权限,
再执行访问决策器myAccessDecisionManagerBean中的decide方法。我是url相同既有权限访问,基本上就这样了。
再看下数据表的结构:
权限关系表privilege
基础资源表baseResources
系统模块表application
button表
menu表
用户角色表user_role
用户表user
角色表role
由于application,button,menu表中没有定义一些其他的url资源,如:用户中心,获取菜单。。。而没有定义这些资源,当访问的时候就不会被拦截,还是可以直接访问。所以我定义了一个基础的资源表base_resources,当新建用户的时候就赋予用户这个权限,就管理了所有的url。
还有button,appliction,menu,base_resource表中的code是该资源需要的权限编码,我在privilege表也放入方便操作。
还有privilege表,主体可以为用户,角色。。。领域可以为模块,菜单,按钮,也就是who,what how 某某在某某领域拥有什么。
大致就这样了