Spring Security –在一个应用程序中有两个安全领域

这篇博客文章主要是关于Spring Security配置的。
更具体地说,它打算显示如何在一个Web应用程序中配置两个不同的安全领域。

第一安全领域是针对浏览器客户端的。 它使我们能够在登录页面中登录并访问受保护的资源。

第二安全领域旨在处理来自android应用程序的REST Web服务请求。 在每个请求上,REST客户端应将所需的信息发送到服务器,并且此信息将用于确定是否应允许RESTfull请求通过。
这两个安全领域(配置)的区别在于Web应用程序中资源的URL模式不同。 在这两种配置中,我们都可以重用相同的身份验证逻辑。

第一安全领域

我们有一个经典的Web应用程序,其中包含一些受保护的资源(页面)。 为了访问这些资源,用户应在登录页面上登录到应用程序。 如果登录成功,则将用户转发到所请求的资源。 如果用户的登录过程由于某种原因(例如,错误的用户名或密码)而失败,则该用户将无法获得受保护的资源,并且将其重定向到登录页面,并显示相应的消息。
我在上一节中刚刚描述的情况可能被认为是“经典的Web应用程序行为”。 普通的互联网用户至少会遇到数百个这样的在线应用程序。 这种行为旨在与客户(浏览器)一起使用。 由于这种行为在当今非常普遍,因此Spring安全性使得实现它非常容易。 显然,基于表单的身份验证机制最适合我们。 在Spring Security中,当您希望定义与客户端身份验证状态相关的操作时,可以定义入口点。 这是我们标准浏览器-客户端入口点的预览:

<http  entry-point-ref="loginUrlAuthenticationEntryPoint" use-expressions="true"><intercept-url pattern="/includes/content/administration.jsp" access="hasAnyRole('ROLE_100','ROLE_1000')" /><intercept-url pattern="/includes/content/userAdministration.jsp" access="hasAnyRole('ROLE_100','ROLE_1000')" /><intercept-url pattern="/includes/content/groupAdministration.jsp" access="hasAnyRole('ROLE_100','ROLE_1000')" /><intercept-url pattern="/includes/content/departmentAdministration.jsp" access="hasAnyRole('ROLE_100','ROLE_1000')" /><intercept-url pattern="/includes/content/shiftAdministration.jsp" access="hasAnyRole('ROLE_100','ROLE_101','ROLE_1000') /><custom-filter position="FORM_LOGIN_FILTER" ref="userAuthenticationProcessingFilter" /><logout logout-url='/logout' />
</http><beans:bean id="loginUrlAuthenticationEntryPoint"class="org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint"><beans:property name="loginFormUrl" value="/login.jsp" />
</beans:bean>

希望这是不言而喻的。 loginUrlAuthenticationEntryPoint是一个入口点,您可以在其中配置实现了登录功能的登录页面。 然后,在http元素中,我们将该入口点的行为配置为更多详细信息。 首先,我们定义了拦截URL元素列表。 仅当请求了这些资源之一时,才会激活此入口点。 我们还用我们自己的自定义版本替换了默认的FORM_LOGIN_FILTER 。 Spring安全性通过应用您在入口点中定义的过滤器链来发挥作用。 这些基本上是标准的servlet过滤器。 您可以使用Spring预定义过滤器,也可以扩展它们并插入自定义过滤器。 在这里,我们使用了Spring的安全过滤器之一。

这是一个UsernamePasswordAuthenticationFilter 。 它用于具有用户名和密码字段的登录页面的情况。 该过滤器使我们能够结合将用于身份验证的自定义机制。 它还允许我们定义在成功和不成功的身份验证的情况下将要采取的措施。 让我们看看这种配置如何:

<beans:bean id="loginSuccessHandler"class="org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler"><beans:property name="defaultTargetUrl" value="/main.jsp" />
</beans:bean><beans:bean id="userAuthenticationProcessingFilter"class="org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter"><beans:property name="authenticationManager" ref="authenticationManager" /><beans:property name="authenticationFailureHandler"ref="loginMappingFailureHandler" /><beans:property name="authenticationSuccessHandler"ref="loginSuccessHandler" />
</beans:bean><beans:bean id="loginMappingFailureHandler"class="org.springframework.security.web.authentication.ExceptionMappingAuthenticationFailureHandler"><beans:property name="exceptionMappings" ref="failureUrlMap" />
</beans:bean><util:map id="failureUrlMap" map-class="java.util.HashMap"><beans:entrykey="org.springframework.security.authentication.BadCredentialsException"value="/login.jsp?errorMessage=bad.credentials" /><beans:entrykey="org.springframework.security.authentication.DisabledException"value="/login.jsp?errorMessage=disabled.user" />
</util:map>

