15. Appendix

15.1 安全数据库架构

框架使用了各种数据库模式,本附录为它们提供了单个参考点。您只需要提供所需功能范围的表即可。

为 HSQLDB 数据库提供了 DDL 语句。您可以将这些用作定义正在使用的数据库的架构的准则。

15.1.1 用户架构

UserDetailsService(JdbcDaoImpl)的标准 JDBC 实现要求表为用户加载密码,帐户状态(启用或禁用)和权限列表(角色)。您将需要调整此架构以匹配您正在使用的数据库方言。

create table users(
    username varchar_ignorecase(50) not null primary key,
    password varchar_ignorecase(50) not null,
    enabled boolean not null
);

create table authorities (
    username varchar_ignorecase(50) not null,
    authority varchar_ignorecase(50) not null,
    constraint fk_authorities_users foreign key(username) references users(username)
);
create unique index ix_auth_username on authorities (username,authority);

对于 Oracle 数据库

CREATE TABLE USERS (
    USERNAME NVARCHAR2(128) PRIMARY KEY,
    PASSWORD NVARCHAR2(128) NOT NULL,
    ENABLED CHAR(1) CHECK (ENABLED IN ('Y','N') ) NOT NULL
);

CREATE TABLE AUTHORITIES (
    USERNAME NVARCHAR2(128) NOT NULL,
    AUTHORITY NVARCHAR2(128) NOT NULL
);
ALTER TABLE AUTHORITIES ADD CONSTRAINT AUTHORITIES_UNIQUE UNIQUE (USERNAME, AUTHORITY);
ALTER TABLE AUTHORITIES ADD CONSTRAINT AUTHORITIES_FK1 FOREIGN KEY (USERNAME) REFERENCES USERS (USERNAME) ENABLE;

Group Authorities

Spring Security 2.0 在JdbcDaoImpl中引入了对组权限的支持。如果启用了组,则表结构如下。您将需要调整此架构以匹配您正在使用的数据库方言。

create table groups (
    id bigint generated by default as identity(start with 0) primary key,
    group_name varchar_ignorecase(50) not null
);

create table group_authorities (
    group_id bigint not null,
    authority varchar(50) not null,
    constraint fk_group_authorities_group foreign key(group_id) references groups(id)
);

create table group_members (
    id bigint generated by default as identity(start with 0) primary key,
    username varchar(50) not null,
    group_id bigint not null,
    constraint fk_group_members_group foreign key(group_id) references groups(id)
);

请记住,仅在使用提供的 JDBC UserDetailsService实现时才需要这些表。如果您自己编写或选择不使用UserDetailsService来实现AuthenticationProvider,那么只要满足接口协定,就可以完全自由地存储数据。

15.1.2 永久登录(记住我)架构

该表用于存储更安全的persistent tokenmeme-me 实现所使用的数据。如果直接或通过名称空间使用JdbcTokenRepositoryImpl,则需要此表。切记调整此架构以匹配您正在使用的数据库方言。

create table persistent_logins (
    username varchar(64) not null,
    series varchar(64) primary key,
    token varchar(64) not null,
    last_used timestamp not null
);

15.1.3 ACL 架构

Spring Security ACL实现使用了四个表。

假设数据库将为每个身份自动生成主键。当JdbcMutableAclServiceacl_sidacl_class表中创建新行时,它们必须能够检索它们。它具有两个属性,这些属性定义检索这些值classIdentityQuerysidIdentityQuery所需的 SQL。两者都默认为call identity()

ACL 工件 JAR 包含用于在 HyperSQL(HSQLDB),PostgreSQL,MySQL/MariaDB,Microsoft SQL Server 和 Oracle 数据库中创建 ACL 模式的文件。在以下各节中还将演示这些架构。

HyperSQL

默认模式与框架中的单元测试中使用的嵌入式 HSQLDB 数据库一起使用。

create table acl_sid(
    id bigint generated by default as identity(start with 100) not null primary key,
    principal boolean not null,
    sid varchar_ignorecase(100) not null,
    constraint unique_uk_1 unique(sid,principal)
);

create table acl_class(
    id bigint generated by default as identity(start with 100) not null primary key,
    class varchar_ignorecase(100) not null,
    constraint unique_uk_2 unique(class)
);

create table acl_object_identity(
    id bigint generated by default as identity(start with 100) not null primary key,
    object_id_class bigint not null,
    object_id_identity varchar_ignorecase(36) not null,
    parent_object bigint,
    owner_sid bigint,
    entries_inheriting boolean not null,
    constraint unique_uk_3 unique(object_id_class,object_id_identity),
    constraint foreign_fk_1 foreign key(parent_object)references acl_object_identity(id),
    constraint foreign_fk_2 foreign key(object_id_class)references acl_class(id),
    constraint foreign_fk_3 foreign key(owner_sid)references acl_sid(id)
);

create table acl_entry(
    id bigint generated by default as identity(start with 100) not null primary key,
    acl_object_identity bigint not null,
    ace_order int not null,
    sid bigint not null,
    mask integer not null,
    granting boolean not null,
    audit_success boolean not null,
    audit_failure boolean not null,
    constraint unique_uk_4 unique(acl_object_identity,ace_order),
    constraint foreign_fk_4 foreign key(acl_object_identity) references acl_object_identity(id),
    constraint foreign_fk_5 foreign key(sid) references acl_sid(id)
);

PostgreSQL

create table acl_sid(
    id bigserial not null primary key,
    principal boolean not null,
    sid varchar(100) not null,
    constraint unique_uk_1 unique(sid,principal)
);

create table acl_class(
    id bigserial not null primary key,
    class varchar(100) not null,
    constraint unique_uk_2 unique(class)
);

create table acl_object_identity(
    id bigserial primary key,
    object_id_class bigint not null,
    object_id_identity varchar(36) not null,
    parent_object bigint,
    owner_sid bigint,
    entries_inheriting boolean not null,
    constraint unique_uk_3 unique(object_id_class,object_id_identity),
    constraint foreign_fk_1 foreign key(parent_object)references acl_object_identity(id),
    constraint foreign_fk_2 foreign key(object_id_class)references acl_class(id),
    constraint foreign_fk_3 foreign key(owner_sid)references acl_sid(id)
);

create table acl_entry(
    id bigserial primary key,
    acl_object_identity bigint not null,
    ace_order int not null,
    sid bigint not null,
    mask integer not null,
    granting boolean not null,
    audit_success boolean not null,
    audit_failure boolean not null,
    constraint unique_uk_4 unique(acl_object_identity,ace_order),
    constraint foreign_fk_4 foreign key(acl_object_identity) references acl_object_identity(id),
    constraint foreign_fk_5 foreign key(sid) references acl_sid(id)
);

您必须将JdbcMutableAclServiceclassIdentityQuerysidIdentityQuery属性分别设置为以下值:

MySQL 和 MariaDB

CREATE TABLE acl_sid (
    id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
    principal BOOLEAN NOT NULL,
    sid VARCHAR(100) NOT NULL,
    UNIQUE KEY unique_acl_sid (sid, principal)
) ENGINE=InnoDB;

CREATE TABLE acl_class (
    id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
    class VARCHAR(100) NOT NULL,
    UNIQUE KEY uk_acl_class (class)
) ENGINE=InnoDB;

CREATE TABLE acl_object_identity (
    id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
    object_id_class BIGINT UNSIGNED NOT NULL,
    object_id_identity VARCHAR(36) NOT NULL,
    parent_object BIGINT UNSIGNED,
    owner_sid BIGINT UNSIGNED,
    entries_inheriting BOOLEAN NOT NULL,
    UNIQUE KEY uk_acl_object_identity (object_id_class, object_id_identity),
    CONSTRAINT fk_acl_object_identity_parent FOREIGN KEY (parent_object) REFERENCES acl_object_identity (id),
    CONSTRAINT fk_acl_object_identity_class FOREIGN KEY (object_id_class) REFERENCES acl_class (id),
    CONSTRAINT fk_acl_object_identity_owner FOREIGN KEY (owner_sid) REFERENCES acl_sid (id)
) ENGINE=InnoDB;

CREATE TABLE acl_entry (
    id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
    acl_object_identity BIGINT UNSIGNED NOT NULL,
    ace_order INTEGER NOT NULL,
    sid BIGINT UNSIGNED NOT NULL,
    mask INTEGER UNSIGNED NOT NULL,
    granting BOOLEAN NOT NULL,
    audit_success BOOLEAN NOT NULL,
    audit_failure BOOLEAN NOT NULL,
    UNIQUE KEY unique_acl_entry (acl_object_identity, ace_order),
    CONSTRAINT fk_acl_entry_object FOREIGN KEY (acl_object_identity) REFERENCES acl_object_identity (id),
    CONSTRAINT fk_acl_entry_acl FOREIGN KEY (sid) REFERENCES acl_sid (id)
) ENGINE=InnoDB;

Microsoft SQL Server

CREATE TABLE acl_sid (
    id BIGINT NOT NULL IDENTITY PRIMARY KEY,
    principal BIT NOT NULL,
    sid VARCHAR(100) NOT NULL,
    CONSTRAINT unique_acl_sid UNIQUE (sid, principal)
);

