springmvc+hibernate+security整合笔记

之前我们整理了关于springMVC的相关文章,包括springMVC入门案例,源代码分析,实战演练以及与hibernate,c3p0等,现在我们要在这个基础上整合spring security3 的相关功能,同样,我们以实例的方式来展示相关配置内容和代码开发,并不追求细节,因为说实话,细节上的东西我也没研究,只是做出一个DEMO来,之后我会发表相关的文章。


1、添加jar包

jar包还是那些jar,可以参考上一篇文章的图片。


2、配置security.xml

这里我们新建了一个文件起名为application-security.xml,然后通过import标签将该文件引入到主配置文件中,如果这个文件现在什么都不配置,是这个样子的:

<?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:sec="http://www.springframework.org/schema/security"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                        http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.0.xsd">
</beans>

现在我们要做的就是往里面增加相关的配置,首先我们要新增的是验证基本配置的入口:

        <!-- 验证基本配置 入口 -->
	<sec:http entry-point-ref="authenticationProcessingFilterEntryPoint" access-denied-page="/jsp/security/accessDenied.jsp" >
		<sec:logout/>
		<sec:remember-me/>
		
		<!-- 匿名用户可以访问 不做校验 -->
		<sec:intercept-url pattern="/js/**" access="IS_AUTHENTICATED_ANONYMOUSLY"/>
		<sec:intercept-url pattern="/jsp/security/login.jsp" filters="none"/>
		<sec:intercept-url pattern="/jsp/security/accessDenied.jsp" access="IS_AUTHENTICATED_ANONYMOUSLY"/>
		<sec:intercept-url pattern="/jsp/security/sessiontimeout.jsp" access="IS_AUTHENTICATED_ANONYMOUSLY"/>
		
		<sec:form-login 
			authentication-success-handler-ref="loginAuthenticationSuccessHandler"
			authentication-failure-handler-ref="loginAuthenticationFailureHandler" />
		
		<!-- session管理器 设置超时以及重复登陆次数--> 
		<sec:session-management invalid-session-url="/jsp/security/login.jsp">
			<sec:concurrency-control max-sessions="10" error-if-maximum-exceeded="false"/>
		</sec:session-management>
		
		 
		<!-- 过滤器 发生在spring内置过滤器之前
		<sec:custom-filter ref="loginFilter" position="FORM_LOGIN_FILTER"/>
		 -->
		<sec:custom-filter ref="securityFilter" before="FILTER_SECURITY_INTERCEPTOR"/>
	</sec:http>

在entry-point-ref,我们的处理类是:authenticationProcessingFilterEntryPoint

	<!-- 验证期入口 登陆页面 -->
	<bean id="authenticationProcessingFilterEntryPoint" class="com.itcast.securtiy.service.handler.MyAuthenticationProcessingFilterEntryPoint">
		<property name="defaultTargetUrl" value="/jsp/security/login.jsp"></property>
		<property name="targetConfigUrl" value="/jsp/security/config.jsp"></property>
	</bean>


他的源代码是:

package com.itcast.securtiy.service.handler;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;

public class MyAuthenticationProcessingFilterEntryPoint implements AuthenticationEntryPoint{

	private String defaultTargetUrl;
	private String targetConfigUrl;
	
	public String getDefaultTargetUrl() {
		return defaultTargetUrl;
	}

	public void setDefaultTargetUrl(String defaultTargetUrl) {
		this.defaultTargetUrl = defaultTargetUrl;
	}

	public String getTargetConfigUrl() {
		return targetConfigUrl;
	}

	public void setTargetConfigUrl(String targetConfigUrl) {
		this.targetConfigUrl = targetConfigUrl;
	}

	@Override
	public void commence(HttpServletRequest request, HttpServletResponse response,
			AuthenticationException object) throws IOException, ServletException {
		//如果没有配置 就重定向到配置页面
		// ---
		//反之 重定向到登陆页面
		response.sendRedirect(request.getContextPath() + "/" + defaultTargetUrl);
	}
	
}