我们再来看一下此配置。 我将解释我们在这里所做的事情。
首先,我们定义了表单登录过滤器。 实际上,我们为此定义了三件事。 我们为它提供了自定义身份验证机制,该机制将在整个应用程序中使用。 该机制通过authenticationManager插入到过滤器中。 我将很快谈论认证管理器。

第二件事,我们定义了一个登录失败处理程序。 基本上,这是Spring异常以及对这些异常采取的措施的映射。 异常由AuthenticationProvider引发,如下所述。 例如,当用户输入错误的用户名或密码时,将引发BadCredentialsException 。 发生这种情况时,用户将再次重定向到登录页面。 还将某些参数附加到登录页面的URL,以使我们能够显示正确的错误消息。

第三,也是最后一件事,我们定义了一个成功的身份验证处理程序。 这确实很明显。 我们正在定义如果登录成功该怎么办。 用户被发送到主页。

现在,让我们来谈谈认证管理器。 这只是Spring使用的接口。 可以是任何东西。 它可以是用户数据库,LDAP服务器或其他数据库。 身份验证管理器不能自行完成工作。 它仅使用身份验证提供程序为其执行实际的身份验证工作。 身份验证提供程序在被调用时可以做两件事:

  1. 可以返回成功填充的对象(这是Spring的Authentication接口的实例)
  2. 可以抛出适当的Spring安全异常之一

身份验证管理器配置如下所示:

<authentication-manager alias="authenticationManager"><authentication-provider ref="authenticationProvider" />
</authentication-manager><beans:bean id="authenticationProvider" class="ba.codecentric.medica.security.UserAuthenticationProvider"><beans:property name="userService" ref="userService"/><beans:property name="licenseInformationWrapper" ref="licenseInformationWrapper"/>
</beans:bean>

这是我的定制身份验证提供程序的源代码:

package ba.codecentric.medica.security;import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;import org.apache.commons.collections.CollectionUtils;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;import ba.codecentric.medica.administration.service.UserService;
import ba.codecentric.medica.model.Group;
import ba.codecentric.medica.model.LicenseInformationWrapper;
import ba.codecentric.medica.model.Role;
import ba.codecentric.medica.model.User;public class UserAuthenticationProvider implements AuthenticationProvider {private static final String ROLE_PREFIX = "ROLE_";private UserService userService;private LicenseInformationWrapper licenseInformationWrapper;@Overridepublic Authentication authenticate(Authentication authentication) throws AuthenticationException {User user = userService.getUserByUsernameAndPassword(authentication.getName(), authentication.getCredentials().toString(), true);if (user != null) {Collection authorities = new ArrayList(buildRolesFromUser(user));authorities.addAll(getActivatedModulesAsRoles());return new UsernamePasswordAuthenticationToken(user.getUsername(), user.getPassword(),authorities);} else {throw new BadCredentialsException("Try again");}}private Collection getActivatedModulesAsRoles() {List activatedModules = new ArrayList();if(CollectionUtils.isNotEmpty(licenseInformationWrapper.getActivatedModules())) {for(String activatedModuleName: licenseInformationWrapper.getActivatedModules()) {activatedModules.add(new SimpleGrantedAuthority(ROLE_PREFIX + activatedModuleName));}}return activatedModules;}private Collection buildRolesFromUser(User user) {Collection authorities = new HashSet();for (Group group : user.getGroups()) {for (Role role : group.getRoles()) {authorities.add(new SimpleGrantedAuthority(ROLE_PREFIX + role.getName()));}}return authorities;}@Overridepublic boolean supports(Class authentication) {return (UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication));}public UserService getUserService() {return userService;}public void setUserService(UserService userService) {this.userService = userService;}public LicenseInformationWrapper getLicenseInformationWrapper() {return licenseInformationWrapper;}public void setLicenseInformationWrapper(LicenseInformationWrapper licenseInformationWrapper) {this.licenseInformationWrapper = licenseInformationWrapper;}}

如您所见,身份验证过程非常简单。 我的自定义身份验证提供程序实现了Spring AuthenticationProvider接口。

