SpringSecurity – 安全认证

AuthenticationManager)

在spring-security官网中认证是由AuthenticationManager接口来进行负责的,定义为

1
2
3
public interface AuthenticationManager {
Authentication authenticate(Authentication authentication) throws AuthenticationException;
}

官方文档解释 :

尝试对传递 Authentication 的对象进行身份验证,如果成功,则返回完全填充 Authentication 的对象(包括授予的权限)。
必须 AuthenticationManager 履行以下有关例外情况的合同:
如果帐户被禁用AuthenticationManager,则必须抛出 ,DisabledException并且可以测试此状态。
如果帐户被锁定AuthenticationManager,则必须抛出 aLockedException,并且可以测试帐户锁定。
如果提供不正确的凭据,则必须抛出 。 BadCredentialsException 虽然上述例外是可选AuthenticationManager 但必须 始终 测试凭据。
应测试异常,如果适用,应按上述顺序抛出(即,如果帐户被禁用或锁定,则身份验证请求将立即被拒绝,并且不执行凭据测试过程)。这可以防止针对已禁用或锁定的帐户测试凭据。
形参:
身份验证 – 身份验证请求对象
返回值:
经过完全身份验证的对象,包括凭据
抛出:
AuthenticationException – 如果身份验证失败

从官方文档我们就可以了解出: 如果

  • 返回 Authentication 表示认证成功
  • 返回 抛出AuthenticationException 异常,表示认证失败。

AuthenticationManager 的主要实现类为 ProviderManager

​ 在 ProviderManager 中管理了众多 AuthenticationProvider 实例。在一次完整的认证流程中,Spring Security 允许存在多个 AuthenticationProvider ,用来实现多种认证方式,这些 AuthenticationProvider 都是由 ProviderManager 进行统一管理的

Authentication —认证信息标记者

image-20230211164537431

认证以及认证成功的信息主要是由 Authentication 的实现类进行保存的,其接口定义为:

1
2
3
4
5
6
7
8
public interface Authentication extends Principal, Serializable {
Collection<? extends GrantedAuthority> getAuthorities();
Object getCredentials();
Object getDetails();
Object getPrincipal();
boolean isAuthenticated();
void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException;
}
  • getAuthorities 获取用户权限信息
  • getCredentials 获取用户凭证信息,一般指密码
  • getDetails 获取用户详细信息
  • getPrincipal 获取用户身份信息,用户名、用户对象等
  • isAuthenticated 用户是否认证成功

它通过实现类封装了我们需要的用户的信息,我们则是通过

1
2
3
4
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
User user = (User) authentication.getPrincipal();
System.out.println("获取username:" + user.getUsername());
System.out.println("获取password:" + user.getPassword());

来实现对用户信息的获取。

SecurityContextHolder

SecurityContextHolder 用来获取登录之后用户信息。Spring Security 会将登录用户数据保存在 Session 中。

  1. Spring Security在此基础上还做了一些改进,其中最主要的一个变化就是线程绑定。

  2. 当用户登录成功后,Spring Security 会将登录成功的用户信息保存到 SecurityContextHolder 中。SecurityContextHolder 中的数据保存默认是通过ThreadLocal 来实现的,使用 ThreadLocal 创建的变量只能被当前线程访问,不能被其他线程访问和修改,也就是用户数据和请求线程绑定在一起。

  3. 请求处理完毕后,Spring Security 会将 SecurityContextHolder 中的数据拿出来保存到 Session 中,同时将 SecurityContexHolder 中的数据清空。

  4. 释放线程

好处: 方便用户在 Controller、Service 层以及任何代码中获取当前登录用户数据

以上就是在安全认证时,最重要的几个接口

认证实现

依赖

web和security依赖

1
2
3
4
5
6
7
8
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

数据库和mybatis依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!--数据库的依赖-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.0</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.38</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.7</version>
</dependency>

thymeleaf 和 security 联合依赖

1
2
3
4
5
6
7
8
9
10
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

<!-- springSecurity 和前端的交互-->
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity5</artifactId>
</dependency>