在配置中,我们发现我们使用了form-login标签,这个标签的作用就是用以处理登陆后的处理,我们提供了两个类分别是loginAuthenticationSuccessHandler和loginAuthenticationFailureHandler用于做登陆成功和失败的处理者,源代码如下:

package com.itcast.securtiy.service.handler;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;

public class LoginAuthenticationSuccessHandler implements AuthenticationSuccessHandler{
	private String defaultTargetUrl;
	public void setDefaultTargetUrl(String defaultTargetUrl) {
		this.defaultTargetUrl = defaultTargetUrl;
	}
	@Override
	public void onAuthenticationSuccess(HttpServletRequest request,
			HttpServletResponse response, Authentication object) throws IOException,
			ServletException {
		System.out.println("验证通过,转入主页");
		//指向主页 或者 个人信息页面等
		response.sendRedirect(request.getContextPath() + "/" + defaultTargetUrl);
	}
}

package com.itcast.securtiy.service.handler;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;


public class LoginAuthenticationFailureHandler implements AuthenticationFailureHandler{
	
	private String defaultFailureUrl;
	public void setDefaultFailureUrl(String defaultFailureUrl) {
		this.defaultFailureUrl = defaultFailureUrl;
	}


	@Override
	public void onAuthenticationFailure(HttpServletRequest request,
			HttpServletResponse response, AuthenticationException arg2)
			throws IOException, ServletException {
		System.out.println("验证失败,转入登陆页");
		//在session可以设置错误信息 在页面中get显示
		response.sendRedirect(request.getContextPath() + "/" + defaultFailureUrl);
	}

}

这两个类也已经在我们的xml中配置了:

	<!-- 验证成功处理器 进入主页面 -->
	<bean id="loginAuthenticationSuccessHandler" class="com.itcast.securtiy.service.handler.LoginAuthenticationSuccessHandler">
		<property name="defaultTargetUrl" value="/jsp/security/index.jsp"></property>
	</bean>
	<!-- 验证失败处理器 回到登陆页面 -->
	<bean id="loginAuthenticationFailureHandler" class="com.itcast.securtiy.service.handler.LoginAuthenticationFailureHandler">
		<property name="defaultFailureUrl" value="/jsp/security/login.jsp"></property>
	</bean>
接下来我们在看一下 我们配置的过滤器,这里我们注释了一个过滤器,只使用了securityFilter过滤器,这些自定义的过滤器都会发生在内置过滤器之前,我们可以发现,springsecurity的机制其实就是建立在过滤器基础上的,而过滤器可以很好的拦截http请求。这个过滤器的真身是:

	<!-- 认证过滤器  -->
	<bean id="securityFilter" class="com.itcast.securtiy.service.filter.MySecurityFilter">
	<!-- 用户拥有的权限 -->
    	<property name="authenticationManager" ref="myAuthenticationManager" />
    	<!-- 用户是否拥有所请求资源的权限 -->
    	<property name="accessDecisionManager" ref="myAccessDecisionManager" />
    	<!-- 资源与权限对应关系 -->
    	<property name="securityMetadataSource" ref="mySecurityMetadataSource" />
	</bean>
他是一个认真过滤器,他有几个重要的属性,  authenticationManager  ,accessDecisionManager ,securityMetadataSource 这几个属性都是必须要有的,每一个属性都负责不同的工作,authenticationManager  负责存储用户拥有的权限,而accessDecisionManager 得作用是判断用户是否拥有所请求资源的权限,securityMetadataSource 负责存储资源和权限的对应关系。在这里我们听到了几个关键的词,像资源,其实说白了,springsecurity和我们自己做权限管理的思路差不多,都是用户-角色-权限的分配方式,但作为一个框架需要对这些概念进行抽象,所以他抽象除了资源的概念,而我们要做的就是将我们平时用的对‘权限’的定义转化为资源,做法就是用它提供给我们的泪进行包装。现在我们对这些类一一讲解,首先看到我们最外层的MySecurityFilter这个过滤器,源代码如下:

