Spring Security部分详解

原文:https://www.2cto.com/kf/201808/765042.html

简介:

Spring Security是一个能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方案的安全框架。它提供了一组可以在Spring应用上下文中配置的Bean,充分利用了Spring IoC,DI(控制反转Inversion of Control ,DI:Dependency Injection 依赖注入)和AOP(面向切面编程)功能,为应用系统提供声明式的安全访问控制功能,减少了为企业系统安全控制编写大量重复代码的工作。

SpringSecurity核心功能:

认证(你是谁) 授权(你能干什么) 攻击防护(防止伪造身份)

1 核心组件

1.1 SecurityContextHolder

SecurityContextHolder用于存储安全上下文(security context)的信息。当前操作的用户是谁,该用户是否已经被认证,他拥有哪些角色权限…这些都被保存在SecurityContextHolder中。

SecurityContextHolder默认使用ThreadLocal 策略来存储认证信息。看到ThreadLocal 也就意味着,这是一种与线程绑定的策略。Spring Security在用户登录时自动绑定认证信息到当前线程,在用户退出时,自动清除当前线程的认证信息。但这一切的前提,是你在web场景下使用Spring Security,而如果是Swing界面,Spring也提供了支持,SecurityContextHolder的策略则需要被替换,鉴于我的初衷是基于web来介绍Spring Security,所以这里以及后续,非web的相关的内容都一笔带过。

获取当前用户的信息

因为身份信息是与线程绑定的,所以可以在程序的任何地方使用静态方法获取用户信息。一个典型的获取当前登录用户的姓名的例子如下所示:

?

1

2

3

4

5

6

Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();

if (principal instanceof UserDetails) {

String username = ((UserDetails)principal).getUsername();

} else {

String username = principal.toString();

}

getAuthentication()返回了认证信息,再次getPrincipal()返回了身份信息,UserDetails便是Spring对身份信息封装的一个接口。Authentication和UserDetails的介绍在下面的小节具体讲解,本节重要的内容是介绍SecurityContextHolder这个容器。

1.2 Authentication

先看看这个接口的源码长什么样:

?

1

2

3

4

5

6

7

8

9

package org.springframework.security.core;// <1>

public interface Authentication extends Principal, Serializable { // <1>

    Collection<!-- extends GrantedAuthority--> getAuthorities(); // <2>

    Object getCredentials();// <2>

    Object getDetails();// <2>

    Object getPrincipal();// <2>

    boolean isAuthenticated();// <2>

    void setAuthenticated(boolean var1) throws IllegalArgumentException;

}

<1> Authentication是spring security包中的接口,直接继承自Principal类,而Principal是位于java.security包中的。可以见得,Authentication在spring security中是*别的身份/认证的抽象。
<2> 由这个*接口,我们可以得到用户拥有的权限信息列表,密码,用户细节信息,用户身份信息,认证信息。
还记得1.1节中,authentication.getPrincipal()返回了一个Object,我们将Principal强转成了Spring Security中最常用的UserDetails,这在Spring Security中非常常见,接口返回Object,使用instanceof判断类型,强转成对应的具体实现类。接口详细解读如下:

getAuthorities(),权限信息列表,默认是GrantedAuthority接口的一些实现类,通常是代表权限信息的一系列字符串。 getCredentials(),密码信息,用户输入的密码字符串,在认证过后通常会被移除,用于保障安全。 getDetails(),细节信息,web应用中的实现接口通常为 WebAuthenticationDetails,它记录了访问者的ip地址和sessionId的值。 getPrincipal(),敲黑板!!!最重要的身份信息,大部分情况下返回的是UserDetails接口的实现类,也是框架中的常用接口之一。UserDetails接口将会在下面的小节重点介绍。

Spring Security是如何完成身份认证的?

1 用户名和密码被过滤器获取到,封装成Authentication,通常情况下是UsernamePasswordAuthenticationToken这个实现类。

2 AuthenticationManager 身份管理器负责验证这个Authentication

3 认证成功后,AuthenticationManager身份管理器返回一个被填充满了信息的(包括上面提到的权限信息,身份信息,细节信息,但密码通常会被移除)Authentication实例。

4 SecurityContextHolder安全上下文容器将第3步填充了信息的Authentication,通过SecurityContextHolder.getContext().setAuthentication(…)方法,设置到其中。

这是一个抽象的认证流程,而整个过程中,如果不纠结于细节,其实只剩下一个AuthenticationManager 是我们没有接触过的了,这个身份管理器我们在后面的小节介绍。将上述的流程转换成代码,便是如下的流程:

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

public class AuthenticationExample {

private static AuthenticationManager am = new SampleAuthenticationManager();

public static void main(String[] args) throws Exception {

    BufferedReader in = new BufferedReader(new InputStreamReader(System.in));

    while(true) {

    System.out.println("Please enter your username:");

    String name = in.readLine();

    System.out.println("Please enter your password:");

    String password = in.readLine();

    try {

        Authentication request = new UsernamePasswordAuthenticationToken(name, password);

        Authentication result = am.authenticate(request);

        SecurityContextHolder.getContext().setAuthentication(result);

        break;

    } catch(AuthenticationException e) {

        System.out.println("Authentication failed: " + e.getMessage());

    }

    }

    System.out.println("Successfully authenticated. Security context contains: " +

            SecurityContextHolder.getContext().getAuthentication());

}

}

class SampleAuthenticationManager implements AuthenticationManager {

static final List<grantedauthority> AUTHORITIES = new ArrayList<grantedauthority>();

static {

    AUTHORITIES.add(new SimpleGrantedAuthority("ROLE_USER"));

}

public Authentication authenticate(Authentication auth) throws AuthenticationException {

    if (auth.getName().equals(auth.getCredentials())) {

    return new UsernamePasswordAuthenticationToken(auth.getName(),

        auth.getCredentials(), AUTHORITIES);

    }

    throw new BadCredentialsException("Bad Credentials");

}

}</grantedauthority></grantedauthority>