就像我们之前讨论的那样,它可以完成工作。 它在数据库的用户表中查找用户名和密码。 如果找到这样的用户,则创建并返回认证对象。 否则,如果未找到此类用户,则authenticate方法将引发适当的异常。 还有一件事情。 Spring使用GrantedAuthority对象的集合来表示分配给用户的角色。 这就是为什么我们构建这样的集合并将其附加到身份验证对象的原因。 必须将数据库中与用户连接的每个角色都添加到已授予权限的集合中,以使Spring将此角色视为角色。 并且每个角色必须具有ROLE_前缀。 我们还有另一件事要展示。 如何从登录网页调用此过滤器? 这是login.jsp的一部分:

<form id="loginForm" method="POST" action="j_spring_security_check"><table>
<tr><td><b><fmt:message key="login.username.label" />:</b></td><c:choose><c:when test="${not empty param.j_username}" ><td><input type="text" name="j_username" id="username" value="${param.j_username }" class="loginInput" /></td>    </c:when><c:otherwise><td><input type="text" name="j_username" id="username" class="loginInput"/></td></c:otherwise></c:choose> </tr>
<tr><td><b><fmt:message key="login.password.label" />:</b></td><c:choose><c:when test="${not empty param.j_password}" ><td><input type="password" name="j_password" id="password" value="${param.j_password }" class="loginInput" /></td>    </c:when><c:otherwise><td><input type="password" name="j_password" id="password" class="loginInput" /></td></c:otherwise></c:choose> </tr>
<tr><td><b><fmt:message key="login.alphabet.label" /></b>:</td><td><select id="alphabet" class="fullWidth" onchange="languageSelect();"><option value="lat"><fmt:message key="login.alphabet.lat" /></option><option value="cir"><fmt:message key="login.alphabet.cyr" /></option></select></td></tr>
<tr><td></td><td><input type="submit" value="<fmt:message key="login.button.label" />" class="right"></td></tr>
</table></form>

默认情况下,标准Spring安全性设置要求您通过调用j_spring_security_check从登录表单中调用安全链。 用户名和密码过滤器将拦截此URL(默认设置),但您可以将其配置为拦截任何其他URL。 好吧,这一切都与“基于浏览器的客户端”安全领域有关。 如果用户未登录并尝试访问受该领域(入口点)保护的资源,则该领域将重定向用户到登录页面并要求他登录。只有当用户登录时,才受保护资源将可用。

第二安全领域

现在最后,让我们讨论应用程序中的第二个安全领域。 我们仅在博客的简介部分提到了它。 该应用程序支持REST服务调用。 我们必须实现将应用程序的某些部分与运行在移动设备上的简单android应用程序同步的要求。 我们认为最简单的方法是从手机到Web应用程序进行RESTfull调用。 当然,我们在这里也需要安全。 我们不想让用户始终能够连接到应用程序。 用户列表及其角色在数据库中维护。 例如,某个用户今天可以处于活动状态,但是明天管理员可以决定该用户不再处于活动状态,并且不应该能够连接到应用程序(也应该不能登录)。 因此,必须在REST服务领域中实现安全性。

让我们考虑一下这个领域。 这些REST调用应该如何工作。 Android应用程序将POST请求(RESTfull请求)发送到Web应用程序以获取某些数据(医生的约会等)。 应用程序查找并返回请求的数据。 然后,Android应用程序处理获取的数据并将其显示给用户。

现在,我们将安全性添加到此RESTfull概念中,并尝试用安全性描述概念。 Android应用程序发送POST请求。 Android应用程序发送一个包含哈希用户名和密码的标头,作为每个请求的一部分(请参阅-> 基本身份验证 )。

Web应用程序的安全领域(入口点)应该接收到此请求,并且如果用户名和密码确实是活动用户,则允许此请求到达Web应用程序中的REST服务,并将对其进行处理。 如果用户名和密码无效(或用户处于非活动状态),则请求应在安全入口点失败,这意味着我们应立即返回格式正确的HTTP响应,该响应将通知客户端应用程序该用户与该用户名称和密码不允许访问Web应用程序中的REST服务。

