一、引言
因项目需要最近研究了下Spring Security3.x,并模拟数据库实现用户,权限,资源的管理。
二、准备
1.了解一些Spring MVC相关知识;
2.了解一些AOP相关知识;
3.了解Spring;
4.了解Maven,并安装。
三、实现步骤
本示例中使用的版本是Spring Security3.2.2.通过数据库实现Spring Security认证授权大致需要以下几个步骤:
1.新建maven web project(因为本示例使用的是maven来构建的),项目结构如下,忽略红叉叉:
2.在pom文件中添加SpringMVC,Spring Security3.2等依赖的相关jar包,代码如下:
1 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 2 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> 3 <modelVersion>4.0.0</modelVersion> 4 <groupId>com.wzhang</groupId> 5 <artifactId>spring-mvc-security-helloworld</artifactId> 6 <packaging>war</packaging> 7 <version>1.0</version> 8 <name>spring-mvc-security-helloworld Maven Webapp</name> 9 <url>http://maven.apache.org</url> 10 <dependencies> 11 <dependency> 12 <groupId>junit</groupId> 13 <artifactId>junit</artifactId> 14 <version>3.8.1</version> 15 </dependency> 16 <dependency> 17 <groupId>commons-logging</groupId> 18 <artifactId>commons-logging</artifactId> 19 <version>1.1.1</version> 20 <scope>compile</scope> 21 <optional>true</optional> 22 </dependency> 23 <dependency> 24 <groupId>org.springframework</groupId> 25 <artifactId>spring-web</artifactId> 26 <version>3.2.3.RELEASE</version> 27 </dependency> 28 <dependency> 29 <groupId>org.springframework</groupId> 30 <artifactId>spring-security-core</artifactId> 31 <version>3.2.2.RELEASE</version> 32 </dependency> 33 <dependency> 34 <groupId>org.springframework</groupId> 35 <artifactId>spring-beans</artifactId> 36 <version>3.2.3.RELEASE</version> 37 </dependency> 38 <dependency> 39 <groupId>org.springframework</groupId> 40 <artifactId>spring-context</artifactId> 41 <version>3.2.3.RELEASE</version> 42 </dependency> 43 <dependency> 44 <groupId>org.springframework.security</groupId> 45 <artifactId>spring-security-config</artifactId> 46 <version>3.2.2.RELEASE</version> 47 <scope>compile</scope> 48 </dependency> 49 <dependency> 50 <groupId>org.springframework</groupId> 51 <artifactId>spring-webmvc</artifactId> 52 <version>3.2.3.RELEASE</version> 53 </dependency> 54 <dependency> 55 <groupId>org.springframework.security</groupId> 56 <artifactId>spring-security-web</artifactId> 57 <version>3.2.2.RELEASE</version> 58 <scope>compile</scope> 59 </dependency> 60 <dependency> 61 <groupId>org.springframework</groupId> 62 <artifactId>spring-core</artifactId> 63 <version>3.2.3.RELEASE</version> 64 <scope>compile</scope> 65 <exclusions> 66 <exclusion> 67 <artifactId>commons-logging</artifactId> 68 <groupId>commons-logging</groupId> 69 </exclusion> 70 </exclusions> 71 </dependency> 72 <dependency> 73 <groupId>jstl</groupId> 74 <artifactId>jstl</artifactId> 75 <version>1.2</version> 76 </dependency> 77 <dependency> 78 <groupId>log4j</groupId> 79 <artifactId>log4j</artifactId> 80 <version>1.2.17</version> 81 </dependency> 82 <dependency> 83 <groupId>javax.servlet</groupId> 84 <artifactId>servlet-api</artifactId> 85 <version>2.5</version> 86 </dependency> 87 </dependencies> 88 <build> 89 <finalName>spring-mvc-security-helloworld</finalName> 90 91 </build> 92 </project>
3.实现FilterInvocationSecurityMetadataSource接口,完成从数据库中获取资源权限的关系;
a.为了模拟数据库我这里定义了几个类/接口:ResourceDao.java(访问数据库资源的接口),ResourceDaoImpl.java(访问数据库资源的实现类).代码如下:
package com.wzhang.dao; import java.util.Map; public interface ResourceDao { /** * 获取资源权限列表 * @return */ Map<String,String> getResources(); } /********************以下是ResourceDao实现类 ************************/ package com.wzhang.dao.impl; import java.util.HashMap; import java.util.Map; import com.wzhang.common.RoleConstants; import com.wzhang.dao.ResourceDao; public class ResourceDaoImpl implements ResourceDao { /** * 获取所有资源权限映射 * key-URL * value-Role */ public Map<String, String> getResources() { Map<String, String> map = new HashMap<String, String>(); map.put("/admin**", RoleConstants.ROLE_ADMIN); map.put("/index**", RoleConstants.ROLE_USER); return map; } }
b.自定义FilterInvocationSecurityMetadataSource接口实现类,利用刚刚的接口和实现类,实现从数据库获取资源权限的关系,代码如下:
1 package com.wzhang.security.service; 2 3 import java.util.ArrayList; 4 import java.util.Collection; 5 import java.util.HashMap; 6 import java.util.Iterator; 7 import java.util.Map; 8 import java.util.Map.Entry; 9 10 import org.apache.log4j.Logger; 11 import org.springframework.security.access.ConfigAttribute; 12 import org.springframework.security.access.SecurityConfig; 13 import org.springframework.security.web.FilterInvocation; 14 import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource; 15 import org.springframework.util.AntPathMatcher; 16 import org.springframework.util.PathMatcher; 17 18 import com.wzhang.dao.ResourceDao; 19 import com.wzhang.dao.impl.ResourceDaoImpl; 20 21 /** 22 * 资源源数据定义,即定义某一资源可以被哪些角色访问 23 * @author wzhang 24 * 25 */ 26 public class CustomFilterInvocationSecurityMetadataSource implements FilterInvocationSecurityMetadataSource { 27 28 private static final Logger logger = Logger 29 .getLogger(CustomFilterInvocationSecurityMetadataSource .class); 30 31 private static Map<String, Collection<ConfigAttribute>> resourceMap = null; 32 private PathMatcher pathMatcher = new AntPathMatcher(); 33 34 private ResourceDao resourceDao; 35 36 public CustomFilterInvocationSecurityMetadataSource(ResourceDao resourceDao ){ 37 this.resourceDao =resourceDao; 38 resourceMap = loadResourceMatchAuthority(); 39 } 40 41 public Collection<ConfigAttribute> getAllConfigAttributes() { 42 43 return null; 44 } 45 46 public CustomFilterInvocationSecurityMetadataSource () { 47 super(); 48 this.resourceDao = new ResourceDaoImpl(); 49 resourceMap = loadResourceMatchAuthority(); 50 } 51 52 /** 53 * 加载资源与权限的映射关系 54 * 55 * @return 56 */ 57 private Map<String, Collection<ConfigAttribute>> loadResourceMatchAuthority() { 58 59 Map<String, Collection<ConfigAttribute>> map = new HashMap<String, Collection<ConfigAttribute>>(); 60 61 // 获取资源权限映射key:url,value:role 62 Map<String, String> configs = resourceDao.getResources(); 63 for (Entry<String, String> entry : configs.entrySet()) { 64 Collection<ConfigAttribute> list = new ArrayList<ConfigAttribute>(); 65 66 String[] vals = entry.getValue().split(","); 67 for (String val : vals) { 68 ConfigAttribute config = new SecurityConfig(val); 69 list.add(config); 70 } 71 map.put(entry.getKey(), list); 72 } 73 74 return map; 75 76 } 77 78 public Collection<ConfigAttribute> getAttributes(Object object) 79 throws IllegalArgumentException { 80 String url = ((FilterInvocation) object).getRequestUrl(); 81 82 System.out.println("requestUrl is " + url); 83 logger.info("requestUrl is " + url); 84 85 if (resourceMap == null) { 86 loadResourceMatchAuthority(); 87 } 88 //比较url是否存在 89 Iterator<String> ite = resourceMap.keySet().iterator(); 90 while (ite.hasNext()) { 91 String resURL = ite.next(); 92 if (pathMatcher.match(resURL,url)) { 93 return resourceMap.get(resURL); 94 } 95 } 96 return resourceMap.get(url); 97 } 98 99 public boolean supports(Class<?> clazz) { 100 return true; 101 } 102 }
4.实现AccessDecisionManager接口,裁定当前用户对应权限authentication是否包含所请求资源所拥有的权限;
当用户访问某一资源时,会被AccessDecisionManager拦截,并调用decide(...)方法,用以判断当前用户是否有权限访问该资源,如果没有则抛出异常。代码如下:
1 package com.wzhang.security.service; 2 3 import java.util.Collection; 4 import java.util.Iterator; 5 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.authentication.InsufficientAuthenticationException; 10 import org.springframework.security.core.Authentication; 11 import org.springframework.security.core.GrantedAuthority; 12 13 /** 14 * 自定义访问决策器,决定某个用户具有的角色,是否有足够的权限去访问某个资源 15 * @author wzhang 16 * 17 */ 18 public class CustomAccessDecisionManager implements AccessDecisionManager { 19 20 /** 21 * 裁定当前用户对应权限authentication是否包含所请求资源所拥有的权限 如果成立 则通过裁定 否则发生异常 22 */ 23 public void decide(Authentication authentication, Object object, 24 Collection<ConfigAttribute> configAttributes) 25 throws AccessDeniedException, InsufficientAuthenticationException { 26 27 if (configAttributes == null) { 28 return; 29 } 30 31 // 所请求的资源拥有的权限(一个资源对多个权限) 32 Iterator<ConfigAttribute> iterator = configAttributes.iterator(); 33 34 while (iterator.hasNext()) { 35 ConfigAttribute configAttribute = iterator.next(); 36 37 // 访问所请求资源所需要的权限 38 String needPermission = configAttribute.getAttribute(); 39 40 System.out.println("needPermission is " + needPermission); 41 42 // 用户所拥有的权限authentication 43 for (GrantedAuthority ga : authentication.getAuthorities()) { 44 if (needPermission.equals(ga.getAuthority())) { 45 return; 46 } 47 } 48 } 49 50 // 没有权限 51 throw new AccessDeniedException(" No Access Dendied "); 52 53 } 54 55 public boolean supports(ConfigAttribute configAttribute) { 56 return true; 57 } 58 59 public boolean supports(Class<?> clazz) { 60 return true; 61 } 62 63 }
5.实现UserDetailsService接口,完成从数据库获取用户-权限的关系。
a.为了获取用户,以及用户的权限,我这里先定义了两个bean:UserBean和RoleBean,代码如下:
1 /************RoleBean定义***********/ 2 package com.wzhang.domain; 3 4 public class RoleBean { 5 private int roleId; 6 private String roleName; 7 private String roleDesc; 8 9 10 public RoleBean() { 11 super(); 12 // TODO Auto-generated constructor stub 13 } 14 public RoleBean(int roleId, String roleName, String roleDesc) { 15 super(); 16 this.roleId = roleId; 17 this.roleName = roleName; 18 this.roleDesc = roleDesc; 19 } 20 /** 21 * @return the roleId 22 */ 23 public int getRoleId() { 24 return roleId; 25 } 26 /** 27 * @param roleId the roleId to set 28 */ 29 public void setRoleId(int roleId) { 30 this.roleId = roleId; 31 } 32 /** 33 * @return the roleName 34 */ 35 public String getRoleName() { 36 return roleName; 37 } 38 /** 39 * @param roleName the roleName to set 40 */ 41 public void setRoleName(String roleName) { 42 this.roleName = roleName; 43 } 44 /** 45 * @return the roleDesc 46 */ 47 public String getRoleDesc() { 48 return roleDesc; 49 } 50 /** 51 * @param roleDesc the roleDesc to set 52 */ 53 public void setRoleDesc(String roleDesc) { 54 this.roleDesc = roleDesc; 55 } 56 } 57 58 59 /**************UserBean定义*****************/ 60 61 62 package com.wzhang.domain; 63 64 public class UserBean { 65 private String userName; 66 private String password; 67 private Integer access; 68 private RoleBean role; 69 70 /** 71 * @return the userName 72 */ 73 public String getUserName() { 74 return userName; 75 } 76 /** 77 * @param userName the userName to set 78 */ 79 public void setUserName(String userName) { 80 this.userName = userName; 81 } 82 /** 83 * @return the password 84 */ 85 public String getPassword() { 86 return password; 87 } 88 /** 89 * @param password the password to set 90 */ 91 public void setPassword(String password) { 92 this.password = password; 93 } 94 /** 95 * @return the access 96 */ 97 public Integer getAccess() { 98 return access; 99 } 100 /** 101 * @param access the access to set 102 */ 103 public void setAccess(Integer access) { 104 this.access = access; 105 } 106 /** 107 * @return the role 108 */ 109 public RoleBean getRole() { 110 return role; 111 } 112 /** 113 * @param role the role to set 114 */ 115 public void setRole(RoleBean role) { 116 this.role = role; 117 } 118 }
b.接着定义了数据访问层接口和实现:UserDao.java,UserDaoImpl.java以获取用户-权限。代码如下:
1 /*********Interface UserDao*************/ 2 3 package com.wzhang.dao; 4 5 import com.wzhang.domain.UserBean; 6 7 public interface UserDao { 8 UserBean getUser(String userName); 9 } 10 11 12 /**********实现类*******************/ 13 14 package com.wzhang.dao.impl; 15 16 import java.util.ArrayList; 17 import java.util.List; 18 19 import org.apache.log4j.Logger; 20 21 import com.wzhang.common.RoleConstants; 22 import com.wzhang.dao.UserDao; 23 import com.wzhang.domain.RoleBean; 24 import com.wzhang.domain.UserBean; 25 26 public class UserDaoImpl implements UserDao { 27 protected static Logger logger = Logger.getLogger("dao"); 28 public UserBean getUser(String userName) { 29 List<UserBean> users = internalDatabase(); 30 31 for (UserBean ub : users) { 32 if (ub.getUserName().equals(userName)) { 33 logger.debug("User found"); 34 return ub; 35 } 36 } 37 logger.error("User does not exist!"); 38 throw new RuntimeException("User does not exist!"); 39 } 40 41 42 private List<UserBean> internalDatabase() { 43 44 List<UserBean> users = new ArrayList<UserBean>(); 45 UserBean user = null; 46 47 //创建用户admin/admin,角色ROLE_ADMIN 48 user = new UserBean(); 49 user.setUserName("admin"); 50 // "admin"经过MD5加密后 51 user.setPassword("21232f297a57a5a743894a0e4a801fc3"); 52 user.setAccess(1); 53 user.setRole(new RoleBean(1,RoleConstants.ROLE_ADMIN,"")); 54 users.add(user); 55 56 //创建用户user/user,角色ROLE_USER 57 user = new UserBean(); 58 user.setUserName("user"); 59 // "user"经过MD5加密后 60 user.setPassword("ee11cbb19052e40b07aac0ca060c23ee"); 61 user.setAccess(2); 62 user.setRole(new RoleBean(2,RoleConstants.ROLE_USER,"")); 63 users.add(user); 64 65 return users; 66 67 } 68 }
c.代码中用到的一些常量,定在在RoleContants.kava类中了:
1 package com.wzhang.common; 2 3 /** 4 * 角色常量 5 * @author wzhang 6 * 7 */ 8 public class RoleConstants { 9 /** 10 * 管理员角色 11 */ 12 public static final String ROLE_ADMIN = "ROLE_ADMIN"; 13 14 /** 15 * 普通用户角色 16 */ 17 public static final String ROLE_USER = "ROLE_USER"; 18 19 }
d.实现UserDetailsService接口,完成用户-权限的获取(实现loadUserByUsername(...)方法):
1 package com.wzhang.security.service; 2 3 import java.util.ArrayList; 4 import java.util.Collection; 5 import java.util.List; 6 7 import org.apache.log4j.Logger; 8 import org.springframework.security.core.GrantedAuthority; 9 import org.springframework.security.core.authority.SimpleGrantedAuthority; 10 import org.springframework.security.core.userdetails.User; 11 import org.springframework.security.core.userdetails.UserDetails; 12 import org.springframework.security.core.userdetails.UserDetailsService; 13 import org.springframework.security.core.userdetails.UsernameNotFoundException; 14 15 import com.wzhang.common.RoleConstants; 16 import com.wzhang.dao.UserDao; 17 import com.wzhang.dao.impl.UserDaoImpl; 18 import com.wzhang.domain.UserBean; 19 20 /** 21 * 自定义用户与权限的关系 22 * @author wzhang 23 * 24 */ 25 public class CustomUserDetailsService implements UserDetailsService { 26 protected static Logger logger = Logger.getLogger("service"); 27 private UserDao userDAO = new UserDaoImpl(); 28 29 /** 30 * 根据用户名获取用户-权限等用户信息 31 */ 32 public UserDetails loadUserByUsername(String username) 33 throws UsernameNotFoundException { 34 35 UserDetails user = null; 36 try { 37 UserBean dbUser = userDAO.getUser(username); 38 user = new User(dbUser.getUserName(), dbUser.getPassword().toLowerCase(), true, true, true, true,getAuthorities(dbUser)); 39 } catch (Exception e) { 40 logger.error("Error in retrieving user"); 41 throw new UsernameNotFoundException("Error in retrieving user"); 42 } 43 return user; 44 } 45 46 /** 47 * 获得访问角色权限 48 * 49 * @param access 50 * @return 51 */ 52 private Collection<GrantedAuthority> getAuthorities(UserBean dbUser) { 53 54 List<GrantedAuthority> authList = new ArrayList<GrantedAuthority>(2); 55 56 // 所有的用户默认拥有ROLE_USER权限 57 logger.debug("Grant ROLE_USER to this user"); 58 authList.add(new SimpleGrantedAuthority(RoleConstants.ROLE_USER)); 59 60 // 如果参数access为1.则拥有ROLE_ADMIN权限 61 if (dbUser.getRole().getRoleName().equals(RoleConstants.ROLE_ADMIN)) { 62 logger.debug("Grant ROLE_ADMIN to this user"); 63 authList.add(new SimpleGrantedAuthority(RoleConstants.ROLE_ADMIN)); 64 } 65 66 return authList; 67 } 68 69 }
6.自定义Filter实现类CustomFilterSecurityInterceptor;
1 package com.wzhang.web.filter; 2 3 import java.io.IOException; 4 5 import javax.servlet.Filter; 6 import javax.servlet.FilterChain; 7 import javax.servlet.FilterConfig; 8 import javax.servlet.ServletException; 9 import javax.servlet.ServletRequest; 10 import javax.servlet.ServletResponse; 11 12 import org.apache.log4j.Logger; 13 import org.springframework.security.access.SecurityMetadataSource; 14 import org.springframework.security.access.intercept.AbstractSecurityInterceptor; 15 import org.springframework.security.access.intercept.InterceptorStatusToken; 16 import org.springframework.security.web.FilterInvocation; 17 import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource; 18 19 /** 20 * 配置过滤器 21 * @author wzhang 22 * 23 */ 24 public class CustomFilterSecurityInterceptor extends AbstractSecurityInterceptor implements Filter { 25 private static final Logger logger = Logger 26 .getLogger(CustomFilterSecurityInterceptor.class); 27 private FilterInvocationSecurityMetadataSource securityMetadataSource; 28 29 /** 30 * Method that is actually called by the filter chain. Simply delegates to 31 * the {@link #invoke(FilterInvocation)} method. 32 * 33 * @param request 34 * the servlet request 35 * @param response 36 * the servlet response 37 * @param chain 38 * the filter chain 39 * 40 * @throws IOException 41 * if the filter chain fails 42 * @throws ServletException 43 * if the filter chain fails 44 */ 45 public void doFilter(ServletRequest request, ServletResponse response, 46 FilterChain chain) throws IOException, ServletException { 47 FilterInvocation fi = new FilterInvocation(request, response, chain); 48 invoke(fi); 49 50 } 51 52 public void invoke(FilterInvocation fi) throws IOException, 53 ServletException { 54 InterceptorStatusToken token = super.beforeInvocation(fi); 55 try { 56 fi.getChain().doFilter(fi.getRequest(), fi.getResponse()); 57 } catch (Exception e) { 58 logger.error(e.getStackTrace()); 59 } finally { 60 super.afterInvocation(token, null); 61 } 62 } 63 64 public FilterInvocationSecurityMetadataSource getSecurityMetadataSource() { 65 return this.securityMetadataSource; 66 } 67 68 public Class<? extends Object> getSecureObjectClass() { 69 return FilterInvocation.class; 70 } 71 72 public SecurityMetadataSource obtainSecurityMetadataSource() { 73 return this.securityMetadataSource; 74 } 75 76 public void setSecurityMetadataSource( 77 FilterInvocationSecurityMetadataSource newSource) { 78 this.securityMetadataSource = newSource; 79 } 80 81 public void destroy() { 82 // TODO Auto-generated method stub 83 84 } 85 86 public void init(FilterConfig arg0) throws ServletException { 87 // TODO Auto-generated method stub 88 89 } 90 91 }
7.配置web.xml;
1 <?xml version="1.0" encoding="UTF-8"?> 2 <web-app xmlns="http://java.sun.com/xml/ns/j2ee" 3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 4 xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"> 5 <display-name>Hello World-Spring MVC Security</display-name> 6 <!-- Spring MVC --> 7 <servlet> 8 <servlet-name>spring</servlet-name> 9 <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> 10 <load-on-startup>1</load-on-startup> 11 </servlet> 12 <servlet-mapping> 13 <servlet-name>spring</servlet-name> 14 <url-pattern>/</url-pattern> 15 </servlet-mapping> 16 17 <listener> 18 <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> 19 </listener> 20 21 <context-param> 22 <param-name>contextConfigLocation</param-name> 23 <param-value> 24 /WEB-INF/spring-security.xml 25 </param-value> 26 </context-param> 27 28 <!-- Spring Security --> 29 <filter> 30 <filter-name>springSecurityFilterChain</filter-name> 31 <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> 32 </filter> 33 34 <filter-mapping> 35 <filter-name>springSecurityFilterChain</filter-name> 36 <url-pattern>/*</url-pattern> 37 </filter-mapping> 38 </web-app>
需要注意的是:
a.这里的servlet-name配置的是spring,后面的springmvc配置文件就得命名为spring-servlet.xml;
b.spring-security.xml路径需要配置正确;
c.不要忘记配置spring security的flter。
8.配置spring-security.xml;
1 <beans:beans xmlns="http://www.springframework.org/schema/security" 2 xmlns:beans="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 3 xsi:schemaLocation="http://www.springframework.org/schema/beans 4 http://www.springframework.org/schema/beans/spring-beans-3.0.xsd 5 http://www.springframework.org/schema/security 6 http://www.springframework.org/schema/security/spring-security-3.2.xsd"> 7 8 9 <http auto-config="true" 10 access-denied-page="/denied"> 11 <!-- <intercept-url pattern="/admin**" access="hasRole('ROLE_ADMIN')" /> 12 <intercept-url pattern="/index" access="hasRole('ROLE_USER')" /> --> 13 <form-login login-page="/login" default-target-url="/welcome" 14 authentication-failure-url="/login?error" username-parameter="username" 15 password-parameter="password" /> 16 <logout invalidate-session="true" logout-success-url="/login?logout" /> 17 <custom-filter ref="customFilter" before="FILTER_SECURITY_INTERCEPTOR"/> 18 </http> 19 20 21 22 <!-- 认证管理器,实现用户认证的入口,主要实现UserDetailsService接口即可 --> 23 <authentication-manager alias="authenticationManager"> 24 <authentication-provider user-service-ref="customUserDetailsService"> 25 <password-encoder ref="passwordEncoder" /> 26 </authentication-provider> 27 </authentication-manager> 28 29 <!-- 密码加密方式 --> 30 <beans:bean id="passwordEncoder" 31 class="org.springframework.security.authentication.encoding.Md5PasswordEncoder" /> 32 33 <!-- 自定义用户验证服务 --> 34 <beans:bean id="customUserDetailsService" 35 class="com.wzhang.security.service.CustomUserDetailsService" /> 36 37 <!-- 资源源数据定义,即定义某一资源可以被哪些角色访问 --> 38 <beans:bean id="customSecurityMetadataSource" 39 class="com.wzhang.security.service.CustomFilterInvocationSecurityMetadataSource" /> 40 41 <!-- 自定义访问决策器,决定某个用户具有的角色,是否有足够的权限去访问某个资源 --> 42 <beans:bean id="customAccessDecisionManager" 43 class="com.wzhang.security.service.CustomAccessDecisionManager" /> 44 <!-- <beans:property name="allowIfAllAbstainDecisions" 45 value="false" /> 46 <beans:property name="decisionVoters"> 47 <beans:list> 48 <beans:bean class="org.springframework.security.access.vote.RoleVoter" /> 49 <beans:bean 50 class="org.springframework.security.access.vote.AuthenticatedVoter" /> 51 </beans:list> 52 </beans:property> --> 53 <!-- </beans:bean> --> 54 55 56 <beans:bean id="customFilter" 57 class="com.wzhang.web.filter.CustomFilterSecurityInterceptor"> 58 <beans:property name="authenticationManager" ref="authenticationManager" /> 59 <beans:property name="accessDecisionManager" ref="customAccessDecisionManager" /> 60 <beans:property name="securityMetadataSource" ref="customSecurityMetadataSource" /> 61 </beans:bean> 62 63 </beans:beans>
注意事项:
a.<intercept-url /> 中配置的资源,已经修改为从数据库获取;
b.需要将自定义的AccessDecisionManager,FilterInvocationSecurityMetadataSource,以及authenticationManager注入到自定义filter中。
9.实例中使用了SpringMVC框架所以还需要配置spring-servlet.xml
1 <beans xmlns="http://www.springframework.org/schema/beans" 2 xmlns:context="http://www.springframework.org/schema/context" 3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 4 xsi:schemaLocation=" 5 http://www.springframework.org/schema/beans 6 http://www.springframework.org/schema/beans/spring-beans-3.0.xsd 7 http://www.springframework.org/schema/context 8 http://www.springframework.org/schema/context/spring-context-3.0.xsd"> 9 10 <!-- 扫描注解组件并且自动的注入spring beans中. --> 11 <!-- 例如,扫描@Controller 和@Service下的文件.所以确保此base-package设置正确. --> 12 <context:component-scan base-package="com.wzhang.*" /> 13 14 <!-- 定义一个视图解析器。pages下的jsp文件映射到controller--> 15 <bean 16 class="org.springframework.web.servlet.view.InternalResourceViewResolver"> 17 <property name="prefix"> 18 <value>/WEB-INF/pages/</value> 19 </property> 20 <property name="suffix"> 21 <value>.jsp</value> 22 </property> 23 </bean> 24 25 <bean id="resourceDao" class="com.wzhang.dao.impl.ResourceDaoImpl" ></bean> 26 </beans>
10.新建几个页面
a.login.jsp,登录页面,任何人都能访问;
b.admin.jsp,需要admin权限才能访问;
c.index.jsp,user权限,admin权限均可访问;
d.hello.jsp欢迎页面,登录后跳转,无需权限也能访问;
11.新建一个Controller,针对不同的页面定义不同的@RequestMapping;
1 package com.wzhang.web.controller; 2 3 import org.springframework.stereotype.Controller; 4 import org.springframework.web.bind.annotation.RequestMapping; 5 import org.springframework.web.bind.annotation.RequestMethod; 6 import org.springframework.web.bind.annotation.RequestParam; 7 import org.springframework.web.servlet.ModelAndView; 8 import org.apache.log4j.Logger; 9 10 @Controller 11 public class HelloController { 12 13 protected static Logger logger = Logger.getLogger("controller"); 14 15 @RequestMapping(value = { "/", "/welcome" }, method = RequestMethod.GET) 16 public ModelAndView welcome() { 17 18 ModelAndView model = new ModelAndView(); 19 model.addObject("title", "Welcome - Spring Security Hello World"); 20 model.addObject("message", "This is welcome page!"); 21 model.setViewName("hello"); 22 return model; 23 24 } 25 26 @RequestMapping(value = "/admin**", method = RequestMethod.GET) 27 public ModelAndView adminPage() { 28 29 ModelAndView model = new ModelAndView(); 30 model.addObject("title", "Admin - Spring Security Hello World"); 31 model.addObject("message", "This is protected page!"); 32 model.setViewName("admin"); 33 34 return model; 35 36 } 37 38 //Spring Security see this : 39 @RequestMapping(value = "/login**", method = RequestMethod.GET) 40 public ModelAndView loginPage( 41 @RequestParam(value = "error", required = false) String error, 42 @RequestParam(value = "logout", required = false) String logout) { 43 44 ModelAndView model = new ModelAndView(); 45 if (error != null) { 46 model.addObject("error", "Invalid username and password!"); 47 } 48 49 if (logout != null) { 50 model.addObject("msg", "You've been logged out successfully."); 51 } 52 model.setViewName("login"); 53 54 return model; 55 56 } 57 58 @RequestMapping(value = "/index**", method = RequestMethod.GET) 59 public ModelAndView indexPage() { 60 61 ModelAndView model = new ModelAndView(); 62 model.addObject("title", "User - Home Page"); 63 model.addObject("message", "This is User access page!"); 64 model.setViewName("index"); 65 66 return model; 67 68 } 69 70 71 /** 72 * 指定无访问额权限页面 73 * 74 * @return 75 */ 76 @RequestMapping(value = "/denied**", method = RequestMethod.GET) 77 public String deniedPage() { 78 79 logger.debug("Received request to show denied page"); 80 81 return "denied"; 82 83 } 84 85 }
四、测试
编译运行,
a.输入http://localhost:8080/spring-mvc-security-helloworld/admin 将会跳转登录页面:
b.输入user,user,登录后访问刚刚的网页:
c.退出登录,重新使用admin,admin访问
从上述结果可以看出我们的自定义用户,权限,资源认证授权起到了作用。
五、总结
Spring Security3还有很多内容,比如在登录成功后的做一些处理,自定义一些provider等等。