`
liukai
  • 浏览: 706786 次
  • 性别: Icon_minigender_1
  • 来自: 成都
社区版块
存档分类
最新评论

springsecurity3和JCaptcha的整合

阅读更多
JCaptcha是一个验证码的框架.
照着白衣的springside3的例子鼓捣了半天,弄出来感觉不复杂但是很有用.
不多说,放货.

创建一个jcaptcha包
在jcaptcha包里创建2个类:GMailEngine 和 JCaptchaFilter
GMailEngine :是JCaptcha验证码图片生成引擎,仿照JCaptcha2.0编写类似GMail验证码的样式.
JCaptchaFilter:是针对 JCaptcha 专门的过滤器(Filter).

另外 remember-me 这个功能我测试用 把系统时间更改下,确实是可以实现在两周之类免登录.


包结构



GMailEngine.java
package com.sjax.myapp.jcaptcha;

import java.awt.Color;
import java.awt.Font;
import java.awt.image.ImageFilter;

import com.octo.captcha.component.image.backgroundgenerator.BackgroundGenerator;
import com.octo.captcha.component.image.backgroundgenerator.UniColorBackgroundGenerator;
import com.octo.captcha.component.image.color.RandomListColorGenerator;
import com.octo.captcha.component.image.deformation.ImageDeformation;
import com.octo.captcha.component.image.deformation.ImageDeformationByFilters;
import com.octo.captcha.component.image.fontgenerator.FontGenerator;
import com.octo.captcha.component.image.fontgenerator.RandomFontGenerator;
import com.octo.captcha.component.image.textpaster.DecoratedRandomTextPaster;
import com.octo.captcha.component.image.textpaster.TextPaster;
import com.octo.captcha.component.image.textpaster.textdecorator.TextDecorator;
import com.octo.captcha.component.image.wordtoimage.DeformedComposedWordToImage;
import com.octo.captcha.component.image.wordtoimage.WordToImage;
import com.octo.captcha.component.word.FileDictionary;
import com.octo.captcha.component.word.wordgenerator.ComposeDictionaryWordGenerator;
import com.octo.captcha.component.word.wordgenerator.WordGenerator;
import com.octo.captcha.engine.image.ListImageCaptchaEngine;
import com.octo.captcha.image.gimpy.GimpyFactory;

/**
 * JCaptcha验证码图片生成引擎,仿照JCaptcha2.0编写类似GMail验证码的样式.
 * 
 * @author liukai
 * 
 */
public class GMailEngine extends ListImageCaptchaEngine {

	@Override
	protected void buildInitialFactories() {
		int minWordLength = 4;
		int maxWordLength = 5;
		int fontSize = 50;
		int imageWidth = 250;
		int imageHeight = 100;
		WordGenerator dictionnaryWords = new ComposeDictionaryWordGenerator(
				new FileDictionary("toddlist"));

		// word2image components
		TextPaster randomPaster = new DecoratedRandomTextPaster(minWordLength,
				maxWordLength, new RandomListColorGenerator(new Color[] {
						new Color(23, 170, 27), new Color(220, 34, 11),
						new Color(23, 67, 172) }), new TextDecorator[] {});
		BackgroundGenerator background = new UniColorBackgroundGenerator(
				imageWidth, imageHeight, Color.white);
		FontGenerator font = new RandomFontGenerator(fontSize, fontSize,
				new Font[] { new Font("nyala", Font.BOLD, fontSize),
						new Font("Bell MT", Font.PLAIN, fontSize),
						new Font("Credit valley", Font.BOLD, fontSize) });
		ImageDeformation postDef = new ImageDeformationByFilters(
				new ImageFilter[] {});
		ImageDeformation backDef = new ImageDeformationByFilters(
				new ImageFilter[] {});
		ImageDeformation textDef = new ImageDeformationByFilters(
				new ImageFilter[] {});

		WordToImage word2image = new DeformedComposedWordToImage(font,
				background, randomPaster, backDef, textDef, postDef);
		addFactory(new GimpyFactory(dictionnaryWords, word2image));
	}

}


JCaptchaFilter.java
package com.sjax.myapp.jcaptcha;

import java.awt.image.BufferedImage;
import java.io.IOException;

import javax.imageio.ImageIO;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;

import com.octo.captcha.service.CaptchaService;
import com.octo.captcha.service.CaptchaServiceException;

/**
 * 针对 JCaptcha 专门的过滤器(Filter)
 * @author liukai
 *
 */
public class JCaptchaFilter implements Filter {

	//web.xml中的参数名定义
	public static final String PARAM_CAPTCHA_PARAMTER_NAME = "captchaParamterName";
	public static final String PARAM_CAPTCHA_SERVICE_ID = "captchaServiceId";
	public static final String PARAM_FILTER_PROCESSES_URL = "filterProcessesUrl";
	public static final String PARAM_FAILURE_URL = "failureUrl";
	public static final String PARAM_AUTO_PASS_VALUE = "autoPassValue";

	//默认值定义
	public static final String DEFAULT_FILTER_PROCESSES_URL = "/j_spring_security_check";
	public static final String DEFAULT_CAPTCHA_SERVICE_ID = "captchaService";
	public static final String DEFAULT_CAPTCHA_PARAMTER_NAME = "j_captcha";
	
	private static Logger logger = LoggerFactory.getLogger(JCaptchaFilter.class);
	
	private String failureUrl;
	private String filterProcessesUrl = DEFAULT_FILTER_PROCESSES_URL;
	private String captchaServiceId = DEFAULT_CAPTCHA_SERVICE_ID;
	private String captchaParamterName = DEFAULT_CAPTCHA_PARAMTER_NAME;
	private String autoPassValue;

	private CaptchaService captchaService;
	
	/**
	 * Filter回调初始化函数.
	 */
	public void init(FilterConfig filterConfig) throws ServletException {
		// TODO Auto-generated method stub
		initParameters(filterConfig);
		initCaptchaService(filterConfig);

	}

	public void doFilter(ServletRequest theRequest, ServletResponse theResponse,
			FilterChain chain) throws IOException, ServletException {
		HttpServletRequest request = (HttpServletRequest) theRequest;
		HttpServletResponse response = (HttpServletResponse) theResponse;
		String servletPath = request.getServletPath();
		logger.info("servletPath:"+servletPath);
		//符合filterProcessesUrl为验证处理请求,其余为生成验证图片请求.
		if (StringUtils.startsWith(servletPath, filterProcessesUrl)) {
			boolean validated = validateCaptchaChallenge(request);
			if (validated) {
				chain.doFilter(request, response);
			} else {
				redirectFailureUrl(request, response);
			}
		} else {
			genernateCaptchaImage(request, response);
		}
	}

	/**
	 * Filter回调退出函数.
	 */
	public void destroy() {
		// TODO Auto-generated method stub

	}
	
	/**
	 * 初始化web.xml中定义的filter init-param.
	 */
	protected void initParameters(final FilterConfig fConfig) {
		if (StringUtils.isBlank(fConfig.getInitParameter(PARAM_FAILURE_URL))) {
			throw new IllegalArgumentException("CaptchaFilter缺少failureUrl参数");
		}

		failureUrl = fConfig.getInitParameter(PARAM_FAILURE_URL);
		logger.info("failureUrl:"+failureUrl);

		if (StringUtils.isNotBlank(fConfig.getInitParameter(PARAM_FILTER_PROCESSES_URL))) {
			filterProcessesUrl = fConfig.getInitParameter(PARAM_FILTER_PROCESSES_URL);
		}

		if (StringUtils.isNotBlank(fConfig.getInitParameter(PARAM_CAPTCHA_SERVICE_ID))) {
			captchaServiceId = fConfig.getInitParameter(PARAM_CAPTCHA_SERVICE_ID);
		}

		if (StringUtils.isNotBlank(fConfig.getInitParameter(PARAM_CAPTCHA_PARAMTER_NAME))) {
			captchaParamterName = fConfig.getInitParameter(PARAM_CAPTCHA_PARAMTER_NAME);
		}

		if (StringUtils.isNotBlank(fConfig.getInitParameter(PARAM_AUTO_PASS_VALUE))) {
			autoPassValue = fConfig.getInitParameter(PARAM_AUTO_PASS_VALUE);
		}
	}
	
	/**
	 * 从ApplicatonContext获取CaptchaService实例.
	 */
	protected void initCaptchaService(final FilterConfig fConfig) {
		ApplicationContext context = WebApplicationContextUtils.getWebApplicationContext(fConfig.getServletContext());
		captchaService = (CaptchaService) context.getBean(captchaServiceId);
	}
	
	/**
	 * 生成验证码图片.
	 */
	protected void genernateCaptchaImage(final HttpServletRequest request, final HttpServletResponse response)
			throws IOException {

		setDisableCacheHeader(response);
		response.setContentType("image/jpeg");

		ServletOutputStream out = response.getOutputStream();
		try {
			String captchaId = request.getSession(true).getId();
			BufferedImage challenge = (BufferedImage) captchaService.getChallengeForID(captchaId, request.getLocale());
			ImageIO.write(challenge, "jpg", out);
			out.flush();
		} catch (CaptchaServiceException e) {
			logger.error(e.getMessage(), e);
		} finally {
			out.close();
		}
	}
	
	/**
	 * 验证验证码.
	 */
	protected boolean validateCaptchaChallenge(final HttpServletRequest request) {
		try {
			String captchaID = request.getSession().getId();
			logger.info("captchaID:"+captchaID);
			String challengeResponse = request.getParameter(captchaParamterName);
			logger.info("challengeResponse:"+challengeResponse);
			//自动通过值存在时,检验输入值是否等于自动通过值
			if (StringUtils.isNotBlank(autoPassValue) && autoPassValue.equals(challengeResponse)) {
				return true;
			}
			return captchaService.validateResponseForID(captchaID, challengeResponse);
		} catch (CaptchaServiceException e) {
			logger.error(e.getMessage(), e);
			return false;
		}
	}
	/**
	 * 跳转到失败页面.
	 * 
	 * 可在子类进行扩展, 比如在session中放入SpringSecurity的Exception.
	 */
	protected void redirectFailureUrl(final HttpServletRequest request, final HttpServletResponse response)
			throws IOException {
		logger.info("跳转到失败页面:"+request.getContextPath()+failureUrl);
		response.sendRedirect(request.getContextPath() + failureUrl);
	}
	
	/**
	 * 设置禁止客户端缓存的Header.
	 */
	public static void setDisableCacheHeader(HttpServletResponse response) {
		//Http 1.0 header
		response.setDateHeader("Expires", 1L);
		response.addHeader("Pragma", "no-cache");
		//Http 1.1 header
		response.setHeader("Cache-Control", "no-cache, no-store, max-age=0");
	}
	

}


然后在springsecurity的配置文件里添加,直接把文件全部拷贝过来 JCaptcha相关的配置就一段,在最下面.

application-security.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:s="http://www.springframework.org/schema/security" 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"
	default-lazy-init="true">

	<s:authentication-manager>
		<s:authentication-provider>
			<s:password-encoder hash="md5" />
			<s:jdbc-user-service data-source-ref="dataSource" />
		</s:authentication-provider>
	</s:authentication-manager>

	<!-- 导入自定义的springsecurity国际化文件 -->
	<bean id="messageSource"
		class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
		<property name="basename" value="classpath:messages_zh_CN" />
	</bean>
	<bean id="localeResolver"
		class="org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver" />

	<s:http auto-config="true">
		<!-- 指定登录页面 -->
		<s:form-login login-page="/login.jsp" />
		<s:logout logout-success-url="/login.jsp" />
		<!-- 对登录页面不进行拦截,这个页面也许带有参数 -->
		<s:intercept-url pattern="/login.jsp*" filters="none" />
		<s:intercept-url pattern="/resources/**" filters="none" />
		
		<s:remember-me  />
		
		<!-- 会话配置管理 -->
		<s:session-management invalid-session-url="/login.jsp">
		<!-- 只允许一个人登陆,并且第二个人登陆不了 -->
			<s:concurrency-control max-sessions="1"
				error-if-maximum-exceeded="true" />
		</s:session-management>
	</s:http>

	<!-- 启动annotation -->
	<s:global-method-security secured-annotations="enabled" />



	<!-- Jcaptcha相关的配置 -->
	<bean id="captchaService"
		class="com.octo.captcha.service.image.DefaultManageableImageCaptchaService">
		<property name="captchaEngine">
			<bean class="com.sjax.myapp.jcaptcha.GMailEngine" />
		</property>
		<!-- 默认生成的图片180秒过期 , 可另行设置 
		<property name="minGuarantedStorageDelayInSeconds" value="180" />
		-->
	</bean>
	
</beans>


然后是web.xml 这就不把所有的配置列出来 ,只列出和JCaptcha相关的东西

web.xml

<!-- JCaptcha`s filter -->
	<filter>
		<filter-name>jcaptchaFilter</filter-name>
		<filter-class>com.sjax.myapp.jcaptcha.JCaptchaFilter</filter-class>
		<init-param>
			<param-name>failureUrl</param-name>
			<param-value>/login.jsp</param-value>
		</init-param>
	</filter>
	
	<!-- jcaptcha图片生成URL. -->
	<filter-mapping>
		<filter-name>jcaptchaFilter</filter-name>
		<url-pattern>/jcaptcha.jpg</url-pattern>
	</filter-mapping>
	
	<!-- jcaptcha登录表单处理URL.
	             必须放在springSecurityFilter的filter-mapping定义之前 -->
	<filter-mapping>
		<filter-name>jcaptchaFilter</filter-name>
		<url-pattern>/j_spring_security_check</url-pattern>
	</filter-mapping>


最后就是JSP页面了
login.jsp
<%@ page contentType="text/html;charset=UTF-8"%>
<%@ include file="/common/taglibs.jsp"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<html>
	<head>
		<title>Login</title>
		<%@ include file="/common/meta.jsp"%>
		<link href="<c:url value="/resources/css/screen.css" />"
			rel="stylesheet" type="text/css" />
		<link href="<c:url value="/resources/css/ie.css" />" rel="stylesheet"
			type="text/css" />
		<script type="text/javascript"
			src="<c:url value="/resources/jquery/1.4/jquery.js" />">
</script>
	</head>
	<body>
		<div class="container">
			<%@ include file="/common/header.jsp"%>
			<div id="content">
				<div class="span-24 last">
					<h3>
						用户登录
					</h3>
					<div class="error">
						${sessionScope.SPRING_SECURITY_LAST_EXCEPTION.message }
					</div>
					<form id="loginForm" style="margin-top: 1em;"
						action="<c:url value="/j_spring_security_check" />" method="post"">
						<table class="noborder">
							<tr>
								<td>
									<label for="username">
										用户名:
									</label>
								</td>
								<td>
									<input type="text" name="j_username" class="required" />
								</td>
								<td rowspan="3">
									<img id="captchaImg" src="<c:url value="/jcaptcha.jpg"/>" />
								</td>
							</tr>
							<tr>
								<td>
									<label for="password">
										密码:
									</label>
								</td>
								<td>
									<input type="password" name="j_password" class="required">
								</td>
							</tr>
							<tr>
								<td>
									<label for="j_captcha">
										验证码:
									</label>
								</td>
								<td>
									<input type='text' name='j_captcha' class="required" size='5' />
								</td>
							</tr>
							<tr>
								<td colspan='3'>
									<input type="checkbox" name="_spring_security_remember_me" />
									两周内记住我
									<span style="margin-left: 25px"><a
										href="javascript:refreshCaptcha()">看不清楚换一张</a>
									</span>
								</td>
							</tr>
							<tr>
								<td colspan="2">
									<input type="submit" class="button" value="登录">
								</td>
							</tr>
						</table>
					</form>
				</div>
			</div>
			<%@ include file="/common/footer.jsp"%>
		</div>
	</body>
	<script type="text/javascript">
