Spring Security自定義認證器
阿新 • • 發佈:2022-05-24
在瞭解過Security的認證器後,如果想自定義登陸,只要實現AuthenticationProvider還有對應的Authentication就可以了
Authentication
首先要建立一個自定義的Authentication,Security提供了一個Authentication的子類AbstractAuthenticationToken
我們實現這個類可以了,他已經實現了Authentication的一些方法
public class NamePassAuthenticationToken extends AbstractAuthenticationToken { private static final long serialVersionUID = 520L; private final Object principal; private Object credentials; //提供第一次進來的構造方法 public NamePassAuthenticationToken(Object principal, Object credentials) { super((Collection)null); this.principal = principal; this.credentials = credentials; this.setAuthenticated(false); } //提供填充Authentication的構造方法 public NamePassAuthenticationToken(Object principal, Object credentials, Collection<? extends GrantedAuthority> authorities) { super(authorities); this.principal = principal; this.credentials = credentials; super.setAuthenticated(true); } @Override public Object getCredentials() { return this.credentials; } @Override public Object getPrincipal() { return this.principal; } @Override public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException { if (isAuthenticated) { throw new IllegalArgumentException("Cannot set this token to trusted - use constructor which takes a GrantedAuthority list instead"); } else { super.setAuthenticated(false); } } @Override public void eraseCredentials() { super.eraseCredentials(); this.credentials = null; } }
這個類關鍵就是一個是認證的,一個沒認證的的構造器
AuthenticationProvider
接著是AuthenticationProvider,需要實現他的authenticate方法
@Setter public class NamePassAuthenticationProvider implements AuthenticationProvider { private CustomUserDetailsService userDetailsService; private PasswordEncoder passwordEncoder; @Override //具體認證邏輯 public Authentication authenticate(Authentication authentication) { NamePassAuthenticationToken authenticationToken = (NamePassAuthenticationToken) authentication; String username = (String) authenticationToken.getPrincipal(); String password = (String) authenticationToken.getCredentials(); //讓具體認證類去認證 UserDetails user = userDetailsService.loadUserByUsername(username); boolean matches = passwordEncoder.matches(password, user.getPassword()); if (!matches) { ResMsg.throwException(AuthExceptionGroup.AUTH_ERROR); } //填充Authentication NamePassAuthenticationToken authenticationResult = new NamePassAuthenticationToken(user, password, user.getAuthorities()); authenticationResult.setDetails(authenticationToken.getDetails()); return authenticationResult; } @Override //指定具體的Authentication //根據你指定的Authentication來找到具體的Provider public boolean supports(Class<?> authentication) { return NamePassAuthenticationToken.class.isAssignableFrom(authentication); } }
SecurityConfigurerAdapter
接著就是填充配置了
@Component public class NamePassAuthenticationSecurityConfig extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> { @Autowired private CustomUserDetailsService customUserDetailsService; @Autowired private PasswordEncoder passwordEncoder; @Override public void configure(HttpSecurity http) { //phonePass provider NamePassAuthenticationProvider provider = new NamePassAuthenticationProvider(); provider.setUserDetailsService(customUserDetailsService); provider.setPasswordEncoder(passwordEncoder); http.authenticationProvider(provider); } }
接下來就是匯入配置了
通常都會有一個實現了WebSecurityConfigurerAdapter的配置類
把配置類注入進來
@Autowired
private NamePassAuthenticationSecurityConfig namePassAuthenticationSecurityConfig;
protected void configure(HttpSecurity http) throws Exception {
http.apply(namePassAuthenticationSecurityConfig);
}
UserDetailsService
UserDetailsService是具體的認證實現類
這個類就非常熟悉了,只需要實現他的loadUserByUsername方法,就可以實現認證了
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
AuthmsViewAccount account = accountService.getAccount(username);
if(account == null) {
ResMsg.throwException(AUTH_ERROR);
}
if (account.getStatus() != 1) {
ResMsg.throwException(ACCOUNT_HAS_BANED);
}
String spliceStaffInfo = String.format("%d-%s",account.getAccountId(),account.getUsername());
//只要Collection<? extends GrantedAuthority> authorities
//這個引數不為空,就表明認證通過,所以空集合也可以通過
return new User(spliceStaffInfo,account.getPassword(), AuthorityUtils.NO_AUTHORITIES);
}
把認證結果填充到上下文中
TokenFilter
如果結合了Token,那麼需要從token中識別該使用者
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
String bearerToken = resolveToken(request);
if (bearerToken != null && !"".equals(bearerToken.trim()) && SecurityContextHolder.getContext().getAuthentication() == null) {
//從redis中獲取該使用者
NamePassAuthenticationToken namePassAuthenticationToken = authRedisHelper.get(bearerToken);
if(namePassAuthenticationToken != null) {
//將資訊儲存到上下文中
SecurityContextHolder.getContext().setAuthentication(namePassAuthenticationToken);
}
}
chain.doFilter(request, response);
}
private String resolveToken(HttpServletRequest request) {
String bearerToken = request.getHeader("Authorization");
if (StringUtils.hasText(bearerToken) && bearerToken.startsWith(TOKEN_PREFIX)) {
return bearerToken.substring(7);
}
return null;
}
public NamePassAuthenticationToken get(String bearerToken){
String spliceStaffInfo = (String)redisRepository.get(formatKey(bearerToken));
if(spliceStaffInfo == null) {
return null;
}
return new NamePassAuthenticationToken(new AuthStaff(spliceStaffInfo),null,AuthorityUtils.NO_AUTHORITIES);
}
登入過程
在登入的時候,就需要用到這個自定義的認證器了
// 通過使用者名稱和密碼建立一個 Authentication 認證物件,實現類為 NamePassAuthenticationToken
NamePassAuthenticationToken authenticationToken = new NamePassAuthenticationToken(user.getUsername(), user.getPassword());
//通過 AuthenticationManager(預設實現為ProviderManager)的authenticate方法驗證 Authentication 物件
//AuthenticationManager會通過你傳入的authenticationToken來找到具體的Provider
Authentication authentication = authenticationManager.authenticate(authenticationToken);
//填充使用者資訊到secrity中的user裡
User principal = (User) authentication.getPrincipal();
//獲取認證後的資訊
NamePassAuthenticationToken namePassAuthenticationToken = new NamePassAuthenticationToken(new AuthStaff(principal.getUsername()), null, authentication.getAuthorities());
// 生成token
String bearerToken = IdUtil.fastSimpleUUID();
// 載入到reids
authRedisHelper.set(bearerToken, namePassAuthenticationToken);
這樣就實現了自定義的認證器了