package com.itcast.securtiy.service.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.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{

	//与applicationContext-security.xml里的myFilter的属性securityMetadataSource对应,
	//其他的两个组件,已经在AbstractSecurityInterceptor定义
	private FilterInvocationSecurityMetadataSource securityMetadataSource;
	public FilterInvocationSecurityMetadataSource getSecurityMetadataSource() {
		return securityMetadataSource;
	}
	public void setSecurityMetadataSource(
			FilterInvocationSecurityMetadataSource securityMetadataSource) {
		this.securityMetadataSource = securityMetadataSource;
	}

	@Override
	public Class<? extends Object> getSecureObjectClass() {
		//下面的MyAccessDecisionManager的supports方面必须放回true,否则会提醒类型错误
		return FilterInvocation.class;
	}

	@Override
	public SecurityMetadataSource obtainSecurityMetadataSource() {
		return this.securityMetadataSource;
	}

	@Override
	public void destroy() {
		
	}

	@Override
	public void doFilter(ServletRequest request, ServletResponse response,
			FilterChain chain) throws IOException, ServletException {
		FilterInvocation fi = new FilterInvocation(request, response, chain);
		invoke(fi);
	}

	private void invoke(FilterInvocation fi) throws IOException, ServletException {
		// object为FilterInvocation对象
		//1.获取请求资源的权限
		//执行Collection<ConfigAttribute> attributes = SecurityMetadataSource.getAttributes(object);
		//2.是否拥有权限
		//获取安全主体,可以强制转换为UserDetails的实例
		//1) UserDetails
		// Authentication authenticated = authenticateIfRequired();
		//this.accessDecisionManager.decide(authenticated, object, attributes);
		//用户拥有的权限
		//2) GrantedAuthority
		//Collection<GrantedAuthority> authenticated.getAuthorities()
		System.out.println("用户发送请求! ");
		InterceptorStatusToken token = null;
		
//		try {
			token = super.beforeInvocation(fi);
//		} catch (Exception e) {
			//如果用户的角色没有该访问权限 就抛 no right 错误
			if(e.getMessage().equals("no right")){
				fi.getResponse().sendRedirect(fi.getRequest().getContextPath() + "/jsp/security/accessDenied.jsp");
			}
//		}
		try {
			fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
		} catch(Exception e){
			e.printStackTrace();
		} finally {
			super.afterInvocation(token, null);
		}
	}
	
	@Override
	public void init(FilterConfig filterConfig) throws ServletException {
		
	}

}


这个过滤器的invoke方法是一个核心方法,在方法体中,我们可以看到核心代码:

token = super.beforeInvocation(fi);
fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
这两句的意思其实就是说 这执行内置的过滤器之前,先执行我自定义的过滤机制。



在这个类的最上面 ,我们可以看到它显示的定义了属性MySecurityMetadataSource,这个属性对应的类是:com.itcast.securtiy.service.filter.MySecurityMetadataSource,作用:用于加载资源与权限对应关系。再用户请求时,过滤器会使用这个MySecurityMetadataSource做用户的权限判断。我们来看一下这个类的代码是什么:

package com.itcast.securtiy.service.filter;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

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 org.springframework.security.web.util.AntUrlPathMatcher;
import org.springframework.security.web.util.UrlMatcher;

import com.itcast.securtiy.service.model.Resource;
import com.itcast.securtiy.service.model.Role;

/**
 * 作用:用于加载资源与权限对应关系
 * @author lvpeng
 *
 */
public class MySecurityMetadataSource implements FilterInvocationSecurityMetadataSource{

	//该Map集合用于存放资源-角色的对应关系
	private static Map<String,Collection<ConfigAttribute>> resourceMap = new HashMap<String,Collection<ConfigAttribute>>();
	
	//决定配置的路径是否匹配提交候选人URL
	private UrlMatcher urlMatcher = new AntUrlPathMatcher();
	
	@Override
	public Collection<ConfigAttribute> getAllConfigAttributes() {
		return null;
	}
	