function refreshCaptcha() {
	$('#captchaImg').hide().attr(
			'src',
			'<c:url value="/jcaptcha.jpg"/>' + '?' + Math
					.floor(Math.random() * 100)).fadeIn();
}
</script>

</html>



我的开发环境是STS+tomcat6.29,工程是maven项目.框架用的spring3.0.5



效果图1.




效果图2.




BTW:因为是maven项目,所以自己要配好maven路径之类的.然后install下就行了.
如果实在不喜欢或者不会用maven,源码下下来, 照着拷贝也行的.
注意要加上JCaptcha的包还有commons-lang包
其它的spring3 springsecurity3之类的包就不多说了 打开pom.xml一个一个的看吧.

  • 大小: 52.1 KB
  • 大小: 35.9 KB
  • 大小: 12.3 KB
  • security.rar (111.7 KB)
  • 描述: 项目的maven源码
  • 下载次数: 747
分享到:
评论
8 楼 yajie870423 2017-06-20  
为什么验证码到时间180秒了,还可以用
7 楼 l975764577 2015-07-15  
为什么用你的项目导入都出现了问题
6 楼 liu7028218 2012-10-26  
很感谢啊,按照楼主的方式集成成功了
5 楼 liukai 2012-07-26  
tingbao 写道
请问,我用的是eclipse+tomcat,jar按照pom.xml上引了,可是启动tomcat的时候报错:
java.lang.NoSuchMethodError: com.jhlabs.image.WaterFilter.setAmplitude(D)V。不知道什么原因