注意:上述这段代码只是为了让大家了解Spring Security的工作流程而写的,不是什么源码。在实际使用中,整个流程会变得更加的复杂,但是基本思想,和上述代码如出一辙。

1.3 AuthenticationManager

初次接触Spring Security的朋友相信会被AuthenticationManager,ProviderManager ,AuthenticationProvider …这么多相似的Spring认证类搞得晕头转向,但只要稍微梳理一下就可以理解清楚它们的联系和设计者的用意。AuthenticationManager(接口)是认证相关的核心接口,也是发起认证的出发点,因为在实际需求中,我们可能会允许用户使用用户名+密码登录,同时允许用户使用邮箱+密码,手机号码+密码登录,甚至,可能允许用户使用指纹登录(还有这样的操作?没想到吧),所以说AuthenticationManager一般不直接认证,AuthenticationManager接口的常用实现类ProviderManager 内部会维护一个List列表,存放多种认证方式,实际上这是委托者模式的应用(Delegate)。也就是说,核心的认证入口始终只有一个:AuthenticationManager,不同的认证方式:用户名+密码(UsernamePasswordAuthenticationToken),邮箱+密码,手机号码+密码登录则对应了三个AuthenticationProvider。这样一来四不四就好理解多了?熟悉shiro的朋友可以把AuthenticationProvider理解成Realm。在默认策略下,只需要通过一个AuthenticationProvider的认证,即可被认为是登录成功。

只保留了关键认证部分的ProviderManager源码:

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

public class ProviderManager implements AuthenticationManager, MessageSourceAware,

        InitializingBean {

    // 维护一个AuthenticationProvider列表

    private List providers = Collections.emptyList();

  

    public Authentication authenticate(Authentication authentication)

          throws AuthenticationException {

       Class<!-- extends Authentication--> toTest = authentication.getClass();

       AuthenticationException lastException = null;

       Authentication result = null;

       // 依次认证

       for (AuthenticationProvider provider : getProviders()) {

          if (!provider.supports(toTest)) {

             continue;

          }

          try {

             result = provider.authenticate(authentication);

             if (result != null) {

                copyDetails(authentication, result);

                break;

             }

          }

          ...

          catch (AuthenticationException e) {

             lastException = e;

          }

       }

       // 如果有Authentication信息,则直接返回

       if (result != null) {

            if (eraseCredentialsAfterAuthentication

                    && (result instanceof CredentialsContainer)) {

                 //移除密码

                ((CredentialsContainer) result).eraseCredentials();

            }

             //发布登录成功事件

            eventPublisher.publishAuthenticationSuccess(result);

            return result;

       }

       ...

       //执行到此,说明没有认证成功,包装异常信息

       if (lastException == null) {

          lastException = new ProviderNotFoundException(messages.getMessage(

                "ProviderManager.providerNotFound",

                new Object[] { toTest.getName() },

                "No AuthenticationProvider found for {0}"));

       }

       prepareException(lastException, authentication);

       throw lastException;

    }

}</authenticationprovider>

ProviderManager 中的List,会依照次序去认证,认证成功则立即返回,若认证失败则返回null,下一个AuthenticationProvider会继续尝试认证,如果所有认证器都无法认证成功,则ProviderManager 会抛出一个ProviderNotFoundException异常。

到这里,如果不纠结于AuthenticationProvider的实现细节以及安全相关的过滤器,认证相关的核心类其实都已经介绍完毕了:身份信息的存放容器SecurityContextHolder,身份信息的抽象Authentication,身份认证器AuthenticationManager及其认证流程。姑且在这里做一个分隔线。下面来介绍下AuthenticationProvider接口的具体实现。

1.4 DaoAuthenticationProvider

AuthenticationProvider最最最常用的一个实现便是DaoAuthenticationProvider。顾名思义,Dao正是数据访问层的缩写,也暗示了这个身份认证器的实现思路。由于本文是一个Overview,姑且只给出其UML类图:Spring Security部分详解

按照我们最直观的思路,怎么去认证一个用户呢?用户前台提交了用户名和密码,而数据库中保存了用户名和密码,认证便是负责比对同一个用户名,提交的密码和保存的密码是否相同便是了。在Spring Security中。提交的用户名和密码,被封装成了UsernamePasswordAuthenticationToken,而根据用户名加载用户的任务则是交给了UserDetailsService,在DaoAuthenticationProvider中,对应的方法便是retrieveUser,虽然有两个参数,但是retrieveUser只有第一个参数起主要作用,返回一个UserDetails。还需要完成UsernamePasswordAuthenticationToken和UserDetails密码的比对,这便是交给additionalAuthenticationChecks方法完成的,如果这个void方法没有抛异常,则认为比对成功。比对密码的过程,用到了PasswordEncoder和SaltSource,密码加密和盐的概念相信不用我赘述了,它们为保障安全而设计,都是比较基础的概念。

如果你已经被这些概念搞得晕头转向了,不妨这么理解DaoAuthenticationProvider:它获取用户提交的用户名和密码,比对其正确性,如果正确,返回一个数据库中的用户信息(假设用户信息被保存在数据库中)。

1.5 UserDetails与UserDetailsService

上面不断提到了UserDetails这个接口,它代表了最详细的用户信息,这个接口涵盖了一些必要的用户信息字段,具体的实现类对它进行了扩展。

?

1

2

3

4

5

6

7

8

9

public interface UserDetails extends Serializable {

   Collection<!-- extends GrantedAuthority--> getAuthorities();

   String getPassword();

   String getUsername();

   boolean isAccountNonExpired();

   boolean isAccountNonLocked();

   boolean isCredentialsNonExpired();

   boolean isEnabled();

}