正如我们在这种情况下所看到的,先前定义的入口点的行为与REST服务不对应。 上一个入口点,如果用户未通过身份验证,则将其重定向到登录页面。 这意味着,如果用户未通过身份验证,则服务器实际上会返回包含登录页面HTML代码的HTTP响应。 我们无法在android应用程序中处理这种行为,因为它不会显示任何HTML网页。 那么,当它收到登录页面HTML时会怎么做?
这就是为什么我们实际上需要Web应用程序中的第二个安全域(安全性入口点)的主要原因,该安全域的工作方式不同于处理浏览器客户端的机制。 如果用户无法通过身份验证,此新的安全领域将仅将格式正确的HTTP响应返回给客户端应用程序(它将在响应上设置特定的HTTP状态和HTTP消息)。 我们知道,在Java Server环境中,我们有一种称为基本身份验证的安全性。 它基于发送散列的用户名和密码作为请求标头的一部分(标头必须以特定方式进行格式化)。 然后,如果可以在用户数据池中找到用户名和密码,则允许通过请求。 否则,将返回HTTP响应以及相应的状态和消息,以告知客户端不允许其访问某些资源。 对我们来说幸运的是,Spring支持这种身份验证机制。 我们将添加另一个入口点和一个过滤器。 它将是这样的:

<http entry-point-ref="basicAuthEntryPoint" pattern="/ws/**" use-expressions="true"><intercept-url pattern="/ws/schedule/patients" access="hasAnyRole('ROLE_1','ROLE_100','ROLE_300','ROLE_1000')" /><custom-filter ref="basicAuthenticationFilter" after="BASIC_AUTH_FILTER" />
</http><beans:bean id="basicAuthEntryPoint" class="org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint"><beans:property name="realmName" value="REST Realm" />
</beans:bean><beans:bean id="basicAuthenticationFilter" class="org.springframework.security.web.authentication.www.BasicAuthenticationFilter"><beans:property name="authenticationManager" ref="authenticationManager"/><beans:property name="authenticationEntryPoint" ref="basicAuthEntryPoint" />
</beans:bean>

基本上,我们添加了一个新的入口点(安全领域),该入口点拦截URL路径/ ws / **上的所有请求。 这是我们所有REST服务调用通过的路径。 我们使用了Springs BasicAuthenticationFilter ,它提供了读取请求标头和调用身份验证管理器的功能。 如果在数据库中找到了用户名和密码(由身份验证管理器处理),将允许请求进一步传递。 如果在数据库中找不到用户名和密码,入口点将在HTTP响应上设置状态401,并立即将此响应返回给客户端。 这只是我们Android应用程序所需的行为。

这就是我们的应用程序需要的所有安全配置。 现在剩下要做的就是在web.xml文件中启用Spring安全过滤器。 我已经提到过,Spring安全性可以通过在请求上调用过滤器链来实现。 这意味着存在某种“主”过滤器,它是所有其他后续过滤器和服务的基础。 在web.xml中启用并配置了该“主”过滤器。 这是我的配置:

<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><dispatcher>ERROR</dispatcher><dispatcher>REQUEST</dispatcher><dispatcher>INCLUDE</dispatcher><dispatcher>FORWARD</dispatcher>
</filter-mapping>

如您所见,主要的Spring安全过滤器已配置为拦截对应用程序中所有资源的所有请求。 但是,哪些资源真正受到保护,哪些资源是公共资源,则由入口点控制(通过http元素中的URL模式)。 例如,位于/ css文件夹中的所有资源都被视为公用资源,不需要用户进行身份验证就可以访问它们:

<http pattern="/css/**" security="none" />

另一方面,像管理页面这样的资源受到保护,并且如果用户希望访问该页面,则不仅要求用户进行身份验证,还需要分配某些角色。 这是此xml代码段中的示例:

<!-- more xml -->
<intercept-url pattern="/includes/content/administration.jsp" access="hasAnyRole('ROLE_100','ROLE_1000')" />
<!-- more xml -->

还有两件事要说!

当您的安全配置中有多个http元素时,请确保具有最特定模式属性的元素位于不那么特定甚至可能没有模式属性的元素之前。 否则,当Spring开始抱怨应用程序中的过滤器排序没有意义时,您将在日志文件中看到很长的堆栈跟踪。

阅读此博客后,您可能会开始认为添加基于表单的身份验证或基本身份验证就足够了,您的应用程序将是安全的。 但是,那不是完全正确的。 任何对HTTP协议和网络具有“技术”知识的人都可以想到如何在网络内部拦截HTTP数据流的方式。 对于基本身份验证和基于表单的身份验证,诸如用户名和密码之类的信息直接通过HTTP协议发送。 对于基本身份验证,它们将作为HTTP请求标头发送。 对于基于表单的身份验证,它们将作为请求参数发送。 因此,可以拦截和读取这些HTTP流的人员可以轻松读取您的标头并请求参数。 稍后,同一个人可以手动创建请求,并将那些标头或参数附加到请求。 当然,此新请求现在将由容器授权,因为它包含正确的身份验证详细信息。