	//返回所请求资源所需要的权限
	@Override
	public Collection<ConfigAttribute> getAttributes(Object object)
			throws IllegalArgumentException {
		
		Collection<ConfigAttribute> attribute = null;
		
		//如果是管理员用户就直接返回null
//		if(UserHelper.isAdmin){
//			return attribute;
//		}
		
		//获取URL的WEB应用程序的特定片段,不包括任何服务器的名称,上下文路径或者serlvet路径
		String requestUrl = ((FilterInvocation) object).getRequestUrl();
		System.out.println("requestUrl is " + requestUrl);
		
		if(resourceMap.size() == 0)
			loadResourceDefine();
		
		Set<String> keySet = resourceMap.keySet();
		for(String key: keySet){
			//pathMatchesUrl方法判断请求的URL是否匹配 如果有匹配的 就和返回保存又角色信息的configAttribute对象的集合
			if(urlMatcher.pathMatchesUrl(key,requestUrl)){
				attribute = resourceMap.get(key);
			}
		}
		//如果没有匹配的URL 表示这个URL没有和角色绑定 也可以放行
		return attribute;
	}

	
	/**
	 * 加载角色和资源的关系 正常应该是系统启动时就要提要加载作为缓存
	 * 这里我们使用临时加载 写死的信息
	 */
	private void loadResourceDefine() {
		Collection<ConfigAttribute> returnAttributes = null;
		
		//加载所有的资源信息以及他们对应的角色 这里我们写死 实际应该是从数据库查询 start
		Role admin = new Role(1,"ADMIN");
		Role user = new Role(1,"USER");
		
		Set<Role> adminRoles = new HashSet<Role>();
		adminRoles.add(admin);adminRoles.add(user);
		
		Set<Role> userRoles = new HashSet<Role>();
		userRoles.add(user);
		
		List<Resource> allResource = new ArrayList<Resource>();
		Resource adminjsp = new Resource(1,"admin","/jsp/security/admin.jsp");
		adminjsp.getRoles().add(admin);
		
		Resource indexjsp = new Resource(1,"index","/jsp/security/index.jsp");
		indexjsp.getRoles().add(admin);
		indexjsp.getRoles().add(user);
		
		
//		Resource loginjsp = new Resource(1,"admin","/jsp/security/admin.jsp");
//		loginjsp.setRoles(adminRoles);
//		allResource.add(loginjsp);
		
		allResource.add(adminjsp);allResource.add(indexjsp);
		//加载所有的资源信息以及他们对应的角色 这里我们写死 实际应该是从数据库查询 end
		
		
		//遍历所有的权限和角色 将它们构造成spring的认证对象 ConfigAttribute 放置在resourceMap中
		for(Resource res : allResource){
			String url = res.getUrl(); //获取访问路径
//			for(Role role : res.getRoles()){ //这里的遍历可以省略 因为我们这儿做判断的一句是权限 也可以存放角色ID
				//注意这里的SecurityConfig的参数和要MyUserDetailServiceImpl类的obtionGrantedAuthorities方法保持的参数一致:
				/**
				 * ...
				 *  遍历用户的权限集合 构造Set<GrantedAuthority> authSet
				 *  for(Resource rs : finalRes){
		 				authSet.add(new GrantedAuthorityImpl(rs.getName().toUpperCase()));
		 			}
				 *  ...
				 */
				ConfigAttribute configAttribute = new SecurityConfig(url); //获取角色对应的权限  可以存权限也可以存角色名  取决你你对比时拿什么来对比
				if(resourceMap.containsKey(url)){
					//更新缓存或者修改新增时调用
					returnAttributes = resourceMap.get(url);
					returnAttributes.add(configAttribute);
				}else{
					//第一次启动或者初始化时调用
					returnAttributes = new ArrayList<ConfigAttribute>();
					returnAttributes.add(configAttribute);
				}
				resourceMap.put(url, returnAttributes);
//			}
		}
	}

	@Override
	public boolean supports(Class<?> arg0) {
		return true;
	}

}

