spring security 学习二
spring security 學習二
doc:https://docs.spring.io/spring-security/site/docs/
基于表單的認證(個性化認證流程):
一、自定義登錄頁面
1、在securityConfigy配置類中的config方法中添加調用鏈方法
@Overrideprotected void configure(HttpSecurity http) throws Exception {http.formLogin()//指定是表單登錄.loginPage("/cus_login.html")//登錄頁面.and().authorizeRequests()//授權.anyRequest()//任何請求.authenticated();//都需要身份認證}2、同時在resources/resources下創建一個html文件
?
<!DOCTYPE html> <html lang="en"> <head><meta charset="UTF-8"><title>Title</title> </head> <body>自定義登錄頁面 </body> </html>/3、配置好之后,啟動項目,訪問localhost:9999/h? api
瀏覽器后報錯:,重定向到登錄頁面次數太多
4、為什么出現這種情況:
因為在securityconfigy配置類中指定了一個loginPage,但是在方法鏈中,有表明:任何請求都需要認證
.anyRequest()//任何請求.authenticated();//都需要身份認證處理:在anyRequest方法前和authorizeRequests方法后添加antMatchers匹配規則,指定登錄頁面允許訪問
.authorizeRequests()//授權.antMatchers("/cus_login.html").permitAll()配置完成后再次訪問localhost:9999/h? :,即可跳轉至自定義的登錄頁面
5、完善登錄頁面
//在usernamePasswordAuthenticationFilter中處理的action為/login,請求方式是post public UsernamePasswordAuthenticationFilter() {super(new AntPathRequestMatcher("/login", "POST"));} <!DOCTYPE html> <html lang="en"> <head><meta charset="UTF-8"><title>Title</title> </head> <body> 自定義登錄頁面 <form action="/authentication/form" method="POST"><table><tr><td>用戶名:</td><td><input type="text" name="username"></td></tr><tr><td>密碼:</td><td><input type="text" name="password"></td></tr><tr><td><button type="submit">登錄</button></td></tr></table> </form> </body> </html>?因為在登錄頁面中設定的action為“/authentication/form”,所以在securityConfig配置類的方法鏈中添加loginProcessingUrl方法
@Overrideprotected void configure(HttpSecurity http) throws Exception {http.formLogin()//指定是表單登錄.loginPage("/cus_login.html")//登錄頁面.loginProcessingUrl("/authentication/form").and().authorizeRequests()//授權.antMatchers("/cus_login.html").permitAll().anyRequest()//任何請求.authenticated();//都需要身份認證}重啟項目:訪問localhost:9999/h
顯示為自定義的頁面。
csrf().disable();//跨站防護不適用6、將loginPage(/cus_login.html) html頁面改為一個controller代碼
①新建一個controller
@RestController public class LoginSecurityController {@RequestMapping("/authentication/require")public String requireAuthentication(HttpServletRequest request, HttpServletResponse response) {return null;} }②修改securityconfig配置類中的config中的方法調用鏈中的loginPage
@Overrideprotected void configure(HttpSecurity http) throws Exception {http.formLogin()//指定是表單登錄.loginPage("/authentication/require")//登錄頁面.loginProcessingUrl("/authentication/form").and().authorizeRequests()//授權.antMatchers("/authentication/require").permitAll().anyRequest()//任何請求.authenticated()//都需要身份認證.and().csrf().disable();//跨站防護不適用}?
二、自定義登錄成功的處理
?實現AuthenticationSucessHandler接口,一下實現方式是:繼承SaveRequestAwareAuthenticationSuccessHandler類(這樣的話是可以根據請求方式的類型,來返回不同個數的數據,json或者默認)
package com.nxz.security.handler;import com.fasterxml.jackson.databind.ObjectMapper; import com.nxz.security.core.properties.LoginType; import com.nxz.security.core.properties.SecurityProperties; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.Authentication; import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler; import org.springframework.stereotype.Component;import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException;@Component("custAuthenticationSuccessHandler") @Slf4j public class CustAuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {@Autowired//springmvc自動注冊的一個mapper類private ObjectMapper objectMapper;@Autowiredprivate SecurityProperties securityProperties;@Overridepublic void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {log.info("登錄成功");if (LoginType.JSON.equals(securityProperties.getBrowser().getLoginType())) {httpServletResponse.setContentType("application/json;UTF-8");httpServletResponse.getWriter().write(objectMapper.writeValueAsString(authentication));} else {super.onAuthenticationSuccess(httpServletRequest, httpServletResponse, authentication);}} } @Autowiredprivate AuthenticationSuccessHandler custAuthenticationSuccessHandler;http.formLogin().loginPage("/authentication/require").loginProcessingUrl("/authentication/form").successHandler(custAuthenticationSuccessHandler).failureHandler(custAuthenticationFailerHandler).and().authorizeRequests().antMatchers("/authentication/require",loginPage,"/code/image").permitAll().anyRequest().authenticated();?
三、自定義登錄失敗的處理
實現AuthenticationFailureHandler接口,繼承SimpleUrlAuthenticationFailureHandler,根據請求方式返回不同的類型
package com.nxz.security.handler;import com.alibaba.fastjson.JSON; import com.fasterxml.jackson.databind.ObjectMapper; import com.nxz.security.core.properties.LoginType; import com.nxz.security.core.properties.SecurityProperties; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.security.core.AuthenticationException; import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler; import org.springframework.stereotype.Component;import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException;@Slf4j @Component("custAuthenticationFailerHandler") public class CustAuthenticationFailerHandler extends SimpleUrlAuthenticationFailureHandler {@Autowiredprivate ObjectMapper objectMapper;@Autowiredprivate SecurityProperties securityProperties;@Overridepublic void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {log.info("登錄失敗!");if (LoginType.JSON.equals(securityProperties.getBrowser().getLoginType())) {response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());response.setContentType("application/json;charset=UTF-8");response.getWriter().write(JSON.toJSONString(objectMapper.writeValueAsString(exception)));} else {super.onAuthenticationFailure(request, response, exception);}} } @Autowiredprivate AuthenticationFailureHandler custAuthenticationFailerHandler;?
?
四、源碼學習
1、認證流程說明
登錄請求進來是,會先到UsernamePasswordAuthentication類的,其實最先走的是它的父類AbstractAuthenticationProcessingFilter.java,在父類中會走attemptAuthentication方法,父類沒有實現,因此會走子類的方法,當認證成功后,會走到最后successFulAuthentication方法
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)throws IOException, ServletException {。。。。。。。。Authentication authResult;try {authResult = attemptAuthentication(request, response);if (authResult == null) {// return immediately as subclass has indicated that it hasn't completed// authenticationreturn;}sessionStrategy.onAuthentication(authResult, request, response);}。。。。。。。。// Authentication successif (continueChainBeforeSuccessfulAuthentication) {chain.doFilter(request, response);}successfulAuthentication(request, response, chain, authResult);}attemptAuthentication方法(子類usernamepasswordAuthenticationFilter.java)
public Authentication attemptAuthentication(HttpServletRequest request,HttpServletResponse response) throws AuthenticationException {if (postOnly && !request.getMethod().equals("POST")) {throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());}String username = obtainUsername(request);String password = obtainPassword(request);。。。。。。。。username = username.trim();//這個UsernamepasswordAuthenticationToken其實就是封裝了username 和password信息UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);// 這個setDetails會把請求的一些信息設置到authRequest對象中setDetails(request, authRequest);return this.getAuthenticationManager().authenticate(authRequest);}上邊那個this.getAuthenticationManager() 會獲取一個authenticationManager類
作用:收集相關的provider,當請求到的時候,循環判斷是否支持當前的provider類型
public interface AuthenticationManager {//authenticate認證方法,交給實現類實現Authentication authenticate(Authentication var1) throws AuthenticationException; }他的實現類(用的就是ProviderManger類),不同的provider支持的Authentication是不同的
?
例如:UsernamePasswordAuthenticationToken類型的Authentication時,provider就是DaoAuthenticationProvider,
?
public Authentication authenticate(Authentication authentication) throws AuthenticationException {Class<? extends Authentication> toTest = authentication.getClass();AuthenticationException lastException = null;AuthenticationException parentException = null;Authentication result = null;Authentication parentResult = null;boolean debug = logger.isDebugEnabled();Iterator var8 = this.getProviders().iterator();while(var8.hasNext()) {AuthenticationProvider provider = (AuthenticationProvider)var8.next();if (provider.supports(toTest)) {if (debug) {logger.debug("Authentication attempt using " + provider.getClass().getName());}try {result = provider.authenticate(authentication);if (result != null) {this.copyDetails(authentication, result);break;}} catch (AccountStatusException var13) {this.prepareException(var13, authentication);throw var13;} catch (InternalAuthenticationServiceException var14) {this.prepareException(var14, authentication);throw var14;} catch (AuthenticationException var15) {lastException = var15;}}}。。。。。}上邊那個標紅的provider.authenticate方法會走到DaoAuthenticationProvider對象的父類對象的authticate方法,
?
public Authentication authenticate(Authentication authentication) throws AuthenticationException {Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication, () -> {return this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.onlySupports", "Only UsernamePasswordAuthenticationToken is supported");});String username = authentication.getPrincipal() == null ? "NONE_PROVIDED" : authentication.getName();boolean cacheWasUsed = true;UserDetails user = this.userCache.getUserFromCache(username);if (user == null) {cacheWasUsed = false;try {//這塊會進入子類DaoAuthenticationPrivoderuser = this.retrieveUser(username, (UsernamePasswordAuthenticationToken)authentication);} catch (UsernameNotFoundException var6) {this.logger.debug("User '" + username + "' not found");if (this.hideUserNotFoundExceptions) {throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));}throw var6;}Assert.notNull(user, "retrieveUser returned null - a violation of the interface contract");}try {//校驗是否鎖定。。(userdetails里邊的幾個返回值)this.preAuthenticationChecks.check(user);//校驗密碼是否匹配this.additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken)authentication);} catch (AuthenticationException var7) {if (!cacheWasUsed) {throw var7;}cacheWasUsed = false;user = this.retrieveUser(username, (UsernamePasswordAuthenticationToken)authentication)this.preAuthenticationChecks.check(user);this.additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken)authentication);}//后置查詢 this.postAuthenticationChecks.check(user);if (!cacheWasUsed) {this.userCache.putUserInCache(user);}Object principalToReturn = user;if (this.forcePrincipalAsString) {principalToReturn = user.getUsername();}return this.createSuccessAuthentication(principalToReturn, authentication, user);}子類DapAuthenticationPrivoder類的
protected final UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {this.prepareTimingAttackProtection();try {//這一塊就是調用自定義得人UserDetailsService類UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);if (loadedUser == null) {throw new InternalAuthenticationServiceException("UserDetailsService returned null, which is an interface contract violation");} else {return loadedUser;}} catch (UsernameNotFoundException var4) {this.mitigateAgainstTimingAttack(authentication);throw var4;} catch (InternalAuthenticationServiceException var5) {throw var5;} catch (Exception var6) {throw new InternalAuthenticationServiceException(var6.getMessage(), var6);}}進入自定義的CusUserDetailsService
public class MyUserDetailsService implements UserDetailsService {@Overridepublic UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {//根據username查找用戶信息,在這,先手動寫一個user信息log.info("查找用戶信息{}", s);BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();String password = encoder.encode("password");//user前兩個參數是進行認證的,第三個參數是當前用戶所擁有的權限,security回根據授權代碼進行驗證return new User(s, password, true, true, true, true,AuthorityUtils.commaSeparatedStringToAuthorityList("admin")); // AuthorityUtils.commaSeparatedStringToAuthorityList 這個方法是將一個字符一“,” 分割開} }?
posted @ 2019-05-02 22:38 巡山小妖N 閱讀(...) 評論(...) 編輯 收藏總結
以上是生活随笔為你收集整理的spring security 学习二的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: idea 使用maven构建项目时,ta
- 下一篇: bootstrap学习(三)表单