SpringSecurity简介与使用

     Spring Security是一个能够为基于Spring的企业应用系统提供描述性安全访问控制解决方案的安全框架。它提供了一组可以在Spring应用上下文中配置的Bean,充分利用了Spring IoC(依赖注入,也称控制反转)和AOP(面向切面编程)功能,为应用系统提供声明式的安全访问控制功能,减少了为企业系统安全控制编写大量重复代码的工作。
     Spring Security对Web安全性的支持大量地依赖于Servlet过滤器。这些过滤器拦截进入请求,并且在应用程序处理该请求之前进行某些安全处理。 Spring Security提供有若干个过滤器,它们能够拦截Servlet请求,并将这些请求转给认证和访问决策管理器处理,从而增强安全性
   接下来构建一个SpringSecurity示例:
   1、到官网上下载SpringSecurity需要的jar文件,实例中还使用到了其他第三方jar,这里就不一一链接了,所有jar都已放在demo中。
   2、新建web工程开始相关的配置工作:
    2.1、web.xml的配置:
   
<context-param>
	<param-name>contextConfigLocation</param-name>
	<param-value>
       classpath:applicationContext.xml,
       classpath:application-security.xml
   </param-value>
</context-param>

<listener>
	<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

<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>
     2.2、application-security.xml的配置:   
<!-- 指定login.jsp不提供安全拦截 -->
<http pattern="/login.jsp" security="none"></http>

<!-- access-denied-page:访问拒绝后跳转的页面 -->
<http access-denied-page="/accessDenied.jsp" auto-config="true">
<!-- 
<http use-expressions="true" auto-config="true" access-denied-page="/403.html"> -->
	<!--
	    form-login标签: 
		login-page:指定登录页面,不指定默认使用spring自带的登录页面
		always-use-default-target:是否使用登录成功后跳入指定网页,true使用
		default-target-url:登录成功后默认进入的目标网页,必须以"/"或"http://"开头,例如:“http://www.baidu.com”
	 	<form-login login-page="/login.jsp" />
	 -->
	<form-login always-use-default-target="false" default-target-url="http://www.baidu.com"/>
	
	<!--   
		logout标签:
		logout-url:指定注销的url连接,默认为/j_spring_security_logout
		logout-success-url:指定注销成功后跳转的连接,默认为/
		delete-cookies:删除指定的cookid集合,以逗号分割
		invalidate-session:指定注销后是否是session失效,默认是true
	 -->
	<logout logout-url="/myLinyLogout" logout-success-url="/loggedout.jsp" delete-cookies="JSESSIONID" invalidate-session="true"/>
	
	<remember-me />
	
	<!-- 拦截指定URL,并指定所需要的权限
	<intercept-url pattern="/admin.jsp" access="ROLE_ADMIN"/>
	<intercept-url pattern="/**" access="ROLE_USER"/>
	  -->
	<session-management invalid-session-url="/invalidSession.jsp">
		<!-- 同时只能支持2个人在线(两个人使用同一个账户登录),第三着登录系统强行是第一个登录用户失效
		<concurrency-control max-sessions="2" error-if-maximum-exceeded="false"/>
		-->
		<!-- 
			error-if-maximum-exceeded:当登录的用户数大于max-sessions指定的数量之后,系统默认的处理行为是使前者失效(先进先失效).
			默认值为false。
			设置为true,则后登录的用户无法登录到系统.
		 -->
		<concurrency-control max-sessions="1" error-if-maximum-exceeded="true"/>
	</session-management>
	
	<!-- 自定义安全拦截器 -->
	<custom-filter ref="mySecurityFilter" before="FILTER_SECURITY_INTERCEPTOR"/>
</http>