这个类其实没什么难度,他实现了FilterInvocationSecurityMetadataSource接口,重写了一个关键的方法:getAttributes,这个方法的作用就是返回资源对应的权限,而这些资源是指什么呢,就是指用户有没有权限访问这个url,所以这些资源我们会在系统启动时就加载到内存缓存中,这里我没有使用db,所以都是用的死数据,实际开发中可以直接替换成从数据库查询就可以。在getAttributes方法中,我们可以看到逻辑很简单:

		//获取URL的WEB应用程序的特定片段,不包括任何服务器的名称,上下文路径或者serlvet路径
		String requestUrl = ((FilterInvocation) object).getRequestUrl();
		System.out.println("requestUrl is " + requestUrl);
		
		if(resourceMap.size() == 0)
			loadResourceDefine();
		
		Set<String> keySet = resourceMap.keySet();
		for(String key: keySet){
			//pathMatchesUrl方法判断请求的URL是否匹配 如果有匹配的 就和返回保存又角色信息的configAttribute对象的集合
			if(urlMatcher.pathMatchesUrl(key,requestUrl)){
				attribute = resourceMap.get(key);
			}
		}
		//如果没有匹配的URL 表示这个URL没有和角色绑定 也可以放行
		return attribute;

获取用户请求的url,与resourceMap中的url做对比,如果一样,就将该url对应的Collection<ConfigAttribute>返回。而这个Collection<ConfigAttribute>是如何构造出来的呢,我们看loadResourceDefine方法:

/**
	 * 加载角色和资源的关系 正常应该是系统启动时就要提要加载作为缓存
	 * 这里我们使用临时加载 写死的信息
	 */
	private void loadResourceDefine() {
		Collection<ConfigAttribute> returnAttributes = null;
		
		//加载所有的资源信息以及他们对应的角色 这里我们写死 实际应该是从数据库查询 start
		Role admin = new Role(1,"ADMIN");
		Role user = new Role(1,"USER");
		
		Set<Role> adminRoles = new HashSet<Role>();
		adminRoles.add(admin);adminRoles.add(user);
		
		Set<Role> userRoles = new HashSet<Role>();
		userRoles.add(user);
		
		List<Resource> allResource = new ArrayList<Resource>();
		Resource adminjsp = new Resource(1,"admin","/jsp/security/admin.jsp");
		adminjsp.getRoles().add(admin);
		
		Resource indexjsp = new Resource(1,"index","/jsp/security/index.jsp");
		indexjsp.getRoles().add(admin);
		indexjsp.getRoles().add(user);
		
		
//		Resource loginjsp = new Resource(1,"admin","/jsp/security/admin.jsp");
//		loginjsp.setRoles(adminRoles);
//		allResource.add(loginjsp);
		
		allResource.add(adminjsp);allResource.add(indexjsp);
		//加载所有的资源信息以及他们对应的角色 这里我们写死 实际应该是从数据库查询 end
		
		
		//遍历所有的权限和角色 将它们构造成spring的认证对象 ConfigAttribute 放置在resourceMap中
		for(Resource res : allResource){
			String url = res.getUrl(); //获取访问路径
//			for(Role role : res.getRoles()){ //这里的遍历可以省略 因为我们这儿做判断的一句是权限 也可以存放角色ID
				//注意这里的SecurityConfig的参数和要MyUserDetailServiceImpl类的obtionGrantedAuthorities方法保持的参数一致:
				/**
				 * ...
				 *  遍历用户的权限集合 构造Set<GrantedAuthority> authSet
				 *  for(Resource rs : finalRes){
		 				authSet.add(new GrantedAuthorityImpl(rs.getName().toUpperCase()));
		 			}
				 *  ...
				 */
				ConfigAttribute configAttribute = new SecurityConfig(url); //获取角色对应的权限  可以存权限也可以存角色名  取决你你对比时拿什么来对比
				if(resourceMap.containsKey(url)){
					//更新缓存或者修改新增时调用
					returnAttributes = resourceMap.get(url);
					returnAttributes.add(configAttribute);
				}else{
					//第一次启动或者初始化时调用
					returnAttributes = new ArrayList<ConfigAttribute>();
					returnAttributes.add(configAttribute);
				}
				resourceMap.put(url, returnAttributes);
//			}
		}
	}


