主要實(shí)現(xiàn)
主要通過過濾器實(shí)現(xiàn)某宪,通過一層層攔截來實(shí)現(xiàn)登錄認(rèn)證等操作悼做。主要講一下UsernamePasswordAuthenticationFilter
和BasicAuthenticationFilter
的實(shí)現(xiàn)
// 啟動springboot的時候,控制臺打印的日志囱皿。都是默認(rèn)的過濾器實(shí)現(xiàn)
2019-12-05 18:36:23.748 INFO 10472 --- [ main] o.s.s.web.DefaultSecurityFilterChain : Creating filter chain: any request, [org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter@1115433e,
org.springframework.security.web.context.SecurityContextPersistenceFilter@21ba2445,
org.springframework.security.web.header.HeaderWriterFilter@257e0827,
org.springframework.web.filter.CorsFilter@4fdca00a,
org.springframework.security.web.authentication.logout.LogoutFilter@7fb48179,
co.jratil.springsecuritydemo.filter.LoginAuthenticationFilter@513b52af,
co.jratil.springsecuritydemo.filter.JwtAuthorizationFilter@5a8c93,
org.springframework.security.web.savedrequest.RequestCacheAwareFilter@69d23296,
org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter@5434e40c,
org.springframework.security.web.authentication.AnonymousAuthenticationFilter@3bed3315,
org.springframework.security.web.session.SessionManagementFilter@22752544,
org.springframework.security.web.access.ExceptionTranslationFilter@78b612c6,
org.springframework.security.web.access.intercept.FilterSecurityInterceptor@2d55e826]
簡單的demo勇婴,主要通過
- 繼承
UsernamePasswordAuthenticationFilter
來實(shí)現(xiàn)賬號密碼的驗(yàn)證- 繼承
BasicAuthenticationFilter
來實(shí)現(xiàn)授權(quán)的問題,比如是否登錄嘱腥,和獲取用戶的權(quán)限放入全局的SecurityContext
中- 需要自定義繼承
UserDetails
和UserDetailsSevice
兩個接口耕渴,來覆蓋默認(rèn)的實(shí)現(xiàn),從而從數(shù)據(jù)庫獲取到所需的用戶和用戶信息
1. 繼承UsernamePasswordAuthenticationFilter
過濾器實(shí)現(xiàn)
主要實(shí)現(xiàn)過程:
- 先通過過濾器中的
attemptAuthentication()
方法齿兔,把request中的登錄的賬號密碼取出來橱脸。- 然后通過
AuthenticationManager
的authenticate()
方法來認(rèn)證,其中默認(rèn)是通過ProviderManager
來實(shí)現(xiàn)該方法- 在
ProviderManager
中分苇,會循環(huán)獲取到所有可以處理該認(rèn)證的provider添诉,再調(diào)用其authentication()
方法來認(rèn)證,默認(rèn)有個AbstractUserDetailsAuthenticationProvider
實(shí)現(xiàn)AbstractUserDetailsAuthenticationProvider
中有一個retrieveUser()的虛方法医寿,默認(rèn)通過DaoAuthenticationProvider
來實(shí)現(xiàn)- 在
DaoAuthenticationProvider
中會獲取到自定義的UserDetrailsService
的實(shí)現(xiàn)類栏赴,通過調(diào)用該實(shí)現(xiàn)類中的loadUserByUsername()
來獲取到UserDetails
的對象。- 最終該對象會放進(jìn)一個
UsernamePasswordAuthenticationoken
對象中靖秩。- 在認(rèn)證成功后會調(diào)用
successfulAuthentication()
方法须眷,在里面將token放入header中竖瘾。返回給前端- 失敗則調(diào)用
unsuccessfulAuthentication()
方法,將錯誤返回
public class LoginAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
private static final Logger log = LoggerFactory.getLogger(LoginAuthenticationFilter.class);
private AuthenticationManager authenticationManager;
private boolean rememberMe = false;
// 通過構(gòu)造函數(shù)獲取AuthenticationManager,最后主要通過該對象的authenticate來實(shí)現(xiàn)認(rèn)證
public LoginAuthenticationFilter(AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
super.setFilterProcessesUrl("/auth/login");
}
// 重寫方法花颗,過濾器的主要實(shí)現(xiàn)
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
ObjectMapper objectMapper = new ObjectMapper();
try {
// 從request中獲取json數(shù)據(jù)捕传,就是body中的數(shù)據(jù),前端請求傳入一個LoginUser的Json
LoginUser loginUser = objectMapper.readValue(request.getInputStream(), LoginUser.class);
log.info(loginUser.toString());
this.rememberMe = loginUser.isRememberMe();
// 設(shè)置一個authentication獲取賬號密碼用來驗(yàn)證
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
loginUser.getUsername(), loginUser.getPassword()
);
// 使用AuthencationManager來實(shí)現(xiàn)認(rèn)證 ---- 1.
return authenticationManager.authenticate(authentication);
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
/**
*上面方法認(rèn)證成功后調(diào)用
*/
@Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
// 在下面講到了扩劝,上面的操作最后會將JwtUser對象放入UsernamePasswordAuthenticationToken中
JwtUser user = (JwtUser) authResult.getPrincipal();
List<String> roles = user.getAuthorities()
.stream()
.map(GrantedAuthority::getAuthority)
.collect(Collectors.toList());
String token = JwtUtils.createJwtToken(user.getUsername(), roles, this.rememberMe);
response.setHeader(SecurityConstant.TOKEN_HEADER, token);
response.setContentType("text/json;charset=utf-8");
PrintWriter writer = response.getWriter();
writer.write(ResultUtils.success(null).toString());
}
/**
*上面方法認(rèn)證失敗后調(diào)用
*/
@Override
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, failed.getMessage());
}
}
--- 1.authenticationManager.authencate(authentication)實(shí)現(xiàn)
默認(rèn)spring security會通過ProviderManager來實(shí)現(xiàn)
內(nèi)部會繼續(xù)使用provider.authenticate()方法來實(shí)現(xiàn)乐横。provider會通過循環(huán),找到適合的今野,如果沒定義會有默認(rèn)的實(shí)現(xiàn)葡公。
首先會從緩存中查找是否有UserDetails對象存在,如果沒有會有一個NullUserCache來實(shí)現(xiàn)条霜,返回null
然后再通過retrieveUser()
方法催什,其默認(rèn)實(shí)現(xiàn)的DaoAuthenticationProcider
來實(shí)現(xiàn)該方法
getUserDetailsService().loadUserByUsername會調(diào)用用戶自己實(shí)現(xiàn)的類來獲取到UserDetails,代碼如下:其中JwtUser是自己設(shè)置的實(shí)現(xiàn)UserDetails的實(shí)現(xiàn)類
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
// 在config中已經(jīng)設(shè)置了的密碼加密
@Autowired
BCryptPasswordEncoder passwordEncoder;
/**
* 下面是自己模擬的數(shù)據(jù)宰睡,具體可以 通過這里傳入的username從數(shù)據(jù)庫中
* 查詢用戶然后再把用戶的賬號密碼權(quán)限等信息存入JwtUser類中蒲凶,再返回該類
*/
@Override
public UserDetails loadUserByUsername(String username) {
if (!"aa".equals(username)) {
throw new GlobalException("username" + username +"不存在");
}
String password = passwordEncoder.encode("aa");
JwtUser jwtUser = new JwtUser(1, "aa", password, new ArrayList<GrantedAuthority>() {{
add(new SimpleGrantedAuthority("ROLE_USER"));
add(new SimpleGrantedAuthority("ROLE_ADMIN"));
}});
return jwtUser;
}
}