Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 0dffa1a

Browse files
通过源码分析Spring Security用户认证流程
1 parent a2b22ac commit 0dffa1a

File tree

2 files changed

+200
-4
lines changed

2 files changed

+200
-4
lines changed
Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
Spring Security的登录主要是由一系列的过滤器组成,我们如果需要修改登录的校验逻辑,只需要在过滤器链路上添加修改相关的逻辑即可。这里主要通过Spring Security的源码来了解相关的认证登录的逻辑。
2+
3+
#### 1.Spring Security的认证流程
4+
5+
主要分析:
6+
7+
1. 认证用户的流程
8+
2. 如何进行认证校验
9+
3. 认证成功后怎么获取用户信息
10+
11+
具体的过滤器链路如下所示:
12+
13+
[![cT2G4g.png](https://z3.ax1x.com/2021/04/19/cT2G4g.png)](https://imgtu.com/i/cT2G4g)
14+
15+
Spring Security的认证流程图如下,认证的主要过程有:
16+
17+
1. 用户提交用户名和密码,然后通过UsernamePasswordAuthenticationFilter对其进行封装成为UsernamePasswordAuthenticationToken对象,这个是AbstractAuthenticationToken的子类,而AbstractAuthenticationToken又是Authentication的一个实现,所以可以看到后续获取的都是Authentication类型的对象实例;
18+
2. 将第一步的UsernamePasswordAuthenticationToken对象传递给AuthenticationManager;
19+
3. 通过AbstractUserDetailsAuthenticationProvider的默认实现类DaoAuthenticationProvider的retrieveUser方法,这个方法会调用UserDetailsService的loadUserByUsername方法来进行用户名和密码的判断,使用的默认的逻辑进行处理;
20+
4. 将成功认证后的用户信息放入到SecurityContextHolder中,之后可以通过SecurityContext获取用户的相关信息。
21+
22+
[![coGpvR.png](https://z3.ax1x.com/2021/04/19/coGpvR.png)](https://imgtu.com/i/coGpvR)
23+
24+
spring-security源码下载地址:
25+
26+
```java
27+
https://github.com/spring-projects/spring-security
28+
```
29+
30+
#### 2.Spring Security的认证源码分析
31+
32+
##### 2.1 搭建项目并访问
33+
34+
首先我们搭建一个Spring Security的项目,使用Spring Boot可以很方便的进行集成开发,主要引入如下的依赖即可(当然也可以查看官网,选择合适的版本):
35+
36+
```java
37+
<dependency>
38+
<groupId>org.springframework.boot</groupId>
39+
<artifactId>spring-boot-starter-security</artifactId>
40+
</dependency>
41+
```
42+
43+
启动项目后会随机生成一个密码串,这里需要复制保存以便登录的时候使用:
44+
45+
[![coJ0ld.png](https://z3.ax1x.com/2021/04/19/coJ0ld.png)](https://imgtu.com/i/coJ0ld)
46+
47+
访问登录地址:
48+
49+
```java
50+
http://localhost:8080/login
51+
```
52+
53+
[![coJfpQ.png](https://z3.ax1x.com/2021/04/19/coJfpQ.png)](https://imgtu.com/i/coJfpQ)
54+
55+
默认的账户名和密码:
56+
57+
```java
58+
账户名: user
59+
密码: 项目启动时生成的密码串
60+
```
61+
62+
##### 2.2 进行源码分析
63+
64+
1. 进行断点后会发现首先进入的是UsernamePasswordAuthenticationFilter的attemptAuthentication(HttpServletRequest request, HttpServletResponse response)方法,会对用户名和密码进行封装成UsernamePasswordAuthenticationToken对象,然后调用this.getAuthenticationManager().authenticate(authRequest)方法进入到AuthenticationManager中。
65+
66+
attemptAuthentication方法源码如下所示:
67+
68+
```java
69+
@Override
70+
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
71+
throws AuthenticationException {
72+
if (this.postOnly && !request.getMethod().equals("POST")) {
73+
throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
74+
}
75+
String username = obtainUsername(request);
76+
username = (username != null) ? username : "";
77+
username = username.trim();
78+
String password = obtainPassword(request);
79+
password = (password != null) ? password : "";
80+
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
81+
// Allow subclasses to set the "details" property
82+
setDetails(request, authRequest);
83+
return this.getAuthenticationManager().authenticate(authRequest);
84+
}
85+
```
86+
87+
2. 随后请求进入到WebSecurityConfigurerAdapter的AuthenticationManagerDelegator中,AuthenticationManagerDelegator是AuthenticationManager的一个子类,最后封装成为UsernamePasswordAuthenticationToken对象,供DaoAuthenticationProvider使用。
88+
89+
AuthenticationManagerDelegator的源码如下:
90+
91+
```java
92+
static final class AuthenticationManagerDelegator implements AuthenticationManager {
93+
private AuthenticationManagerBuilder delegateBuilder;
94+
private AuthenticationManager delegate;
95+
private final Object delegateMonitor = new Object();
96+
private Set<String> beanNames;
97+
98+
AuthenticationManagerDelegator(AuthenticationManagerBuilder delegateBuilder, ApplicationContext context) {
99+
Assert.notNull(delegateBuilder, "delegateBuilder cannot be null");
100+
Field parentAuthMgrField = ReflectionUtils.findField(AuthenticationManagerBuilder.class, "parentAuthenticationManager");
101+
ReflectionUtils.makeAccessible(parentAuthMgrField);
102+
this.beanNames = getAuthenticationManagerBeanNames(context);
103+
validateBeanCycle(ReflectionUtils.getField(parentAuthMgrField, delegateBuilder), this.beanNames);
104+
this.delegateBuilder = delegateBuilder;
105+
}
106+
107+
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
108+
if (this.delegate != null) {
109+
return this.delegate.authenticate(authentication);
110+
} else {
111+
synchronized(this.delegateMonitor) {
112+
if (this.delegate == null) {
113+
this.delegate = (AuthenticationManager)this.delegateBuilder.getObject();
114+
this.delegateBuilder = null;
115+
}
116+
}
117+
118+
return this.delegate.authenticate(authentication);
119+
}
120+
}
121+
122+
private static Set<String> getAuthenticationManagerBeanNames(ApplicationContext applicationContext) {
123+
String[] beanNamesForType = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(applicationContext, AuthenticationManager.class);
124+
return new HashSet(Arrays.asList(beanNamesForType));
125+
}
126+
127+
private static void validateBeanCycle(Object auth, Set<String> beanNames) {
128+
if (auth != null && !beanNames.isEmpty() && auth instanceof Advised) {
129+
TargetSource targetSource = ((Advised)auth).getTargetSource();
130+
if (targetSource instanceof LazyInitTargetSource) {
131+
LazyInitTargetSource lits = (LazyInitTargetSource)targetSource;
132+
if (beanNames.contains(lits.getTargetBeanName())) {
133+
throw new FatalBeanException("A dependency cycle was detected when trying to resolve the AuthenticationManager. Please ensure you have configured authentication.");
134+
}
135+
}
136+
}
137+
}
138+
}
139+
```
140+
141+
org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration.AuthenticationManagerDelegator#authenticate
142+
143+
```java
144+
@Override
145+
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
146+
if (this.delegate != null) {
147+
return this.delegate.authenticate(authentication);
148+
}
149+
synchronized (this.delegateMonitor) {
150+
if (this.delegate == null) {
151+
this.delegate = this.delegateBuilder.getObject();
152+
this.delegateBuilder = null;
153+
}
154+
}
155+
return this.delegate.authenticate(authentication);
156+
}
157+
```
158+
159+
3. 进入到DaoAuthenticationProvider的retrieveUser(String username, UsernamePasswordAuthenticationToken authentication)方法进行用户的认证,这里的认证主要会调用默认的UserDetailsService对用户名和密码进行校验,如果是使用的类似于Mysql的数据源,其默认的实现是JdbcDaoImpl。
160+
161+
org.springframework.security.authentication.dao.DaoAuthenticationProvider#retrieveUser
162+
163+
```java
164+
@Override
165+
protected final UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication)
166+
throws AuthenticationException {
167+
prepareTimingAttackProtection();
168+
try {
169+
UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);
170+
if (loadedUser == null) {
171+
throw new InternalAuthenticationServiceException(
172+
"UserDetailsService returned null, which is an interface contract violation");
173+
}
174+
return loadedUser;
175+
}
176+
catch (UsernameNotFoundException ex) {
177+
mitigateAgainstTimingAttack(authentication);
178+
throw ex;
179+
}
180+
catch (InternalAuthenticationServiceException ex) {
181+
throw ex;
182+
}
183+
catch (Exception ex) {
184+
throw new InternalAuthenticationServiceException(ex.getMessage(), ex);
185+
}
186+
}
187+
188+
```
189+
190+
4. 将上一步认证后的用户实例放入SecurityContextHolder中,至此我们可以很方便的从SecurityContextHolder中获取用户信息,方法如下:
191+
192+
```java
193+
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
194+
```
195+
196+

‎docs/basis/PowerMockRunnerAndMockito.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -61,9 +61,9 @@ PowerMock是一个Java模拟框架,可用于解决通常认为很难甚至无
6161
```java
6262
InterfaceToMock mock = Powermockito.mock(InterfaceToMock.class)
6363

64-
Powermockito.when(mock.method(Params...)).thenReturn(value)
64+
Powermockito.when(mock.method(Params...)).thenReturn(value)
6565

66-
Powermockito.when(mock.method(Params..)).thenThrow(Exception)
66+
Powermockito.when(mock.method(Params..)).thenThrow(Exception)
6767
```
6868

6969
##### 4.2 设置对象的private属性
@@ -137,9 +137,9 @@ Powermockito.mockStatic(FinalClassToMock.class);
137137
```java
138138
1) PowerMockito.spy(TargetClass.class);
139139

140-
2) Powemockito.when(TargetClass.targetMethod()).doReturn()
140+
2) Powemockito.when(TargetClass.targetMethod()).doReturn()
141141

142-
3) 注意加入
142+
3) 注意加入
143143

144144
@RunWith(PowerMockRunner.class)
145145

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /