`
com0606
  • 浏览: 60963 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

关于spring security的一个小例子

阅读更多

最近学习了一下spring mvc,顺便就把spring security给看了看。看的官方文档,并借鉴了网上别人的学习经验,看了些源码,大致上对其工作原理有了个了解。把自己弄的做个记录,以后用得着了再翻翻

用的是spring3.0.5,lib里面需要导入的包都移去了

 

先贴个项目的结构

                                          

 

与spring相关的一些jar包

                                          

 

这个例子中只对url的权限进行控制,没有用spring的method权限控制。

感觉spring mvc确实比较轻巧,不像struts2包装了太多的东西。不过在这里并没有对spring mvc过多研究,更多的是关于spring security的。

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">
	
	<context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>
        	classpath:/applicationContext.xml
        	/WEB-INF/applicationContext-security.xml
        </param-value>
    </context-param>
	
	<!-- Spring Security的 配置-->
	<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>
	
	<!-- 配置UTF-8编码过滤 -->
	<filter>
		<filter-name>Set Character Encoding</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>
	
	<!-- Spring MVC的配置 -->
	<!-- 默认所对应的配置文件是WEB-INF下的{servlet-name}-servlet.xml -->
	<servlet>
		<servlet-name>springmvc</servlet-name>
		<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
		<load-on-startup>1</load-on-startup>
	</servlet>
	
	<servlet-mapping>
		<servlet-name>springmvc</servlet-name>
		<url-pattern>/</url-pattern>
	</servlet-mapping>
	
	<listener>
      <listener-class>org.springframework.security.web.session.HttpSessionEventPublisher</listener-class>
    </listener>
	
	<!-- 默认的spring配置文件是在WEB-INF下的applicationContext.xml-->
	<listener>
		<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
	</listener> 
	
	<!-- spring security监听session -->
	<listener>
		<listener-class>org.springframework.security.web.session.HttpSessionEventPublisher</listener-class>
	</listener>
	
	<welcome-file-list>
		<welcome-file>index.jsp</welcome-file>
	</welcome-file-list>
</web-app>

 springmvc-servlet.xml

此文件名根据web.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:p="http://www.springframework.org/schema/p"
	xmlns:context="http://www.springframework.org/schema/context"
	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.0.xsd 
           http://www.springframework.org/schema/context 
           http://www.springframework.org/schema/context/spring-context-3.0.xsd
           http://www.springframework.org/schema/mvc 
           http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd" 
           default-autowire="byName">
    
    <!-- 扫描所有的controller -->
    <context:component-scan base-package="cn.henu.springmvc.control" />
    
    <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    	<property name="prefix" value="/"></property>
    	<property name="suffix" value=".jsp"></property>
    </bean>
    
</beans>  

applicationContext-security.xml

这是专门用来配置spring security的,这里没有用默认的拦截器链,并使用了一个自定义的Filter。使用自定义的Filter需要继承AbstractSecurityInterceptor,并实现两个接口FilterInvocationSecurityMetadataSource,

AccessDecisionManager。如果想使用自定义的用户数据源还需要实现UserDetailsService接口

<?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="true" access-denied-page="/error.jsp">
		
		<!-- 不过滤图片等静态资源,其中**代表可以跨越目录,*不可以跨越目录-->
		<intercept-url pattern="/image/**" filters="none" />
		
		<form-login login-page="/login.jsp" login-processing-url="/login"
			authentication-success-handler-ref="authenticationDispatcher" 
			authentication-failure-url="/login.jsp?error=-1" />
		<!-- login-processing-url默认是/j_spring_security_check -->

		<logout logout-url="/logout" logout-success-url="/" />
		<!-- 此配置debug时会报logout-success-url项路径未找到的错,可无视之 -->
			
		<session-management session-fixation-protection="none">  
			<concurrency-control max-sessions="1" expired-url="/login.jsp"/>  
		</session-management>
		<custom-filter ref="myFilter" before="FILTER_SECURITY_INTERCEPTOR" /> 
	</http>
	
	<!-- 一个自定义的Filter,必须包含以下三项配置 -->
	<beans:bean id="myFilter" class="cn.henu.spring.security.FilterSecurityInterceptor">
		<beans:property name="authenticationManager" ref="authenticationManager" />  
        <beans:property name="accessDecisionManager" ref="accessDecisionManager" />  
        <beans:property name="securityMetadataSource" ref="securityMetadataSource" />
	</beans:bean>
	
	<!-- 资源源数据定义,将所有的资源和权限对应关系建立起来,即定义某一资源可以被哪些角色访问 -->  
	<beans:bean id="securityMetadataSource" init-method="loadResourceDefine"
		class="cn.henu.spring.security.InvocationSecurityMetadataSourceService">
		<beans:property name="interceptUrl">
			<beans:props>
				<beans:prop key="/admin/**">admin</beans:prop>
				<beans:prop key="/user/**">user,admin</beans:prop>
			</beans:props>
		</beans:property>
	</beans:bean>
	
	<!-- 访问决策器,决定某个用户具有的角色,是否有足够的权限去访问某个资源     -->  
	<beans:bean id="accessDecisionManager" 
		class="cn.henu.spring.security.AccessDecisionSecurityManager">
	</beans:bean>
	
	<authentication-manager alias="authenticationManager">
		<authentication-provider user-service-ref="userDetailService">
		</authentication-provider>
	</authentication-manager>
	
	<!-- 查询用户信息 -->
	<beans:bean id="userDetailService" class="cn.henu.spring.security.UserDetailService">
	</beans:bean>
	
	<!-- 自定义一个角色控制器,根据不同角色登录到不同页面 -->
	<beans:bean id="authenticationDispatcher" class="cn.henu.spring.security.SuccessHandler">
		<beans:property name="authDispatcherMap">
			<beans:props>
				<beans:prop key="admin">/admin/index.jsp</beans:prop>
				<beans:prop key="user">/user/index.jsp</beans:prop>
			</beans:props>
		</beans:property>
	</beans:bean>
</beans:beans>

以下是三个实现类:

FilterSecurityInterceptor.java

package cn.henu.spring.security;

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.log4j.Logger;
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 FilterSecurityInterceptor extends AbstractSecurityInterceptor
	implements Filter {
	private static final Logger logger = Logger.getLogger(FilterSecurityInterceptor.class);
	private FilterInvocationSecurityMetadataSource securityMetadataSource;	
	public void init(FilterConfig arg0) throws ServletException {
	}
	public void doFilter(ServletRequest request, ServletResponse response,
			FilterChain chain) throws IOException, ServletException {
		if(logger.isInfoEnabled()) {
			logger.info("doFilter~start");
		}
		FilterInvocation invocation = new FilterInvocation(request, response, chain);
		invoke(invocation);
		if(logger.isInfoEnabled()) {
			logger.info("doFilter~end");
		}
	}
	
	public void invoke(FilterInvocation invocation)throws IOException, ServletException {
		InterceptorStatusToken token = super.beforeInvocation(invocation);
		
		try {
			invocation.getChain().doFilter(invocation.getRequest(), invocation.getResponse());
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			super.afterInvocation(token, null);
		}
	}
	
	public void destroy() {
	}
	
	@Override
	public SecurityMetadataSource obtainSecurityMetadataSource() {
		return this.securityMetadataSource;
	}
	
	@Override
	public Class<? extends Object> getSecureObjectClass() {
		return FilterInvocation.class;
	}
	public void setSecurityMetadataSource(
			FilterInvocationSecurityMetadataSource securityMetadataSource) {
		this.securityMetadataSource = securityMetadataSource;
	}
}

 InvocationSecurityMetadataSourceService.java

package cn.henu.spring.security;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import org.apache.log4j.Logger;
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;

public class InvocationSecurityMetadataSourceService implements
		FilterInvocationSecurityMetadataSource {
	private static final Logger logger = 
		Logger.getLogger(InvocationSecurityMetadataSourceService.class);
	private UrlMatcher urlMatcher = new AntUrlPathMatcher();
	private static Map<String, Collection<ConfigAttribute>> resourceMap;
	//用以接收配置文件中的对应关系,类似于http配置项中的<intercept-url />
	private Map<String, String> interceptUrl;

	@SuppressWarnings("unused")
	private void loadResourceDefine() {
		
		resourceMap = new HashMap<String, Collection<ConfigAttribute>>();
		if(interceptUrl==null) {
			if(logger.isInfoEnabled()) {
				logger.info("未建立任何对应关系");
			}
			return;
		}
		//从配置文件中读取对应关系
		for(Map.Entry<String, String> entry : interceptUrl.entrySet()) {
			Collection<ConfigAttribute> atts = new ArrayList<ConfigAttribute>(); 
	        ConfigAttribute ca = null;
	        if(entry.getValue()==null) {
	        	continue;
	        }
	        for(String configAttribute : entry.getValue().split(",")) {
	        	ca = new SecurityConfig(configAttribute);
	        	atts.add(ca);
	        }
	        resourceMap.put(entry.getKey(), atts);
		}
		
		/*
		//通过硬编码设置,resouce和role
        Collection<ConfigAttribute> atts = new ArrayList<ConfigAttribute>(); 
        ConfigAttribute ca = new SecurityConfig("admin");
        atts.add(ca);
        resourceMap.put("/admin/**", atts);
        ca = new SecurityConfig("user");
        atts = new ArrayList<ConfigAttribute>();
        atts.add(ca);
        resourceMap.put("/user/**", atts);
        */
		
		if(logger.isInfoEnabled()) {
			logger.info("资源和权限对应关系读取完成!");
		}
	}
	
	public Collection<ConfigAttribute> getAllConfigAttributes() {
		return null;
	}

	public Collection<ConfigAttribute> getAttributes(Object object)
			throws IllegalArgumentException {
		String resUrl = ((FilterInvocation) object).getRequestUrl();
		Iterator<String> iterator = resourceMap.keySet().iterator();
		while (iterator.hasNext()) {
			String url = iterator.next();
			
			if(urlMatcher.pathMatchesUrl(url, resUrl)) {
				Collection<ConfigAttribute> col = resourceMap.get(url);
				if(logger.isInfoEnabled()) {
					logger.info("请求的url与资源中的url相匹配.");
				}
				return col;
			}
		}
		return null;
	}

	public boolean supports(Class<?> clazz) {
		return true;
	}

	public void setInterceptUrl(Map<String, String> interceptUrl) {
		this.interceptUrl = interceptUrl;
	}
}

 AccessDecisionSecurityManager.java

package cn.henu.spring.security;

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

import org.apache.log4j.Logger;
import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.SecurityConfig;
import org.springframework.security.authentication.InsufficientAuthenticationException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;

public class AccessDecisionSecurityManager implements AccessDecisionManager {
	private static final Logger logger = Logger.getLogger(AccessDecisionManager.class);
	
	public void decide(Authentication authentication, Object object,
			Collection<ConfigAttribute> collection) throws AccessDeniedException,
			InsufficientAuthenticationException {
		if(logger.isInfoEnabled()) {
			logger.debug("decide(Authentication, Object," + 
					"Collection<ConfigAttribute>)~start");
		}
		if(collection==null) {
			if(logger.isInfoEnabled()) {
				logger.info("decide(Authentication, Object," + 
						"Collection<ConfigAttribute>)~end");
			}	
			return;
		}
		if(logger.isInfoEnabled()) {
			logger.info("正在访问的url是: "+object.toString());
		}
		Iterator<ConfigAttribute> iterator = collection.iterator();
		
		while (iterator.hasNext()) {
			ConfigAttribute ca = iterator.next();
			String needRole = ((SecurityConfig) ca).getAttribute();
			
			for (GrantedAuthority ga : authentication.getAuthorities()) {
				if(needRole.equals(ga.getAuthority())) {
					if(logger.isInfoEnabled()) {
						logger.info("用户角色" + needRole+" 与 "+
								"权限" + ga.getAuthority()+" 相匹配.");
						logger.info("decide(Authentication, Object," + 
							"Collection<ConfigAttribute>)~end");
					}
					return;
				}
			}
		}
		throw new AccessDeniedException("没有权限");
	}

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

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

}

 使用自定义数据源

UserDetailService.java

package cn.henu.spring.security;

import java.util.ArrayList;
import java.util.Collection;

import javax.annotation.Resource;

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 cn.henu.service.UserService;

public class UserDetailService implements UserDetailsService {
	
	@Resource private UserService userService;
	public UserDetails loadUserByUsername(String username)
			throws UsernameNotFoundException, DataAccessException {
		Collection<GrantedAuthority> auths = new ArrayList<GrantedAuthority>();
		String password = null;
		cn.henu.model.User newUser = userService.getObject(username);
		if(newUser==null) {
			throw new UsernameNotFoundException("用户 "+username+" 不存在");
		}
		password = newUser.getPassword();
		GrantedAuthorityImpl grantedAuthorityImpl = 
			new GrantedAuthorityImpl(newUser.getRole());
		auths.add(grantedAuthorityImpl);

                /*
		 * 此处若用继承自UserDetails的User需要覆写其中的equals和hashCode方法,
		 * 否则无法通过session-management控制max-sessions="1"
		 */
		return new User(username, password, true, true, true, true, auths);
	}

}

 如果希望不同用户登录成功之后进入不同页面,可以实现AuthenticationSuccessHandler接口,并在<form-login />中配置authentication-success-handler-ref项

SuccessHandler.java

package cn.henu.spring.security;

import java.io.IOException;
import java.util.Collection;
import java.util.Map;

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

import org.apache.log4j.Logger;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.GrantedAuthorityImpl;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.util.Assert;

public class SuccessHandler implements AuthenticationSuccessHandler {
	private Map<String, String> map;
	private static final Logger logger = Logger.getLogger(SuccessHandler.class);
	public void onAuthenticationSuccess(HttpServletRequest request,
			HttpServletResponse response, Authentication authentication) throws IOException,
			ServletException {
		Assert.notNull(map, "AuthInterceptMap is null!");
		String url = "";
		Collection<GrantedAuthority> autCollection = authentication.getAuthorities();
		
		if(autCollection.isEmpty()) {
			return;
		}
		GrantedAuthority[] ga = new GrantedAuthorityImpl[]{}; 
		url = map.get(autCollection.toArray(ga)[0].toString());		//取第一角色权限
		
		if(logger.isInfoEnabled()) {
			logger.info("登陆成功,跳转至"+url);
		}
		response.sendRedirect(request.getContextPath() + url);
	}

	public void setAuthDispatcherMap(Map<String, String> map) {
		this.map = map;
	}
}

 

 

关于hibernate的一些配置和spring mvc相关的文件就不贴出来了。

其实spring security中一些对于权限的管理自己写一个过滤器完全可以实现,但是毕竟不如spring security的工作做的全面

4
0
分享到:
评论
3 楼 laowoof 2012-05-30  
为啥我看不懂
2 楼 mojunbin 2011-11-22  
感觉还可以
1 楼 guxinghanshe 2011-09-25  
很不错的例子,一步一步来,基本功能已经实现

相关推荐

    Spring Security 小例子(1)

    Spring Security 是一个强大的且高度可定制的身份验证和访问控制框架,它是Java开发人员在构建安全Web应用程序时的首选工具。这个“Spring Security 小例子(1)”很可能是针对初学者或者那些希望快速了解该框架基本...

    Spring Security的例子

    Spring Security 是一个强大的且高度可定制的身份验证和访问控制框架,用于保护Java应用程序。这个框架提供了全面的安全解决方案,包括Web安全、方法级访问控制、密码管理以及会话管理。在这个"Spring Security的...

    spring security2 例子

    Spring Security 是一个强大的安全框架,用于为Java应用提供身份验证和授权服务。在这个"spring security2 例子"中,我们可能会探索如何利用Spring Security 2版本来保护我们的应用程序。Spring Security 2虽然已经...

    Spring Security实战例子

    Spring Security 是一个强大的且高度可定制的身份验证和访问控制框架,专为 Java 应用程序设计。它提供了全面的安全解决方案,包括登录、授权、权限管理等,广泛应用于Web应用程序和企业级系统。在本实战例子中,...

    spring security 完整项目实例

    Spring Security 是一个强大的安全框架,用于为Java应用提供身份验证和授权服务。在这个完整的项目实例中,我们将深入探讨Spring Security的核心概念以及如何将其应用于实际的Web应用程序开发。 首先,我们从用户、...

    spring security 4 小例子带自定义过滤器

    Spring Security 是一个强大的安全框架,用于为Java应用提供安全控制,包括身份验证、授权和访问控制。在Spring Security 4中,我们可以通过自定义过滤器来扩展其功能,以满足特定的安全需求。在这个小例子中,我们...

    spring security maven 例子

    Spring Security 是一个强大的安全框架,主要用于Java应用的安全管理,它为Web和企业级应用程序提供了全面的身份验证、授权和访问控制功能。在这个“Spring Security Maven 示例”中,我们可以通过官方提供的Maven...

    SpringSecurity官网修改例子 tutorial

    这个“SpringSecurity官网修改例子 tutorial”提供了一个可以直接运行的示例项目,无需连接到数据库,这使得开发者能够快速理解和学习SpringSecurity的核心概念。 在SpringSecurity中,主要涉及以下几个关键知识点...

    springSecurity3例子

    Spring Security 是一个强大的安全框架,主要用于Java应用的安全管理。它为Web应用程序提供了全面的身份验证、授权和访问控制功能。在Spring Security 3版本中,这个框架进一步完善了其特性和性能,使其成为开发者...

    spring-security结合spring boot超简单的例子

    Spring Security 是一个强大的安全框架,主要用于Java应用的安全管理,它为Spring Boot提供了全面的安全服务。在本示例中,我们将探讨如何将Spring Security与Spring Boot整合,以实现一个基础的用户登录验证和权限...

    s2sh+springSecurity的注解配置例子

    标题 "s2sh+springSecurity的注解配置例子" 提供了一个关于整合Spring Security与Struts 2(S2)和Hibernate(SH)框架的注解配置实践的线索。这通常涉及到在Java web应用程序中创建一个安全的环境,通过利用Spring ...

    spring security3.0.4 的acl使用例子

    总的来说,这个例子提供了一个使用Spring Security 3.0.4的ACL特性的实践指南,帮助开发者实现对象级别的细粒度权限控制。通过理解和应用这些概念,你可以为你的应用程序构建出强大且灵活的安全体系。

    spring security4登陆例子

    本文将通过一个具体的示例——Spring MVC + Spring Security 4 的整合登录实例来深入探讨Spring Security的使用方法。 #### 二、核心配置类:`SecurityConfig` 在本例中,我们使用的是Java Config的方式进行Spring...

    Spring security认证授权

    Spring Security 是一个强大的和高度可定制的身份验证和访问控制框架,用于Java应用程序。它提供了全面的安全解决方案,包括用户认证、权限授权、会话管理、CSRF防护以及基于HTTP的访问控制。在这个例子中,我们将...

    Spring Security例子源代码

    总的来说,这个"Spring Security例子源代码"是一个实践性的学习资源,涵盖了从用户登录、权限控制到数据库验证的整个安全流程。通过深入研究这个示例,开发者可以更好地理解和应用Spring Security,从而提升其构建...

    spring-cloud-security例子

    本文将深入探讨Spring Cloud Security在用户身份认证和权限管理方面的应用,以"spring-cloud-security例子"为基础,结合spring-boot-security-master项目,详细阐述其核心概念和技术实现。 一、用户身份认证 1.1 ...

    SSH + Spring Security3.2例子

    SSH + Spring Security3.2例子

    关于ip的过滤器 spring security例子

    在IT行业中,Spring Security是一个广泛使用的安全框架,它为Java应用程序提供了全面的安全管理解决方案。本文将深入探讨如何在Spring Security中实现IP过滤器,以便控制访问应用的客户端IP地址。 首先,我们需要...

    spring-Security简单例子

    在这个"spring-Security简单例子"中,我们可能会看到一个基础的配置文件,一个登录表单,以及一些简单的URL访问控制规则。通过理解这些核心概念和步骤,开发者可以进一步扩展Spring Security以满足更复杂的安全需求...

    spring security 3 demos

    Spring Security 是一个强大的、高度可定制的身份验证和访问控制框架,广泛用于Java应用程序的安全管理,尤其是在Spring生态体系中。"Spring Security 3 Demos" 是一套针对Spring Security 3 版本的示例项目,旨在...

Global site tag (gtag.js) - Google Analytics