数据库和Mybatis配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 设置thymeleaf的缓存
spring.thymeleaf.cache=false
# datasource
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/ssm?characterEncoding=UTF-8&useSSL=false
spring.datasource.username=root
spring.datasource.password=root
# mybatis
mybatis.mapper-locations=classpath:security/mapper/*.xml
mybatis.type-aliases-package=security.pojo

# log为了展现mybatis运行 sql 语句
logging.level.com.security=debug

自定义配置MVC层

1
2
3
4
5
6
7
8
@Configuration
public class MvcConfig implements WebMvcConfigurer {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/loginPages").setViewName("loginPage");
registry.addViewController("/index").setViewName("index");
}
}

主要配置了常用的公共视图跳转资源的接口,减少了controller层的代码量

自定义配置Security

1
2
3
4
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
//配置资源...
}

void configure(WebSecurity web)

覆盖此方法以配置 WebSecurity。

​ 例如,如果您希望忽略某些请求。Spring Security将忽略此方法中指定的端点,这意味着它不会保护它们免受CSRF,XSS,点击劫持等的侵害。相反,如果要保护终结点免受常见漏洞的影响,请参阅 configure(HttpSecurity) 和 HttpSecurity.authorizeRequests 配置方法。

void configure(HttpSecurity http) —认证主要配置

重写此方法以配置 HttpSecurity.通常,子类不应通过调用 super 来调用此方法,因为它可能会覆盖其配置。默认配置为:
http.authorizeRequests().anyRequest().authenticated().and().formLogin().and().httpBasic();

可以在此处指定任何需要防御常见漏洞的终结点,包括公共终结点。有关公共终结点的更多详细信息,请参阅 HttpSecurity.authorizeRequests 和“permitAll()”授权规则。
形参:
HTTP – HttpSecurity 要修改的
抛出:
Exception – 如果发生错误

  • permitAll() 代表放行该资源,该资源为公共资源 无需认证和授权可以直接访问
  • anyRequest().authenticated() 代表所有请求,必须认证之后才能访问
  • formLogin() 代表开启表单认证

注意: 放行资源必须放在所有认证请求之前!

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
//认证
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.mvcMatchers("/index").permitAll()
.mvcMatchers("/loginPages").permitAll()
.anyRequest().authenticated()
.and()
.formLogin().loginPage("/loginPages")
.loginProcessingUrl("/doLogin")
.usernameParameter("username")
.passwordParameter("password")
.defaultSuccessUrl("/index")
//.successForwardUrl("/index")
//.failureUrl("/loginPages")
// .failureForwardUrl("/loginPages")
//.successHandler(new MyAuthenticationSuccessHandler()) //前后端分离的处理方式
.failureHandler(new FailureHandler())
.and()
.logout()
.invalidateHttpSession(true) //默认删除session会话
.clearAuthentication(true) // 默认清楚认证标记
.logoutSuccessUrl("/loginPages")
.logoutSuccessHandler(new MyLogout()) //前后端分离注销成功的处理
.and()
.csrf().disable();
}

各个方法的详细说明

1
2
3
4
5
6
7
8

.and()
.logout()
.invalidateHttpSession(true) //默认删除session会话
.clearAuthentication(true) // 默认清楚认证标记
.logoutSuccessUrl("/loginPages")
.logoutSuccessHandler(new MyLogout()) //前后端分离注销成功的处理
.and()
  • 通过 logout() 方法开启注销配置
  • logoutUrl 指定退出登录请求地址,默认是 GET 请求,路径为 /logout
  • invalidateHttpSession 退出时是否是 session 失效,默认值为 true
  • clearAuthentication 退出时是否清除认证信息,默认值为 true
  • logoutSuccessUrl 退出登录时跳转地
1
2
3
.loginProcessingUrl("/doLogin")
.usernameParameter("username")
.passwordParameter("password")

一般来讲,我们用户完成输入,表单进行提交时都需要与security中的方法进行匹配

默认的loginProcessingUrl为login 、 usernameParameter 为 username 、passwordParameter 为 password

因此我们可以通过修改其中的值,来匹配我们自己的接口,及其属性value

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
*  .anyRequest().authenticated()
* 下面的所有请求都是需要认证之后的
*
* .formLogin()
* 开启表单认证(value为登录页面)
*
* .formLogin().loginPage("/loginPages")
* 用于覆盖默认的登录页面 ,“/loginPages”为一个请求接口
*
* .loginProcessingUrl("/login")
* 用来处理登录请求的url

* .defaultSuccessUrl("/index")
* 默认成功地址, 是一个重定向,
* 比如之前打开/hello请求,但是它跳转到了login,登录完成后
* 如果使用的是defaultSuccessUrl ,那么他依然会跳转至/hello请求

* .successForwardUrl("/index")
* 成功跳转路径, 始终跳转到指定的请求, 比如之前打开/hello请求,但是它跳转到了login,登录完成后
* 如果使用的是上面这个successForwardUrl,那么他就会优先跳转至指定的index请求,而不是/hello
*
* .failureForwardUrl("/toLogin")
* 登录失败跳转路径 ,返回的错误信息是在request作用域中
* 展示错误信息 :th:text="${SPRING_SECURITY_LAST_EXCEPTION}">
*
* .failureUrl()
* 登录失败跳转路径 ,返回的错误信息是在session作用域中
* 展示错误信息 th:text="${session.SPRING_SECURITY_LAST_EXCEPTION}">

* .and().csrf().disable();
* 关闭跨站请求保护,为了测试

​ 一般来讲 , .anyRequest().authenticated() 后面配置的资源/请求 , 他们都是需要实现认证才能被访问的

所以,我们通常将公共资源放置他之前, 然后用permitAll() 来过滤

1
2
.mvcMatchers("/index").permitAll()
.mvcMatchers("/loginPages").permitAll()

void setApplicationContext(ApplicationContext context)

作用: 设置应用上下文

public AuthenticationManager authenticationManagerBean()

如果我们想要自己设置userDetailsService认证 , 我们就可以通过重写protected void configure(AuthenticationManagerBuilder builder)方法

1
2
3
4
@Override
protected void configure(AuthenticationManagerBuilder builder) throws Exception {
builder.userDetailsService(userDetailsService);
}

来设置我们自己定义的service接口,然后注入到容器中,等待被调用

重写此方法以将 AuthenticationManager 要公开的 from configure(AuthenticationManagerBuilder) 作为 Bean 公开。例如:
@Bean(name name=”myAuthenticationManager”)
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}

返回值:
这 AuthenticationManager
抛出:
Exception

1
2
3
4
5
6
//将自定义的认证暴露在工厂中 (加入到容器中去管理)
@Override
@Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}

UserDetailsService userDetailsServiceBean()

重写自己的userDetailsService()然后通过上面的authenticationManagerBean,将自己重写的serivce注入到容器中,作为公开的bean

重写此方法以将 UserDetailsService 创建自 configure(AuthenticationManagerBuilder) 公开为 Bean。通常,此方法只应执行以下覆盖:
@Bean(name = “myUserDetailsService”)
// any or no name specified is allowed
@Override
public UserDetailsService userDetailsServiceBean() throws Exception {
return super.userDetailsServiceBean();
}

要更改返回的实例,开发人员应改为更改userDetailsService()
返回值:
这 UserDetailsService
抛出:
Exception –
请参阅:
userDetailsService()

补充前后端分离实现

配置登录成功

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
package security.config;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler {

@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
Map<String, Object> result = new HashMap<>();
result.put("msg", "登录成功!");
result.put("status", 200);
response.setContentType("application/json;charset=UTF-8");
String s = new ObjectMapper().writeValueAsString(result);
response.getWriter().println(s);
}
}

登录失败

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package security.config;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

public class MyLogout implements LogoutSuccessHandler {
@Override
public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
Map<String, Object> result = new HashMap<>();
result.put("msg", "注销成功!");
result.put("status", 500);
response.setContentType("application/json;charset=UTF-8");
String s = new ObjectMapper().writeValueAsString(result);
response.getWriter().println(s);
}

前端Thymeleaf整合security实现

外部资源: xmlns:sec=”http://www.thymeleaf.org/extras/spring-security

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<!DOCTYPE html>
<html lang="en" xmlns:sec="http://www.thymeleaf.org/extras/spring-security" xmlns:th="http://www.thymeleaf.otg">
<head>
<meta charset="UTF-8">
<title>用户界面</title>
</head>
<body>

<h2>获取用户详细信息</h2>
<ul>
<li sec:authentication="principal.username"></li>
<li sec:authentication="principal.authorities"></li>
<li sec:authentication="principal.accountNonExpired"></li>
<li sec:authentication="principal.accountNonLocked"></li>
<li sec:authentication="principal.credentialsNonExpired"></li>
</ul>

</body>
</html>

重写UserDetailsService

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 final UserDao userDao;

@Autowired
public MyUserDetailsService(UserDao userDao) {
this.userDao = userDao;
}

@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {

User user = userDao.loadUserByUsername(username);
if (ObjectUtils.isEmpty(user)) throw new UsernameNotFoundException("用户名不正确!");
//存在的话 , 赋予权限信息
List<Role> roles = userDao.getRolesByUid(user.getId());
user.setRoles(roles);
return user;
}
}

实现Dao层接口及其mapper

Dao

1
2
3
4
5
6
7
8
9
@Mapper
public interface UserDao {
//根据用户名查询user
User loadUserByUsername(String username);

//根据用户id查询角色
List<Role> getRolesByUid(Integer uid);

}

mapper

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
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="security.dao.UserDao">
<!--查询单个loadUserByUsername-->
<select id="loadUserByUsername" resultType="User">
select id,
username,
password,
enabled,
accountNonExpired,
accountNonLocked,
credentialsNonExpired
from user
where username = #{username}
</select>

<!--查询指定行数据 ,表连接-->
<select id="getRolesByUid" resultType="Role">
select r.id,
r.name,
r.name_zh nameZh
from role r,
user_role ur
where r.id = ur.rid
and ur.uid = #{uid}
</select>
</mapper>

多表联查新思路

image-20230211174448919

用户登录数据获取 (SecurityContextHolder)

Spring Security 会将登录用户数据保存在 Session 中。但是,为了使用方便,Spring Security在此基础上还做了一些改进,其中最主要的一个变化就是线程绑定。当用户登录成功后,Spring Security 会将登录成功的用户信息保存到 SecurityContextHolder 中。

​ SecurityContextHolder 中的数据保存默认是通过ThreadLocal 来实现的,使用 ThreadLocal 创建的变量只能被当前线程访问,不能被其他线程访问和修改,也就是用户数据和请求线程绑定在一起。当登录请求处理完毕后,Spring Security 会将 SecurityContextHolder 中的数据拿出来保存到 Session 中,同时将 SecurityContexHolder 中的数据清空。以后每当有请求到来时,Spring Security 就会先从 Session 中取出用户登录数据,保存到SecurityContextHolder 中,方便在该请求的后续处理过程中使用,同时在请求结束时将 SecurityContextHolder 中的数据拿出来保存到 Session 中,然后将SecurityContextHolder 中的数据清空。

实际上 SecurityContextHolder 中存储是 SecurityContext,在 SecurityContext 中存储是 Authentication。

image-20230211174448919

  1. MODE THREADLOCAL:这种存放策略是将 SecurityContext 存放在 ThreadLocal中,大家知道 Threadlocal 的特点是在哪个线程中存储就要在哪个线程中读取,这其实非常适合 web 应用,因为在默认情况下,一个请求无论经过多少 Filter 到达 Servlet,都是由一个线程来处理的。这也是 SecurityContextHolder 的默认存储策略,这种存储策略意味着如果在具体的业务处理代码中,开启了子线程,在子线程中去获取登录用户数据,就会获取不到。
  2. MODE INHERITABLETHREADLOCAL:这种存储模式适用于多线程环境,如果希望在子线程中也能够获取到登录用户数据,那么可以使用这种存储模式。
  3. MODE GLOBAL:这种存储模式实际上是将数据保存在一个静态变量中,在 JavaWeb开发中,这种模式很少使用到。

自定义认证数据源

image-20230211180308076

  • 发起认证请求,请求中携带用户名、密码,该请求会被UsernamePasswordAuthenticationFilter 拦截
  • UsernamePasswordAuthenticationFilterattemptAuthentication方法中将请求中用户名和密码,封装为Authentication对象,并交给AuthenticationManager 进行认证
  • 认证成功,将认证信息存储到 SecurityContextHodler 以及调用记住我等,并回调 AuthenticationSuccessHandler 处理
  • 认证失败,清除 SecurityContextHodler 以及 记住我中信息,回调 AuthenticationFailureHandler 处理

三者关系

从上面分析中得知,AuthenticationManager 是认证的核心类,但实际上在底层真正认证时还离不开 ProviderManager 以及 AuthenticationProvider 。他们三者关系是样的呢?

  • AuthenticationManager 是一个认证管理器,它定义了 Spring Security 过滤器要执行认证操作。
  • ProviderManager AuthenticationManager接口的实现类。Spring Security 认证时默认使用就是 ProviderManager。
  • AuthenticationProvider 就是针对不同的身份类型执行的具体的身份认证。

Spring Seourity 中,允许系统同时支持多种不同的认证方式,例如同时支持用户名/密码认证、ReremberMe 认证、手机号码动态认证等,而不同的认证方式对应了不同的 AuthenticationProvider,所以一个完整的认证流程可能由多个 AuthenticationProvider 来提供。

多个 AuthenticationProvider 将组成一个列表,这个列表将由 ProviderManager 代理。换句话说,在ProviderManager 中存在一个 AuthenticationProvider 列表,在Provider Manager 中遍历列表中的每一个 AuthenticationProvider 去执行身份认证,最终得到认证结果。

ProviderManager 本身也可以再配置一个 AuthenticationManager 作为 parent,这样当当前ProviderManager 认证失败之后,就可以进入到 parent 中再次进行认证。理论上来说,ProviderManager 的 parent 可以是任意类型的 AuthenticationManager,但是通常都是由ProviderManager 来扮演 parent 的角色,也就是 ProviderManager 是 ProviderManager 的 parent。(自己多理解几遍)

ProviderManager本身也可以有多个,多个ProviderManager 共用同一个 parent。有时,一个应用程序有受保护资源的逻辑组(例如,所有符合路径模式的网络资源,如/api/**),每个组可以有自己的专用 AuthenticationManager。通常,每个组都是一个ProviderManager,它们共享一个父级。然后,父级是一种 全局资源,作为所有提供者的后备资源。

根据上面的介绍,我们绘出新的 AuthenticationManager、ProvideManager 和 AuthentictionProvider 关系

image-20230211180915203

数据源的获取。默认情况下 AuthenticationProvider 是由 DaoAuthenticationProvider 类来实现认证的,在DaoAuthenticationProvider 认证时又通过 UserDetailsService 完成数据源的校验。

总结: AuthenticationManager 是认证管理器,在 Spring Security 中有全局AuthenticationManager,也可以有局部AuthenticationManager。

  • 全局的AuthenticationManager用来对全x局认证进行处理
  • 局部的AuthenticationManager用来对某些特殊资源认证处理。

当然无论是全局认证管理器还是局部认证管理器都是由 ProviderManger 进行实现。 每一个ProviderManger中都代理一个AuthenticationProvider的列表,列表中每一个实现代表一种身份认证方式。认证时底层数据源需要调用 UserDetailService 来实现。

配置全局 AuthenticationManager

默认的全局 AuthenticationManager

1
2
3
4
5
6
7
@Configuration
public class WebSecurityConfigurer extends WebSecurityConfigurerAdapter {
@Autowired
public void initialize(AuthenticationManagerBuilder builder) {
//builder..
}
}
  • springboot 对 security 进行自动配置时自动在工厂中创建一个全局AuthenticationManager

总结

  1. 默认自动配置创建全局AuthenticationManager 默认找当前项目中是否存在自定义 UserDetailService 实例 自动将当前项目 UserDetailService 实例设置为数据源
  2. 默认自动配置创建全局AuthenticationManager 在工厂中使用时直接在代码中注入即可

自定义全局 AuthenticationManager

1
2
3
4
5
6
7
@Configuration
public class WebSecurityConfigurer extends WebSecurityConfigurerAdapter {
@Override
public void configure(AuthenticationManagerBuilder builder) {
//builder ....
}
}

总结

  1. 一旦通过 configure 方法自定义 AuthenticationManager实现 就回将工厂中自动配置AuthenticationManager 进行覆盖
  2. 一旦通过 configure 方法自定义 AuthenticationManager实现 需要在实现中指定认证数据源对象 UserDetaiService 实例
  3. 一旦通过 configure 方法自定义 AuthenticationManager实现 这种方式创建AuthenticationManager对象工厂内部本地一个 AuthenticationManager 对象 不允许在其他自定义组件中进行注入
  • 用来在工厂中暴露自定义AuthenticationManager 实例

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    @Configuration
    public class WebSecurityConfigurer extends WebSecurityConfigurerAdapter {

    //1.自定义AuthenticationManager 推荐 并没有在工厂中暴露出来
    @Override
    public void configure(AuthenticationManagerBuilder builder) throws Exception {
    System.out.println("自定义AuthenticationManager: " + builder);
    builder.userDetailsService(userDetailsService());
    }

    //作用: 用来将自定义AuthenticationManager在工厂中进行暴露,可以在任何位置注入
    @Override
    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
    return super.authenticationManagerBean();
    }
    }

文献参考

  • 编程不良人
  • 深入浅出Spring Security (王松)