title: SpringBoot+Security+JWT進階:一、自定義認證
date: 2019-07-04
author: maxzhao
tags:
- JAVA
- SpringBoot
- Security
- JWT
- Authentication
categories:
- SpringBoot
- Security+JWT
這里談談自定義認證
不使用自定義認證的 WebSecurityConfig
@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
驗證的方法造成,根據(jù)自己的理解趁餐,我寫上了注釋,////
為重點強調
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接口實現(xiàn))取出用戶信息
boolean cacheWasUsed = true;
UserDetails user = this.userCache.getUserFromCache(username);
if (user == null) {
// 如果從緩存中取不到用戶,則設置cacheWasUsed 為false咬最,供后面使用
cacheWasUsed = false;
try {
// retrieveUser是抽象方法恳啥,通過子類來實現(xiàn)獲取用戶的信息,以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 類實現(xiàn)的,名如其義翁垂,該類的 putUserInCache 沒做任何事
//也可以使用緩存 比如 EhCacheBasedUserCache 或者 SpringCacheBasedUserCache
if (!cacheWasUsed) {
this.userCache.putUserInCache(user);
}
//以下代碼主要是把用戶的信息和之前用戶提交的認證信息重新組合成一個 authentication 實例返回铆遭,返回類是 UsernamePasswordAuthenticationToken 類的實例
Object principalToReturn = user;
if (forcePrincipalAsString) {
principalToReturn = user.getUsername();
}
return createSuccessAuthentication(principalToReturn, authentication, user);
}
@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
@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
/**
* 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后才會執(zhí)行上面的authenticate方法,這步能確保authentication能正確轉換類型
return UsernamePasswordAuthenticationToken.class.equals(authentication);
}
}
本文地址:
SpringBoot+Security+JWT進階:一、自定義認證推薦
SpringBoot+Security+JWT基礎
SpringBoot+Security+JWT進階:一沿猜、自定義認證
SpringBoot+Security+JWT進階:二枚荣、自定義認證實踐