CREATE TABLE acl_class (
    id BIGINT NOT NULL IDENTITY PRIMARY KEY,
    class VARCHAR(100) NOT NULL,
    CONSTRAINT uk_acl_class UNIQUE (class)
);

CREATE TABLE acl_object_identity (
    id BIGINT NOT NULL IDENTITY PRIMARY KEY,
    object_id_class BIGINT NOT NULL,
    object_id_identity VARCHAR(36) NOT NULL,
    parent_object BIGINT,
    owner_sid BIGINT,
    entries_inheriting BIT NOT NULL,
    CONSTRAINT uk_acl_object_identity UNIQUE (object_id_class, object_id_identity),
    CONSTRAINT fk_acl_object_identity_parent FOREIGN KEY (parent_object) REFERENCES acl_object_identity (id),
    CONSTRAINT fk_acl_object_identity_class FOREIGN KEY (object_id_class) REFERENCES acl_class (id),
    CONSTRAINT fk_acl_object_identity_owner FOREIGN KEY (owner_sid) REFERENCES acl_sid (id)
);

CREATE TABLE acl_entry (
    id BIGINT NOT NULL IDENTITY PRIMARY KEY,
    acl_object_identity BIGINT NOT NULL,
    ace_order INTEGER NOT NULL,
    sid BIGINT NOT NULL,
    mask INTEGER NOT NULL,
    granting BIT NOT NULL,
    audit_success BIT NOT NULL,
    audit_failure BIT NOT NULL,
    CONSTRAINT unique_acl_entry UNIQUE (acl_object_identity, ace_order),
    CONSTRAINT fk_acl_entry_object FOREIGN KEY (acl_object_identity) REFERENCES acl_object_identity (id),
    CONSTRAINT fk_acl_entry_acl FOREIGN KEY (sid) REFERENCES acl_sid (id)
);

Oracle Database

CREATE TABLE ACL_SID (
    ID NUMBER(18) PRIMARY KEY,
    PRINCIPAL NUMBER(1) NOT NULL CHECK (PRINCIPAL IN (0, 1 )),
    SID NVARCHAR2(128) NOT NULL,
    CONSTRAINT ACL_SID_UNIQUE UNIQUE (SID, PRINCIPAL)
);
CREATE SEQUENCE ACL_SID_SQ START WITH 1 INCREMENT BY 1 NOMAXVALUE;
CREATE OR REPLACE TRIGGER ACL_SID_SQ_TR BEFORE INSERT ON ACL_SID FOR EACH ROW
BEGIN
    SELECT ACL_SID_SQ.NEXTVAL INTO :NEW.ID FROM DUAL;
END;

CREATE TABLE ACL_CLASS (
    ID NUMBER(18) PRIMARY KEY,
    CLASS NVARCHAR2(128) NOT NULL,
    CONSTRAINT ACL_CLASS_UNIQUE UNIQUE (CLASS)
);
CREATE SEQUENCE ACL_CLASS_SQ START WITH 1 INCREMENT BY 1 NOMAXVALUE;
CREATE OR REPLACE TRIGGER ACL_CLASS_ID_TR BEFORE INSERT ON ACL_CLASS FOR EACH ROW
BEGIN
    SELECT ACL_CLASS_SQ.NEXTVAL INTO :NEW.ID FROM DUAL;
END;

CREATE TABLE ACL_OBJECT_IDENTITY(
    ID NUMBER(18) PRIMARY KEY,
    OBJECT_ID_CLASS NUMBER(18) NOT NULL,
    OBJECT_ID_IDENTITY NVARCHAR2(64) NOT NULL,
    PARENT_OBJECT NUMBER(18),
    OWNER_SID NUMBER(18),
    ENTRIES_INHERITING NUMBER(1) NOT NULL CHECK (ENTRIES_INHERITING IN (0, 1)),
    CONSTRAINT ACL_OBJECT_IDENTITY_UNIQUE UNIQUE (OBJECT_ID_CLASS, OBJECT_ID_IDENTITY),
    CONSTRAINT ACL_OBJECT_IDENTITY_PARENT_FK FOREIGN KEY (PARENT_OBJECT) REFERENCES ACL_OBJECT_IDENTITY(ID),
    CONSTRAINT ACL_OBJECT_IDENTITY_CLASS_FK FOREIGN KEY (OBJECT_ID_CLASS) REFERENCES ACL_CLASS(ID),
    CONSTRAINT ACL_OBJECT_IDENTITY_OWNER_FK FOREIGN KEY (OWNER_SID) REFERENCES ACL_SID(ID)
);
CREATE SEQUENCE ACL_OBJECT_IDENTITY_SQ START WITH 1 INCREMENT BY 1 NOMAXVALUE;
CREATE OR REPLACE TRIGGER ACL_OBJECT_IDENTITY_ID_TR BEFORE INSERT ON ACL_OBJECT_IDENTITY FOR EACH ROW
BEGIN
    SELECT ACL_OBJECT_IDENTITY_SQ.NEXTVAL INTO :NEW.ID FROM DUAL;
END;

CREATE TABLE ACL_ENTRY (
    ID NUMBER(18) NOT NULL PRIMARY KEY,
    ACL_OBJECT_IDENTITY NUMBER(18) NOT NULL,
    ACE_ORDER INTEGER NOT NULL,
    SID NUMBER(18) NOT NULL,
    MASK INTEGER NOT NULL,
    GRANTING NUMBER(1) NOT NULL CHECK (GRANTING IN (0, 1)),
    AUDIT_SUCCESS NUMBER(1) NOT NULL CHECK (AUDIT_SUCCESS IN (0, 1)),
    AUDIT_FAILURE NUMBER(1) NOT NULL CHECK (AUDIT_FAILURE IN (0, 1)),
    CONSTRAINT ACL_ENTRY_UNIQUE UNIQUE (ACL_OBJECT_IDENTITY, ACE_ORDER),
    CONSTRAINT ACL_ENTRY_OBJECT_FK FOREIGN KEY (ACL_OBJECT_IDENTITY) REFERENCES ACL_OBJECT_IDENTITY (ID),
    CONSTRAINT ACL_ENTRY_ACL_FK FOREIGN KEY (SID) REFERENCES ACL_SID(ID)
);
CREATE SEQUENCE ACL_ENTRY_SQ START WITH 1 INCREMENT BY 1 NOMAXVALUE;
CREATE OR REPLACE TRIGGER ACL_ENTRY_ID_TRIGGER BEFORE INSERT ON ACL_ENTRY FOR EACH ROW
BEGIN
    SELECT ACL_ENTRY_SQ.NEXTVAL INTO :NEW.ID FROM DUAL;
END;

15.2 安全命名空间

本附录提供了对安全名称空间中可用元素的引用以及有关它们创建的基础 Bean 的信息(假定了解各个类以及它们如何协同工作-您可以在项目 Javadoc 和本文档的其他地方找到更多信息.)。如果您以前没有使用过命名空间,请阅读有关命名空间配置的introductory chapter,因为这是对此处信息的补充。建议在编辑基于模式的配置时使用高质量的 XML 编辑器,因为这将提供有关哪些元素和属性可用的上下文信息,以及解释其用途的 Comments。名称空间以RELAX NG Compact 格式编写,然后转换为 XSD 模式。如果您熟悉此格式,则不妨直接检查schema file

15.2.1 Web 应用程序安全性

<debug>

启用 Spring Security 调试基础结构。这将提供人类可读的(多行)调试信息,以监视进入安全过滤器的请求。这可能包括敏感信息,例如请求参数或 Headers,并且仅应在开发环境中使用。

<http>

如果在应用程序中使用<http>元素,则会创建一个名为“ springSecurityFilterChain”的FilterChainProxy bean,并且该元素中的配置用于在FilterChainProxy中构建过滤器链。从 Spring Security 3.1 开始,其他http元素可用于添加额外的过滤器链[22]。某些核心过滤器始终在过滤器链中创建,而其他核心过滤器将根据存在的属性和子元素添加到堆栈中。标准过滤器的位置是固定的(请参见名称空间介绍中的过滤器 Order 表),当用户必须在FilterChainProxy bean 中显式配置过滤器链时,消除了以前版本框架中常见的错误源。当然,如果需要完全控制配置,您仍然可以这样做。

所有需要引用AuthenticationManager的过滤器都将自动注入由名称空间配置创建的内部实例(有关AuthenticationManager的更多信息,请参见introductory chapter)。

每个<http>名称空间块始终创建SecurityContextPersistenceFilterExceptionTranslationFilterFilterSecurityInterceptor。这些是固定的,不能用替代方法替代。

<http> Attributes

<http>元素上的属性控制核心过滤器上的某些属性。

<http>的子元素

<access-denied-handler>

该元素允许您使用error-page属性为ExceptionTranslationFilter使用的默认AccessDeniedHandler设置errorPage属性,或者使用ref属性提供自己的实现。关于ExceptionTranslationFilter的部分将对此进行详细讨论。

<access-denied-handler>的父元素
<access-denied-handler> Attributes

<cors>