<!-- 配置过滤器 -->
<beans:bean id="mySecurityFilter" class="com.liny.sys.security.util.MySecurityFilter">
	<!-- 用户认证 -->
	<beans:property name="authenticationManager" ref="myAuthenticationManager" />
	<!-- 用户校验(用户是否拥有所请求资源的权限) -->
	<beans:property name="accessDecisionManager" ref="myAccessDecisionManager" />
	<!-- 获取请求资源所需要的权限 -->
	<beans:property name="securityMetadataSource" ref="mySecurityMetadataSource" />
</beans:bean>

<!-- 实现了UserDetailsService的Bean -->
<!-- 
	加载用户权限的管理器:
	1、可以以配置方式定义,user-service
	2、 可以以sql语句定义,jdbc-user-service
	3、ldap-user-service
-->
<authentication-manager alias="myAuthenticationManager">
	<authentication-provider user-service-ref="myUserDetailServiceImpl" />
</authentication-manager>

<!-- 
<beans:bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close" scope="singleton">
	<beans:property name="driverClassName" value="oracle.jdbc.driver.OracleDriver" />
	<beans:property name="url" value="jdbc:oracle:thin:@10.10.39.217:1521:orcl" />
	<beans:property name="username" value="liny" />
	<beans:property name="password" value="liny" />
</beans:bean> 
 -->
 
<beans:bean id="myAccessDecisionManager" class="com.liny.sys.security.util.MyAccessDecisionManager" />

<beans:bean id="mySecurityMetadataSource" class="com.liny.sys.security.util.MySecurityMetadataSource"/>

<beans:bean id="myUserDetailServiceImpl" class="com.liny.sys.security.util.MyUserDetailServiceImpl" />
    3、自定义安全拦截器、用户认证、用户授权实现:
    3.1、自定义安全拦截器(mySecurityFilter):   
public class MySecurityFilter extends AbstractSecurityInterceptor implements Filter{

	/**
		与applicationContext-security.xml里的myFilter的属性securityMetadataSource对应,  
	    其他的两个组件,已经在AbstractSecurityInterceptor定义
    **/
	
    private FilterInvocationSecurityMetadataSource securityMetadataSource;  

	@Override
	public void doFilter(ServletRequest request, ServletResponse response,
			FilterChain chain) throws IOException, ServletException {
		FilterInvocation filterInvocation = new FilterInvocation(request, response, chain);
		invoke(filterInvocation);
	}
	
	private void invoke(FilterInvocation fi) throws IOException, ServletException {  
        //1.获取请求资源的权限  
        //执行Collection<ConfigAttribute> attributes = SecurityMetadataSource.getAttributes(object);  
        //2.是否拥有权限    
        //this.accessDecisionManager.decide(authenticated, object, attributes);  
        InterceptorStatusToken token = super.beforeInvocation(fi);  //获取请求资源的权限 
        try {  
            fi.getChain().doFilter(fi.getRequest(), fi.getResponse());  
        } finally {  
            super.afterInvocation(token, null);  
        }  
    }  
    
	@Override
	public Class<?> getSecureObjectClass() {
		return FilterInvocation.class;
	}

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

	@Override
	public void destroy() {
	}
	
	@Override
	public void init(FilterConfig arg0) throws ServletException {
	}

	public FilterInvocationSecurityMetadataSource getSecurityMetadataSource() {
		return securityMetadataSource;
	}

	public void setSecurityMetadataSource(
			FilterInvocationSecurityMetadataSource securityMetadataSource) {
		this.securityMetadataSource = securityMetadataSource;
	}
}
    3.2、获取请求资源需要的权限:  
public class MySecurityMetadataSource implements FilterInvocationSecurityMetadataSource{
	
	private static final Logger LOGGER = LoggerFactory.getLogger(MySecurityMetadataSource.class);
	
    private static Map<String, Collection<ConfigAttribute>> resourceMap = null;  
    
    @Resource
    private SecurityDAO securityDAO;
    	
    @Resource
    private MyAccessDecisionManager accessDecisionManager;
    
	public MySecurityMetadataSource() {
        loadResourceDefine(); 
	}