它和Authentication接口很类似,比如它们都拥有username,authorities,区分他们也是本文的重点内容之一。Authentication的getCredentials()与UserDetails中的getPassword()需要被区分对待,前者是用户提交的密码凭证,后者是用户正确的密码,认证器其实就是对这两者的比对。Authentication中的getAuthorities()实际是由UserDetails的getAuthorities()传递而形成的。还记得Authentication接口中的getUserDetails()方法吗?其中的UserDetails用户详细信息便是经过了AuthenticationProvider之后被填充的。

?

1

2

3

public interface UserDetailsService {

   UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;

}

UserDetailsService和AuthenticationProvider两者的职责常常被人们搞混,关于他们的问题在文档的FAQ和issues中屡见不鲜。记住一点即可,敲黑板!!!UserDetailsService只负责从特定的地方(通常是数据库)加载用户信息,仅此而已,记住这一点,可以避免走很多弯路。UserDetailsService常见的实现类有JdbcDaoImpl,InMemoryUserDetailsManager,前者从数据库加载用户,后者从内存中加载用户,也可以自己实现UserDetailsService,通常这更加灵活。

1.6 架构概览图

为了更加形象的理解上述我介绍的这些核心类,附上一张按照我的理解,所画出Spring Security的一张非典型的UML图

Spring Security部分详解

2 Spring Security Guides

2.1 引入依赖

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

<dependencies>

    <dependency>

        <groupid>org.springframework.boot</groupid>

        spring-boot-starter-web</artifactid>

    </dependency>

    <dependency>

        <groupid>org.springframework.boot</groupid>

        spring-boot-starter-security</artifactid>

    </dependency>

    <dependency>

        <groupid>org.springframework.boot</groupid>

        spring-boot-starter-thymeleaf</artifactid>

    </dependency>

</dependencies>

由于我们集成了springboot,所以不需要显示的引入Spring Security文档中描述core,config依赖,只需要引入spring-boot-starter-security即可。

2.2 创建一个不受安全限制的web应用

这是一个首页,不受安全限制

src/main/resources/templates/home.html

?

1

 

Welcome!

Click here to see a greeting.

这个简单的页面上包含了一个链接,跳转到”/hello”。对应如下的页面

src/main/resources/templates/hello.html

?

1

 

Hello world!

接下来配置Spring MVC,使得我们能够访问到页面。

首先在application.yml配置文件中配置thymeleaf模板的配置信息

?

1

2

3

4

5

6

7

8

spring:

 thymeleaf:

       cache: false

       prefix: classpath:/templates/

       suffix: .html

       encoding: UTF-8

       content-type: text/html

       mode: HTML5

配置WebSecurityConfig 继承WebSecurityConfigurerAdapter,配置自定义的登录页面及成功返回页面等。

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

@Configuration           // 声明为配置类

@EnableWebSecurity      // 启用 Spring Security web 安全的功能

public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    private final static Logger logger = LoggerFactory.getLogger(WebSecurityConfig.class);

     

        /**

         * 自定义配置

         */

        @Override

        protected void configure (HttpSecurity http) throws Exception {

            http.authorizeRequests()//配置安全策略

                    .antMatchers("/", "/user/hello").permitAll()//定义/请求不需要验证

                    .anyRequest().authenticated()//其余的所有请求都需要验证

                    .and()

                    .formLogin()

                    .loginPage("/login")//拦截后get请求跳转的页面

                    .defaultSuccessUrl("/hello")

                    .permitAll()

                    .and()

                    .logout()

                    .permitAll();

        }

}

控制层:

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

@Controller

public class UserController {

 

    @GetMapping("/hello")

    public String hello() {

        return "hello";

    }

    @GetMapping(value = {"/","index"})

    public String index(){

        return "index";

    }

    @GetMapping(value = "login")

    public String loginUI(){

        return "login";

    }

    @RequestMapping(value = "aa",method = RequestMethod.GET)

    public String aa(){

        return "index";

    }

    @GetMapping(value = "admin")

    public String admin(Model model){

        model.addAttribute("title","标题");

        model.addAttribute("content","内容");

        model.addAttribute("extraInfo","你是admin");

        return "admin";

    }

 

 

 

 

}

注意:使用@Controller,而不能使用@RestController。因为@RestController会导致返回的结果为Json串信息,而不是与前端模板封装的.HTML文件。

2.3 配置Spring Security

一个典型的安全配置如下所示:

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

@Configuration

@EnableWebSecurity <1>

public class WebSecurityConfig extends WebSecurityConfigurerAdapter { <1>

    @Override

    protected void configure(HttpSecurity http) throws Exception {

        http <2>

            .authorizeRequests()

                .antMatchers("/", "/home").permitAll()

                .anyRequest().authenticated()

                .and()

            .formLogin()

                .loginPage("/login")

                .permitAll()

                .and()

            .logout()

                .permitAll();

    }

    @Autowired

    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {

        auth <3>

            .inMemoryAuthentication()

                .withUser("admin").password("admin").roles("USER");

    }

}

<1> @EnableWebSecurity注解使得SpringMVC集成了Spring Security的web安全支持。另外,WebSecurityConfig配置类同时集成了WebSecurityConfigurerAdapter,重写了其中的特定方法,用于自定义Spring Security配置。整个Spring Security的工作量,其实都是集中在该配置类,不仅仅是这个guides,实际项目中也是如此。
<2> configure(HttpSecurity)定义了哪些URL路径应该被拦截,如字面意思所描述:”/“, “/home”允许所有人访问,”/login”作为登录入口,也被允许访问,而剩下的”/hello”则需要登陆后才可以访问。
<3> configureGlobal(AuthenticationManagerBuilder)在内存中配置一个用户,admin/admin分别是用户名和密码,这个用户拥有USER角色。
我们目前还没有登录页面,下面创建登录页面:

?

1

 

Invalid username and password.

You have been logged out.

User Name : 

Password: 

 

这个Thymeleaf模板提供了一个用于提交用户名和密码的表单,其中name=”username”,name=”password”是默认的表单值,并发送到“/ login”。 在默认配置中,Spring Security提供了一个拦截该请求并验证用户的过滤器。 如果验证失败,该页面将重定向到“/ loginerror”,并显示相应的错误消息。 当用户选择注销,请求会被发送到“/ loginlogout”。