该元素允许配置CorsFilter。如果未指定CorsFilterCorsConfigurationSource并且 Spring MVC 在 Classpath 中,则将HandlerMappingIntrospector用作CorsConfigurationSource

<cors> Attributes

<cors>元素上的属性控制 headers 元素。

<cors>的父元素

<headers>

此元素允许配置要与响应一起发送的其他(安全)Headers。它可以轻松配置多个 Headers,还可以通过header元素设置自定义 Headers。其他信息,可以在参考的Security Headers部分中找到。

<headers> Attributes

<headers>元素上的属性控制 headers 元素。

<headers>的父元素
<headers>的子元素

<cache-control>

添加Cache-ControlPragmaExpiresHeaders,以确保浏览器不缓存您的安全页面。

<cache-control> Attributes

<cache-control>的父元素

<hsts>

启用后,会将Strict-Transport-SecurityHeaders 添加到任何安全请求的响应中。这允许服务器指示浏览器自动将 HTTPS 用于将来的请求。

<hsts> Attributes

<hsts>的父元素

<hpkp>

启用后,会将HTTP 的公钥固定扩展Headers 添加到任何安全请求的响应中。这允许 HTTPS 网站使用错误签发或欺诈性证书来抵制攻击者的冒名顶替。

<hpkp> Attributes

<hpkp>的父元素

<pins>

引脚列表

<pins>的子元素

<pin>

使用 base64 编码的 SPKI 指纹作为值并使用密码哈希算法作为属性来指定引脚

<pin> Attributes

<pin>的父元素

<content-security-policy>

启用后,将内容安全 Policy(CSP)Headers 添加到响应中。 CSP 是 Web 应用程序可以用来减轻内容注入漏洞(例如跨站点脚本(XSS))的机制。

<content-security-policy> Attributes

<content-security-policy>的父元素

<referrer-policy>

启用后,将Referrer PolicyHeaders 添加到响应中。

<referrer-policy> Attributes

<referrer-policy>的父元素

<feature-policy>

启用后,将Feature PolicyHeaders 添加到响应中。

<feature-policy> Attributes

<feature-policy>的父元素

<frame-options>

启用后,会将X-Frame-Options header添加到响应中,这使较新的浏览器可以进行一些安全检查并防止clickjacking攻击。

<frame-options> Attributes

换句话说,如果指定 DENY,则从其他站点加载时,不仅尝试在框架中加载页面失败,而且从同一站点加载时,尝试也会失败。另一方面,如果指定 SAMEORIGIN,则只要框架中包含该站点的页面与提供该页面的站点相同,您仍可以在框架中使用该页面。

<frame-options>的父元素

<xss-protection>

X-XSS-Protection header添加到响应中,以帮助防范反映的/ Type-1 跨站点脚本(XSS)攻击。这绝不是对 XSS 攻击的全面保护!

<xss-protection> Attributes

<xss-protection>的父元素

<content-type-options>

将带有 nosniff 值的 X-Content-Type-OptionsHeaders 添加到响应中。此disables MIME-sniffing用于 IE8 和 Chrome 扩展程序。

<content-type-options> Attributes

<content-type-options>的父元素

将其他 Headers 添加到响应中,需要同时指定名称和值。

<header-attributes> Attributes

<header>的父元素

<anonymous>

AnonymousAuthenticationFilter添加到堆栈中,并将AnonymousAuthenticationProvider添加到堆栈中。如果使用IS_AUTHENTICATED_ANONYMOUSLY属性,则为必需。

<anonymous>的父元素
<anonymous> Attributes

<csrf>

此元素将为应用程序添加跨站请求伪造(CSRF)保护。它还将默认的 RequestCache 更新为仅在成功身份验证后重播“ GET”请求。其他信息可以在参考的跨站请求伪造(CSRF)部分中找到。

<csrf>的父元素
<csrf> Attributes

<custom-filter>

该元素用于向过滤器链添加过滤器。它不会创建任何其他 bean,而是用于选择类型javax.servlet.Filter的 bean,该 bean 已在应用程序上下文中定义,并将其添加到 Spring Security 维护的过滤器链中的特定位置。完整的详细信息可以在namespace chapter中找到。

<custom-filter>的父元素
<custom-filter> Attributes

<expression-handler>

定义启用基于表达式的访问控制时将使用的SecurityExpressionHandler实例。如果未提供,则将使用默认实现(不支持 ACL)。

<expression-handler>的父元素
<expression-handler> Attributes

<form-login>

用于将UsernamePasswordAuthenticationFilter添加到过滤器堆栈,并将LoginUrlAuthenticationEntryPoint添加到应用程序上下文以按需提供身份验证。这将始终优先于其他由名称空间创建的入口点。如果未提供任何属性,则将在 URL“/login” [23]上自动生成一个登录页面。可以使用<form-login> Attributes来定制行为。

<form-login>的父元素
<form-login> Attributes

<http-basic>

BasicAuthenticationFilterBasicAuthenticationEntryPoint添加到配置中。如果未启用基于表单的登录,则后者将仅用作配置入口点。

<http-basic>的父元素
<http-basic> Attributes

<http-firewall> Element

这是一个顶级元素,可用于将HttpFirewall的自定义实现注入由名称空间创建的FilterChainProxy。默认实现应适用于大多数应用程序。

<http-firewall> Attributes

<intercept-url>

此元素用于定义应用程序感兴趣的 URL 模式集,并配置应如何处理它们。它用于构造FilterSecurityInterceptor使用的FilterInvocationSecurityMetadataSource。例如,如果需要通过 HTTPS 访问特定的 URL,它还负责配置ChannelProcessingFilter。当将指定的模式与传入的请求进行匹配时,将按照声明元素的 Sequences 进行匹配。因此,最具体的模式应该放在首位,最一般的模式应该放在最后。

<intercept-url>的父元素
<intercept-url> Attributes

Note

该属性对filter-security-metadata-source无效

如果添加了<port-mappings>配置,则SecureChannelProcessorInsecureChannelProcessor bean 将使用它来确定用于重定向到 HTTP/HTTPS 的端口。

Note

该属性对filter-security-metadata-source无效

Note

该属性对filter-security-metadata-source无效

<jee>

将 J2eePreAuthenticatedProcessingFilter 添加到过滤器链以提供与容器身份验证的集成。

<jee>的父元素
<jee> Attributes

<logout>

LogoutFilter添加到过滤器堆栈。这是使用SecurityContextLogoutHandler配置的。

<logout>的父元素
<logout> Attributes

设置此属性将为SessionManagementFilter注入配置了属性值的SimpleRedirectInvalidSessionStrategy。提交无效的会话 ID 后,将调用该策略,并重定向到配置的 URL。

<openid-login>

<form-login>类似,并且具有相同的属性。 login-processing-url的默认值为“/login/openid”。 OpenIDAuthenticationFilterOpenIDAuthenticationProvider将被注册。后者需要引用UserDetailsService。同样,这可以由id使用user-service-ref属性指定,或者将自动位于应用程序上下文中。

<openid-login>的父元素
<openid-login> Attributes

<openid-login>的子元素

<attribute-exchange>

attribute-exchange元素定义应从身份提供者请求的属性列表。可以在名称空间配置一章的OpenID Support部分中找到一个示例。可以使用多个,在这种情况下,每个属性都必须具有identifier-match属性,该属性包含与提供的 OpenID 标识符匹配的正则表达式。这允许从不同的提供程序(Google,Yahoo 等)获取不同的属性列表。

<attribute-exchange>的父元素
<attribute-exchange> Attributes

<attribute-exchange>的子元素

<openid-attribute>

制作 OpenID AX Fetch Request时使用的属性

<openid-attribute>的父元素
<openid-attribute> Attributes

<port-mappings>

默认情况下,实例PortMapperImpl将添加到配置中,用于重定向到安全和不安全的 URL。可以选择使用此元素来覆盖该类定义的默认 Map。每个子<port-mapping>元素定义一对 HTTP:HTTPS 端口。默认 Map 为 80:443 和 8080:8443. namespace introduction中提供了覆盖这些示例的示例。

<port-mappings>的父元素
<port-mappings>的子元素

<port-mapping>

提供一种在强制重定向时将 http 端口 Map 到 https 端口的方法。

<port-mapping>的父元素
<port-mapping> Attributes

<remember-me>

RememberMeAuthenticationFilter添加到堆栈中。依次使用TokenBasedRememberMeServicesPersistentTokenBasedRememberMeServices或由用户指定的 Bean 来实现RememberMeServices,具体取决于属性设置。

<remember-me>的父元素
<remember-me> Attributes

<request-cache> Element

设置RequestCache实例,ExceptionTranslationFilter将在调用AuthenticationEntryPoint之前将其用于存储请求信息。

<request-cache>的父元素
<request-cache> Attributes

<session-management>

与会话 Management 相关的功能是通过在过滤器堆栈中添加SessionManagementFilter来实现的。

<session-management>的父元素
<session-management> Attributes

如果启用了会话固定保护,则为SessionManagementFilter注入适当配置的DefaultSessionAuthenticationStrategy。有关更多详细信息,请参见此类的 Javadoc。

<session-management>的子元素

<concurrency-control>