那么,我们该怎么做才能避免对应用程序的这些安全攻击?
真正的答案是:我们应该在保护应用程序资源的地方使用HTTPS协议 。 仅通过使用HTTPS协议和Java服务器的身份验证机制,我们就可以肯定地说我们的应用程序确实是安全的。

参考: Spring Security –我们的JCG合作伙伴 Branislav Vidovi提供了一个应用程序中的两个安全领域 ? 在极客的东西:-)博客上。


翻译自: https://www.javacodegeeks.com/2012/08/spring-security-two-security-realms-in.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/372068.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

基于Activiti工作流引擎实现的请假审核流程

概要 本文档介绍的是某商用中集成的Activiti工作流的部署及使用&#xff0c;该框架用的Activiti版本为5.19.0。本文档中主要以一个请假流程为例子进行说明&#xff0c;该例子的流程图如下&#xff1a; 这是一个可以正常运作的工作流业务了&#xff0c;但是它也有不足的地方&…

linux编译ffmpeg成so,「ffmpeg」一 mac 环境下编译ffmpeg,生成so库文件

1.下载ffmpeg源码,官网&#xff0c;我这里直接采用git 方式下载&#xff1a;下载ffmpeg.png终端输入git命令&#xff1a;静静等待~最后下载的版本为3.4.6 。image.png这里注意一下&#xff0c;刚开始我用的ndk版本是ndk-17b&#xff0c;在编译该版本的ffmpeg时始终失败&#xf…

4Web Service中的几个重要术语

4.1WSDL: web service definition language 直译:Webservice定义语言 1.对应一种类型的文件.wsdl 2.定义了webservice的服务端与客户端应用交互传递请求和响应数据的格式和方式 3.一个webservice对应一个唯一的esdl文档 4.2SOAP: simple object access protocal 直译:简单对象访…

云端:亚马逊,谷歌应用引擎,Windows Azure,Heroku,Jelastic

您想在云端吗&#xff1f; 您有很多选择。 我已经评估或使用了许多方法&#xff0c;因此这里有几句话。 &#xff08;当我使用Java时&#xff0c;我将包括一些与Java相关的注释&#xff0c;但大多数情况适用于所有&#xff08;受支持的&#xff09;语言。&#xff09; 但是在深…

JS-字符串操作-替换

