SpringBoot+Security+JWT进阶:一、自定义认证

这里谈谈自定义认证

不使用自定义认证的 WebSecurityConfig

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
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
private final Logger logger = LoggerFactory.getLogger(WebSecurityConfig.class);

@Resource(name = "userDetailsService")
private UserDetailsService userDetailsService;
@Resource(name = "passwordEncoder")
private PasswordEncoder passwordEncoder;
//**********************
// 略
//**********************

/**
* 身份验证
*
* @param auth
* @throws Exception
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.userDetailsService(userDetailsService)
.passwordEncoder(passwordEncoder)
;

}
}

不使用自定义认证的验证类 AbstractUserDetailsAuthenticationProvider

这里只看 authenticate验证的方法,根据自己的理解,我写上了注释,//// 为重点强调

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
public Authentication authenticate(Authentication authentication)
throws AuthenticationException {
// 对 supports 方法的二次校验,为空或不等抛出错误
Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication,
() -> messages.getMessage(
"AbstractUserDetailsAuthenticationProvider.onlySupports",
"Only UsernamePasswordAuthenticationToken is supported"));

// Determine username,authentication.getPrincipal()获取的就是UserDetail
String username = (authentication.getPrincipal() == null) ? "NONE_PROVIDED"
: authentication.getName();
// 默认情况下从缓存中(UserCache接口实现)取出用户信息
boolean cacheWasUsed = true;
UserDetails user = this.userCache.getUserFromCache(username);
if (user == null) {
// 如果从缓存中取不到用户,则设置cacheWasUsed 为false,供后面使用
cacheWasUsed = false;
try {
// retrieveUser是抽象方法,通过子类来实现获取用户的信息,以UserDetails接口形式返回,默认的子类为 DaoAuthenticationProvider
user = retrieveUser(username,
(UsernamePasswordAuthenticationToken) authentication);
}
//// 这就是为什么 UsernameNotFoundException 抛出信息却获取不到的原因
catch (UsernameNotFoundException notFound) {
logger.debug("User '" + username + "' not found");
if (hideUserNotFoundExceptions) {
throw new BadCredentialsException(messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials","Bad credentials"));
}
else {
throw notFound;
}
}
Assert.notNull(user,
"retrieveUser returned null - a violation of the interface contract");
}
try {// 验证帐号是否锁定\是否禁用\帐号是否到期
preAuthenticationChecks.check(user);
// 进一步验证凭证 和 密码
additionalAuthenticationChecks(user,
(UsernamePasswordAuthenticationToken) authentication);
}
catch (AuthenticationException exception) {
if (cacheWasUsed) {// 如果是内存用户,则再次获取并验证
// There was a problem, so try again after checking
// we're using latest data (i.e. not from the cache)
cacheWasUsed = false;
user = retrieveUser(username,
(UsernamePasswordAuthenticationToken) authentication);
preAuthenticationChecks.check(user);
additionalAuthenticationChecks(user,
(UsernamePasswordAuthenticationToken) authentication);
}
else {
throw exception;
}
}
//验证凭证是否已过期
postAuthenticationChecks.check(userDetail);
//如果没有缓存则进行缓存,此处的 userCache是 由 NullUserCache 类实现的,名如其义,该类的 putUserInCache 没做任何事
//也可以使用缓存 比如 EhCacheBasedUserCache 或者 SpringCacheBasedUserCache
if (!cacheWasUsed) {
this.userCache.putUserInCache(user);
}
//以下代码主要是把用户的信息和之前用户提交的认证信息重新组合成一个 authentication 实例返回,返回类是 UsernamePasswordAuthenticationToken 类的实例
Object principalToReturn = user;

if (forcePrincipalAsString) {
principalToReturn = user.getUsername();
}

return createSuccessAuthentication(principalToReturn, authentication, user);
}
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
@startuml
Title "Authentication类图"
interface Principal
interface Authentication
interface AuthenticationManager
interface AuthenticationProvider
abstract class AbstractUserDetailsAuthenticationProvider
class ProviderManager
class DaoAuthenticationProvider
interface UserDetailsService


Principal <|-- Authentication
Authentication <.. AuthenticationManager
AuthenticationManager <|-- ProviderManager
ProviderManager o--> AuthenticationProvider
AuthenticationProvider <|.. AbstractUserDetailsAuthenticationProvider
AbstractUserDetailsAuthenticationProvider <|-- DaoAuthenticationProvider
UserDetailsService <.. AbstractUserDetailsAuthenticationProvider

interface AuthenticationManager{
# Authentication authenticate(Authentication authentication)
throws AuthenticationException;
}
abstract class AbstractUserDetailsAuthenticationProvider{
+ public Authentication authenticate(Authentication authentication)
throws AuthenticationException;
+public boolean supports(Class<?> authentication);
}
@enduml

使用自定义认证的 WebSecurityConfig

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
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
private final Logger logger = LoggerFactory.getLogger(WebSecurityConfig.class);

private PasswordEncoder passwordEncoder;
@Resource(name = "authenticationProvider")
private AuthenticationProvider authenticationProvider;
//**********************
// 略
//**********************

/**
* 身份验证
*
* @param auth
* @throws Exception
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.authenticationProvider(authenticationProvider)
;

}
}

自定义认证类 AuthenticationProviderImpl

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
/**
* AuthenticationProviderImpl
* 自定义认证服务
*
* @author maxzhao
* @date 2019-05-23 15:43
*/
@Service("authenticationProvider")
public class AuthenticationProviderImpl implements AuthenticationProvider {
@Resource(name = "userDetailsService")
private UserDetailsService userDetailsService;

@Resource(name = "passwordEncoder")
private PasswordEncoder passwordEncoder;

/**
*
* @param authenticate
* @return
* @throws AuthenticationException
*/
@Override
public Authentication authenticate(Authentication authenticate) throws AuthenticationException {
UsernamePasswordAuthenticationToken token
= (UsernamePasswordAuthenticationToken) authenticate;
String username = token.getName();
UserDetails userDetails = null;

if (username != null) {
userDetails = userDetailsService.loadUserByUsername(username);
}
if (userDetails == null) {
throw new UsernameNotFoundException("用户名/密码无效");
} else if (!userDetails.isEnabled()) {
System.out.println("jinyong用户已被禁用");
throw new DisabledException("用户已被禁用");
} else if (!userDetails.isAccountNonExpired()) {
System.out.println("guoqi账号已过期");
throw new AccountExpiredException("账号已过期");
} else if (!userDetails.isAccountNonLocked()) {
System.out.println("suoding账号已被锁定");
throw new LockedException("账号已被锁定");
} else if (!userDetails.isCredentialsNonExpired()) {
System.out.println("pingzheng凭证已过期");
throw new CredentialsExpiredException("凭证已过期");
}
String password = userDetails.getPassword();
//与authentication里面的credentials相比较 ; todo 加密 token 中的密码
if (!password.equals(token.getCredentials())) {
throw new BadCredentialsException("Invalid username/password");
}
//授权
return new UsernamePasswordAuthenticationToken(userDetails, password, userDetails.getAuthorities());
}

@Override
public boolean supports(Class<?> authentication) {
//返回true后才会执行上面的authenticate方法,这步能确保authentication能正确转换类型
return UsernamePasswordAuthenticationToken.class.equals(authentication);
}
}

本文地址: SpringBoot+Security+JWT进阶:一、自定义认证

推荐

SpringBoot+Security+JWT基础
SpringBoot+Security+JWT进阶:一、自定义认证
SpringBoot+Security+JWT进阶:二、自定义认证实践

本文地址: https://github.com/maxzhao-it/blog/post/53124/