增加了对并发会话控制的支持,从而可以限制用户可以拥有的活动会话的数量。将创建一个ConcurrentSessionFilter,并将一个ConcurrentSessionControlAuthenticationStrategySessionManagementFilter一起使用。如果已声明form-login元素,则策略对象也将注入到创建的身份验证过滤器中。将创建SessionRegistry的实例(除非用户希望使用自定义 bean,否则为SessionRegistryImpl实例)供策略使用。

<concurrency-control>的父元素
<concurrency-control> Attributes

<x509>

添加了对 X.509 身份验证的支持。 X509AuthenticationFilter将添加到堆栈中,并且将创建Http403ForbiddenEntryPoint bean。仅当没有其他身份验证机制在使用时才使用后者(其唯一功能是返回 HTTP 403 错误代码)。还将创建一个PreAuthenticatedAuthenticationProvider,将PreAuthenticatedAuthenticationProvider的用户权限加载委托给UserDetailsService

<x509>的父元素
<x509> Attributes

<filter-chain-map>

用于通过 FilterChainMap 显式配置 FilterChainProxy 实例

<filter-chain-map> Attributes

<filter-chain-map>的子元素

<filter-chain>

用于定义一个特定的 URL 模式和适用于与该模式匹配的 URL 的过滤器列表。当将多个过滤器链元素组合到一个列表中以配置 FilterChainProxy 时,必须将最特定的模式放在列表的顶部,而将最常规的模式放在底部。

<filter-chain>的父元素
<filter-chain> Attributes

<filter-security-metadata-source>

用于显式配置 FilterSecurityMetadataSource bean 与 FilterSecurityInterceptor 一起使用。通常仅在显式配置 FilterChainProxy 而不是使用\ 元素时才需要。使用的拦截 URL 元素应仅包含模式,方法和访问属性。其他任何情况都将导致配置错误。

<filter-security-metadata-source> Attributes

<filter-security-metadata-source>的子元素

15.2.2 WebSocket 安全

Spring Security 4.0 提供了对消息授权的支持。一个有用的具体示例是在基于 WebSocket 的应用程序中提供授权。

<websocket-message-broker>

websocket-message-broker 元素具有两种不同的模式。如果未指定[email protected],它将执行以下操作:

如果需要其他控制,则可以指定 ID,并将 ChannelSecurityInterceptor 分配给指定的 ID。然后,可以手动完成与 Spring 的消息传递基础结构的所有连接。这比较麻烦,但是可以更好地控制配置。

<websocket-message-broker> Attributes

<websocket-message-broker>的子元素

<intercept-message>

定义消息的授权规则。

<intercept-message>的父元素
<intercept-message> Attributes

15.2.3 身份验证服务

在 Spring Security 3.0 之前,AuthenticationManager是在内部自动注册的。现在,您必须使用<authentication-manager>元素显式注册一个。这将创建 Spring Security 的ProviderManager类的实例,该实例需要配置一个或多个AuthenticationProvider实例的列表。这些可以使用命名空间提供的语法元素创建,也可以是标准 Bean 定义,并使用authentication-provider元素标记为要添加到列表中。

<authentication-manager>

每个使用名称空间的 Spring Security 应用程序都必须在某处包含此元素。它负责注册为应用程序提供身份验证服务的AuthenticationManager。所有创建AuthenticationProvider实例的元素都应该是该元素的子元素。

<authentication-manager> Attributes

<authentication-manager>的子元素

<authentication-provider>

除非与ref属性一起使用,否则此元素是配置DaoAuthenticationProvider的简写。 DaoAuthenticationProviderUserDetailsService加载用户信息,并将用户名/密码组合与登录时提供的值进行比较。可以通过使用可用的命名空间元素(jdbc-user-service或通过使用user-service-ref属性指向应用程序上下文中其他位置定义的 bean)来定义UserDetailsService实例。您可以在namespace introduction中找到这些变化的示例。

<authentication-provider>的父元素
<authentication-provider> Attributes

