spring security 4 filter 顺序及作用
Spring Security 有两个作用:认证和授权
一、Srping security 4 filter 别名及顺序
spring security 4 标准filter别名和顺序,因为经常要用就保存到自己博客吧 点击访问官网链接
Table 6.1. Standard Filter Aliases and Ordering
| Alias | Filter Class | Namespace Element or Attribute |
|---|---|---|
|
CHANNEL_FILTER |
|
|
|
SECURITY_CONTEXT_FILTER |
|
|
|
CONCURRENT_SESSION_FILTER |
|
|
|
HEADERS_FILTER |
|
|
|
CSRF_FILTER |
|
|
|
LOGOUT_FILTER |
|
|
|
X509_FILTER |
|
|
|
PRE_AUTH_FILTER |
|
N/A |
|
CAS_FILTER |
|
N/A |
|
FORM_LOGIN_FILTER |
|
|
|
BASIC_AUTH_FILTER |
|
|
|
SERVLET_API_SUPPORT_FILTER |
|
|
|
JAAS_API_SUPPORT_FILTER |
|
|
|
REMEMBER_ME_FILTER |
|
|
|
ANONYMOUS_FILTER |
|
|
|
SESSION_MANAGEMENT_FILTER |
|
|
|
EXCEPTION_TRANSLATION_FILTER |
|
|
|
FILTER_SECURITY_INTERCEPTOR |
|
|
|
SWITCH_USER_FILTER |
|
N/A |
二、Spring security filter作用
2.1 默认filter链
在程序启动时会打印出如下日志,该日志打印出了默认的filter链和顺序,其中SecurityContextPersistenceFilter为第一个filter,FilterSecurityInterceptor为最后一个filter。
2018-02-11 15:24:17,204 INFO DefaultSecurityFilterChain - Creating filter chain:
org.springframework.security.web.util.matcher.AnyRequestMatcher@1,
[org.springframework.security.web.context.SecurityContextPersistenceFilter@3cf3957d,
org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter@7ff34bd,
org.springframework.security.web.header.HeaderWriterFilter@4dad11a2,
org.springframework.security.web.authentication.logout.LogoutFilter@5be6ee89,
org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter@5426eed3,
org.springframework.security.web.savedrequest.RequestCacheAwareFilter@5da2a66c,
org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter@23169e35,
org.springframework.security.web.session.SessionManagementFilter@5b1627ea,
org.springframework.security.web.access.ExceptionTranslationFilter@70b913f5,
org.springframework.security.web.access.intercept.FilterSecurityInterceptor@2dfe7327]
2.2 默认filter链作用
默认有10条过滤链,下面逐个看下去。
2.2.1 /index.html at position 1 of 10 in additional filter chain; firing Filter: 'SecurityContextPersistenceFilter'
SecurityContextPersistenceFilter 两个主要职责:
a.请求到来时,通过HttpSessionSecurityContextRepository接口从Session中读取SecurityContext,如果读取结果为null,则创建之。
public SecurityContext loadContext(HttpRequestResponseHolder requestResponseHolder) {
HttpServletRequest request = requestResponseHolder.getRequest();
HttpServletResponse response = requestResponseHolder.getResponse();
HttpSession httpSession = request.getSession(false);
// 从session中获取SecurityContext
SecurityContext context = readSecurityContextFromSession(httpSession);
if (context == null) {
if (logger.isDebugEnabled()) {
logger.debug("No SecurityContext was available from the HttpSession: "
+ httpSession + ". " + "A new one will be created.");
}
// 未读取到SecurityContext则新建一个SecurityContext
context = generateNewContext();
}
SaveToSessionResponseWrapper wrappedResponse = new SaveToSessionResponseWrapper(
response, request, httpSession != null, context);
requestResponseHolder.setResponse(wrappedResponse);
if (isServlet3) {
requestResponseHolder.setRequest(new Servlet3SaveToSessionRequestWrapper(
request, wrappedResponse));
}
return context;
}
获得SecurityContext之后,会将其存入SecurityContextHolder,其中SecurityContextHolder默认是ThreadLocalSecurityContextHolderStrategy实例
private static void initialize() {
if ((strategyName == null) || "".equals(strategyName)) {
// Set default
strategyName = MODE_THREADLOCAL;
}
if (strategyName.equals(MODE_THREADLOCAL)) {
strategy = new ThreadLocalSecurityContextHolderStrategy();
}
// 以下内容省略
}
ThreadLocalSecurityContextHolderStrategy中的ContextHolder定义如下,注意这是一个ThreadLocal变量,线程局部变量。
private static final ThreadLocal<SecurityContext> contextHolder = new ThreadLocal<SecurityContext>();
b.请求结束时清空SecurityContextHolder,并将SecurityContext保存到Session中。
finally {
SecurityContext contextAfterChainExecution = SecurityContextHolder
.getContext();
// Crucial removal of SecurityContextHolder contents - do this before anything
// else.
SecurityContextHolder.clearContext();
repo.saveContext(contextAfterChainExecution, holder.getRequest(),
holder.getResponse());
request.removeAttribute(FILTER_APPLIED);
if (debug) {
logger.debug("SecurityContextHolder now cleared, as request processing completed");
}
}
2.2.2 /index.html at position 2 of 10 in additional filter chain; firing Filter: 'WebAsyncManagerIntegrationFilter'
提供了对securityContext和WebAsyncManager的集成,其会把SecurityContext设置到异步线程中,使其也能获取到用户上下文认证信息。
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request); SecurityContextCallableProcessingInterceptor securityProcessingInterceptor = (SecurityContextCallableProcessingInterceptor) asyncManager
.getCallableInterceptor(CALLABLE_INTERCEPTOR_KEY);
if (securityProcessingInterceptor == null) {
asyncManager.registerCallableInterceptor(CALLABLE_INTERCEPTOR_KEY,
new SecurityContextCallableProcessingInterceptor());
} filterChain.doFilter(request, response);
}
2.2.3 /index.html at position 3 of 10 in additional filter chain; firing Filter: 'HeaderWriterFilter'
用来给http response添加一些Header,比如X-Frame-Options、X-XSS-Protection*、X-Content-Type-Options。
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException { HeaderWriterResponse headerWriterResponse = new HeaderWriterResponse(request,
response, this.headerWriters);
try {
filterChain.doFilter(request, headerWriterResponse);
}
finally {
// 向response header中添加header
headerWriterResponse.writeHeaders();
}
}
2.2.4 /index.html at position 4 of 10 in additional filter chain; firing Filter: 'LogoutFilter'
处理退出登录的Filter,如果请求的url为/logout则会执行退出登录操作。
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
// 判断是否需要logout,判断request url是否匹配/logout
if (requiresLogout(request, response)) {
Authentication auth = SecurityContextHolder.getContext().getAuthentication(); if (logger.isDebugEnabled()) {
logger.debug("Logging out user '" + auth
+ "' and transferring to logout destination");
}
// 执行一系列的退出登录操作
for (LogoutHandler handler : handlers) {
handler.logout(request, response, auth);
}
// 退出成功,执行logoutSuccessHandler进行重定向等操作
logoutSuccessHandler.onLogoutSuccess(request, response, auth); return;
} chain.doFilter(request, response);
}
2.2.5 /index.html at position 5 of 10 in additional filter chain; firing Filter: 'UsernamePasswordAuthenticationFilter'
表单认证是最常用的一个认证方式,一个最直观的业务场景便是允许用户在表单中输入用户名和密码进行登录,而这背后的UsernamePasswordAuthenticationFilter,在整个Spring Security的认证体系中则扮演着至关重要的角色。
UsernamePasswordAuthenticationFilter是继承自AbstractAuthenticationProcessingFilter。
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
// 判断是否需要执行登录认证,判断request url 是否能匹配/login
if (!requiresAuthentication(request, response)) {
chain.doFilter(request, response); return;
} if (logger.isDebugEnabled()) {
logger.debug("Request is to process authentication");
} Authentication authResult; try {
// UsernamePasswordAuthenticationFilter 实现该方法
authResult = attemptAuthentication(request, response);
if (authResult == null) {
// 子类未完成认证,立即返回
return;
}
sessionStrategy.onAuthentication(authResult, request, response);
}
// 在认证过程中抛出异常
catch (InternalAuthenticationServiceException failed) {
logger.error(
"An internal error occurred while trying to authenticate the user.",
failed);
unsuccessfulAuthentication(request, response, failed); return;
}
catch (AuthenticationException failed) {
// Authentication failed
unsuccessfulAuthentication(request, response, failed); return;
} // Authentication success
if (continueChainBeforeSuccessfulAuthentication) {
chain.doFilter(request, response);
} successfulAuthentication(request, response, chain, authResult);
}
在UsernamePasswordAuthenticationFilter中实现了类attemptAuthentication,不过该类只实现了一个非常简化的版本,如果真的需要通过表单登录,是需要自己继承UsernamePasswordAuthenticationFilter并重载attemptAuthentication方法的。
在AbstractAuthenticationProcessingFilter的doFilter方法中一开始是判断是否有必要进入到认证filter,这个过程其实是判断request url是否匹配/login,当然也可以通过filterProcessesUrl属性去配置匹配所使用的pattern。
2.2.6 /index.html at position 6 of 10 in additional filter chain; firing Filter: 'RequestCacheAwareFilter'
将request存到session中,用于缓存request请求,可以用于恢复被登录而打断的请求
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
// 从session中获取与当前request匹配的缓存request,并将缓存request从session删除
HttpServletRequest wrappedSavedRequest = requestCache.getMatchingRequest(
(HttpServletRequest) request, (HttpServletResponse) response);
// 如果requestCache中缓存了request,则使用缓存的request
chain.doFilter(wrappedSavedRequest == null ? request : wrappedSavedRequest,
response);
}
此处从session中取出request,存储request是在ExceptionTranslationFilter中。具体可以参考探究 Spring Security 缓存请求
2.2.7 /index.html at position 7 of 10 in additional filter chain; firing Filter: 'SecurityContextHolderAwareRequestFilter'
此过滤器对ServletRequest进行了一次包装,使得request具有更加丰富的API
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
chain.doFilter(this.requestFactory.create((HttpServletRequest) req,
(HttpServletResponse) res), res);
}
2.2.8 /index.html at position 8 of 10 in additional filter chain; firing Filter: 'SessionManagementFilter'
和session相关的过滤器,内部维护了一个SessionAuthenticationStrategy,两者组合使用,常用来防止session-fixation protection attack,以及限制同一用户开启多个会话的数量
与登录认证拦截时作用一样,持久化用户登录信息,可以保存到session中,也可以保存到cookie或者redis中。
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res; if (request.getAttribute(FILTER_APPLIED) != null) {
chain.doFilter(request, response);
return;
} request.setAttribute(FILTER_APPLIED, Boolean.TRUE); if (!securityContextRepository.containsContext(request)) {
Authentication authentication = SecurityContextHolder.getContext()
.getAuthentication(); if (authentication != null && !trustResolver.isAnonymous(authentication)) {
// The user has been authenticated during the current request, so call the
// session strategy
try {
sessionAuthenticationStrategy.onAuthentication(authentication,
request, response);
}
catch (SessionAuthenticationException e) {
// The session strategy can reject the authentication
logger.debug(
"SessionAuthenticationStrategy rejected the authentication object",
e);
SecurityContextHolder.clearContext();
failureHandler.onAuthenticationFailure(request, response, e); return;
}
// Eagerly save the security context to make it available for any possible
// re-entrant
// requests which may occur before the current request completes.
// SEC-1396.
securityContextRepository.saveContext(SecurityContextHolder.getContext(),
request, response);
}
else {
// No security context or authentication present. Check for a session
// timeout
if (request.getRequestedSessionId() != null
&& !request.isRequestedSessionIdValid()) {
if (logger.isDebugEnabled()) {
logger.debug("Requested session ID "
+ request.getRequestedSessionId() + " is invalid.");
} if (invalidSessionStrategy != null) {
invalidSessionStrategy
.onInvalidSessionDetected(request, response);
return;
}
}
}
} chain.doFilter(request, response);
}
2.2.9 /index.html at position 9 of 10 in additional filter chain; firing Filter: 'ExceptionTranslationFilter'
异常拦截,其处在Filter链后部分,只能拦截其后面的节点并且只处理AuthenticationException与AccessDeniedException两个异常。
AuthenticationException指的是未登录状态下访问受保护资源,AccessDeniedException指的是登陆了但是由于权限不足(比如普通用户访问管理员界面)。
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res; try {
// 直接执行后面的filter,并捕获异常
chain.doFilter(request, response); logger.debug("Chain processed normally");
}
catch (IOException ex) {
throw ex;
}
catch (Exception ex) {
// 从异常堆栈中提取SpringSecurityException
Throwable[] causeChain = throwableAnalyzer.determineCauseChain(ex);
RuntimeException ase = (AuthenticationException) throwableAnalyzer
.getFirstThrowableOfType(AuthenticationException.class, causeChain); if (ase == null) {
ase = (AccessDeniedException) throwableAnalyzer.getFirstThrowableOfType(
AccessDeniedException.class, causeChain);
} if (ase != null) {
// 处理异常
handleSpringSecurityException(request, response, chain, ase);
}
else {
// Rethrow ServletExceptions and RuntimeExceptions as-is
if (ex instanceof ServletException) {
throw (ServletException) ex;
}
else if (ex instanceof RuntimeException) {
throw (RuntimeException) ex;
} // Wrap other Exceptions. This shouldn't actually happen
// as we've already covered all the possibilities for doFilter
throw new RuntimeException(ex);
}
}
}
在这个catch代码中通过从异常堆栈中捕获到Throwable[],然后通过handleSpringSecurityException方法处理异常,在该方法中只会去处理AuthenticationException和AccessDeniedException异常。
private void handleSpringSecurityException(HttpServletRequest request, HttpServletResponse response, FilterChain chain, RuntimeException exception) throws IOException, ServletException {
if (exception instanceof AuthenticationException) {
// 认证异常,由sendStartAuthentication方法发起认证过程
logger.debug("Authentication exception occurred; redirecting to authentication entry point", exception);
sendStartAuthentication(request, response, chain, (AuthenticationException) exception);
} else if (exception instanceof AccessDeniedException) {
// 访问权限异常
if (authenticationTrustResolver.isAnonymous(SecurityContextHolder.getContext().getAuthentication())) {
logger.debug("Access is denied (user is anonymous); redirecting to authentication entry point", exception);
// 匿名用户重定向到认证入口点执行认证过程
sendStartAuthentication(request, response, chain, new InsufficientAuthenticationException("Full authentication is required to access this resource"));
} else {
// 拒绝访问,由accessDeniedHandler处理,response 403
logger.debug("Access is denied (user is not anonymous); delegating to AccessDeniedHandler", exception);
accessDeniedHandler.handle(request, response, (AccessDeniedException) exception);
}
}
}
protected void sendStartAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, AuthenticationException reason) throws ServletException, IOException {
// SEC-112: Clear the SecurityContextHolder's Authentication, as the existing Authentication is no longer considered valid
// 将SecurityContext中的Authentication置为null
SecurityContextHolder.getContext().setAuthentication(null);
// 在调用认证前先将request保存到session
requestCache.saveRequest(request, response);
logger.debug("Calling Authentication entry point.");
// 重定向到认证入口点执行认证
authenticationEntryPoint.commence(request, response, reason);
}
2.2.10 /index.html at position 10 of 10 in additional filter chain; firing Filter: 'FilterSecurityInterceptor'
这个filter用于授权验证。FilterSecurityInterceptor的工作流程引用一下,可以理解如下:FilterSecurityInterceptor从SecurityContextHolder中获取Authentication对象,然后比对用户拥有的权限和资源所需的权限。前者可以通过Authentication对象直接获得,而后者则需要引入我们之前一直未提到过的两个类:SecurityMetadataSource,AccessDecisionManager。
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
FilterInvocation fi = new FilterInvocation(request, response, chain);
invoke(fi);
}
public void invoke(FilterInvocation fi) throws IOException, ServletException {
if ((fi.getRequest() != null) && (fi.getRequest().getAttribute(FILTER_APPLIED) != null) && observeOncePerRequest) {
// filter already applied to this request and user wants us to observe
// once-per-request handling, so don't re-do security checking
fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
} else {
// first time this request being called, so perform security checking
if (fi.getRequest() != null) {
fi.getRequest().setAttribute(FILTER_APPLIED, Boolean.TRUE);
}
InterceptorStatusToken token = super.beforeInvocation(fi);
try {
// 如果后面还有filter则继续执行
fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
}
finally {
// 保存securityContext
super.finallyInvocation(token);
}
super.afterInvocation(token, null);
}
}
protected InterceptorStatusToken beforeInvocation(Object object) {
Assert.notNull(object, "Object was null");
final boolean debug = logger.isDebugEnabled();
if (!getSecureObjectClass().isAssignableFrom(object.getClass())) {
throw new IllegalArgumentException(
"Security invocation attempted for object "
+ object.getClass().getName()
+ " but AbstractSecurityInterceptor only configured to support secure objects of type: "
+ getSecureObjectClass());
}
// 获取配置的权限属性
Collection<ConfigAttribute> attributes = this.obtainSecurityMetadataSource().getAttributes(object);
if (attributes == null || attributes.isEmpty()) {
if (rejectPublicInvocations) {
throw new IllegalArgumentException(
"Secure object invocation "
+ object
+ " was denied as public invocations are not allowed via this interceptor. "
+ "This indicates a configuration error because the "
+ "rejectPublicInvocations property is set to 'true'");
}
if (debug) {
logger.debug("Public object - authentication not attempted");
}
publishEvent(new PublicInvocationEvent(object));
return null; // no further work post-invocation
}
if (debug) {
logger.debug("Secure object: " + object + "; Attributes: " + attributes);
}
if (SecurityContextHolder.getContext().getAuthentication() == null) {
credentialsNotFound(messages.getMessage(
"AbstractSecurityInterceptor.authenticationNotFound",
"An Authentication object was not found in the SecurityContext"),
object, attributes);
}
// 获取Authentication,如果没有进行认证则认证后返回authentication
Authentication authenticated = authenticateIfRequired();
// Attempt authorization
try {
// 使用voter决策是否拥有资源需要的权限
this.accessDecisionManager.decide(authenticated, object, attributes);
} catch (AccessDeniedException accessDeniedException) {
// 捕获到异常继续上抛
publishEvent(new AuthorizationFailureEvent(object, attributes, authenticated, accessDeniedException));
throw accessDeniedException;
}
if (debug) {
logger.debug("Authorization successful");
}
if (publishAuthorizationSuccess) {
publishEvent(new AuthorizedEvent(object, attributes, authenticated));
}
// Attempt to run as a different user
Authentication runAs = this.runAsManager.buildRunAs(authenticated, object,
attributes);
if (runAs == null) {
if (debug) {
logger.debug("RunAsManager did not change Authentication object");
}
// no further work post-invocation
return new InterceptorStatusToken(SecurityContextHolder.getContext(), false,
attributes, object);
}
else {
if (debug) {
logger.debug("Switching to RunAs Authentication: " + runAs);
}
SecurityContext origCtx = SecurityContextHolder.getContext();
SecurityContextHolder.setContext(SecurityContextHolder.createEmptyContext());
SecurityContextHolder.getContext().setAuthentication(runAs);
// need to revert to token.Authenticated post-invocation
return new InterceptorStatusToken(origCtx, true, attributes, object);
}
}
参考文章:
https://blog.coding.net/blog/Explore-the-cache-request-of-Security-Spring
http://blog.didispace.com/xjf-spring-security-4/
http://blog.csdn.net/benjamin_whx/article/details/39204679
spring security 4 filter 顺序及作用的更多相关文章
- 解决Spring Security自定义filter重复执行问题
今天做项目的时候,发现每次拦截器日志都会打两遍,很纳闷,怀疑是Filter被执行了两遍.结果debug之后发现还真是!记录一下这个神奇的BUG! 问题描述 项目中使用的是Spring-security ...
- Spring Security 入门(1-6-2)Spring Security - 内置的filter顺序、自定义filter、http元素和对应的filterChain
Spring Security 的底层是通过一系列的 Filter 来管理的,每个 Filter 都有其自身的功能,而且各个 Filter 在功能上还有关联关系,所以它们的顺序也是非常重要的. 1.S ...
- Spring Security(09)——Filter
目录 1.1 Filter顺序 1.2 添加Filter到FilterChain 1.3 DelegatingFilterProxy 1.4 FilterChainPr ...
- Spring Security学习笔记
Spring Web Security是Java web开发领域的一个认证(Authentication)/授权(Authorisation)框架,基于Servlet技术,更确切的说是基于Servle ...
- Spring Security 入门原理及实战
目录 从一个Spring Security的例子开始 创建不受保护的应用 加入spring security 保护应用 关闭security.basic ,使用form表单页面登录 角色-资源 访问控 ...
- Spring Security 梳理 - DelegatingFilterProxy
可能你会觉得奇怪,我们在web应用中使用Spring Security时只在web.xml文件中定义了如下这样一个Filter,为什么你会说是一系列的Filter呢? <filter> ...
- spring security基本知识(三) 过滤详细说明
在我们前面的文章Spring Security 初识(一)中,我们看到了一个最简单的 Spring Security 配置,会要求所有的请求都要经过认证.但是,这并不是我们想要的,我们通常想自定义应用 ...
- Spring Security 04
转至:Elim的博客http://elim.iteye.com/blog/2161648 Filter Porxy DelegatingFilterProxy DelegationFilterProx ...
- 基于Spring Security 的JSaaS应用的权限管理
1. 概述 权限管理,一般指根据系统设置的安全规则或者安全策略,用户可以访问而且只能访问自己被授权的资源.资源包括访问的页面,访问的数据等,这在传统的应用系统中比较常见.本文介绍的则是基于Saas系统 ...
随机推荐
- Linux系统调优权威指南
1.关闭SELINUX功能1.1 修改配置文件,使关闭SELINUX永久生效sed 's#SELINUX=enforcing#SELINUX=disables#g' /etc/selinux/conf ...
- [国嵌攻略][067][tftp协议分析]
TFTP作用 用于网络下载,TFTP客户机在TFTP服务器中下载文件. TFTP交换过程 1.配置TFTP服务器 vim /etc/xinetd.d/tftp 2.交换过程 客户端发请求包到服务器 服 ...
- <input type="text">和<textarea>的区别
在我们开发时经常需要用到输入框,通常解决办法就是<input type="text">和<textarea>,那么这两个标签有什么区别呢? 一:<i ...
- Spark算子--flatMapValues
转载请标明出处http://www.cnblogs.com/haozhengfei/p/e7a46cecc65720997392516d553d9891.html flatMapValues--Tra ...
- 小白的Python之路 day5 configparser模块的特点和用法
configparser模块的特点和用法 一.概述 主要用于生成和修改常见配置文件,当前模块的名称在 python 3.x 版本中变更为 configparser.在python2.x版本中为Conf ...
- 关于userInteractionEnabled的属性的理解
userInteractionEnabled A Boolean value that determines whether user events are ignored and removed f ...
- 【开发技术】 java和JSP和JavaScript有什么区别
JSP全称是:java server page,意思是基于JAVA服务器的网页技术,跟asp,php一样,都是网页制作用的语言 JavaScript:也成为JS,跟JAVA没啥关系,就是赶时髦起个这名 ...
- CCF系列之数位之和(201512-1)
试题编号: 201512-1试题名称: 数位之和时间限制: 1.0s内存限制: 256.0MB问题描述: 问题描述 给定一个十进制整数n,输出n的各位数字之和. 输入格式 输入一个整数n. 输出格式 ...
- 洛谷 P1099 树网的核
P1099 树网的核 题目描述 设T=(V, E, W) 是一个无圈且连通的无向图(也称为无根树),每条边到有正整数的权,我们称T为树网(treebetwork),其中V,E分别表示结点与边的集合,W ...
- bash中声明变量方法
bash提供了declare命令来声明变量,该命令的基本语法如下: declare attribute variable 其中,attribute表示变量的属性,常用的属性有如下所述. ...