《SpringBoot实战》笔记3

springBoot实战 第三章自定义配置

本章内容 
覆盖自动配置的Bean 
用外置属性进行配置 
自定义错误页

3.1 覆盖Spring Boot自动配置

一般来说,如果不用配置就能得到和显式配置一样的结果,那么不写配置是直接的选择。

既然如此,那干嘛还要多做额外的工作呢?如果不用编写和维护额外的配置代码也行,那何必还要它们呢? 大多数情况下,自动配置的Bean刚好能满足你的需要,不需要去覆盖它们。但某些情况下, Spring Boot在自动配置时还不能很好地进行推断。这里有个不错的例子:当你在应用程序里添加安全特性时,自动配置做得还不够好。

安全配置并不是放之四海而皆准的,围绕应用程序安全有很多决策要做,Spring Boot不能替你做决定。

虽然Spring Boot为安全提供了一些基本的自动配置,但是你还是需要自己覆盖一些配置以满足特 定的安全要求。

想知道如何用显式的配置来覆盖自动配置,我们先从为阅读列表应用程序添加Spring Security入手。

在了解自动配置提供了什么之后,我们再来覆盖基础的安全配置,以满足特定的 场景需求

3.1.1 保护应用程序
Spring Boot自动配置让应用程序的安全工作变得易如反掌,
你要做的只是添加Security起步 依赖。以Gradle为例,应添加如下依赖:
compile(“org.springframework.boot:spring-boot-starter-security”)

如果使用Maven,那么你要在项目的块中加入如下:

<dependency>   
<groupId>org.springframework.bootgroupId>   
<artifactId>spring-boot-starter-securityartifactId> 
dependency> 

这样就搞定了!重新构建应用程序后运行即可,现在这就是一个安全的Web应用程序了!
Security起步依赖在应用程序的Classpath里添加了Spring Secuirty(和其他一些东西)。
Classpath里 有Spring Security后,自动配置就能介入其中创建一个基本的Spring Security配置。

试着在浏览器里打开该应用程序,你马上就会看到HTTP基础身份验证对话框。
此处的用户 名是user,密码就有点麻烦了。密码是在应用程序每次运行时随机生成后写入日志的,

你需要查 找日志消息(默认写入标准输出),找到此类内容:
Using default security password: d9d8abe5-42b5-4f20-a32a-76ee3df658d9

3.1.2 创建自定义的安全配置

package com.example.readinglist;

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;
import org.springframework.security.provisioning.UserDetailsManager;

/**
 * 覆盖自动配置很简单,就当自动配置不存在,直接显式地写一段配置。
 * 这段显式配置的形式 不限,Spring支持的XML和Groovy形式配置都可以。
 * 在编写显式配置时,我们会专注于Java形式的配置。在Spring Security的场景下,
 * 这意味着写 一个扩展了WebSecurityConfigurerAdapter的配置类
 */
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private ReaderRepository readerRepository;

    @Override
    protected  void configure(HttpSecurity http)throws Exception{
        http
                .authorizeRequests()
                .antMatchers("/").access("hasRole('READER')")
                .antMatchers("/**").permitAll()
                .and()
                .formLogin()
                .loginPage("/login")
                .failureUrl("/login?error=true");
    }

    @Override
    protected  void configure(AuthenticationManagerBuilder auth)throws Exception{
        auth
                .userDetailsService(new UserDetailsService() {
                    @Override
                    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
                        //eturn readerRepository.getOne(username);
                        return readerRepository.getOne(username);
                    }
        });
    }
}
package com.example.readinglist;

import org.springframework.data.jpa.repository.JpaRepository;
import  java.util.List;

/**
 * 接下来,我们就要定义用于把Book对象持久化到数据库的仓库了。
 * ①因为用了Spring Data JPA, 所以我们要做的就是简单地定义一个接口,
 * 扩展一下Spring Data JPA的JpaRepository接口:
 */

/**
 * 通过扩展JpaRepository,ReadingListRepository直接继承了18个执行常用持久化操作 的方法。
 * JpaRepository是个泛型接口,有两个参数:仓库操作的领域对象类型,及其ID属性的 类型
 */
public interface ReadingListRepository extends JpaRepository<Book,Long> {
    /**
     * 可以根据读者的用户名来查找阅读列表。
     * @param reader
     * @return List
     */
    List<Book> findByReader(String reader);
}
package com.example.readinglist;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

import java.util.List;

//这样组件扫描会自动将ReadingListController注册为 Spring应用程序上下文里的一个Bean
@Controller
//将其中所有的处理器 方法都映射到了“/”这个URL路径上。
@RequestMapping("/")
public class ReadingListController {
    private ReadingListRepository readingListRepository;

    public ReadingListController(ReadingListRepository readingListRepository){
        this.readingListRepository = readingListRepository;
    }

    /**
     * 处理/{reader}上的HTTP GET请求,根据路径里指定的读者,从(通 过控制器的构造器注入的)仓库获取Book列表。
     * 随后将这个列表塞入模型,用的键是 books,后返回readingList作为呈现模型的视图逻辑名称。
     * @param reader
     * @param model
     * @return String
     */
    @RequestMapping(value="/{reader}",method = RequestMethod.GET)
    public String readersBooks(
            @PathVariable("reader") String reader, Model model){
        List<Book> readingList = readingListRepository.findByReader(reader);
        if(readingList != null){
            model.addAttribute("books",readingList);
        }
        return "readingList";
    }