最后,我们为hello.html添加一些内容,用于展示用户信息。

?

1

 

Hello [[${#httpServletRequest.remoteUser}]]!

 

我们使用Spring Security之后,HttpServletRequest#getRemoteUser()可以用来获取用户名。 登出请求将被发送到“/ logout”。 成功注销后,会将用户重定向到“/ loginlogout”。

2.4 添加启动类

?

1

2

3

4

5

6

@SpringBootApplication

public class Application {

    public static void main(String[] args) throws Throwable {

        SpringApplication.run(Application.class, args);

    }

}

2.5 测试

访问首页https://localhost:8080/:

Spring Security部分详解

点击here,尝试访问受限的页面:/hello,由于未登录,结果被强制跳转到登录也/login:

Spring Security部分详解

输入正确的用户名和密码之后,跳转到之前想要访问的/hello:

Spring Security部分详解

点击Sign out退出按钮,访问:/logout,回到登录页面:

Spring Security部分详解

 

3 核心配置解读

3.1 功能介绍

这是Spring Security入门指南中的配置项:

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

@Configuration

@EnableWebSecurity

public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

  @Override

  protected void configure(HttpSecurity http) throws Exception {

      http

          .authorizeRequests()

              .antMatchers("/", "/home").permitAll()

              .anyRequest().authenticated()

              .and()

          .formLogin()

              .loginPage("/login")

              .permitAll()

              .and()

          .logout()

              .permitAll();

  }

  @Autowired

  public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {

      auth

          .inMemoryAuthentication()

              .withUser("admin").password("admin").roles("USER");

  }

}

当配置了上述的javaconfig之后,我们的应用便具备了如下的功能:

除了“/”,”/home”(首页),”/login”(登录),”/logout”(注销),之外,其他路径都需要认证。 指定“/login”该路径为登录页面,当未认证的用户尝试访问任何受保护的资源时,都会跳转到“/login”。 默认指定“/logout”为注销页面 配置一个内存中的用户认证器,使用admin/admin作为用户名和密码,具有USER角色 防止CSRF攻击 Session Fixation protection(可以参考我之前讲解Spring Session的文章,防止别人篡改sessionId) Security Header(添加一系列和Header相关的控制) HTTP Strict Transport Security for secure requests 集成X-Content-Type-Options 缓存控制 集成X-XSS-Protection.aspx) X-Frame-Options integration to help prevent Clickjacking(iframe被默认禁止使用) 为Servlet API集成了如下的几个方法 HttpServletRequest#getRemoteUser()) HttpServletRequest.html#getUserPrincipal()) HttpServletRequest.html#isUserInRole(java.lang.String)) HttpServletRequest.html#login(java.lang.String, java.lang.String)) HttpServletRequest.html#logout())

3.2 解读@EnableWebSecurity

我们自己定义的配置类WebSecurityConfig加上了@EnableWebSecurity注解,同时继承了WebSecurityConfigurerAdapter。你可能会在想谁的作用大一点,先给出结论:毫无疑问@EnableWebSecurity起到决定性的配置作用,他其实是个组合注解,背后SpringBoot做了非常多的配置。

---------------------------------------------------------------继续研究----------------------------------------------------------------------------------------------

配置用户认证逻辑,因为我们是有自己自定义的一套用户体系的:

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

@Component

public class MyUserDetailsService implements UserDetailsService {

 

    private Logger logger = LoggerFactory.getLogger(getClass());

 

    @Override

    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {

        logger.info("用户的用户名: {}", username);

        // TODO 根据用户名,查找到对应的密码,与权限

 

        // 封装用户信息,并返回。参数分别是:用户名,密码,用户权限

        User user = new User(username, "123456",

                            AuthorityUtils.commaSeparatedStringToAuthorityList("admin"));

        return user;

  }

}

这里我们没有进行过多的校验,用户名可以随意的填写,但是密码必须得是“123456”,这样才能登录成功。
同时可以看到,这里User对象的第三个参数,它表示的是当前用户的权限,我们将它设置为”admin”。

UserDetails

刚刚我们在写MyUserDetailsService的时候,里面实现了一个方法,并返回了一个UserDetails。这个UserDetails 就是封装了用户信息的对象,里面包含了七个方法

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

public interface UserDetails extends Serializable {

    // 封装了权限信息

    Collection<!-- extends GrantedAuthority--> getAuthorities();

    // 密码信息

    String getPassword();

    // 登录用户名

    String getUsername();

    // 帐户是否过期

    boolean isAccountNonExpired();

    // 帐户是否被冻结

    boolean isAccountNonLocked();

    // 帐户密码是否过期,一般有的密码要求性高的系统会使用到,比较每隔一段时间就要求用户重置密码

    boolean isCredentialsNonExpired();

    // 帐号是否可用

    boolean isEnabled();

}

我们在返回UserDetails的实现类User的时候,可以通过User的构造方法,设置对应的参数。

运行一下程序进行测试,会发现登录界面有所改变
Spring Security部分详解
这是因为我们在配置文件中配置了http.formLogin()

我们这里随便填写一个User,然后Password写填写一个错误的(非123456)的。这时会提示校验错误:
Spring Security部分详解
同时在控制台,也会打印出刚才登录时填写的user

现在我们再来使用正确的密码进行登录试试,可以发现就会通过校验,跳转到正确的接口调用页面了。

密码加密解密

SpringSecurity中有一个PasswordEncoder接口

?

1

2

3

4

5

6

public interface PasswordEncoder {

    // 对密码进行加密

    String encode(CharSequence var1);

    // 对密码进行判断匹配

    boolean matches(CharSequence var1, String var2);

}

我们只需要自己实现这个接口,并在配置文件中配置一下就可以了。
这里我暂时以默认提供的一个实现类进行测试

?

1

2

3

4

5

// 放到WebSecurityConfig 自定义的Config类中  继承WebSecurityConfigurerAdapter

@Bean

public PasswordEncoder passwordEncoder() {

     return new BCryptPasswordEncoder();

 }

加密使用:

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

@Component

public class MyUserDetailsService implements UserDetailsService {

 

 

    private Logger logger = LoggerFactory.getLogger(getClass());

 

    @Autowired

    private PasswordEncoder passwordEncoder;

 

    @Override

    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {

        logger.info("用户的用户名: {}", username);

 

        String password = passwordEncoder.encode("123456");

        logger.info("password: {}", password);

 

        // 参数分别是:用户名,密码,用户权限

        User user = new User(username, password, AuthorityUtils.commaSeparatedStringToAuthorityList("admin"));

        return user;

    }

}

这里简单的对123456进行了加密的处理。我们可以进行测试,发现每次打印出来的password都是不一样的,这就是配置的BCryptPasswordEncoder所起到的作用。

自定义登录页面

完成了登录页面之后,就需要将它配置进行SpringSecurity

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

//  WebSecurityConfig.java

@Override

protected void configure(HttpSecurity http) throws Exception {

    http.formLogin()                    //  定义当需要用户登录时候,转到的登录页面。

            .loginPage("/login.html")           // 设置登录页面

            .loginProcessingUrl("/user/login"// 自定义的登录接口

            .and()

            .authorizeRequests()        // 定义哪些URL需要被保护、哪些不需要被保护

            .antMatchers("/login.html").permitAll()     // 设置所有人都可以访问登录页面

            .anyRequest()               // 任何请求,登录后可以访问

            .authenticated()

            .and()

            .csrf().disable();          // 关闭csrf防护

}

处理不同类型的请求

因为现在一般都前后端分离了,后端提供接口供前端调用,返回JSON格式的数据给前端。刚才那样,调用了被保护的接口,直接进行了页面的跳转,在web端还可以接受,但是在App端就不行了, 所以我们还需要做进一步的处理。
这里做一下简单的思路整理

Spring Security部分详解

首先来写自定义的Controller,当需要身份认证的时候就跳转过来

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

@RestController

public class BrowserSecurityController {

 

    private Logger logger = LoggerFactory.getLogger(getClass());

 

    // 原请求信息的缓存及恢复

    private RequestCache requestCache = new HttpSessionRequestCache();

 

    // 用于重定向

    private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();

 

    /**

     * 当需要身份认证的时候,跳转过来

     * @param request

     * @param response

     * @return

     */

    @RequestMapping("/authentication/require")

    @ResponseStatus(code = HttpStatus.UNAUTHORIZED)

    public BaseResponse requireAuthenication(HttpServletRequest request, HttpServletResponse response) throws IOException {

        SavedRequest savedRequest = requestCache.getRequest(request, response);

 

        if (savedRequest != null) {

            String targetUrl = savedRequest.getRedirectUrl();

            logger.info("引发跳转的请求是:" + targetUrl);

            if (StringUtils.endsWithIgnoreCase(targetUrl, ".html")) {

                redirectStrategy.sendRedirect(request, response, "/login.html");

            }

        }

 

        return new BaseResponse("访问的服务需要身份认证,请引导用户到登录页");

    }

}

当然还需要将配置文件进行相应的修改, 这里我就不贴代码了。 就是将该接口开放出来 。

扩展:
这里我们是写死了如果是从网页访问的接口,那么就跳转到”/login.html”页面,其实我们可以扩展一下,将该跳转地址配置到配置文件中,这样会更方便的。

自定义处理登录成功/失败

在之前的测试中,登录成功了都是进行了页面的跳转。
在前后端分离的情况下,我们登录成功了可能需要向前端返回用户的个人信息,而不是直接进行跳转。登录失败也是同样的道理。

这里涉及到了Spring Security中的两个接口AuthenticationSuccessHandler和AuthenticationFailureHandler。我们可以实现这个接口,并进行相应的配置就可以了。 当然框架是有默认的实现类的,我们可以继承这个实现类再来自定义自己的业务

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

@Component("myAuthenctiationSuccessHandler")

public class MyAuthenctiationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {

 

    private Logger logger = LoggerFactory.getLogger(getClass());

 

    @Autowired

    private ObjectMapper objectMapper;

 

    @Override

    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,

                                        Authentication authentication) throws IOException, ServletException {

 

        logger.info("登录成功");

 

        response.setContentType("application/json;charset=UTF-8");

        response.getWriter().write(objectMapper.writeValueAsString(authentication));

    }

}

这里我们通过response返回一个JSON字符串回去。
这个方法中的第三个参数Authentication,它里面包含了登录后的用户信息(UserDetails),Session的信息,登录信息等。

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

@Component("myAuthenctiationFailureHandler")

public class MyAuthenctiationFailureHandler extends SimpleUrlAuthenticationFailureHandler {

 

    private Logger logger = LoggerFactory.getLogger(getClass());

 

    @Autowired

    private ObjectMapper objectMapper;

 

    @Override

    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,

                                        AuthenticationException exception) throws IOException, ServletException {

 

        logger.info("登录失败");

 

        response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());

        response.setContentType("application/json;charset=UTF-8");

        response.getWriter().write(objectMapper.writeValueAsString(new BaseResponse(exception.getMessage())));

    }

}