这个方法就比较乱了 ,让我都看不清头绪,一句话就是该方法将不同的角色对应的权限都查出来,然后遍历,封装成ConfigAttribute对象,保存在Collection<ConfigAttribute>中,相当于一个集合封装了所有的url,这里其实可以扩展,我们这里让Collection<ConfigAttribute>存储的是url,只要判断用户请求的url和其角色对应的url能否匹配就OK了,你也可以判断用户的url输入的xxx角色和用户的角色是否匹配也可以。这取决你自己的的控制力度。这里重点是看懂Collection<ConfigAttribute>的存储结构。

说了这么多,我们现在做的都是在准备数据,准备权限,而真正调用getAttribute方法的的人还未出现,是谁在什么时机调用了这个方法呢,我们想肯定是在得知用户角色以后才能调用该方法,因为要通过其角色来获取对应的权限,所以肯定是在用户登录以后通过一个类来调用的,而这个类就是MyUserDetailServiceImpl类:

package com.itcast.securtiy.service.filter;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.springframework.dao.DataAccessException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.GrantedAuthorityImpl;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;

import com.itcast.securtiy.service.model.Resource;
import com.itcast.securtiy.service.model.Role;


/**
 * 作用:用于获取用户拥有的权限
 * @author lvpeng
 *
 */
public class MyUserDetailServiceImpl implements UserDetailsService{
	/**
	 * 查询用户信息
	 */
	public UserDetails loadUserByUsername(String username)
			throws UsernameNotFoundException, DataAccessException {
		//这里一般都是要从数据库中把用户查询出来,这里我们就写死吧,用户密码一致
		String password = username;
		boolean enables = true;
		boolean accountNonExpired = true;
		boolean credentialsNonExpired = true;
		boolean accountNonLocked = true;
		Collection<GrantedAuthority> grantedAuths = obtionGrantedAuthorities(username);
		
		//注意这个User是org.springframework.security.core.userdetails.User;
		User userdetail = new User(username,password, enables, 
				accountNonExpired, credentialsNonExpired, 
				accountNonLocked, grantedAuths);
		return userdetail;
	}

	/**
	 * GrantedAuthority[]为可访问该资源的角色数组。
	 */
	private Set<GrantedAuthority> obtionGrantedAuthorities(
			String username) {
		Set<GrantedAuthority> authSet = new HashSet<GrantedAuthority>();
		Set<Role> roles = new HashSet<Role>();
		
		//得到用户的角色 以及角色里封装的权限URL 一般从数据库获取 这里写死
		Resource indexjsp = new Resource(1,"index","/jsp/security/index.jsp");
		//所有用户都是普通用户角色
		Role user = new Role(1,"USER");
		user.getResources().add(indexjsp);
		roles.add(user); //每个人都是普通用户
		
		//如果用户是admin 则再添加ADMIN的角色
		if(username.equals("admin")){
			Resource adminjsp = new Resource(1,"admin","/jsp/security/admin.jsp");
			Role admin = new Role(1,"ADMIN");
			admin.getResources().add(adminjsp);
			roles.add(admin);
		}
 		//定义一个集合 存放最终用户所拥有的权限
		List<Resource> finalRes = new ArrayList<Resource>();
 		for(Role role : roles){
 			Set<Resource> res = role.getResources();
 			for(Resource r : res){
 				finalRes.add(r);
 			}
 		}
 		
 		//遍历用户的权限集合 构造Set<GrantedAuthority> authSet  构造权限是 是将getUrl放置的 比较的时候要比较url
 		for(Resource rs : finalRes){ 
 			//这里存放的是url 所以在座权限比较时  要在resourceMap的 value中 也要传入url作为参数具体代码是:MySecurityMetadataSource类的117行
 			authSet.add(new GrantedAuthorityImpl(rs.getUrl()));
 		}
 		
		return authSet;
	}
}

在类中注意的问题时使用的User是:

		//注意这个User是org.springframework.security.core.userdetails.User;
		User userdetail = new User(username,password, enables, 
				accountNonExpired, credentialsNonExpired, 
				accountNonLocked, grantedAuths);



而真正对url进行比对,执行权限判断的类是:

package com.itcast.securtiy.service.filter;

import java.util.Collection;
import java.util.Iterator;

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;

/**
 * 认证过滤器 用户是否拥有所请求资源的权限
 * @author lvpeng
 *
 */
public class MyAccessDecisionManager implements AccessDecisionManager{

	@Override
	public void decide(Authentication authentication, Object object,
			Collection<ConfigAttribute> configAttributes) throws AccessDeniedException,
			InsufficientAuthenticationException {
		if(configAttributes == null){
			return ;
		}
		//请求的资源所需要的权限
		Iterator<ConfigAttribute> iterator = configAttributes.iterator();
		//获取当前用户具有的角色信息
		Collection<GrantedAuthority> grantedAuthorities = authentication.getAuthorities();
		//对请求的资源所需要的权限进行遍历
		while(iterator.hasNext()) {
			ConfigAttribute configAttribute = iterator.next();
			//访问所请求资源所需要的权限
			String needUrl = configAttribute.getAttribute();
			System.out.println("needLimit is " + needUrl);
			//对当前用户具有的角色信息进行遍历
			for(GrantedAuthority ga : grantedAuthorities) {
				System.out.println("ga.getAuthority():"+ga.getAuthority());
				//获取当前用户的角色名字与请求的资源所需要的角色的名称进行比对 如果匹配就返回
				//ga.getAuthority() 是获取构造权限时的参数
				if(needUrl.equals(ga.getAuthority())) {
					return;
				}
			}
		}
		//如果当前用户的角色名字和请求资源所需要的角色名称不匹配 就跑错
		throw new AccessDeniedException("no right");
	}

	@Override
	public boolean supports(ConfigAttribute arg0) {
		return true;
	}

	@Override
	public boolean supports(Class<?> arg0) {
		return true;
	}

}

如此,3个类分别负责了:资源权限数据准备,资源权限获取,资源权限对比三个功能。唯一有点绕的是什么呢,就是我们需要把我们数据库中自定义的这些个权限啊什么的封装成他为我们提供好的封装类。如果对这些类不是很熟悉,肯定是写不对得。虽然我们要为此学习很多的东西,但作为一个框架,这是不得不付出的。