	@Override
	public Collection<ConfigAttribute> getAttributes(Object obj)throws IllegalArgumentException {
		String requestUrl = ((FilterInvocation) obj).getRequestUrl();
		LOGGER.debug(requestUrl);
        if(resourceMap == null) {  
            loadResourceDefine();   
        }
        return resourceMap.get(requestUrl);  
	}

	 //加载所有资源与权限的关系,一个资源需要哪些权限
    private void loadResourceDefine() {  
        if(resourceMap == null && this.securityDAO != null) {  
            resourceMap = new HashMap<String, Collection<ConfigAttribute>>();  
            List<ResourceVO> resources = this.securityDAO.getAllResource();
            for (ResourceVO resource : resources) {  
            	//以权限名封装为Spring的security Object  
                Collection<ConfigAttribute> configAttributes = new ArrayList<ConfigAttribute>();  
                ConfigAttribute configAttribute = new SecurityConfig(resource.getRoleName());
                configAttributes.add(configAttribute);  
                resourceMap.put(resource.getResourceURL(), configAttributes);  
            }  
        }  
    }  

	@Override
	public Collection<ConfigAttribute> getAllConfigAttributes() {
		return null;
	}

	@Override
	public boolean supports(Class<?> class1) {
		return true;
	}
	
}
    3.3、请求资源的权限校验:  
public class MyAccessDecisionManager implements AccessDecisionManager {
	
	private static final Logger LOGGER = LoggerFactory.getLogger(MyAccessDecisionManager.class);

	@Override
	public void decide(Authentication authentication, Object obj,Collection<ConfigAttribute> configAttributes)
			throws AccessDeniedException, InsufficientAuthenticationException {
		if (configAttributes == null) {
			return;
		}
		// 所请求的资源拥有的权限
		Iterator<ConfigAttribute> iterator = configAttributes.iterator();
		while (iterator.hasNext()) {
			ConfigAttribute configAttribute = iterator.next();
			// 访问所请求资源所需要的权限
			String needPermission = configAttribute.getAttribute();
			LOGGER.debug(needPermission);
			// 用户所拥有的权限authentication
			for (GrantedAuthority ga : authentication.getAuthorities()) {
				if (needPermission.equals(ga.getAuthority())) {
					return;
				}
			}
		}
		// 没有权限
		throw new AccessDeniedException("没有权限访问! ");
	}

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

	@Override
	public boolean supports(Class<?> class1) {
		return true;
	}
}
    3.4、用户身份认证:   
public class MyUserDetailServiceImpl implements UserDetailsService {

	@Resource
	private SecurityDAO securityDAO;

	public MyUserDetailServiceImpl() {
		super();
	}

	@Override
	public UserDetails loadUserByUsername(String username)
			throws UsernameNotFoundException {
		UserVO userVO = new UserVO();
		userVO.setAccount(username);
		userVO = securityDAO.getUserByAccount(userVO);
		if (userVO == null) {
			throw new UsernameNotFoundException(username);
		}
		List<RoleVO> roles = securityDAO.getRoleByUser(userVO);
		Set<GrantedAuthority> authSet = new HashSet<GrantedAuthority>();
		if (roles != null && roles.size() > 0) {
			for (RoleVO roleVO : roles) {
				authSet.add(new SimpleGrantedAuthority(roleVO.getRoleName()));
			}
		}
		UserVO objUserVO = new UserVO(userVO.getAccount(),
				userVO.getPassword(), userVO.isAccountNonExpired(),
				userVO.isCredentialsNonExpired(), userVO.isAccountNonLocked(),
				userVO.isEnabled(), roles, authSet);
		return objUserVO;
	}
}
    大致的请求流程如下:
    1、用户请求过滤器
    2、获取请求资源需要的权限
    3、进行请求资源的权限校验,如果未进行身份认证,则执行步骤4、否则执行步骤5
    4、进行用户身份认证,认证成功执行步骤1
    5、校验成功显示请求的资源,失败则拒绝访问请求的资源

相关推荐