如果您已经编写了自己的AuthenticationProvider实现(或者出于某种原因想要将 Spring Security 自己的实现之一配置为传统 Bean,则可以使用以下语法将其添加到ProviderManager的内部列表中:

<security:authentication-manager>
<security:authentication-provider ref="myAuthenticationProvider" />
</security:authentication-manager>
<bean id="myAuthenticationProvider" class="com.something.MyAuthenticationProvider"/>

<authentication-provider>的子元素

<jdbc-user-service>

导致创建基于 JDBC 的 UserDetailsService。

<jdbc-user-service> Attributes

默认是

select username, authority from authorities where username = ?

select
g.id, g.group_name, ga.authority
from
groups g, group_members gm, group_authorities ga
where
gm.username = ? and g.id = ga.group_id and g.id = gm.group_id

select username, password, enabled from users where username = ?

<password-encoder>

可以将验证提供程序配置为使用namespace introduction中所述的密码编码器。这将导致使用适当的PasswordEncoder实例注入 Bean。

<password-encoder>的父元素
<password-encoder> Attributes

<user-service>

从属性文件或“用户”子元素列表创建内存 UserDetailsService。用户名在内部会转换为小写形式,以允许不区分大小写的查找,因此如果需要区分大小写,则不应使用此名称。

<user-service> Attributes

username=password,grantedAuthority[,grantedAuthority][,enabled|disabled]
<user-service>的子元素

<user>

代表应用程序中的用户。

<user>的父元素
<user> Attributes

15.2.4 方法安全性

<global-method-security>

该元素是添加对 Spring Security bean 上的安全方法的支持的主要方法。可以使用 AspectJ 语法通过使用 Comments(在接口或类级别定义)或将一组切入点定义为子元素来保护方法。

<global-method-security> Attributes

重要的是要注意,AspectJ 遵循 Java 的规则,即不继承接口上的 Comments。这意味着在接口上定义安全性 Comments 的方法将不安全。相反,在使用 AspectJ 时,必须在类上放置 Security 注解。

<global-method-security>的子元素

<after-invocation-provider>

此元素可用于装饰AfterInvocationProvider,以供<global-method-security>名称空间维护的安全拦截器使用。您可以在global-method-security元素中定义零个或多个,每个都具有ref属性,该属性指向应用程序上下文中的AfterInvocationProvider bean 实例。

<after-invocation-provider>的父元素
<after-invocation-provider> Attributes

<pre-post-annotation-handling>

允许完全替换基于默认表达式的机制来处理 Spring Security 的调用前后 Comments(@ PreFilter,@ PreAuthorize,@ PostFilter,@ PostAuthorize)。仅在启用这些 Comments 的情况下适用。

<pre-post-annotation-handling>的父元素
<pre-post-annotation-handling>的子元素

<invocation-attribute-factory>

定义 PrePostInvocationAttributeFactory 实例,该实例用于从带 Comments 的方法中生成调用前后的元数据。

<invocation-attribute-factory>的父元素
<invocation-attribute-factory> Attributes

<post-invocation-advice>

使用 ref 作为\ 元素的PostInvocationAuthorizationAdvice自定义PostInvocationAdviceProvider

<post-invocation-advice>的父元素
<post-invocation-advice> Attributes

<pre-invocation-advice>

使用 ref 作为\ 元素的PreInvocationAuthorizationAdviceVoter自定义PreInvocationAuthorizationAdviceVoter

<pre-invocation-advice>的父元素
<pre-invocation-advice> Attributes

使用以下方法保护方法

<protect-pointcut>而不是使用@SecuredComments 在单个方法或类的基础上定义安全属性,而是可以使用<protect-pointcut>元素跨服务层中的整个方法和接口集定义跨领域安全约束。您可以在namespace introduction中找到示例。

<protect-pointcut>的父元素
<protect-pointcut> Attributes

<intercept-methods>

可以在 bean 定义内使用,以向 bean 添加安全拦截器并为 bean 的方法设置访问配置属性

<intercept-methods> Attributes

<intercept-methods>的子元素

<method-security-metadata-source>

创建一个 MethodSecurityMetadataSource 实例

<method-security-metadata-source> Attributes

<method-security-metadata-source>的子元素

<protect>

定义一个受保护的方法以及适用于该方法的访问控制配置属性。强烈建议您不要将“保护”声明与“全局方法安全性”提供的任何服务混合使用。

<protect>的父元素
<protect> Attributes

15.2.5 LDAP 命名空间选项

自己的章节中详细介绍了 LDAP。我们将在这里扩展它,并提供一些有关名称空间选项如何 Map 到 Spring Bean 的解释。 LDAP 实现广泛使用 Spring LDAP,因此熟悉该项目的 API 可能会有用。

使用以下命令定义 LDAP 服务器

<ldap-server>元素此元素设置一个供其他 LDAP Bean 使用的 Spring LDAP ContextSource,以定义 LDAP 服务器的位置以及用于与其连接的其他信息(例如用户名和密码,如果不允许匿名访问)。它还可以用于创建嵌入式服务器以进行测试。 LDAP chapter涵盖了这两个选项的语法详细信息。实际的ContextSource实现是DefaultSpringSecurityContextSource,它扩展了 Spring LDAP 的LdapContextSource类。 manager-dnmanager-password属性分别 Map 到后者的userDnpassword属性。

如果在应用程序上下文中仅定义了一个服务器,则其他 LDAP 名称空间定义的 Bean 将自动使用它。否则,您可以为元素赋予“ id”属性,并使用server-ref属性从其他名称空间 bean 引用该元素。如果要在其他传统的 Spring Bean 中使用它,则实际上是ContextSource实例的 Bean id

<ldap-server> Attributes

<ldap-authentication-provider>

此元素是创建LdapAuthenticationProvider实例的简写。默认情况下,它将使用BindAuthenticator实例和DefaultAuthoritiesPopulator配置。与所有名称空间身份验证提供程序一样,它必须作为authentication-provider元素的子元素包括在内。

<ldap-authentication-provider>的父元素
<ldap-authentication-provider> Attributes

如果需要执行搜索以在目录中找到用户,则可以设置这些属性来控制搜索。 BindAuthenticator将配置为FilterBasedLdapUserSearch,并且属性值直接 Map 到该 bean 的构造函数的前两个参数。如果未设置这些属性,并且没有提供user-dn-pattern作为替代,那么将使用user-search-filter="(uid={0})"user-search-base=""的默认搜索值。

如果需要执行搜索以在目录中找到用户,则可以设置这些属性来控制搜索。 BindAuthenticator将配置为FilterBasedLdapUserSearch,并且属性值直接 Map 到该 bean 的构造函数的前两个参数。如果未设置这些属性,并且没有提供user-dn-pattern作为替代,那么将使用user-search-filter="(uid={0})"user-search-base=""的默认搜索值。

<ldap-authentication-provider>的子元素

<password-compare>

这用作<ldap-provider>的子元素,并将身份验证策略从BindAuthenticator切换到PasswordComparisonAuthenticator

<password-compare>的父元素
<password-compare> Attributes

<password-compare>的子元素

<ldap-user-service>

该元素配置 LDAP UserDetailsService。所使用的类是LdapUserDetailsService,它是FilterBasedLdapUserSearchDefaultLdapAuthoritiesPopulator的组合。它支持的属性与<ldap-provider>中的用法相同。

<ldap-user-service> Attributes

15.3 Spring 安全性依赖性

本附录提供了 Spring Security 中的模块以及它们在运行中的应用程序中运行所需的其他依赖项的参考。我们不包括仅在构建或测试 Spring Security 本身时使用的依赖项。我们也没有包括外部依赖项所要求的传递性依赖项。

项目网站上列出了所需的 Spring 版本,因此下面的 Spring 依赖项省略了特定版本。请注意,Spring 应用程序中的其他非安全功能可能仍需要下面列出为“可选”的某些依赖项。此外,如果大多数应用程序中都使用了列为“可选”的依赖项,则在项目的 Maven POM 文件中可能实际上并未将其标记为此类依赖项。除非您使用指定的功能,否则它们仅在不需要它们的意义上是“可选的”。

在模块依赖于另一个 Spring Security 模块的情况下,也假定该模块所依赖的模块的非可选依赖关系是必需的,因此未单独列出。

15.3.1 spring-security-core

使用 Spring Security 的任何项目中都必须包含核心模块。

表 15.1 核心依赖

Dependency Version Description
ehcache 1.6.2 如果使用基于 Ehcache 的用户缓存实现,则为必需(可选)。
spring-aop 方法安全性基于 Spring AOP
spring-beans Spring 配置必需
spring-expression 基于表达式的方法安全性必需(可选)
spring-jdbc 如果使用数据库存储用户数据,则为必需(可选)。
spring-tx 如果使用数据库存储用户数据,则为必需(可选)。
aspectjrt 1.6.10 如果使用 AspectJ 支持,则为必需(可选)。
jsr250-api 1.0 如果您使用的是 JSR-250 方法安全性 Comments(可选),则为必需。

15.3.2 spring-security-remoting

使用 Servlet API 的 Web 应用程序通常需要此模块。

表 15.2 远程依赖

Dependency Version Description
spring-security-core
spring-web 使用 HTTP 远程支持的 Client 端需要。

15.3.3 spring-security-web

使用 Servlet API 的 Web 应用程序通常需要此模块。

表 15.3 网络依赖

Dependency Version Description
spring-security-core
spring-web Spring Web 支持类被广泛使用。
spring-jdbc 对于基于 JDBC 的永久性“记住我”令牌存储库是必需的(可选)。
spring-tx “记住我”持久令牌存储库实现必需(可选)。

15.3.4 spring-security-ldap

仅在使用 LDAP 身份验证时才需要此模块。

表 15.4. LDAP 依赖关系

Dependency Version Description
spring-security-core
spring-ldap-core 1.3.0 LDAP 支持基于 Spring LDAP。
spring-tx 数据异常类是必需的。
apache-ds [1] 1.5.5 如果您使用嵌入式 LDAP 服务器(可选),则为必需。
shared-ldap 0.9.15 如果您使用嵌入式 LDAP 服务器(可选),则为必需。
ldapsdk 4.1 Mozilla LdapSDK。例如,如果您在 OpenLDAP 中使用密码策略功能,则用于解码 LDAP 密码策略控件。
[1]需要模块apacheds-coreapacheds-core-entryapacheds-protocol-sharedapacheds-protocol-ldapapacheds-server-jndi

15.3.5 spring-security-config

如果您使用的是 Spring Security 名称空间配置,那么此模块是必需的。

表 15.5. 配置依赖项

Dependency Version Description
spring-security-core
spring-security-web 如果使用任何与 Web 相关的名称空间配置,则为必需(可选)。
spring-security-ldap 如果使用 LDAP 名称空间选项(可选),则为必需。
spring-security-openid 如果使用的是 OpenID 身份验证,则为必需(可选)。
aspectjweaver 1.6.10 如果使用 protect-pointcut 名称空间语法(必需),则为必需。

15.3.6 spring-security-acl

ACL 模块。

表 15.6. ACL 依赖项

Dependency Version Description
spring-security-core
ehcache 1.6.2 如果使用基于 Ehcache 的 ACL 缓存实现,则为必需(如果使用自己的实现,则为可选)。
spring-jdbc 如果使用的是默认的基于 JDBC 的 AclService,则为必需(如果实现自己的,则为可选)。
spring-tx 如果使用的是默认的基于 JDBC 的 AclService,则为必需(如果实现自己的,则为可选)。

15.3.7 spring-security-cas

CAS 模块提供与 JA-SIG CAS 的集成。

表 15.7. CAS 依赖性

Dependency Version Description
spring-security-core
spring-security-web
cas-client-core 3.1.12 JA-SIG CASClient 端。这是 Spring Security 集成的基础。
ehcache 1.6.2 如果您正在使用基于 Ehcache 的票证缓存(可选),则为必需。

15.3.8 spring-security-openid

OpenID 模块。

表 15.8. OpenID 依赖项

Dependency Version Description
spring-security-core
spring-security-web
openid4java-nodeps 0.9.6 Spring Security 的 OpenID 集成使用 OpenID4Java。
httpclient 4.1.1 openid4java-nodeps 取决于 HttpClient 4.
guice 2.0 openid4java-nodeps 取决于 Guice 2.

15.3.9 spring-security-taglibs

提供 Spring Security 的 JSP 标签实现。

表 15.9. Taglib 依赖项

Dependency Version Description
spring-security-core
spring-security-web
spring-security-acl 如果您将accesscontrollist标记或hasPermission()表达式与 ACL 一起使用,则为必填项(可选)。
spring-expression 如果在标记访问约束中使用 SPEL 表达式,则为必需。

15.4 代理服务器配置

使用代理服务器时,确保已正确配置应用程序很重要。例如,许多应用程序将具有一个负载平衡器,该负载平衡器通过将请求转发到http://192.168.1:8080处的应用服务器来响应对https://example.com/的请求,如果没有适当的配置,则应用服务器将不知道负载平衡器是否存在并将该请求视为http://192.168.1:8080被处理由 Client。

要解决此问题,您可以使用RFC 7239来指定正在使用负载平衡器。为了使应用程序意识到这一点,您需要配置应用程序服务器以了解 X-ForwardedHeaders。例如,Tomcat 使用RemoteIpValve,而 Jetty 使用ForwardedRequestCustomizer。另外,Spring 4.3 用户可以使用ForwardedHeaderFilter

15.5 Spring Security 常见问题解答

15.5.1 一般问题

Spring Security 会满足我所有的应用程序安全要求吗?

Spring Security 为您的身份验证和授权要求提供了一个非常灵活的框架,但是在构建安全应用程序时,还有许多其他注意事项。 Web 应用程序容易受到各种您应该熟悉的攻击的攻击,最好在开始开发之前就进行攻击,因此您可以从一开始就牢记这些内容进行设计和编码。请访问 http://www.owasp.org/[OWASP 网站],以获取有关 Web 应用程序开发人员面临的主要问题的信息,以及可以针对他们采取的对策。

为什么不只使用 web.xml 安全性?

假设您正在基于 Spring 开发企业应用程序。通常需要解决四个安全问题:身份验证,Web 请求安全性,服务层安全性(即,实现业务逻辑的方法)和域对象实例安全性(即不同的域对象具有不同的权限)。牢记以下典型要求:

对于简单的应用程序,servlet 规范安全性可能就足够了。尽管在 Web 容器可移植性,配置要求,有限的 Web 请求安全性以及不存在的服务层和域对象实例安全性的上下文中进行考虑,但很清楚的是,为什么开发人员经常寻求替代解决方案。

需要哪些 Java 和 Spring Framework 版本?

Spring Security 3.0 和 3.1 至少需要 JDK 1.5,并且也至少需要 Spring 3.0.3. 理想情况下,您应该使用最新版本,以避免出现问题。

Spring Security 2.0.x 要求最低 JDK 版本为 1.4,并且是针对 Spring 2.0.x 构建的。它也应该与使用 Spring 2.5.x 的应用程序兼容。

我是 Spring Security 的新手,我需要构建一个支持通过 HTTPS 进行 CAS 单一登录的应用程序,同时允许本地对某些 URL 进行基本身份验证,并针对多个后端用户信息源(LDAP 和 JDBC)进行身份验证。我已经复制了一些找到的配置文件,但是它不起作用。

有什么事吗

或替代其他复杂方案...

实际上,您需要先了解要使用的技术,然后才能成功使用它们构建应用程序。安全性很复杂。使用登录表单来设置简单的配置,并使用 Spring Security 的命名空间来设置一些硬编码的用户是相当简单的。迁移到使用支持的 JDBC 数据库也很容易。但是,如果您尝试直接跳入这种复杂的部署方案,几乎肯定会感到沮丧。设置 CAS 之类的系统,配置 LDAP 服务器和正确安装 SSL 证书所需的学习曲线有很大的跳跃。因此,您需要一次采取一步。

从 Spring Security 的角度来看,您应该做的第一件事是遵循网站上的“入门”指南。这将带您完成一系列步骤,以启动并运行并了解框架的运行方式。如果使用的是您不熟悉的其他技术,则应进行一些研究,并尝试确保在将它们组合到复杂系统中之前可以单独使用它们。

15.5.2 常见问题

尝试登录时,收到一条错误消息,提示“凭据错误”。怎么了?

这意味着认证失败。它并没有说明原因,因为最好避免提供可能有助于攻击者猜测帐户名或密码的详细信息。

这也意味着,如果您在论坛中提出此问题,除非您提供其他信息,否则您将找不到答案。与任何问题一样,您应该检查调试日志的输出,注意所有异常堆栈跟踪和相关消息。在调试器中单步执行代码以查看身份验证失败的原因以及原因。编写一个测试案例,在应用程序外部练习您的身份验证配置。失败通常是由于数据库中存储的密码数据与用户 Importing 的密码数据不同。如果您使用的是哈希密码,请确保数据库中存储的值与应用程序中配置的PasswordEncoder完全相同。

当我尝试登录时,我的应用程序进入“无限循环”,这是怎么回事?

无限循环和重定向到登录页面的常见用户问题是由于不小心将登录页面配置为“安全”资源引起的。确保您的配置允许匿名访问登录页面,方法是将其从安全过滤器链中排除,或者将其标记为需要 ROLE_ANONYMOUS。

如果您的 AccessDecisionManager 包含 AuthenticatedVoter,则可以使用属性“ IS_AUTHENTICATED_ANONYMOUSLY”。如果您正在使用标准名称空间配置设置,则该选项自动可用。

从 Spring Security 2.0.1 开始,当您使用基于命名空间的配置时,将在加载应用程序上下文时进行检查,并在登录页面似乎受到保护的情况下记录警告消息。

我收到一条消息“访问被拒绝(用户是匿名用户);”的异常。怎么了?

这是一条调试级别的消息,它在匿名用户首次尝试访问受保护的资源时发生。

DEBUG [ExceptionTranslationFilter] - Access is denied (user is anonymous); redirecting to authentication entry point
org.springframework.security.AccessDeniedException: Access is denied
at org.springframework.security.vote.AffirmativeBased.decide(AffirmativeBased.java:68)
at org.springframework.security.intercept.AbstractSecurityInterceptor.beforeInvocation(AbstractSecurityInterceptor.java:262)

这是正常现象,不必担心。

为什么即使我退出了应用程序,仍然可以看到受保护的页面?

造成这种情况的最常见原因是您的浏览器已经缓存了该页面,并且您看到的是从浏览器缓存中检索到的副本。通过检查浏览器是否确实在发送请求来验证这一点(检查服务器访问日志,调试日志或使用合适的浏览器调试插件,例如 Firefox 的“ Tamper Data”)。这与 Spring Security 无关,您应该配置应用程序或服务器以设置适当的Cache-Control响应头。请注意,永远不会缓存 SSL 请求。

我收到一条消息“在 SecurityContext 中找不到身份验证对象”的异常。怎么了?

这是另一条调试级别消息,该消息在匿名用户首次尝试访问受保护的资源时出现,但是在您的过滤器链配置中没有AnonymousAuthenticationFilter时出现。

DEBUG [ExceptionTranslationFilter] - Authentication exception occurred; redirecting to authentication entry point
org.springframework.security.AuthenticationCredentialsNotFoundException:
                            An Authentication object was not found in the SecurityContext
at org.springframework.security.intercept.AbstractSecurityInterceptor.credentialsNotFound(AbstractSecurityInterceptor.java:342)
at org.springframework.security.intercept.AbstractSecurityInterceptor.beforeInvocation(AbstractSecurityInterceptor.java:254)

这是正常现象,不必担心。

我无法使用 LDAP 身份验证。

我的配置有什么问题?

请注意,LDAP 目录的权限通常不允许您读取用户密码。因此,通常无法使用“什么是 UserDetailsService,我需要一个吗?”部分,Spring Security 会将存储的密码与用户提交的密码进行比较。最常见的方法是使用 LDAP“ bind”,这是LDAP 协议支持的操作之一。通过这种方法,Spring Security 通过尝试以用户身份验证目录来验证密码。

LDAP 认证最常见的问题是缺乏对目录服务器树结构和配置的了解。不同公司的情况会有所不同,因此您必须自己找出来。在将 Spring Security LDAP 配置添加到应用程序之前,最好使用标准 Java LDAP 代码(不涉及 Spring Security)编写一个简单的测试,并确保您可以使其首先工作。例如,要验证用户身份,可以使用以下代码:

@Test
public void ldapAuthenticationIsSuccessful() throws Exception {
        Hashtable<String,String> env = new Hashtable<String,String>();
        env.put(Context.SECURITY_AUTHENTICATION, "simple");
        env.put(Context.SECURITY_PRINCIPAL, "cn=joe,ou=users,dc=mycompany,dc=com");
        env.put(Context.PROVIDER_URL, "ldap://mycompany.com:389/dc=mycompany,dc=com");
        env.put(Context.SECURITY_CREDENTIALS, "joespassword");
        env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");

        InitialLdapContext ctx = new InitialLdapContext(env, null);

}

Session Management

会话 Management 问题是论坛问题的常见来源。如果要开发 Java Web 应用程序,则应了解如何在 servlet 容器和用户浏览器之间维护会话。您还应该了解安全和非安全 Cookie 的区别,以及使用 HTTP/HTTPS 以及在两者之间进行切换的含义。 Spring Security 与维护会话或提供会话标识符无关。这完全由 servlet 容器处理。

我正在使用 Spring Security 的并发会话控制来防止用户一次登录多次。

登录后打开另一个浏览器窗口时,它不会阻止我再次登录。为什么我可以多次登录?

浏览器通常每个浏览器实例维护一个会话。您不能一次有两个单独的会话。因此,如果您再次在另一个窗口或选项卡中登录,则仅在同一会话中重新进行身份验证。服务器对选项卡,窗口或浏览器实例一无所知。它所看到的只是 HTTP 请求,并根据它们所包含的 JSESSIONID cookie 的值将它们绑定到特定会话。当用户在会话期间进行身份验证时,Spring Security 的并发会话控件会检查他们拥有的“其他身份验证会话”的数量。如果它们已经通过同一会话进行了身份验证,则重新身份验证将无效。

通过 Spring Security 进行身份验证时,为什么会话 ID 会更改?

使用默认配置,Spring Security 在用户认证时更改会话 ID。如果您使用的是 Servlet 3.1 或更高版本的容器,则只需更改会话 ID。如果您使用的是较旧的容器,Spring Security 将使现有会话无效,创建一个新会话,并将会话数据传输到新会话。以这种方式改变会话标识符可以防止“会话固定”攻击。您可以在网上和参考手册中找到有关此内容的更多信息。

我正在使用 Tomcat(或其他一些 servlet 容器),并且已为登录页面启用 HTTPS,然后再切换回 HTTP。

它不起作用-经过身份验证后,我只能回到登录页面。

发生这种情况是因为在 HTTPS 下创建的会话(会话 cookie 标记为“安全”)无法随后在 HTTP 下使用。浏览器不会将 cookie 发送回服务器,并且任何会话状态都将丢失(包括安全上下文信息)。首先使用 HTTP 启动会话应该可以正常工作,因为会话 cookie 不会被标记为安全。但是,Spring Security 的会话固定保护会对此产生干扰,因为它会导致通常使用安全标志将新的会话 ID cookie 发送回用户的浏览器。要解决此问题,可以禁用会话固定保护,但是在较新的 Servlet 容器中,您还可以配置会话 cookie,使其从不使用安全标志。请注意,在 HTTP 和 HTTPS 之间切换通常不是一个好主意,因为任何完全使用 HTTP 的应用程序都容易受到中间人攻击。为了 true 安全,用户应开始使用 HTTPS 访问您的站点并 continue 使用它,直到注销为止。即使从通过 HTTP 访问的页面上单击 HTTPS 链接也可能存在风险。如果您需要更多说服力,请查看sslstrip之类的工具。

我没有在 HTTP 和 HTTPS 之间切换,但是我的会话仍然丢失

通过交换会话 cookie 或向 URL 添加jsessionid参数来维护会话(如果使用 JSTL 输出 URL 或在 URL 上调用HttpServletResponse.encodeUrl(例如,在重定向之前),则会自动发生)。禁用,并且您不重写 URL 来包含jsessionid,则会话将丢失。请注意,出于安全原因,首选使用 cookie,因为它不会在 URL 中公开会话信息。

我正在尝试使用并发会话控制支持,但是即使我确定我已经注销并且没有超出允许的会话,它也不允许我重新登录。

确保已将侦听器添加到 web.xml 文件。必须确保在会话被销毁时通知 Spring Security 会话注册表。没有它,会话信息将不会从注册表中删除。

<listener>
        <listener-class>org.springframework.security.web.session.HttpSessionEventPublisher</listener-class>
</listener>

Spring Security 通过将 create-session 属性设置为 never,即使在未配置会话的情况下也可以创建会话。

这通常意味着用户的应用程序正在某个地方创建会话,但是他们不知道该会话。最常见的罪魁祸首是 JSP。许多人并不知道 JSP 默认情况下会创建会话。为了防止 JSP 创建会话,请将指令<%@ page session="false" %>添加到页面顶部。

如果无法确定要在何处创建会话,可以添加一些调试代码来跟踪位置。一种方法是将javax.servlet.http.HttpSessionListener添加到您的应用程序,该应用程序在sessionCreated方法中调用Thread.dumpStack()

执行 POST 时收到 403 禁止

如果为 HTTP POST 返回了 HTTP 403 Forbidden,但对于 HTTP GET 适用,则该问题很可能与CSRF有关。提供 CSRF 令牌或禁用 CSRF 保护(不建议)。

我正在使用 RequestDispatcher 将请求转发到另一个 URL,但是没有应用我的安全约束。

过滤器默认情况下不应用于转发或包含。如果您确实希望将安全过滤器应用于转发和/或包含,则必须使用\ 元素( 的子元素)在 web.xml 中显式配置这些过滤器。

我已经在应用程序上下文中添加了 Spring Security 的\ 元素,但是如果我在 Spring MVC 控制器 bean(Struts 操作等)中添加了安全 Comments,那么它们似乎没有作用。

在 Spring Web 应用程序中,保存用于调度程序 Servlet 的 Spring MVC bean 的应用程序上下文通常与主应用程序上下文分开。它通常在名为myapp-servlet.xml的文件中定义,其中“ myapp”是在web.xml中分配给 Spring DispatcherServlet的名称。一个应用程序可以有多个DispatcherServlet,每个都有自己的隔离应用程序上下文。这些“子”上下文中的 Bean 对应用程序的其余部分不可见。 “父”应用程序上下文由您在web.xml中定义的ContextLoaderListener加载,并且对所有子上下文可见。通常在此父上下文中定义安全配置(包括<global-method-security>元素)。结果,由于无法从DispatcherServlet上下文中看到 Bean,因此不会强制应用到这些 Web Bean 中的方法的任何安全性约束。您需要将<global-method-security>声明移至 Web 上下文,或将要保护的 Bean 移至主应用程序上下文。

通常,我们建议在服务层而不是单个 Web 控制器上应用方法安全性。

我有一个已经通过身份验证的用户,但是当我在某些请求期间尝试访问 SecurityContextHolder 时,Authentication 为 null。

为什么看不到用户信息?

如果使用与 URL 模式匹配的<intercept-url>元素中的属性filters='none'从安全过滤器链中排除了该请求,则不会为该请求填充SecurityContextHolder。检查调试日志以查看请求是否正在通过筛选器链。 (您正在阅读调试日志,对吗?)。

使用 URL 属性时,授权 JSP 标记不遵守我的方法安全 Comments。

当在<sec:authorize>中使用url属性时,方法安全性不会隐藏链接,因为我们无法轻易地反向工程哪些 URLMap 到哪个控制器端点,因为控制器可以依赖 Headers,当前用户等来确定要调用的方法。

15.5.3 Spring 安全架构问题

如何知道 X 属于哪个包类?

定位类的最佳方法是在 IDE 中安装 Spring Security 源代码。该发行版包括项目分成的每个模块的源 jar。将它们添加到项目源路径中,您可以直接导航到 Spring Security 类(在 Eclipse 中为Ctrl-Shift-T)。这也使调试更加容易,并允许您通过直接查看异常发生的地方来查看异常情况,从而对异常进行故障排除。

名称空间元素如何 Map 到常规 bean 配置?

在参考指南的名称空间附录中,概述了由名称空间创建的 bean。 blog.springsource.com上还有一篇详细的博客文章,名为“ Spring Security 命名空间的背后”。如果想知道全部细节,那么代码在 Spring Security 3.0 发行版的spring-security-config模块中。您可能应该先阅读标准 Spring Framework 参考文档中有关名称空间解析的章节。

“ ROLE_”是什么意思,为什么我在角色名称上需要它?

Spring Security 具有基于投票者的架构,这意味着访问决策由一系列AccessDecisionVoter s 决定。投票者根据为安全资源指定的“配置属性”(例如方法调用)进行操作。使用这种方法,并非所有属性都可能与所有选民都相关,因此选民需要知道何时应该忽略属性(弃权)以及何时应该投票基于属性值授予或拒绝访问权限。最常见的投票者是RoleVoter,默认情况下,只要找到具有“ ROLE_”前缀的属性,投票者就会投票。它将属性(例如“ ROLE_USER”)与当前用户已分配的权限名称进行简单比较。如果找到匹配项(它们具有称为“ ROLE_USER”的权限),则投票批准授予访问权限,否则投票拒绝访问。

可以通过设置RoleVoterrolePrefix属性来更改前缀。如果只需要在应用程序中使用角色,而无需其他自定义投票者,则可以将前缀设置为空白字符串,在这种情况下RoleVoter会将所有属性视为角色。

如何知道要添加到我的应用程序中的哪些依赖项才能使用 Spring Security?

这将取决于您使用的功能以及所开发的应用程序类型。使用 Spring Security 3.0,项目 jar 可以分为明显不同的功能区域,因此可以很容易地从应用程序需求中确定所需的 Spring Security jar。所有应用程序都需要spring-security-core jar。如果要开发 Web 应用程序,则需要spring-security-web jar。如果使用安全名称空间配置,则需要spring-security-config jar,要获得 LDAP 支持,则需要spring-security-ldap jar,依此类推。

对于第三方罐子,情况并不总是那么明显。一个好的起点是从一个预先构建的示例应用程序 WEB-INF/lib 目录中复制这些文件。对于基本应用程序,您可以从教程示例开始。如果要对嵌入式测试服务器使用 LDAP,请以 LDAP 示例为起点。参考手册还包括 http://static.springsource.org/spring-security/site/docs/3.1.x/reference/springsecurity-single.html#appendix-dependencies [附录]列出了每个 Spring 的第一级依赖关系安全模块,其中包含有关它们是否可选以及所需功能的一些信息。

如果您使用 Maven 构建项目,则将适当的 Spring Security 模块作为依赖项添加到 pom.xml 中,将自动提取框架所需的核心 jar。如果需要,任何在 Spring Security POM 文件中标记为“可选”的文件都必须添加到您自己的 pom.xml 文件中。

运行嵌入式 ApacheDS LDAP 服务器需要哪些依赖项?

如果使用的是 Maven,则需要将以下内容添加到 pom 依赖项中:

<dependency>
        <groupId>org.apache.directory.server</groupId>
        <artifactId>apacheds-core</artifactId>
        <version>1.5.5</version>
        <scope>runtime</scope>
</dependency>
<dependency>
        <groupId>org.apache.directory.server</groupId>
        <artifactId>apacheds-server-jndi</artifactId>
        <version>1.5.5</version>
        <scope>runtime</scope>
</dependency>

其他需要的罐子应暂时移入。

什么是 UserDetailsService,我需要一个吗?

UserDetailsService是 DAO 界面,用于加载特定于用户帐户的数据。除了加载该数据以供框架中的其他组件使用外,它没有其他功能。它不负责验证用户身份。使用用户名/密码组合对用户进行身份验证通常是由DaoAuthenticationProvider执行的,DaoAuthenticationProvider被注入UserDetailsService以便允许它为用户加载密码(和其他数据),以便将其与提交的值进行比较。请注意,如果您使用的是 LDAP,则为这种方法可能行不通

如果要自定义身份验证过程,则应自己实现AuthenticationProvider。有关将 Spring Security 身份验证与 Google App Engine 集成的示例,请参见此blog article

15.5.4 常见的“操作方法”请求

我需要登录的信息不仅仅是用户名。

如何添加对额外登录字段(例如公司名称)的支持?

这个问题在 Spring Security 论坛中反复出现,因此您可以通过搜索 Files(或通过 google)在那里找到更多信息。

提交的登录信息由UsernamePasswordAuthenticationFilter实例处理。您将需要自定义此类以处理额外的数据字段。一种选择是使用您自己的自定义身份验证令牌类(而不是标准的UsernamePasswordAuthenticationToken),另一种是简单地将多余的字段与用户名连接起来(例如,使用“:”作为分隔符),并将其传递给 username 属性的UsernamePasswordAuthenticationToken

您还需要自定义实际的身份验证过程。例如,如果使用的是自定义身份验证令牌类,则必须编写一个AuthenticationProvider来处理它(或扩展标准DaoAuthenticationProvider)。如果您串联了这些字段,则可以实现自己的UserDetailsService,将其拆分并加载适当的用户数据以进行身份验证。

如何仅在所请求的 URL 的片段值不同的情况下应用不同的拦截 URL 约束(例如,/ foo#bar 和/ foo#blah?

您无法执行此操作,因为该片段不会从浏览器传输到服务器。从服务器的角度来看,上面的 URL 是相同的。这是 GWT 用户的常见问题。

如何在 UserDetailsService 中访问用户的 IP 地址(或其他 Web 请求数据)?

显然,您不能(不求助于线程局部变量),因为提供给接口的唯一信息是用户名。代替实现UserDetailsService,您应直接实现AuthenticationProvider并从提供的Authentication令牌中提取信息。

在标准的 Web 设置中,Authentication对象上的getDetails()方法将返回WebAuthenticationDetails的实例。如果需要其他信息,可以将自定义AuthenticationDetailsSource注入正在使用的身份验证过滤器中。如果使用命名空间(例如,使用<form-login>元素),则应删除该元素,并用指向明确配置的UsernamePasswordAuthenticationFilter<custom-filter>声明替换它。

如何从 UserDetailsService 访问 HttpSession?

您不能这样做,因为UserDetailsService不了解 Servlet API。如果要存储自定义用户数据,则应自定义返回的UserDetails对象。然后可以通过本地线程SecurityContextHolder随时访问它。调用SecurityContextHolder.getContext().getAuthentication().getPrincipal()将返回此自定义对象。

如果您确实需要访问该会话,则必须通过自定义 Web 层来完成。

如何在 UserDetailsService 中访问用户密码?

您不能(也不应该)。您可能会误解其目的。请参阅上方的“ 什么是 UserDetailsService?”。

如何动态定义应用程序中的安全 URL?

人们经常问如何在数据库中而不是在应用程序上下文中存储安全 URL 和安全元数据属性之间的 Map。

您应该问自己的第一件事是您是否真的需要这样做。如果应用程序需要安全保护,则还需要根据定义的策略对安全性进行彻底测试。在将其推广到生产环境之前,可能需要进行审核和验收测试。一个安全意识强的组织应该意识到,通过更改配置数据库中的一两行,可以在运行时修改安全设置,可以立即消除其辛苦的测试过程的好处。如果考虑到这一点(可能在应用程序中使用多层安全性),那么 Spring Security 允许您完全自定义安全性元数据的来源。如果选择,可以使其完全动态。

方法和 Web 安全都受到AbstractSecurityInterceptor子类的保护,该子类配置了SecurityMetadataSource,从子类中可以获取特定方法或过滤器调用的元数据。为了网络安全,拦截器类为FilterSecurityInterceptor,并且使用标记接口FilterInvocationSecurityMetadataSource。它操作的“受保护对象”类型是FilterInvocation。使用的默认实现(在名称空间<http>中,并且在显式配置拦截器时)都将 URL 模式列表及其对应的“配置属性”列表(ConfigAttribute的实例)存储在内存 Map 中。

要从备用源加载数据,必须使用显式声明的安全过滤器链(通常是 Spring Security 的FilterChainProxy)来定制FilterSecurityInterceptor bean。您不能使用名称空间。然后,您可以实现FilterInvocationSecurityMetadataSource以根据需要为特定的FilterInvocation [25]加载数据。一个非常基本的轮廓如下所示:

public class MyFilterSecurityMetadataSource implements FilterInvocationSecurityMetadataSource {

    public List<ConfigAttribute> getAttributes(Object object) {
        FilterInvocation fi = (FilterInvocation) object;
            String url = fi.getRequestUrl();
            String httpMethod = fi.getRequest().getMethod();
            List<ConfigAttribute> attributes = new ArrayList<ConfigAttribute>();

            // Lookup your database (or other source) using this information and populate the
            // list of attributes

            return attributes;
    }

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

    public boolean supports(Class<?> clazz) {
        return FilterInvocation.class.isAssignableFrom(clazz);
    }
}

有关更多信息,请查看DefaultFilterInvocationSecurityMetadataSource的代码。

如何针对 LDAP 进行身份验证,但如何从数据库中加载用户角色?

LdapAuthenticationProvider bean(在 Spring Security 中处理普通的 LDAP 身份验证)配置有两个单独的策略接口,一个用于执行身份验证,另一个用于加载用户权限,分别称为LdapAuthenticatorLdapAuthoritiesPopulatorDefaultLdapAuthoritiesPopulator从 LDAP 目录中加载用户权限,并具有各种配置参数,以允许您指定应如何检索这些权限。

要改为使用 JDBC,您可以使用适合您的模式的任何 SQL 自己实现接口:

public class MyAuthoritiesPopulator implements LdapAuthoritiesPopulator {
    @Autowired
    JdbcTemplate template;

    List<GrantedAuthority> getGrantedAuthorities(DirContextOperations userData, String username) {
        List<GrantedAuthority> = template.query("select role from roles where username = ?",
                                                                                                new String[] {username},
                                                                                                new RowMapper<GrantedAuthority>() {
            /**
             *  We're assuming here that you're using the standard convention of using the role
             *  prefix "ROLE_" to mark attributes which are supported by Spring Security's RoleVoter.
             */
            public GrantedAuthority mapRow(ResultSet rs, int rowNum) throws SQLException {
                return new SimpleGrantedAuthority("ROLE_" + rs.getString(1);
            }
        }
    }
}

然后,您可以将这种类型的 bean 添加到您的应用程序上下文中,并将其注入LdapAuthenticationProvider。在参考手册的 LDAP 章节中有关使用显式 Spring Bean 配置 LDAP 的部分中对此进行了介绍。请注意,在这种情况下,不能使用名称空间进行配置。您还应该向 Javadoc 查询相关的类和接口。

我想修改由名称空间创建的 bean 的属性,但是架构中没有任何东西可以支持它。

除了放弃命名空间使用外,我还能做什么?

命名空间功能是有意限制的,因此不能涵盖使用普通 bean 可以做的所有事情。如果您想做一些简单的事情,例如修改 bean 或注入不同的依赖项,则可以通过在配置中添加BeanPostProcessor来实现。可以在Spring 参考手册中找到更多信息。为此,您需要对创建哪些 bean 有一点了解,因此,您还应该阅读有关命名空间如何 Map 到 Spring Bean的上述问题中的博客文章。

通常,您需要将所需的功能添加到BeanPostProcessorpostProcessBeforeInitialization方法中。假设您要自定义UsernamePasswordAuthenticationFilter使用的AuthenticationDetailsSource(由form-login元素创建)。您想要从请求中提取名为CUSTOM_HEADER的特定 Headers,并在验证用户身份时使用它。处理器类如下所示:

public class BeanPostProcessor implements BeanPostProcessor {

        public Object postProcessAfterInitialization(Object bean, String name) {
                if (bean instanceof UsernamePasswordAuthenticationFilter) {
                        System.out.println("********* Post-processing " + name);
                        ((UsernamePasswordAuthenticationFilter)bean).setAuthenticationDetailsSource(
                                        new AuthenticationDetailsSource() {
                                                public Object buildDetails(Object context) {
                                                        return ((HttpServletRequest)context).getHeader("CUSTOM_HEADER");
                                                }
                                        });
                }
                return bean;
        }

        public Object postProcessBeforeInitialization(Object bean, String name) {
                return bean;
        }
}

然后,您将在应用程序上下文中注册此 bean。 Spring 将在应用程序上下文中定义的 bean 上自动调用它。


[22]有关如何从web.xml设置 Map 的信息,请参见introductory chapter

[23]实际上,此功能只是为了方便起见而提供的,并非供生产使用(已选择一种查看技术,可用于呈现自定义的登录页面)。 DefaultLoginPageGeneratingFilter类负责呈现登录页面,并将在需要时为普通表单登录和/或 OpenID 提供登录表单。

[24]这不会影响PersistentTokenBasedRememberMeServices的使用,令牌存储在服务器端。

[25] FilterInvocation对象包含HttpServletRequest,因此您可以获取 URL 或任何其他相关信息,并根据这些信息来确定返回的属性列表将包含哪些内容。

首页