这样我们基本上就讲解完了所有的配置类和内容,但肯定很多人没看懂,而且我讲的也忽略了很多东西,我们帖一下security.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:sec="http://www.springframework.org/schema/security"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                        http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.0.xsd">
	
	<!-- 验证基本配置 入口 -->
	<sec:http entry-point-ref="authenticationProcessingFilterEntryPoint" access-denied-page="/jsp/security/accessDenied.jsp" >
		<sec:logout/>
		<sec:remember-me/>
		
		<!-- 匿名用户可以访问 不做校验 -->
		<sec:intercept-url pattern="/js/**" access="IS_AUTHENTICATED_ANONYMOUSLY"/>
		<sec:intercept-url pattern="/jsp/security/login.jsp" filters="none"/>
		<sec:intercept-url pattern="/jsp/security/accessDenied.jsp" access="IS_AUTHENTICATED_ANONYMOUSLY"/>
		<sec:intercept-url pattern="/jsp/security/sessiontimeout.jsp" access="IS_AUTHENTICATED_ANONYMOUSLY"/>
		
		<sec:form-login 
			authentication-success-handler-ref="loginAuthenticationSuccessHandler"
			authentication-failure-handler-ref="loginAuthenticationFailureHandler" />
		
		<!-- session管理器 设置超时以及重复登陆次数--> 
		<sec:session-management invalid-session-url="/jsp/security/login.jsp">
			<sec:concurrency-control max-sessions="10" error-if-maximum-exceeded="false"/>
		</sec:session-management>
		
		 
		<!-- 过滤器 发生在spring内置过滤器之前
		<sec:custom-filter ref="loginFilter" position="FORM_LOGIN_FILTER"/>
		 -->
		<sec:custom-filter ref="securityFilter" before="FILTER_SECURITY_INTERCEPTOR"/>
	</sec:http>
	
	<!-- 登陆过滤器 -->
	<bean id="loginFilter" class="com.itcast.securtiy.service.filter.MyLoginFilter">
		<property name="filterProcessesUrl" value="/j_spring_security_check"></property>
		<property name="authenticationSuccessHandler" ref="loginAuthenticationSuccessHandler"></property>
		<property name="authenticationFailureHandler" ref="loginAuthenticationFailureHandler"></property>
		<property name="authenticationManager" ref="myAuthenticationManager"></property>
	</bean>
	
	<!-- 验证成功处理器 进入主页面 -->
	<bean id="loginAuthenticationSuccessHandler" class="com.itcast.securtiy.service.handler.LoginAuthenticationSuccessHandler">
		<property name="defaultTargetUrl" value="/jsp/security/index.jsp"></property>
	</bean>
	<!-- 验证失败处理器 回到登陆页面 -->
	<bean id="loginAuthenticationFailureHandler" class="com.itcast.securtiy.service.handler.LoginAuthenticationFailureHandler">
		<property name="defaultFailureUrl" value="/jsp/security/login.jsp"></property>
	</bean>
	
	
	<!-- 认证过滤器  -->
	<bean id="securityFilter" class="com.itcast.securtiy.service.filter.MySecurityFilter">
		<!-- 用户拥有的权限 -->
    	<property name="authenticationManager" ref="myAuthenticationManager" />
    	<!-- 用户是否拥有所请求资源的权限 -->
    	<property name="accessDecisionManager" ref="myAccessDecisionManager" />
    	<!-- 资源与权限对应关系 -->
    	<property name="securityMetadataSource" ref="mySecurityMetadataSource" />
	</bean>
	
	<!-- 实现了UserDetailsService的Bean 使用它来获取用户的权限 -->
    <sec:authentication-manager alias="myAuthenticationManager">
        <sec:authentication-provider user-service-ref="myUserDetailServiceImpl" />
    </sec:authentication-manager>
    
    <!-- 认证过滤器的3个属性类 -->
	<bean id="myAccessDecisionManager" class="com.itcast.securtiy.service.filter.MyAccessDecisionManager"></bean>
	<bean id="mySecurityMetadataSource" class="com.itcast.securtiy.service.filter.MySecurityMetadataSource"></bean>
	<bean id="myUserDetailServiceImpl" class="com.itcast.securtiy.service.filter.MyUserDetailServiceImpl"></bean>
	
	
	<!-- 验证期入口 登陆页面 -->
	<bean id="authenticationProcessingFilterEntryPoint" class="com.itcast.securtiy.service.handler.MyAuthenticationProcessingFilterEntryPoint">
		<property name="defaultTargetUrl" value="/jsp/security/login.jsp"></property>
		<property name="targetConfigUrl" value="/jsp/security/config.jsp"></property>
	</bean>
	
	
	
</beans>

项目目录是:


下面我们就来测试一下是否行得通,因为我们的数据都是死数据,这里我们使用user和admin登录,user对应的角色就是普通用户角色,只能访问index.jsp 但不能访问admin.jsp 而管理员账户两个url都能访问。首先,我们用user登录:

我们发现我们可以成功登录主页,现在我们尝试点击链接“管理员进入”超链接,看能否进入admin.jsp

我们可以看到security提示我们权限不足了。

现在我们使用admin登录该系统:

尝试访问admin.jsp

我们发现是可以的。


改天我会将源代码发布出来,到时候大家下载下来再研究研究。






评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值