<!DOCTYPE HTML><html><head><meta http-equiv"Content-Type" content"text/html; charsetutf-8"><title>无标题文档</title><style>p { border:10px solid #ccc; background:#FFC; width:400px; padding:20px;…

linux下kegg注释软件,KEGG数据中全部代谢反应和代谢物注释信息的下载

# 加载函数与R包 -----------------------------------------------------------------library(KEGGREST)library(plyr)source("./RbioRXN-master/RbioRXN-master/R/get.kegg.all.R")source("./RbioRXN-master/RbioRXN-master/R/get.kegg.byId.R")## KEGG数…

java常见异常

算术异常类&#xff1a;ArithmeticExecption空指针异常类&#xff1a;NullPointerException 类型强制转换异常&#xff1a;ClassCastException 数组负下标异常&#xff1a;NegativeArrayException 数组下标越界异常&#xff1a;ArrayIndexOutOfBoundsException 违背安全原则异常…

Spring Security 3 Ajax登录–访问受保护的资源

我看过一些有关Spring Security 3 Ajax登录的博客&#xff0c;但是我找不到解决如何调用基于Ajax的登录的博客&#xff0c;匿名用户正在Ajax中访问受保护的资源。 问题 – Web应用程序允许匿名访问某些部分&#xff0c;并且某些部分是受保护的资源&#xff0c;需要用户登录。 …

测试环境下将centos6.8升级到centos7的操作记录(转)

在测试环境下安装openstack&#xff0c;由于在centos6下安装openstack&#xff0c;针对源的问题有很多&#xff0c;安装起来很不顺利&#xff01; 但是在centos7下安装却很顺利&#xff0c;所以考虑将服务器由centos6升级到centos7 这个我是在测试机中运行的&#xff0c;建议不…

linux运维选择题,初学Linux练习题

1、将/etc/issue文件中的内容转换为大写后保存至/tmp/issue.out文件中tr ‘a-z’ ‘A-Z’ < /etc/issue > /tmp/issue.out2、将当前系统登录用户的信息转换为大写后保存至/tmp/who.out文件中3、一个linux用户给root发邮件&#xff0c;要求邮件标题为”help”&#xff0c…

[转]Web Api系列教程第2季(OData篇)(二)——使用Web Api创建只读的OData服务

本文转自&#xff1a;http://www.cnblogs.com/fzrain/p/3923727.html 前言 很久没更新了&#xff0c;之前有很多事情&#xff0c;所以拖了很久&#xff0c;非常抱歉。好了&#xff0c;废话不多说&#xff0c;下面开始正题。本篇仍然使用上一季的的项目背景&#xff08;系列地址…

使用Spring 3 MVC处理表单

本文是有关Spring 3的一系列文章的一部分。该系列的上一篇文章可以在此处获得 。 在本文中&#xff0c;我们向Spring MVC迈出了又一步。 [此外&#xff1a; 术语MVC的创建者提供的pdf 。]从上一篇文章构建&#xff0c;让我们添加将“联系人”添加到应用程序所需的代码。 首先&a…

插入排序法之——直接插入排序、折半插入排序、希尔排序

// test20.cpp : 定义控制台应用程序的入口点。 // #include "stdafx.h" #include<iostream> #include<vector> #include<string> #include<queue> #include<stack> #include<cstring> #include<string.h> #include<de…

linux idea 快捷键,Linux 下 IDEA 的 Ctrl+Alt+S

前言这是个困扰我一年多的问题&#xff0c;今天终于解决了……起因一年前将主系统换成 Arch Linux 后&#xff0c;其他一切正常就是 IDEA 的打开设置的快捷键 ctrlalts 失效&#xff0c;让我很是头疼。虽然不是很重要&#xff0c;但是对于我这种强迫症来说别提多难受了……我曾…

修改input的placeholder颜色

1、CSS选择器 因为每个浏览器的CSS选择器有所差异&#xff0c;所以需要针对每个浏览器做单独的设定。 ::-webkit-input-placeholder { /* WebKit browsers */ color: #999; } :-moz-placeholder { /* Mozilla Firefox 4 to 18 */ color: #999; } ::-moz-placeholder { /* Mozil…

解决Spring自动装配中的循环依赖

我认为这篇文章是在企业应用程序开发中使用Spring的最佳实践。 使用Spring编写企业Web应用程序时&#xff0c;服务层中的服务量可能会增加。 服务层中的每个服务可能会消耗其他服务&#xff0c;这些服务将通过Autowire注入。 问题&#xff1a;当服务数量开始增加时&#xff0…

01.MD5加密

namespace _01.MD5加密{ class Program { static void Main(string[] args) { //MD5加密就是给想要的密码或者其它字符加密 //如果字符串被加密成MD5值之后,是不可逆的. //字符串123 的MD5 64位加密形式是 202cb962ac59075b964b07152d234b70 Console.WriteLine("请输入需要…

C语言数字3转变字符 3 程序,大学c语言知识点总结

大学c语言知识点总结C语言的设计目标是提供一种能以简易的方式编译、处理低级存储器、产生少量的机器码以及不需要任何运行环境支持便能运行的编程语言。一起来看看大学c语言知识点总结吧!大学c语言知识点总结1、编译预处理不是C语言的一部分&#xff0c;不再运行时间。C语言编…

接触Jenkins(Hudson)API,第1部分

哪一个-哈德森还是詹金斯&#xff1f; 都。 几个月前&#xff0c;我开始使用Hudson v1.395来从事这个小项目&#xff0c;在出现巨大分歧之后又回到了这个项目。 我以此为契机&#xff0c;看我将来选择永久搬到詹金斯时是否会遇到任何重大问题。 有很多麻烦-最值得注意的是&…

使用javascript模拟常见数据结构(四)

七、树 树是一种非线性的分层的数据结构&#xff0c;在现实生活中比较常见的例子比如家谱和公司的组织架构图&#xff0c;如下所示&#xff1a; 一个树结构存在着一系列的父子结构&#xff0c;并且有着一个根节点&#xff0c;这种结构本质上表明了一对多的关系。 那&#xff0c…