这个方法中的第三个参数AuthenticationException,包括了登录失败的信息。

同样的,还是需要在配置文件中进行配置,这里就不贴出全部的代码了,只贴出相应的语句

?

1

2

3

//在WebSecurityConfig自定义配置文件中加上如下配置接口信息

.successHandler(myAuthenticationSuccessHandler) // 自定义登录成功处理

.failureHandler(myAuthenticationFailureHandler) // 自定义登录失败处理

记住我功能的基本原理

Spring Security部分详解

 

“记住我”功能SpringSecurity源码分析

根据最开始的原理分析,我们可以知道一般是在认证成功之后,才会去做记住我的操作,所以我们可以看successfulAuthentication方法中的相关代码

?

1

2

3

4

5

6

7

8

9

10

11

// AbstractAuthenticationProcessingFilter.java

protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {

    SecurityContextHolder.getContext().setAuthentication(authResult);

    // 调用rememberMeServices,进行登录操作

    this.rememberMeServices.loginSuccess(request, response, authResult);

    if (this.eventPublisher != null) {

        this.eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(authResult, this.getClass()));

    }

 

    this.successHandler.onAuthenticationSuccess(request, response, authResult);

}

这里在认证成功之后,调用了RememberMeServices的loginSuccess方法,该方法会进一步调用onLoginSuccess:

