在传统的Spring配置的过程,就如同订比萨的时候自己指定全部的辅料。而Spring Boot自动配置就像是从菜单上选一份特色比萨,让Spring Boot处理各种细节比自己声明上下文里全部的Bean要容易很多。
幸运的是,Spring Boot自动配置非常灵活。就像比萨厨师可以不在你的比萨里放蘑菇,而是加墨西哥胡椒一样,Spring Boot能让你参与进来,影响自动配置的实施。
覆盖Spring boot自动配置
大多数情况下,自动配置的Bean刚好能满足你的需要,不需要去覆盖它们。但某些情况下,Spring Boot在自动配置时还不能很好地进行推断。
保护应用程序
Spring Boot自动配置让应用程序的安全工作变得易如反掌,你要做的只是添加Security起步依赖。以Gradle为例,应添加如下依赖:
compile("org.springframework.boot:spring-boot-starter-security")
如果使用Maven,那么你要在项目的<dependencies>
块中加入如下
<dependency>:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
这样就搞定了!重新构建应用程序后运行即可,现在这就是一个安全的Web应用程序了!
对比下,我们之前配置一堆,还运行出错?是不是轻松很多。
然而!我们的密码呢?帐号呢?
我们看到运行后的页面是这样的:
我特么怎么知道帐号密码呢?
此处的用户名是user,密码就有点麻烦了。密码是在应用程序每次运行时随机生成后写入日志的,你需要查找日志消息(默认写入标准输出),找到此类内容。
因此,我们会希望修改Spring Security的一些配置,至少!我们希望有一个好看一些的登录页面。还要有一个基于数据库或LDAP用户存储的身份验证服务。
创建自定义的安全配置
覆盖自动配置很简单,就当自动配置不存在,直接显式地写一段配置。
package com.leesanghyuk.web;
import com.leesanghyuk.data.ReaderRepository;
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
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
http
.authorizeRequests()
.antMatchers("/").access("hasRole('ROLE_READER')") //必须有reader的角色
.antMatchers("/**").permitAll()
.and()
.formLogin()
.loginPage("/login") //登录表单的路径
.failureUrl("/login?error=true");//登录失败路径
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
super.configure(auth);
auth.userDetailsService(new UserDetailsService() {
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
return readerRepository.findOne(username);
}
});
}
}
Spring Security为身份认证提供了众多选项,后端可以是JDBC(Java Database Connectivity)、LDAP和内存用户存储。在这个应用程序中,我们会通过JPA用数据库来存储用户信息。
package com.leesanghyuk.data;
import com.leesanghyuk.domain.Reader;
import org.springframework.data.jpa.repository.JpaRepository;
public interface ReaderRepository extends JpaRepository<Reader, String> {
}
和BookRepository类似,你无需自己实现ReaderRepository。这是因为它扩展了JpaRepository,Spring Data JPA会在运行时自动创建它的实现。这为你提供了18个操作Reader实体的方法。
说到Reader实体,Reader类就是最后一块拼图了,它就是一个简单的JPA实体,其中有几个字段用来存储用户名、密码和用户全名。
package com.leesanghyuk.domain;
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 = 1241887194095689628L;
@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;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return Arrays.asList(new SimpleGrantedAuthority("ROLE_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。
通过属性文件外置配置
在处理应用安全时,你当然会希望完全掌控所有配置。不过,为了微调一些细节,比如改改端口号和日志级别,便放弃自动配置,这就有点得不偿失了。
事实上,Spring Boot自动配置的Bean提供了300多个用于微调的属性。当你调整设置时,只要在环境变量、Java系统属性、JNDI(Java Naming and Directory Interface)、命令行参数或者属性文件里进行指定就好了。
自动配置微调
禁用模版缓存:
如果阅读列表应用程序经过了几番修改,你一定已经注意到了,除非重启应用程序,否则对Thymeleaf模板的变更是不会生效的。这是因为Thymeleaf模板默认缓存。这有助于改善应用程序的性能,因为模板只需编译一次,但在开发过程中就不能实时看到变更的效果了。
将spring.thymeleaf.cache设置为false就能禁用Thymeleaf模板缓存。
在命令行里运行应用程序时,将其设置为命令行参数即可:(一次性)
$ java -jar readinglist-0.0.1-SNAPSHOT.jar --spring.thymeleaf.cache=false
或者,如果你希望每次运行时都禁用缓存,可以创建一个application.yml,包含以下内容:
spring:
thymeleaf:
cache: false
或者放在application.properties中:
spring.thymeleaf.cache=false
配置嵌入式服务器:
从命令行(或者Spring Tool Suite)运行Spring Boot应用程序时,应用程序会启动一个嵌入式的服务器(默认是Tomcat),监听8080端口。大部分情况下这样挺好,但同时运行多个应用程序可能会有问题。要是所有应用程序都试着让Tomcat服务器监听同一个端口,在启动第二个应用程序时就会有冲突。
但如果希望端口变更时间更长一点,可以在其他支持的配置位置上设置server.port。例如,把它放在应用程序Classpath根目录的application.yml文件里:
server:
port: 8000
或者properties:server.port=8000
配置日志:
默认情况下,Spring Boot会用Logback(http://logback.qos.ch)来记录日志,并用INFO级别输出到控制台。在运行应用程序和其他例子时,你应该已经看到很多INFO级别的日志了。
我们可能希望使用log4j或者Log4j2,
那么你只需要修改依赖,引入对应该日志实现的起步依赖,同时排除掉Logback。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
或者使用gradle:
configurations {
all*.exclude group:'org.springframework.boot',
module:'spring-boot-starter-logging'
}
排除默认日志的起步依赖后,就可以引入你想用的日志实现的起步依赖了。在Maven里可以这样添加Log4j:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j</artifactId>
</dependency>
或者compile("org.springframework.boot:spring-boot-starter-log4j")
要完全掌握日志配置,可以在Classpath的根目录(src/main/resources)里创建logback.xml文件。下面是一个logback.xml的简单例子:
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>
%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
</pattern>
</encoder>
</appender>
<logger name="root" level="INFO"/>
<root level="INFO">
<appender-ref ref="STDOUT" />
</root>
</configuration>
除了日志格式之外,这个Logback配置和不加logback.xml文件的默认配置差不多。但是,通过编辑logback.xml,你可以完全掌控应用程序的日志文件。哪些配置应该放进logback.xml这个话题不在本书的讨论范围内,请参考Logback的文档以了解更多信息。
即使如此,你对日志配置最常做的改动就是修改日志级别和指定日志输出的文件。使用了Spring Boot的配置属性后,你可以在不创建logback.xml文件的情况下修改那些配置。
要设置日志级别,你可以创建以logging.level开头的属性,后面是要日志名称。如果根日志级别要设置为WARN,但Spring Security的日志要用DEBUG级别,可以在application.yml里加入以下内容:
logging:
level:
root: WARN
org:
springframework:
security: DEBUG
具体怎么设置,用到再说。
配置数据源:
虽然你可以显式配置自己的DataSource Bean,但通常并不用这么做,只需简单地通过属性配置数据库的URL和身份信息就可以了。
在applicaiton.properties中加入:
spring.datasource.url=jsbc:mysql://localhost/readingList
spring.datasource.username=root
spring.datasource.password=root
通常你都无需指定JDBC驱动,Spring Boot会根据数据库URL识别出需要的驱动,但如果识别出问题了,你还可以设置spring.datasource.driver-class-name属性:
spring.datasource.url=jsbc:mysql://localhost/readingList
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
也可以配置连接池。
除此之外,配置profile,spring.profiles.active=production
之类的。我们不做赘述了。
定制应用程序错误页面
SpringBoot它的自动配置会默认查找一个名为error的视图,如果找不到会默认使用它自己的视图。