    /**
     *  addToReadingList():处理/{reader}上的HTTP POST请求,将请求正文里的数据绑定 到一个Book对象上。
     *  该方法把Book对象的reader属性设置为读者的姓名,
     *  随后通过仓 库的save()方法保存修改后的Book对象,
     *  后重定向到/{reader}(控制器中的另一个方 法会处理该请求)。
     * @param reader
     * @param book
     * @return
     */
    @RequestMapping(value = "/{reader}",method = RequestMethod.POST)
    public String addToReadingList(
            @PathVariable("reader") String reader,Book book){
        book.setReader(reader);
        readingListRepository.save(book);
        return "redirect:/{reader}";
    }
}
package com.example.readinglist;

import org.springframework.data.jpa.repository.JpaRepository;

//通过JPA持久化读者 Java Persistence API
public interface ReaderRepository extends JpaRepository<Reader,String> {
}

package com.example.readinglist;

import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import javax.persistence.Entity;
import javax.persistence.Id;
import java.util.Arrays;
import java.util.Collection;

//Reader用了@Entity注解,所以这是一个JPA实体
@Entity
//你应该还注意到Reader实现了UserDetails接口以及其中的方法,这样Reader就能代表 Spring Security里的用户了
public class Reader implements UserDetails {
    private static final long serialVersionUID = 1L;
//此外,它的username字段 上有@Id注解,表明这是实体的ID
    @Id
    private String username;
    private String fullname;
    private String password;

    public void setUsername(String username) {
        this.username = username;
    }
    public String getPassword() {
        return password;
    }

    public String getUsername() {
        return username;
    }
    public String getFullname() {
        return fullname;
    }

    public void setFullname(String fullname) {
        this.fullname = fullname;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    /**getAuthorities()方法被覆盖过了,始终会为用户授予READER 权限
     * 授予Reader权限
     * @return
     */
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return Arrays.asList(new SimpleGrantedAuthority("READER"));
    }

    //不过期,不加锁,不禁用
    //isAccountNonExpired()、 isAccountNonLocked()、isCredentialsNonExpired()
    // 和isEnabled()方法都返回true,这样读者账户就不会过期,不会被锁定,也不会被撤销。
    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return true;
    }
}

/*
在一个大型应用程序里,赋予用户的授权本身也可能是实体,它们被 维护在独立的数据表里。
同样,表示一个账户是否为非过期、非锁定且可用的布尔值也 是数据库里的字段。
但是,出于演示考虑,我决定让这些细节保持简单,
以免分散我们 的注意力,影响正在讨论的话题——我说的是覆盖Spring Boot自动配置。
 */

/*
再重申一次,想要覆盖Spring Boot的自动配置,你所要做的仅仅是编写一个显式的配置。
 Spring Boot会发现你的配置,随后降低自动配置的优先级,以你的配置为准。
想弄明白这是如何 实现的,让我们揭开Spring Boot自动配置的神秘面纱,
看看它是如何运作的,以及它是怎么允许 自己被覆盖的。
 */
package com.example.readinglist;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

import java.util.List;

//这样组件扫描会自动将ReadingListController注册为 Spring应用程序上下文里的一个Bean
@Controller
//将其中所有的处理器 方法都映射到了“/”这个URL路径上。
@RequestMapping("/")
public class ReadingListController {
    private ReadingListRepository readingListRepository;

    public ReadingListController(ReadingListRepository readingListRepository){
        this.readingListRepository = readingListRepository;
    }

    /**
     * 处理/{reader}上的HTTP GET请求,根据路径里指定的读者,从(通 过控制器的构造器注入的)仓库获取Book列表。
     * 随后将这个列表塞入模型,用的键是 books,后返回readingList作为呈现模型的视图逻辑名称。
     * @param reader
     * @param model
     * @return String
     */
    @RequestMapping(value="/{reader}",method = RequestMethod.GET)
    public String readersBooks(
            @PathVariable("reader") String reader, Model model){
        List<Book> readingList = readingListRepository.findByReader(reader);
        if(readingList != null){
            model.addAttribute("books",readingList);
        }
        return "readingList";
    }

    /**
     *  addToReadingList():处理/{reader}上的HTTP POST请求,将请求正文里的数据绑定 到一个Book对象上。
     *  该方法把Book对象的reader属性设置为读者的姓名,
     *  随后通过仓 库的save()方法保存修改后的Book对象,
     *  后重定向到/{reader}(控制器中的另一个方 法会处理该请求)。
     * @param reader
     * @param book
     * @return
     */
    @RequestMapping(value = "/{reader}",method = RequestMethod.POST)
    public String addToReadingList(
            @PathVariable("reader") String reader,Book book){
        book.setReader(reader);
        readingListRepository.save(book);
        return "redirect:/{reader}";
    }
}

《SpringBoot实战》笔记3_第1张图片
在这里插入图片描述

3.1.3 掀开自动配置的神秘面纱

Spring Boot的DataSourceAutoConfiguration中定义的JdbcTemplate Bean就是一个非常简 单的例子,
演示了@ConditionalOnMissingBean如何工作:

@Bean
@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。以下是其中的一个代码片段:
@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,但并不是每次都要做到这种程度。
让我们来看看怎么通过设置几个简单的配置 属性调整自动配置组件吧。

3.2 通过属性文件外置配置

待续…

你可能感兴趣的:(springBoot教程)