?

1

2

3

4

5

6

7

8

9

10

11

12

13

protected void onLoginSuccess(HttpServletRequest request, HttpServletResponse response, Authentication successfulAuthentication) {

    String username= successfulAuthentication.getName();

    PersistentRememberMeToken persistentToken = new PersistentRememberMeToken(username, this.generateSeriesData(), this.generateTokenData(), new Date());

 

    try {

        // 进行TokenRepository操作

        this.tokenRepository.createNewToken(persistentToken);

        // 进行Cookie操作

        this.addCookie(persistentToken, request, response);

    } catch (Exception var7) {

        this.logger.error("Failed to save persistent token ", var7);

    }

}

这样相关的数据库操作和Token操作就完成了。

接下来看看下次登录的时候,是怎么进行记住我认证的。这里我们就直接看RememberMeAuthenticationFilter

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {

    HttpServletRequest request = (HttpServletRequest)req;

    HttpServletResponse response = (HttpServletResponse)res;

    // 1.先判断之前是否已经通过认证了

    if (SecurityContextHolder.getContext().getAuthentication() == null) {

        // 2.通过RememberMeServices进行登录

        Authentication rememberMeAuth = this.rememberMeServices.autoLogin(request, response);

        if (rememberMeAuth != null) {

            try {

                rememberMeAuth = this.authenticationManager.authenticate(rememberMeAuth);

                SecurityContextHolder.getContext().setAuthentication(rememberMeAuth);

                this.onSuccessfulAuthentication(request, response, rememberMeAuth);

                if (this.logger.isDebugEnabled()) {

                    this.logger.debug("SecurityContextHolder populated with remember-me token: '" + SecurityContextHolder.getContext().getAuthentication() + "'");

                }

 

                if (this.eventPublisher != null) {

                    this.eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(SecurityContextHolder.getContext().getAuthentication(), this.getClass()));

                }

 

                if (this.successHandler != null) {

                    this.successHandler.onAuthenticationSuccess(request, response, rememberMeAuth);

                    return;

                }

            } catch (AuthenticationException var8) {

                if (this.logger.isDebugEnabled()) {

                    this.logger.debug("SecurityContextHolder not populated with remember-me token, as AuthenticationManager rejected Authentication returned by RememberMeServices: '" + rememberMeAuth + "'; invalidating remember-me token", var8);

                }

 

                this.rememberMeServices.loginFail(request, response);

                this.onUnsuccessfulAuthentication(request, response, var8);

            }

        }

 

        chain.doFilter(request, response);

    } else {

        if (this.logger.isDebugEnabled()) {

            this.logger.debug("SecurityContextHolder not populated with remember-me token, as it already contained: '" + SecurityContextHolder.getContext().getAuthentication() + "'");

        }

 

        chain.doFilter(request, response);

    }

 

}

------------------------------------------------------------再次继续研究--------------------------------------------------------------------------

1完整POM文件

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

<!--xml version="1.0" encoding="UTF-8"-->

<project xmlns="https://maven.apache.org/POM/4.0.0" xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance" xsi:schemalocation="https://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">

    <modelversion>4.0.0</modelversion>

 

    <groupid>com.lc</groupid>

    springboot-security</artifactid>

    <version>0.0.1-SNAPSHOT</version>

    <packaging>jar</packaging>

 

    <name>springboot-security</name>

    <description>Demo project for Spring Boot</description>

 

    <parent>

        <groupid>org.springframework.boot</groupid>

        spring-boot-starter-parent</artifactid>

        <version>2.0.4.RELEASE</version>

        <relativepath> <!-- lookup parent from repository -->

</relativepath></parent>

 

    <properties>

        <project.build.sourceencoding>UTF-8</project.build.sourceencoding>

        <project.reporting.outputencoding>UTF-8</project.reporting.outputencoding>

        <java.version>1.8</java.version>

    </properties>

 

    <dependencies>

        <!--springboot-->

        <dependency>

            <groupid>org.springframework.boot</groupid>

            spring-boot-starter-web</artifactid>

        </dependency>

        <!--security-->

        <dependency>

            <groupid>org.springframework.boot</groupid>

            spring-boot-starter-security</artifactid>

        </dependency>

        <!---thymeleaf-->

        <dependency>

            <groupid>org.springframework.boot</groupid>

            spring-boot-starter-thymeleaf</artifactid>

        </dependency>

        <dependency>

            <groupid>org.thymeleaf.extras</groupid>

            thymeleaf-extras-springsecurity4</artifactid>

        </dependency>

 

        <!-- 热部署 -->

        <dependency>

            <groupid>org.springframework.boot</groupid>

            spring-boot-devtools</artifactid>

            <optional>true</optional>

        </dependency>

        <!-- mybatis -->

        <dependency>

            <groupid>org.mybatis.spring.boot</groupid>

            mybatis-spring-boot-starter</artifactid>

            <version>1.3.2</version>

        </dependency>

        <!-- jpa -->

        <dependency>

            <groupid>org.springframework.boot</groupid>

            spring-boot-starter-data-jpa</artifactid>

        </dependency>

        <!--druid-->

        <dependency>

            <groupid>com.alibaba</groupid>

            druid</artifactid>

            <version>1.0.19</version>

        </dependency>

        <dependency>

            <groupid>mysql</groupid>

            mysql-connector-java</artifactid>

            <scope>runtime</scope>

        </dependency>

        <!--jwt-->

        <dependency>

            <groupid>io.jsonwebtoken</groupid>

            jjwt</artifactid>

            <version>0.4</version>

        </dependency>

 

 

 

         <!--test-->

        <dependency>

            <groupid>org.springframework.boot</groupid>

            spring-boot-starter-test</artifactid>

            <scope>test</scope>

        </dependency>

        <dependency>

            <groupid>org.springframework.security</groupid>

            spring-security-test</artifactid>

            <scope>test</scope>

        </dependency>

        <dependency>

            <groupid>org.mybatis</groupid>

            mybatis-spring</artifactid>

            <version>1.3.0</version>

        </dependency>

    </dependencies>

    <build>

        <plugins>

            <plugin>

                <groupid>org.springframework.boot</groupid>

                spring-boot-maven-plugin</artifactid>

            </plugin>

        </plugins>

    </build>

