全配置并不是大家都一样的,围绕应用程序安全有很多决策要做,spring boot不能替我们做决定。springboot 为安全提供了一下基本的自动配置,但是我们还需要自己覆盖一些配置以满足我们特定的安全需求。
# # # # 一、保护应用程序
想了解如何通过显示配置来覆盖自动配置。我们先从为阅读列表应用程序添加 spring Security来入手。
Spring Boot自动配置让应用程序的安全工作变得易如反掌,我们需要做的只是添加Security起步依赖。以Gradle为例,应添加如下依赖:
compile("org.springframework.boot:spring-boot-starter-security")
如果使用Maven,那么你要在项目的 块中加入如下 :
org.springframework.boot
spring-boot-starter-security
这样就搞定了!重新构建应用程序后运行即可,现在这就是一个安全的Web应用程序了!
Security起步依赖在应用程序的Classpath里添加了Spring Secuirty(和其他一些东西)。Classpath里有Spring Security后,自动配置就能介入其中创建一个基本的Spring Security配置。试着在浏览器里打开该应用程序,我们马上就会看到HTTP基础身份验证对话框。此处的用户名是user,密码就有点麻烦了。密码是在应用程序每次运行时随机生成后写入日志的,我们需要查找日志消息(默认写入标准输出),找到此类内容:
Using default security password: d9d8abe5-42b5-4f20-a32a-76ee3df658d9
虽然不能肯定,但我猜这个特定的安全配置并不是你的理想选择。首先,HTTP基础身份验证对话框有点粗糙,对用户并不友好。而且,我们一般不会开发这种只有一个用户的应用程序,而且用户还要从日志文件里找到自己的密码(如果你开发一个这样的用户程序,相信老板一会会找你和你的主管好好聊聊的)。因此,我们会希望修改Spring Security的一些配置,至少要有一个好看一些的登录页,还要有一个基于数据库或LDAP(Lightweight Directory AccessProtocol)用户存储的身份验证服务。
接下来让我们看看如何写出Spring Secuirty配置,覆盖自动配置的安全设置吧
# # # # 二、创建自定义安全配置
覆盖自动配置很简单,就当自动配置不存在,直接显式地写一段配置。这段显式配置的形式不限,Spring支持的XML和Groovy形式配置都可以。
在编写显式配置时,我们会专注于Java形式的配置。在Spring Security的场景下,这意味着写一个扩展了 WebSecurityConfigurerAdapter 的配置类。代码清单1的 SecurityConfig就是我们需要的东西。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.
builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.
HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.
EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.
WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.
UsernameNotFoundException;
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private ReaderRepository readerRepository;
@Override
//要求登录READER角色
// 设置登录表单的路径protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/").access("hasRole('READER')")
.antMatchers("/**").permitAll()
.and()
.formLogin()
.loginPage("/login")
.failureUrl("/login?error=true");
}
UserDetailsService
@Override
protected void configure(
AuthenticationManagerBuilder auth) throws Exception {
auth
.userDetailsService(new UserDetailsService() {
# 定义自定义UserDetailsServices
@Override
public UserDetails loadUserByUsername(String username)
throws UsernameNotFoundException {
return readerRepository.findOne(username);
}
});
}
}
SecurityConfig 是个非常基础的Spring Security配置,尽管如此,它还是完成了不少安全定制工作。通过这个自定义的安全配置类,我们让Spring Boot跳过了安全自动配置,转而使用我们的安全配置。
扩展了 WebSecurityConfigurerAdapter 的配置类可以覆盖两个不同的 configure() 方
法。在 SecurityConfig 里,第一个 configure() 方法指明,“/”( ReadingListController的方法映射到了该路径)的请求只有经过身份认证且拥有READER角色的用户才能访问。其他的所有请求路径向所有用户开放了访问权限。这里还将登录页和登录失败页(带有一个 error 属性)指定到了/login。
Spring Security为身份认证提供了众多选项,后端可以是JDBC(Java Database Connectivity)、
LDAP和内存用户存储。在这个应用程序中,我们会通过JPA用数据库来存储用户信息。第二个configure() 方法设置了一个自定义的 UserDetailsService ,这个服务可以是任意实现了UserDetailsService 的类,用于查找指定用户名的用户。代码清单2提供了一个匿名内部类实现,简单地调用了注入ReaderRepository (这是一个Spring Data JPA仓库接口)的 findOne()方法。
import org.springframework.data.jpa.repository.JpaRepository;
public interface ReaderRepository
extends JpaRepository<Reader, String> {
// 通过JPA持久化读者
}
和 BookRepository 类似,你无需自己实现 ReaderRepository 。这是因为它扩展了
JpaRepository ,Spring Data JPA会在运行时自动创建它的实现。这为你提供了18个操作 Reader
实体的方法。
说到 Reader 实体, Reader 类(如代码清单3所示)就是最后一块拼图了,它就是一个简单的JPA实体,其中有几个字段用来存储用户名、密码和用户全名。
import java.util.Arrays;
import java.util.Collection;
import javax.persistence.Entity;
import javax.persistence.Id;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
@Entity
public class Reader implements UserDetails {
private static final long serialVersionUID = 1L;
@Id
private String username;
private String fullname;
private String password;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getFullname() {
return fullname;
}
public void setFullname(String fullname) {
this.fullname = fullname;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
// UserDetails methods
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return Arrays.asList(new SimpleGrantedAuthority("READER"));
}
// 不过期,不加锁,
// 不禁用
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
}
Reader 用了 @Entity 注解,所以这是一个JPA实体。 username 字段上有 @Id 注解,表明这是实体的ID。因为 username 应该能唯一标识一个Reader 。
我们还注意到 Reader 实现了 UserDetails 接口以及其中的方法,这样 Reader 就能代表Spring Security里的用户了。 getAuthorities() 方法被覆盖过了,始终会为用户授予READER权限。 isAccountNonExpired() 、 isAccountNonLocked() 、 isCredentialsNonExpired()和 isEnabled() 方法都返回 true ,这样读者账户就不会过期,不会被锁定,也不会被撤销。重新构建并重启应用程序后,能以读者身份登录应用程序了。
想要覆盖Spring Boot的自动配置,我们要做的仅仅是编写一个显式的配置。Spring Boot会发现我们的配置,随后降低自动配置的优先级,以我们的配置为准。想弄明白这是如何实现的,让我们揭开Spring Boot自动配置的神秘面纱,看看它是如何运作的,以及它是怎么允许自己被覆盖的。
三、揭开自动配置的神秘面纱
Spring Boot自动配置自带了很多配置类,每一个都能运用在我们的应用程序里。它们都使用了Spring 4.0的条件化配置,可以在运行时判断这个配置是该被运用,还是该被忽略。大部分情况下,@ConditionalOnMissingBean 注解是覆盖自动配置的关键。
Spring Boot的 DataSourceAutoConfiguration 中定义的 JdbcTemplate Bean就是一个非常简
单的例子,演示了 @ConditionalOnMissingBean 如何工作:
@ConditionalOnMissingBean(JdbcOperations.class)
public JdbcTemplate jdbcTemplate() {
return new JdbcTemplate(this.dataSource);
}
jdbcTemplate() 方法上添加了 @Bean 注解,在需要时可以配置出一个 JdbcTemplate Bean。但它上面还加了 @ConditionalOnMissingBean 注解,要求当前不存在 JdbcOperations类型( JdbcTemplate 实现了该接口)的Bean时才生效。如果当前已经有一个 JdbcOperations Bean了,条件即不满足,不会执行 jdbcTemplate() 方法。
什么情况下会存在一个 JdbcOperations Bean呢?Spring Boot的设计是加载应用级配置,随
后再考虑自动配置类。因此,如果我们已经配置了一个 JdbcTemplate Bean,那么在执行自动配置时就已经存在一个 JdbcOperations 类型的Bean了,于是忽略自动配置的 JdbcTemplate Bean。
关于Spring Security,自动配置会考虑几个配置类。在这里就不一一讨论每个配置类的细节,但覆盖Spring Boot自动配置的安全配置时,最重要的一个类是 SpringBootWebSecurity-Configuration 。以下是其中的一个代码片段:
@EnableConfigurationProperties
@ConditionalOnClass({ EnableWebSecurity.class })
@ConditionalOnMissingBean(WebSecurityConfiguration.class)
@ConditionalOnWebApplication
public class SpringBootWebSecurityConfiguration {
...
}
SpringBootWebSecurityConfiguration 上加了好几个注解。看到 @Condi-tionalOnClass 注解后,你就应该知道Classpath里必须要有 @EnableWebSecurity 注解。 @ConditionalOnWebApplication 说 明 这 必 须 是 个 Web 应 用 程 序 。 @ConditionalOn-
MissingBean 注解才是我们的安全配置类代替 SpringBootWebSecurityConfiguration 的关键所在。
@ConditionalOnMissingBean 注解要求当下没有 WebSecurityConfiguration 类型的Bean。虽然表面上我们并没有这么一个Bean,但通过在 SecurityConfig 上添加 @EnableWeb-Security 注解,我们实际上间接创建了一个 WebSecurityConfiguration Bean。所以在自动配置时,这个Bean就已经存在了, @ConditionalOnMissingBean 条件不成立, SpringBoot-WebSecurityConfiguration 提供的配置就被跳过了。
虽然Spring Boot的自动配置和 @ConditionalOnMissingBean 让我们能显式地覆盖那些可以自动配置的Bean,但并不是每次都要做到这种程度。下一篇文章让我们来看看怎么通过设置几个简单的配置属性调整自动配置组件吧。