好像是你少了个包.我用的是maven jar包是自动关联的 你最好关注下JCaptcha的关联包
4 楼 tingbao 2012-07-26  
请问,我用的是eclipse+tomcat,jar按照pom.xml上引了,可是启动tomcat的时候报错:
java.lang.NoSuchMethodError: com.jhlabs.image.WaterFilter.setAmplitude(D)V。不知道什么原因
3 楼 gaomzh1314 2012-06-16  
收藏一下,准备学习。
2 楼 JavaStudyEye 2012-05-23  
很是感谢啊
1 楼 zqb666kkk 2011-10-27  
问个 问题  我项目中登陆也用了 spring security 3 有个页面需要 验证码,另外一个弹窗登陆页面不需要验证码 那我怎么选择不同的spring scurity登陆地址?

相关推荐

    springsecurity3和JCaptcha的整合 验证码

    springsecurity3和JCaptcha的整合 验证码

    Spring Cloud Gateway 整合 Spring Security 统一登录认证鉴权

    3. **定制Filter**:在Spring Cloud Gateway中,我们可以自定义WebFlux Filter,利用Spring Security提供的API进行认证和鉴权。这通常涉及到`@PreAuthorize`和`@Secured`等注解的使用,以控制对特定路由的访问权限。...

    spring security和oauth2整合开发资料汇总

    Spring Security和OAuth2的整合主要是为了实现更复杂的授权场景,比如在单点登录(SSO)系统中,Spring Security可以处理用户登录,OAuth2则用于第三方应用的授权。以下是一些整合的关键点: 1. **Spring Security...

    spring security3 中文版本

    Spring Security 是一个强大的、高度可定制的身份验证和访问控制框架。它提供了许多功能,包括登录表单、记住我功能、多身份验证器、基于注解的安全配置、CSRF 防护、OAuth2 客户端和服务端支持等。Spring Security ...

    SpringBoot+SpringSecurity+WebSocket

    整合SpringBoot、SpringSecurity和WebSocket的过程包括以下几个步骤: 1. 配置SpringBoot:创建SpringBoot项目,引入WebSocket和SpringSecurity的相关依赖。 2. 配置WebSocket:实现WebSocket服务器端点,处理连接...

    spring Security3中文教程,经典的

    ### Spring Security3中文教程知识点概览 #### 一、安全核心概念与起步 Spring Security是Spring框架中的一个重要组成部分,主要用于为Web应用提供安全防护。它不仅提供了强大的认证和授权功能,还支持各种加密...

    spring Security整合SSH

    在本项目中,我们将探讨如何将Spring Security与SSH(Struts2、Spring、Hibernate)框架整合,以实现一个完整的基于数据库的用户认证和授权系统。 SSH是Java开发中常用的三大框架组合,它们各自负责不同的职责:...

    Spring Security 资料合集

    这三份资料——"实战Spring Security 3.x.pdf"、"Spring Security 3.pdf" 和 "Spring Security使用手册.pdf" 将深入探讨这些概念,并提供实践指导,帮助读者掌握如何在实际项目中应用Spring Security。通过学习这些...

    Spring Security 3.pdf

    在Spring Security 3版本中,它引入了许多改进和新特性,以适应不断变化的安全需求和挑战。 一、核心概念 1. **Filter Chain**: Spring Security 的核心在于其过滤器链,它拦截HTTP请求并执行相应的安全处理。过滤...

    Spring Security in Action

    Spring Security 是一个基于 Java 的安全框架,旨在提供身份验证、授权和访问控制等功能。下面是 Spring Security 的主要知识点: 一、身份验证(Authentication) 身份验证是指对用户身份的验证,以确保用户的...

    Spring Security-3中文官方文档(及教程)

    Spring Security 是一个强大的和高度可定制的身份验证和访问控制框架,用于Java应用程序。它为Web应用和企业级应用提供了全面的安全解决方案。Spring Security 3是中国社区翻译的官方文档,为国内开发者提供了方便的...

    SSM+spring security3.x框架整合(附带数据文件)

    SSM+Spring Security 3.x框架整合是一个常见的Java Web开发技术栈,用于构建安全、功能丰富的Web应用程序。在这个框架中,Spring Framework(包括Spring Core、Spring MVC和Spring ORM)提供了基础架构支持,MyBatis...

    Springboot整合Spring security+Oauth2+JWT搭建认证服务器,网关,微服务之间权限认证及授权

    使用Spring Security和OAuth2,网关可以检查每个请求的JWT令牌,确保只有经过授权的请求才能到达后端服务。 6. **微服务间的权限认证**:微服务之间需要进行权限验证,避免恶意调用。通过在服务间传递JWT,每个服务...

    SpringBoot+SpringSecurity整合(实现了登录认证和权限验证)完整案例,基于IDEA项目

    SpringBoot+SpringSecurity整合示例代码,实现了从数据库中获取信息进行登录认证和权限认证。 本项目为idea工程,请用idea2019导入(老版应该也可以)。 本项目用户信息所需sql文件,在工程的resources文件夹下,...

    SpringSecurity笔记,编程不良人笔记

    3. **SpringBoot整合SpringSecurity** - `spring-boot-starter-security`依赖:SpringBoot项目中添加此依赖即可自动配置SpringSecurity。 - 自定义登录页面:通过设置`loginPage`和`loginProcessingUrl`属性,可以...

    SpringSecurity.pdf

    Spring Security架构的设计初衷是为了解决认证和授权的需求,确保应用程序的安全性。它提供了全面的安全功能,能够处理身份验证、请求过滤以及访问控制等多种安全相关的任务。 认证和授权是安全框架的核心概念。...

    Spring Security3 Demo

    总结,Spring Security 3 Demo是一个用于教学和实践的项目,它演示了如何配置和使用Spring Security来保护Web应用。通过研究和理解这个项目,开发者可以深入学习Spring Security的核心概念和实践技巧,从而提高他们...

    spring security 3 demos

    "Spring Security 3 Demos" 是一套针对Spring Security 3 版本的示例项目,旨在帮助开发者理解并掌握该框架的基本用法和核心功能。 在这些示例中,你可以学习到以下关键知识点: 1. **身份验证(Authentication)*...

    Spring Security3中文文档

    Spring Security3中文文档全面而深入地覆盖了Spring Security3的各个方面,从基础知识到高级特性,从理论到实践,都是开发者学习和应用Spring Security3不可或缺的资源。通过阅读和理解这些文档,开发者可以更好地...

    springsecurity学习笔记

    3. **Filter Security Interceptor (FSI) 和 Access Decision Manager (ADM)**:FSI是Spring Security的核心组件,负责拦截HTTP请求并进行安全检查。ADM则负责决定是否允许访问资源,它可以根据多个投票器(例如...

Global site tag (gtag.js) - Google Analytics