</project>

2完整logback.xml文件

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

159

160

161

162

163

164

165

166

167

168

169

170

171

172

173

174

175

176

177

178

179

180

181

182

183

184

185

186

187

188

189

190

191

192

193

194

<!--xml version="1.0" encoding="UTF-8"-->

        <!-- 日志级别从低到高分为TRACE < DEBUG < INFO < WARN < ERROR < FATAL,

        如果设置为WARN,则低于WARN的信息都不会输出 -->

        <!-- scan:当此属性设置为true时,配置文档如果发生改变,将会被重新加载,默认值为true -->

        <!-- scanPeriod:设置监测配置文档是否有修改的时间间隔,如果没有给出时间单位,默认单位是毫秒。

                         当scan为true时,此属性生效。默认的时间间隔为1分钟。 -->

        <!-- debug:当此属性设置为true时,将打印出logback内部日志信息,实时查看logback运行状态。默认值为false。 -->

<configuration scan="true" scanperiod="10 seconds">

    <contextname>logback</contextname>

 

    <!-- name的值是变量的名称,value的值时变量定义的值。通过定义的值会被插入到logger上下文中。定义后,可以使“${}”来使用变量。 -->

    <property name="LOG_HOME" value="./logs">

 

    <!--0. 日志格式和颜色渲染 -->

    <!-- 彩色日志依赖的渲染类 -->

    <conversionrule conversionword="clr" converterclass="org.springframework.boot.logging.logback.ColorConverter">

    <conversionrule conversionword="wex" converterclass="org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter">

    <conversionrule conversionword="wEx" converterclass="org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter">

    <!-- 彩色日志格式 -->

    <property name="CONSOLE_LOG_PATTERN" value="${CONSOLE_LOG_PATTERN:-%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}">

 

    <!--1. 输出到控制台-->

     

        <!--此日志appender是为开发使用,只配置最底级别,控制台输出的日志级别是大于或等于此级别的日志信息-->

        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">

            <level>debug</level>

        </filter>

        <encoder>

            <pattern>${CONSOLE_LOG_PATTERN}</pattern>

            <!-- 设置字符集 -->

            <charset>UTF-8</charset>

        </encoder>

    </appender>

 

    <!--2. 输出到文档-->

    <!-- 2.1 level为 DEBUG 日志,时间滚动输出  -->

     

        <!-- 正在记录的日志文档的路径及文档名 -->

        <file>${LOG_HOME}/web_debug.log</file>

        <!--日志文档输出格式-->

        <encoder>

            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>

            <charset>UTF-8</charset> <!-- 设置字符集 -->

        </encoder>

        <!-- 日志记录器的滚动策略,按日期,按大小记录 -->

        <rollingpolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">

            <!-- 日志归档 -->

            <filenamepattern>${LOG_HOME}/debug.log.%d{yyyy-MM-dd}.%i.log</filenamepattern>

            <timebasedfilenamingandtriggeringpolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">

                <maxfilesize>100MB</maxfilesize>

            </timebasedfilenamingandtriggeringpolicy>

            <!--日志文档保留天数-->

            <maxhistory>15</maxhistory>

        </rollingpolicy>

        <!-- 此日志文档只记录debug级别的 -->

        <filter class="ch.qos.logback.classic.filter.LevelFilter">

            <level>debug</level>

            <onmatch>ACCEPT</onmatch>

            <onmismatch>DENY</onmismatch>

        </filter>

    </appender>

 

    <!-- 2.2 level为 INFO 日志,时间滚动输出  -->

     

        <!-- 正在记录的日志文档的路径及文档名 -->

        <file>${LOG_HOME}/web_info.log</file>

        <!--日志文档输出格式-->

        <encoder>

            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>

            <charset>UTF-8</charset>

        </encoder>

        <!-- 日志记录器的滚动策略,按日期,按大小记录 -->

        <rollingpolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">

            <!-- 每天日志归档路径以及格式 -->

            <filenamepattern>${LOG_HOME}/info.log.%d{yyyy-MM-dd}.%i.log</filenamepattern>

            <timebasedfilenamingandtriggeringpolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">

                <maxfilesize>100MB</maxfilesize>

            </timebasedfilenamingandtriggeringpolicy>

            <!--日志文档保留天数-->

            <maxhistory>15</maxhistory>

        </rollingpolicy>

        <!-- 此日志文档只记录info级别的 -->

        <filter class="ch.qos.logback.classic.filter.LevelFilter">

            <level>info</level>

            <onmatch>ACCEPT</onmatch>

            <onmismatch>DENY</onmismatch>

        </filter>

    </appender>

 

    <!-- 2.3 level为 WARN 日志,时间滚动输出  -->

     

        <!-- 正在记录的日志文档的路径及文档名 -->

        <file>${LOG_HOME}/web_warn.log</file>

        <!--日志文档输出格式-->

        <encoder>

            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>

            <charset>UTF-8</charset> <!-- 此处设置字符集 -->

        </encoder>

        <!-- 日志记录器的滚动策略,按日期,按大小记录 -->

        <rollingpolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">

            <filenamepattern>${LOG_HOME}/warn.log.%d{yyyy-MM-dd}.%i.log</filenamepattern>

            <timebasedfilenamingandtriggeringpolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">

                <maxfilesize>100MB</maxfilesize>

            </timebasedfilenamingandtriggeringpolicy>

            <!--日志文档保留天数-->

            <maxhistory>15</maxhistory>

        </rollingpolicy>

        <!-- 此日志文档只记录warn级别的 -->

        <filter class="ch.qos.logback.classic.filter.LevelFilter">

            <level>warn</level>

            <onmatch>ACCEPT</onmatch>

            <onmismatch>DENY</onmismatch>

        </filter>

    </appender>

 

    <!-- 2.4 level为 ERROR 日志,时间滚动输出  -->

     

        <!-- 正在记录的日志文档的路径及文档名 -->

        <file>${LOG_HOME}/web_error.log</file>

        <!--日志文档输出格式-->

        <encoder>

            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>

            <charset>UTF-8</charset> <!-- 此处设置字符集 -->

        </encoder>

        <!-- 日志记录器的滚动策略,按日期,按大小记录 -->

        <rollingpolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">

            <filenamepattern>${LOG_HOME}/error.log.%d{yyyy-MM-dd}.%i.log</filenamepattern>

            <timebasedfilenamingandtriggeringpolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">

                <maxfilesize>100MB</maxfilesize>

            </timebasedfilenamingandtriggeringpolicy>

            <!--日志文档保留天数-->

            <maxhistory>15</maxhistory>

        </rollingpolicy>

        <!-- 此日志文档只记录ERROR级别的 -->

        <filter class="ch.qos.logback.classic.filter.LevelFilter">

            <level>ERROR</level>

            <onmatch>ACCEPT</onmatch>

            <onmismatch>DENY</onmismatch>

        </filter>

    </appender>

 

    <!--

        <logger>用来设置某一个包或者具体的某一个类的日志打印级别、

        以及指定。<logger>仅有一个name属性,

        一个可选的level和一个可选的addtivity属性。

        name:用来指定受此logger约束的某一个包或者具体的某一个类。

        level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF,

              还有一个特俗值INHERITED或者同义词NULL,代表强制执行上级的级别。

              如果未设置此属性,那么当前logger将会继承上级的级别。

        addtivity:是否向上级logger传递打印信息。默认是true

        <logger name="org.springframework.web" level="info"/>

        <logger name="org.springframework.scheduling.annotation.ScheduledAnnotationBeanPostProcessor" level="INFO"/>

    -->

 

    <!--

        使用mybatis的时候,sql语句是debug下才会打印,而这里我们只配置了info,所以想要查看sql语句的话,有以下两种操作:

        第一种把<root level="info">改成<root level="DEBUG">这样就会打印sql,不过这样日志那边会出现很多其他消息

        第二种就是单独给dao下目录配置debug模式,代码如下,这样配置sql语句会打印,其他还是正常info级别:

        【logging.level.org.mybatis=debug logging.level.dao=debug】

     -->

 

    <!--

        root节点是必选节点,用来指定最基础的日志输出级别,只有一个level属性

        level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF,

        不能设置为INHERITED或者同义词NULL。默认是DEBUG

        可以包含零个或多个元素,标识这个appender将会添加到这个logger。

    -->

 

    <!-- 4. 最终的策略 -->

    <!-- 4.1 开发环境:打印控制台-->

    <!--<springProfile name="dev">

        <logger name="com.sdcm.pmp" level="debug"/>

    </springProfile>-->

 

    <root level="info">

         

         

         

         

         

    </appender-ref></appender-ref></appender-ref></appender-ref></appender-ref></root>

 

    <!-- 4.2 生产环境:输出到文档

    <springProfile name="pro">

        <root level="info">

             

             

             

             

             

        </root>

    </springProfile> -->

 

