用戶登陸基本流程處理以下:html
1 SecurityContextPersistenceFilter前端
2 AbstractAuthenticationProcessingFilterjava
3 UsernamePasswordAuthenticationFilterweb
4 AuthenticationManagerspring
5 AuthenticationProvider數據庫
6 userDetailsService緩存
7 userDetails安全
8 認證經過session
9 SecurityContextmvc
10 SecurityContextHolder
11 AuthenticationSuccessHandler
用戶頁面登陸
1 首先進入 SecurityContextPersistenceFilter 攔截器
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
。。。。。省略
//HttpRequestResponseHolder 對象
HttpRequestResponseHolder holder = new HttpRequestResponseHolder(request, response);
// 判斷session是否存在,若是不存在則新建一個session
SecurityContext contextBeforeChainExecution = this.repo.loadContext(holder);
boolean var13 = false;
try {
var13 = true;
//將securiryContext放入SecurityContextHolder中
SecurityContextHolder.setContext(contextBeforeChainExecution);
//調用下一個攔截器,也就是以後全部的攔截器
chain.doFilter(holder.getRequest(), holder.getResponse());
var13 = false;
} finally {
if (var13) {
//獲取從context
SecurityContext contextAfterChainExecution = SecurityContextHolder.getContext();
//清空SecurityContextHolder 中的contex 臨時保存
SecurityContextHolder.clearContext();
//保存後面過濾器生成的數據 到SecurityContextRepository中
this.repo.saveContext(contextAfterChainExecution, holder.getRequest(), holder.getResponse());
request.removeAttribute("__spring_security_scpf_applied");
if (debug) {
this.logger.debug("SecurityContextHolder now cleared, as request processing completed");
}
}
}
//從SecurityContextHolder獲取SecurityContext實例 8
SecurityContext contextAfterChainExecution = SecurityContextHolder.getContext();
//清空SecurityContextHolder中的SecurityContext
SecurityContextHolder.clearContext();
//將SecurityContext實例保存到session中,以便下次請求時候用 9
this.repo.saveContext(contextAfterChainExecution, holder.getRequest(), holder.getResponse());
request.removeAttribute("__spring_security_scpf_applied");
if (debug) {
this.logger.debug("SecurityContextHolder now cleared, as request processing completed");
}
}
}
複製代碼
loadContext 判斷session是否存在 沒有新建一個
public SecurityContext loadContext(HttpRequestResponseHolder requestResponseHolder) {
HttpServletRequest request = requestResponseHolder.getRequest();
HttpServletResponse response = requestResponseHolder.getResponse();
//若是session不存在則返回null
HttpSession httpSession = request.getSession(false);
//根據 private String springSecurityContextKey = "SPRING_SECURITY_CONTEXT";
//獲取原來的session
SecurityContext context = this.readSecurityContextFromSession(httpSession);
if (context == null) {
if (this.logger.isDebugEnabled()) {
this.logger.debug("No SecurityContext was available from the HttpSession: " + httpSession + ". A new one will be created.");
}
//若是session 爲null 則新建一個
context = this.generateNewContext();
}
//將session 保存到 內部類SaveToSessionResponseWrapper 中
HttpSessionSecurityContextRepository.SaveToSessionResponseWrapper wrappedResponse = new HttpSessionSecurityContextRepository.SaveToSessionResponseWrapper(response, request, httpSession != null, context);
//保存在 HttpRequestResponseHolder對象中
requestResponseHolder.setResponse(wrappedResponse);
if (this.isServlet3) {
requestResponseHolder.setRequest(new HttpSessionSecurityContextRepository.Servlet3SaveToSessionRequestWrapper(request, wrappedResponse));
}
return context;
}
複製代碼
readSecurityContextFromSession 方法中 根據判斷session是否存在 會根據 「 SPRING_SECURITY_CONTEXT 」
獲取session
用戶登陸便是認證
登陸的實現方式:
2 進入AbstractAuthenticationProcessingFilter類
3 UsernamePasswordAuthenticationFilter 實現了類 AbstractAuthenticationProcessingFilter 調用自身的attemptAuthentication方法
獲取用戶名和密碼,構建token
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
if (this.postOnly && !request.getMethod().equals("POST")) {
throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
} else {
String username = this.obtainUsername(request);
String password = this.obtainPassword(request);
if (username == null) {
username = "";
}
if (password == null) {
password = "";
}
username = username.trim();
//構建token ,此時沒有進行驗證
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
this.setDetails(request, authRequest);
// AuthenticationManager 將token傳遞給 的 authenticate方法 進行 token驗證
return this.getAuthenticationManager().authenticate(authRequest);
}
}
//獲取密碼
protected String obtainPassword(HttpServletRequest request) {
return request.getParameter(this.passwordParameter);
}
//獲取帳號
protected String obtainUsername(HttpServletRequest request) {
return request.getParameter(this.usernameParameter);
}
複製代碼
調用authenticate方法,進行token
4 AuthenticationManager 接口
1 AuthenticationManager 接口
public interface AuthenticationManager {
Authentication authenticate(Authentication var1) throws AuthenticationException;
}
複製代碼
2 ProviderManager 實現了AuthenticationManager 接口
authenticate方法中
result = provider.authenticate(authentication); //返回成功的認證
複製代碼
重寫authenticate方法 來獲取用戶驗證信息
5 AuthenticationProvider 接口中方法
Authentication authenticate(Authentication authentication) throws AuthenticationException;
複製代碼
AbstractUserDetailsAuthenticationProvider 實現了AuthenticationProvider 接口
public Authentication authenticate(Authentication authentication) //authentication 傳遞過來token throws AuthenticationException {
Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication,
() -> messages.getMessage(
"AbstractUserDetailsAuthenticationProvider.onlySupports",
"Only UsernamePasswordAuthenticationToken is supported"));
// Determine username 獲取密碼
String username = (authentication.getPrincipal() == null) ? "NONE_PROVIDED"
: authentication.getName();
boolean cacheWasUsed = true;
//從緩存中獲取
UserDetails user = this.userCache.getUserFromCache(username);
//沒有緩存
if (user == null) {
cacheWasUsed = false;
try {
//查詢數據庫 獲取用戶帳號密碼
user = retrieveUser(username,
(UsernamePasswordAuthenticationToken) authentication);
}
catch (UsernameNotFoundException notFound) {
logger.debug("User '" + username + "' not found");
//此處 不能拋出異常 UsernameNotFoundException
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(user);
if (!cacheWasUsed) {
this.userCache.putUserInCache(user);
}
Object principalToReturn = user;
if (forcePrincipalAsString) {
principalToReturn = user.getUsername();
}
//建立成功的認證Authentication
return createSuccessAuthentication(principalToReturn, authentication, user);
}
複製代碼
retrieveUser 方法查詢用戶數據:
調用自身的抽象方法
protected abstract UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException;
複製代碼
DaoAuthenticationProvider 實現類
DaoAuthenticationProvider 繼承 AbstractUserDetailsAuthenticationProvider 重寫 retrieveUser 方法
用來根據用戶名查詢用戶數據
protected final UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
prepareTimingAttackProtection();
try {
// 根據用戶名查詢數據
UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);
if (loadedUser == null) {
throw new InternalAuthenticationServiceException(
"UserDetailsService returned null, which is an interface contract violation");
}
return loadedUser;
}
catch (UsernameNotFoundException ex) {
mitigateAgainstTimingAttack(authentication);
throw ex;
}
catch (InternalAuthenticationServiceException ex) {
throw ex;
}
catch (Exception ex) {
throw new InternalAuthenticationServiceException(ex.getMessage(), ex);
}
}
複製代碼
UserServiceDatils 接口須要本身實現(6.7一塊兒進行)
public class MUserDetailsService implements UserDetailsService {
Logger logger = LoggerFactory.getLogger(getClass());
@Autowired
private UsersMapper usersMapper;
/** *@description: 須要從數據庫中經過用戶名來查詢用戶的信息和用戶所屬的角色 *@author: wangl *@time: 2019/1/8 10:44 *@version 1.0 */
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
logger.info("===========受權============");
UserDetails userDeatils = null;
//經過用戶名 查詢密碼
Users users = usersMapper.selectUserByUserName(username);
Set<MGrantedAuthority> authorities = new HashSet<>();
//查詢用戶角色
if(null != users){
logger.info("用戶 = " + users.getUsername() + "----" + users.getPassword());
//查詢用戶權限
List<Users> usersList = usersMapper.selectRolesAndResourceByUserId(users.getId());
if(null != usersList && usersList.size()>0){
usersList.forEach(user->{
//存放role name 或者 權限名字
authorities.add(new MGrantedAuthority(user.getResourceName()));
});
}else{
System.out.println("用戶無權限。。");
throw new BadCredentialsException("not found ... ");
}
7 返回數據
userDeatils = new Users(users.getUsername(),users.getPassword(),authorities);
}else{
throw new BadCredentialsException("用戶名不存在");
}
return userDeatils;
}
複製代碼
查詢用戶數據以後
返回查詢到的loadUser對象
而後用戶帳號是否被鎖定,過時等驗證
this.preAuthenticationChecks.check(user);
複製代碼
密碼驗證
protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
if (authentication.getCredentials() == null) {
this.logger.debug("Authentication failed: no credentials provided");
throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
} else {
//獲取密碼
String presentedPassword = authentication.getCredentials().toString();
//匹配密碼
if (!this.passwordEncoder.matches(presentedPassword, userDetails.getPassword())) {
this.logger.debug("Authentication failed: password does not match stored value");
throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
}
}
}
複製代碼
自定義密碼驗證器
@Slf4j
@Component
public class PasswordEncorder implements PasswordEncoder {
@Override
public String encode(CharSequence charSequence) {
log.info("============ charSequence.toString() ============== " + charSequence.toString());
return MD5Util.encode(charSequence.toString());
}
@Override
public boolean matches(CharSequence charSequence, String s) {
String pwd = charSequence.toString();
log.info("前端傳過來密碼爲: " + pwd);
log.info("加密後密碼爲: " + MD5Util.encode(charSequence.toString()));
//s 應在數據庫中加密
if( MD5Util.encode(charSequence.toString()).equals(MD5Util.encode(s))){
return true;
}
throw new DisabledException("--密碼錯誤--");
}
}
複製代碼
/** *@description: 自定義登錄成功處理類 *@author: wangl *@time: 2019/1/14 17:46 *@version 1.0 */
@Component
public class MyAuthenctiationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws ServletException, IOException {
logger.info("登錄成功。。");
String name = authentication.getName();
HttpSession session = request.getSession();
session.setAttribute("user",name);
response.sendRedirect("/success");
//super.onAuthenticationSuccess(request, response, authentication);
}
}
複製代碼
配置類
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
MUserDetailsService mUserDetailsService; //userDetails
@Autowired
MyFilterSecurityInterceptor myFilterSecurityInterceptor; //自定義攔截器
@Autowired
PasswordEncoder passwordEncoder; //密碼驗證器
@Autowired
private MyAuthenctiationFailureHandler myAuthenctiationFailureHandler;//失敗處理
@Autowired
private MyAuthenctiationSuccessHandler myAuthenctiationSuccessHandler;//成功處理
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().mvcMatchers("/static/**"); //過濾靜態資源
}
/**定義認證用戶信息獲取來源,密碼校驗規則等**/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//從內存中獲取
//明文方式提交
/* auth.inMemoryAuthentication().withUser("zs").password("1234").roles("USER") .and().withUser("admin").password("1234").roles("ADMIN"); //從內存中獲取 */
auth.userDetailsService(mUserDetailsService) .passwordEncoder(passwordEncoder); //密碼加密方式
/*auth.authenticationProvider(customAuthenticationProvider) .authenticationProvider(authenticationProvider()) //增長 .userDetailsService(userDetailsService) .passwordEncoder(passwordEncoder());*/
/* auth.inMemoryAuthentication().passwordEncoder(new PasswordEncorder()).withUser("zs").password("1234").roles("USER") .and().withUser("admin").password("1234").roles("ADMIN") ;*/
}
/**定義安全策略**/
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests() ////配置安全策略
.antMatchers("/","/login.html","/loginPage").permitAll() // 定義請求不須要驗證
//.antMatchers("/admin/next").hasRole("ADMIN") // 設置只有管理員才能訪問的url
//.antMatchers("/admin/**").hasAnyRole("ADMIN") // 設置多個角色訪問的url
.anyRequest().authenticated() //其他全部請求都須要驗證
.and()
.formLogin() //配置登陸頁面
.loginPage("/loginPage") //登陸頁面訪問路徑
.loginProcessingUrl("/login") //登陸頁面提交表單路徑
.successHandler(myAuthenctiationSuccessHandler)//成功頁面
.failureHandler(myAuthenctiationFailureHandler) //失敗後跳轉路徑
.and()
.logout() //登出不須要驗證
.logoutUrl("/logout").permitAll()
// .and()
//.authorizeRequests()
//.antMatchers("/admin/next").hasRole("ADMIN")
//.and()
//.rememberMe() //記住我功能
//.tokenValiditySeconds(10000);
//自定義的攔截器 , 在適當的地方加入
http.addFilterAt(myFilterSecurityInterceptor, FilterSecurityInterceptor.class);
http.csrf().disable(); ///關閉默認的csrf認證
}
}
複製代碼