Spring Security 5.1 提供了一些新特性。下面是发行版的亮点。
如果你想开始使用 Spring Security,最好的地方是我们的示例应用程序。 表格 4.1. 示例应用程序
在 Spring 3.1 中,Spring Framework 添加了对 Java 配置的普遍支持。自从 Spring Security 3.2 以来,Spring Security Java 配置支持使得用户能够在不使用任何 XML 的情况下轻松配置 Spring Security。 如果你熟悉 小节 6, Security 命名空间配置 Configuration,那么你应该发现它与 Security Java 配置支持之间有很多相似之处。
第一步是创建我们的 Spring Security Java 配置。配置创建了一个名为 import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.*; import org.springframework.security.config.annotation.authentication.builders.*; import org.springframework.security.config.annotation.web.configuration.*; @EnableWebSecurity public class WebSecurityConfig implements WebMvcConfigurer { @Bean public UserDetailsService userDetailsService() throws Exception { InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager(); manager.createUser(User.withDefaultPasswordEncoder().username("user").password("password").roles("USER").build()); return manager; } } 这个配置真的不多,但它做了很多。你可以找到以下特征的摘要:
下一步是在 war 中注册
如果不使用 Spring 或 Spring MVC,则需要将 import org.springframework.security.web.context.*; public class SecurityWebApplicationInitializer extends AbstractSecurityWebApplicationInitializer { public SecurityWebApplicationInitializer() { super(WebSecurityConfig.class); } }
如果我们在应用程序中的其它地方使用 Spring,那么很可能已经有一个 import org.springframework.security.web.context.*; public class SecurityWebApplicationInitializer extends AbstractSecurityWebApplicationInitializer { }
这只会简单地为应用程序中的每个 URL 注册 springSecurityFilterChain 过滤器。之后,我们将确保 public class MvcWebApplicationInitializer extends AbstractAnnotationConfigDispatcherServletInitializer { @Override protected Class<?>[] getRootConfigClasses() { return new Class[] { WebSecurityConfig.class }; } // ... other overrides ... }
到目前为止,我们的 WebSecurityConfig 只包含了如何验证用户的信息。Spring Security 如何知道我们希望所有用户都被认证?Spring Security 如何知道我们想要支持基于表单的身份验证?原因在于, protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() .anyRequest().authenticated() .and() .formLogin() .and() .httpBasic(); } 上面的默认配置:
你将注意到,此配置与 XML 命名空间配置非常相似: <http> <intercept-url pattern="/**" access="authenticated"/> <form-login /> <http-basic /> </http>
关闭 XML 标签的 Java 配置等价于使用 你可能想知道在提示你登录时,登录表单来自哪里,因为我们没有提到任何 HTML 文件或 JSP。由于 Spring Security 的默认配置没有显式地为登录页面设置 URL,因此 Spring Security 根据启用的特性以及使用用于处理提交的登录的 URL 的标准值,自动生成一个默认目标 URL,用户将发送到登录后等等。 虽然自动生成的登录页面便于快速启动和运行,但是大多数应用程序都希望提供自己的登录页面。为了这样做,我们可以更新配置如下所示: protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() .anyRequest().authenticated() .and() .formLogin() .loginPage("/login")
下面我们可以看到一个用 JSP 实现的示例登录页面,用于我们当前的配置:
<c:url value="/login" var="loginUrl"/> <form action="${loginUrl}" method="post">
我们的示例只要求用户进行身份验证,并为我们的应用程序中的每个 URL 都进行了验证。我们可以通过为我们的 protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests()
当使用
与配置登录功能类似,无论如何,你还可以有多种选项来进一步定制注销需求: protected void configure(HttpSecurity http) throws Exception { http .logout()
通常,为了自定义注销功能,可以添加
通常, 更多详细信息请参阅 小节 18.4, “记住我接口和实现”。
除了直接提供
提供以下实现:
如上所述,你不需要直接指定
在 REST API 类型的场景中,
Spring Security 的 WebFlux 支持依赖于
你可以找到如下的最小化 WebFlux Security 配置: @EnableWebFluxSecurity public class HelloWebfluxSecurityConfig { @Bean public MapReactiveUserDetailsService userDetailsService() { UserDetails user = User.withDefaultPasswordEncoder() .username("user") .password("user") .roles("USER") .build(); return new MapReactiveUserDetailsService(user); } } 此配置提供表单和 http 基本身份验证、设置授权以要求经过身份验证的用户访问任何页面、设置默认登录页面和默认注销页面、设置与安全相关的 HTTP 报头、CSRF 保护等等。 你可以找到如下的最小化 WebFlux Security 配置的明确的版本: @EnableWebFluxSecurity public class HelloWebfluxSecurityConfig { @Bean public MapReactiveUserDetailsService userDetailsService() { UserDetails user = User.withDefaultPasswordEncoder() .username("user") .password("user") .roles("USER") .build(); return new MapReactiveUserDetailsService(user); } @Bean public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) { http .authorizeExchange() .anyExchange().authenticated() .and() .httpBasic().and() .formLogin(); return http.build(); } } 这个配置明确地设置了与我们的最小配置相同的所有东西。从这里你可以轻松地对默认值进行更改。 OAuth 2.0 登录特性为应用程序提供了使用用户在 OAuth 2.0 提供者(如 GitHub)或 OpenID 连接 1.0 提供者(如 Google)的现有帐户登录应用程序的能力。OAuth 2.0 登录实现用例:"Login with Google" 或 "Login with GitHub"”。
Spring Boot 2.0 为 OAuth 2.0 登录带来了全自动配置功能。 本节展示如何使用 Google 作为身份验证提供者来配置 OAuth 2.0 登录示例,并介绍以下主题: 要使用 Google 的 OAuth 2.0 身份验证系统进行登录,必须在 Google API 控制台中设置项目以获得 OAuth 2.0 凭证。
遵循 OpenID 连接页面上的说明,从设置 OAuth 2.0 开始。 在完成获取 OAuth 2.0 凭证指令之后,你应该拥有一个新的 OAuth Client,该 Client 具有由 Client ID 和 Client Secret 组成的凭证。 重定向 URI 是应用程序中的路径,最终用户的用户代理在用 Google 进行身份验证并授予访问同意页上的 OAuth 客户端(在上一步中创建)权限之后被重定向回该路径。
在设置重定向 URI 子节中,确保授权重定向 URI 字段设置为
现在有了新的 OAuth Client 和 Google,你需要配置应用程序以使用 OAuth Client 进行身份验证。这样做:
启动 Spring Boot 2.0 示例并转到 点击 Google 链接,然后重定向到 Google 进行身份验证。 在使用你的 Google 帐户凭证进行身份验证后,下一页呈现给你的是同意屏幕。同意屏幕要求你允许或拒绝访问你先前创建的 OAuth Client。单击 Allow 授权 OAuth Client 访问你的电子邮件地址和基本配置文件信息。 此时,OAuth Client 从 UserInfo 端点检索电子邮件地址和基本配置文件信息,并建立经过身份验证的会话。
客户端注册保存信息,例如, 客户端 id、客户端 secret、授权授予类型、重定向 URI、范围、授权 URI、令牌 URI 和其它细节。
public final class ClientRegistration { private String registrationId;
下表概述了 Spring Boot 2.0 OAuth Client 属性到
Spring Boot 2.0 自动配置将
自动配置也将 下面的列表展示了一个示例: @Controller public class OAuth2LoginController { @Autowired private ClientRegistrationRepository clientRegistrationRepository; @RequestMapping("/") public String index() { ClientRegistration googleRegistration = this.clientRegistrationRepository.findByRegistrationId("google"); ... return "index"; } }
例如,
如前所述,当配置 Google 客户端时,只需要 下面的列表展示了一个示例: spring: security: oauth2: client: registration: google: client-id: google-client-id client-secret: google-client-secret
对于可能需要指定不同的 下面的列表展示了一个示例: spring: security: oauth2: client: registration: google-login: 有一些 OAuth 2.0 提供者支持多租户,这导致每个租户(或子域)具有不同的协议端点。 例如,OAuth 客户端使用 Okta 注册,会被分配到指定的子域,并具有不同的协议端点。
对于这些情况,Spring Boot 2.0 提供了用于配置自定义提供程序属性的下列基本属性: 下面的列表展示了一个示例: spring: security: oauth2: client: registration: okta: client-id: okta-client-id client-secret: okta-client-secret provider: okta:
用于 OAuth Client 支持的 Spring Boot 2.0 自动配置类是 它执行以下任务:
如果你需要根据你的具体要求重写自动配置,你可以这样做:
下面的示例展示了如何注册一个 @Configuration public class OAuth2LoginConfig { @Bean public ClientRegistrationRepository clientRegistrationRepository() { return new InMemoryClientRegistrationRepository(this.googleClientRegistration()); } private ClientRegistration googleClientRegistration() { return ClientRegistration.withRegistrationId("google") .clientId("google-client-id") .clientSecret("google-client-secret") .clientAuthenticationMethod(ClientAuthenticationMethod.BASIC) .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE) .redirectUriTemplate("{baseUrl}/login/oauth2/code/{registrationId}") .scope("openid", "profile", "email", "address", "phone") .authorizationUri("https://accounts.google.com/o/oauth2/v2/auth") .tokenUri("https://www.googleapis.com/oauth2/v4/token") .userInfoUri("https://www.googleapis.com/oauth2/v3/userinfo") .userNameAttributeName(IdTokenClaimNames.SUB) .jwkSetUri("https://www.googleapis.com/oauth2/v3/certs") .clientName("Google") .build(); } }
下面的示例展示了如何使用 @EnableWebSecurity public class OAuth2LoginSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() .anyRequest().authenticated() .and() .oauth2Login(); } }
下面的示例展示了如何通过注册一个 @Configuration public class OAuth2LoginConfig { @EnableWebSecurity public static class OAuth2LoginSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() .anyRequest().authenticated() .and() .oauth2Login(); } } @Bean public ClientRegistrationRepository clientRegistrationRepository() { return new InMemoryClientRegistrationRepository(this.googleClientRegistration()); } private ClientRegistration googleClientRegistration() { return ClientRegistration.withRegistrationId("google") .clientId("google-client-id") .clientSecret("google-client-secret") .clientAuthenticationMethod(ClientAuthenticationMethod.BASIC) .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE) .redirectUriTemplate("{baseUrl}/login/oauth2/code/{registrationId}") .scope("openid", "profile", "email", "address", "phone") .authorizationUri("https://accounts.google.com/o/oauth2/v2/auth") .tokenUri("https://www.googleapis.com/oauth2/v4/token") .userInfoUri("https://www.googleapis.com/oauth2/v3/userinfo") .userNameAttributeName(IdTokenClaimNames.SUB) .jwkSetUri("https://www.googleapis.com/oauth2/v3/certs") .clientName("Google") .build(); } }
如果不能使用 Spring Boot 2.0,并且希望配置 @Configuration public class OAuth2LoginConfig { @EnableWebSecurity public static class OAuth2LoginSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() .anyRequest().authenticated() .and() .oauth2Login(); } } @Bean public ClientRegistrationRepository clientRegistrationRepository() { return new InMemoryClientRegistrationRepository(this.googleClientRegistration()); } @Bean public OAuth2AuthorizedClientService authorizedClientService() { return new InMemoryOAuth2AuthorizedClientService(this.clientRegistrationRepository()); } private ClientRegistration googleClientRegistration() { return CommonOAuth2Provider.GOOGLE.getBuilder("google") .clientId("google-client-id") .clientSecret("google-client-secret") .build(); } }
开发人员还可以在应用程序上下文中注册 下面的列表展示了一个示例: @Controller public class OAuth2LoginController { @Autowired private OAuth2AuthorizedClientService authorizedClientService; @RequestMapping("/userinfo") public String userinfo(OAuth2AuthenticationToken authentication) { // authentication.getAuthorizedClientRegistrationId() returns the // registrationId of the Client that was authorized during the Login flow OAuth2AuthorizedClient authorizedClient = this.authorizedClientService.loadAuthorizedClient( authentication.getAuthorizedClientRegistrationId(), authentication.getName()); OAuth2AccessToken accessToken = authorizedClient.getAccessToken(); ... return "userinfo"; } } 下面的附加资源描述高级配置选项: 到目前为止,我们只研究了最基本的身份验证配置。让我们来看看一些配置认证的更高级的选项。 我们已经看到了为单个用户配置内存中的身份验证的示例。下面是配置多个用户的示例: @Bean public UserDetailsService userDetailsService() throws Exception { // ensure the passwords are encoded properly UserBuilder users = User.withDefaultPasswordEncoder(); InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager(); manager.createUser(users.username("user").password("password").roles("USER").build()); manager.createUser(users.username("admin").password("password").roles("USER","ADMIN").build()); return manager; } 你可以找到更新以支持基于 JDBC 的身份验证。下面的示例假定你已经在应用程序中定义了数据源。jdbc-javaconfig 示例提供了使用基于 JDBC 的身份验证的完整示例。 @Autowired private DataSource dataSource; @Autowired public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { // ensure the passwords are encoded properly UserBuilder users = User.withDefaultPasswordEncoder(); auth .jdbcAuthentication() .dataSource(dataSource) .withDefaultSchema() .withUser(users.username("user").password("password").roles("USER")) .withUser(users.username("admin").password("password").roles("USER","ADMIN")); } 你可以找到更新以支持基于 LDAP 的身份验证。ldap-javaconfig 示例提供了使用基于 LDAP 的身份验证的完整示例。 @Autowired private DataSource dataSource; @Autowired public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { auth .ldapAuthentication() .userDnPatterns("uid={0},ou=people") .groupSearchBase("ou=groups"); } 上面的例子使用了下面的 LDIF 和一个嵌入式 Apache DS LDAP 实例。 users.ldif. dn: ou=groups,dc=springframework,dc=org objectclass: top objectclass: organizationalUnit ou: groups dn: ou=people,dc=springframework,dc=org objectclass: top objectclass: organizationalUnit ou: people dn: uid=admin,ou=people,dc=springframework,dc=org objectclass: top objectclass: person objectclass: organizationalPerson objectclass: inetOrgPerson cn: Rod Johnson sn: Johnson uid: admin userPassword: password dn: uid=user,ou=people,dc=springframework,dc=org objectclass: top objectclass: person objectclass: organizationalPerson objectclass: inetOrgPerson cn: Dianne Emu sn: Emu uid: user userPassword: password dn: cn=user,ou=groups,dc=springframework,dc=org objectclass: top objectclass: groupOfNames cn: user uniqueMember: uid=admin,ou=people,dc=springframework,dc=org uniqueMember: uid=user,ou=people,dc=springframework,dc=org dn: cn=admin,ou=groups,dc=springframework,dc=org objectclass: top objectclass: groupOfNames cn: admin uniqueMember: uid=admin,ou=people,dc=springframework,dc=org
你可以通过将自定义
@Bean public SpringAuthenticationProvider springAuthenticationProvider() { return new SpringAuthenticationProvider(); }
您可以通过将自定义
@Bean public SpringDataUserDetailsService springDataUserDetailsService() { return new SpringDataUserDetailsService(); }
还可以通过将 @Bean public BCryptPasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); }
我们可以配置多个 HttpSecurity 实例就像我们可以有多个 @EnableWebSecurity public class MultiHttpSecurityConfig { @Bean
从版本 2.0 起,Spring Security 大大提高了对服务层方法的安全性的支持。它支持 JSR-250 注解安全性以及框架的原始
我们可以在任何 @EnableGlobalMethodSecurity(securedEnabled = true) public class MethodSecurityConfig { // ... } 将注解添加到方法(在类或接口上)将相应地限制对该方法的访问。Spring Security 的本机注解支持定义了该方法的一组属性。这些将传递给 AccessDecisionManager,以便它做出实际决策: public interface BankService { @Secured("IS_AUTHENTICATED_ANONYMOUSLY") public Account readAccount(Long id); @Secured("IS_AUTHENTICATED_ANONYMOUSLY") public Account[] findAccounts(); @Secured("ROLE_TELLER") public Account post(Account account, double amount); } JSR-250 注解的支持可以使用 @EnableGlobalMethodSecurity(jsr250Enabled = true) public class MethodSecurityConfig { // ... } 这些是基于标准的,并且允许应用简单的基于角色的约束,但是没有强大的 Spring Security 的本地注释。若要使用基于表达式的新语法,将使用 @EnableGlobalMethodSecurity(prePostEnabled = true) public class MethodSecurityConfig { // ... } 和等效 Java 代码将是 public interface BankService { @PreAuthorize("isAnonymous()") public Account readAccount(Long id); @PreAuthorize("isAnonymous()") public Account[] findAccounts(); @PreAuthorize("hasAuthority('ROLE_TELLER')") public Account post(Account account, double amount); }
有时,你可能需要执行比使用 @EnableGlobalMethodSecurity(prePostEnabled = true) public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration { @Override protected MethodSecurityExpressionHandler createExpressionHandler() { // ... create and return custom MethodSecurityExpressionHandler ... return expressionHandler; } }
有关可以重写的方法的其它信息,请参阅
Spring Security 使用 Reactor 的上下文支持方法安全,它设置使用
Authentication authentication = new TestingAuthenticationToken("user", "password", "ROLE_USER"); Mono<String> messageByUsername = ReactiveSecurityContextHolder.getContext() .map(SecurityContext::getAuthentication) .map(Authentication::getName) .flatMap(this::findMessageByUsername) // In a WebFlux application the `subscriberContext` is automatically setup using `ReactorContextWebFilter` .subscriberContext(ReactiveSecurityContextHolder.withAuthentication(authentication)); StepVerifier.create(messageByUsername) .expectNext("Hi user") .verifyComplete();
这一 Mono<String> findMessageByUsername(String username) { return Mono.just("Hi " + username); } 下面是在应用程序中使用方法安全时的最小方法安全配置。 @EnableReactiveMethodSecurity public class SecurityConfig { @Bean public MapReactiveUserDetailsService userDetailsService() { User.UserBuilder userBuilder = User.withDefaultPasswordEncoder(); UserDetails rob = userBuilder.username("rob").password("rob").roles("USER").build(); UserDetails admin = userBuilder.username("admin").password("admin").roles("USER","ADMIN").build(); return new MapReactiveUserDetailsService(rob, admin); } } 考虑下面的类: @Component public class HelloWorldMessageService { @PreAuthorize("hasRole('ADMIN')") public Mono<String> findMessage() { return Mono.just("Hello World!"); } }
结合上面的配置, 当与 小节 5.6, “WebFlux Security” 集成时,根据认证的用户,通过 Spring Security 自动建立 Reactor 上下文。 @EnableWebFluxSecurity @EnableReactiveMethodSecurity public class SecurityConfig { @Bean SecurityWebFilterChain springWebFilterChain(ServerHttpSecurity http) throws Exception { return http // Demonstrate that method security works // Best practice to use both for defense in depth .authorizeExchange() .anyExchange().permitAll() .and() .httpBasic().and() .build(); } @Bean MapReactiveUserDetailsService userDetailsService() { User.UserBuilder userBuilder = User.withDefaultPasswordEncoder(); UserDetails rob = userBuilder.username("rob").password("rob").roles("USER").build(); UserDetails admin = userBuilder.username("admin").password("admin").roles("USER","ADMIN").build(); return new MapReactiveUserDetailsService(rob, admin); } } 你可以在 hellowebflux-method 中找到一个完整的示例。 Spring Security 的 Java 配置不公开其配置的每个对象的每个属性。这简化了大多数用户的配置。毕竟,如果每个属性被公开,用户可以使用标准的 bean 配置。
虽然有充分的理由不直接公开每一个属性,但用户可能仍然需要更高级的配置选项。为了解决这个问题,Spring Security 引入了 @Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() .anyRequest().authenticated() .withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() { public <O extends FilterSecurityInterceptor> O postProcess( O fsi) { fsi.setPublishAuthorizationSuccess(true); return fsi; } }); } 你可以在 Spring Security 中提供你自己的自定义 DSL。例如,你可能有这样的东西: public class MyCustomDsl extends AbstractHttpConfigurer<MyCustomDsl, HttpSecurity> { private boolean flag; @Override public void init(H http) throws Exception { // any method that adds another configurer // must be done in the init method http.csrf().disable(); } @Override public void configure(H http) throws Exception { ApplicationContext context = http.getSharedObject(ApplicationContext.class); // here we lookup from the ApplicationContext. You can also just create a new instance. MyFilter myFilter = context.getBean(MyFilter.class); myFilter.setFlag(flag); http.addFilterBefore(myFilter, UsernamePasswordAuthenticationFilter.class); } public MyCustomDsl flag(boolean value) { this.flag = value; return this; } public static MyCustomDsl customDsl() { return new MyCustomDsl(); } }
自定义 DSL 可以这样使用: @EnableWebSecurity public class Config extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http .apply(customDsl()) .flag(true) .and() ...; } } 代码按以下顺序调用:
如果需要,可以通过使用 META-INF/spring.factories. org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer = sample.MyCustomDsl 希望禁用默认值的用户可以显式地这样做。 @EnableWebSecurity public class Config extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http .apply(customDsl()).disable() ...; } } |