</property></conversionrule></conversionrule></conversionrule></property></configuration>

3,完整application.yml文件

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

spring:

 datasource:

 ###################以下为druid增加的配置###########################

      type: com.alibaba.druid.pool.DruidDataSource

      url: jdbc:mysql://localhost:3306/springbootcharacterEncoding=utf-8&useSSL=false

      username: test

      password: 1

      driverClassName: com.mysql.jdbc.Driver

      # 下面为连接池的补充设置,应用到上面所有数据源中

      # 初始化大小,最小,最大

      initialSize: 5

      minIdle: 5

      maxActive: 20

      # 配置获取连接等待超时的时间

      maxWait: 60000

      # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒

      timeBetweenEvictionRunsMillis: 60000

      # 配置一个连接在池中最小生存的时间,单位是毫秒

      minEvictableIdleTimeMillis: 300000

      validationQuery: SELECT 1 FROM DUAL

      testWhileIdle: true

      testOnBorrow: false

      testOnReturn: false

      # 打开PSCache,并且指定每个连接上PSCache的大小

      poolPreparedStatements: true

      maxPoolPreparedStatementPerConnectionSize: 20

      # 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙

      filters: stat,wall,log4j

      # 通过connectProperties属性来打开mergeSql功能;慢SQL记录

      connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000

      # 合并多个DruidDataSource的监控数据

      useGlobalDataSourceStat: true

      ###############以上为配置druid添加的配置########################################

# thymeleaf

 thymeleaf:

       cache: false

       prefix: classpath:/templates/

       suffix: .html

       encoding: UTF-8

       content-type: text/html

       mode: HTML5

jpa:

       properties:

       hibernate:

       database: MySQL

       show_sql: true

       format_sql: true

       database-platform: org.hibernate.dialect.MySQL5Dialect

 

mybatis:

    #mybatis的mapper文件所在路径

  mapper-locations: classpath:mapper/*.xml

   #实体类路径

  type-aliases-package: com.lc.entity

 

logging:

  level:

    org.springframework.security: INFO