springboot security 安全機制
認證流程:
1、由認證配置WebSecurityConfigurerAdapter的configure(HttpSecurity http)方法進入,添加攔截器addFilterBefore
2、進入攔截器AbstractAuthenticationProcessingFilter的attemptAuthentication方法,指定認證對象AbstractAuthenticationToken
3、進入認證邏輯AuthenticationProvider,根據supports的斷定對認證的目標對象指定對哪個攔截器進行認證,進入具體的認證邏輯方法authenticate
4、認證結果:認證成功后進入攔截器的successfulAuthentication方法;認證失敗后進入攔截器的unsuccessfulAuthentication方法
5、對認證結果進行處理
a.認證成功的邏輯:進入SimpleUrlAuthenticationSuccessHandler的onAuthenticationSuccess方法
b.認證失敗的邏輯:進入SimpleUrlAuthenticationFailureHandler的onAuthenticationFailure方法
6、返回結果給頁面,將數據封裝在ObjectMapper對象中,將會以文本形式返回給客戶端(頁面)
代碼塊
自定義認證配置,實現WebSecurityConfigurerAdapter
package com.travelsky.config; import com.travelsky.auto.login.BeforeLoginFilter; import com.travelsky.auto.login.LoginProvider; import com.travelsky.auto.login.OnFailureLogin; import com.travelsky.auto.token.OnFailureToken; import com.travelsky.auto.token.TokenFilter; import com.travelsky.auto.token.TokenProvider; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter; import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; /** * 認證配置 */ @Configuration public class WebSecurityConfigurer extends WebSecurityConfigurerAdapter { /** * 認證成功處理 */ @Autowired private SimpleUrlAuthenticationSuccessHandler successHandler; /** * 登錄認證失敗后處理 */ @Autowired private OnFailureLogin failureHandler; /** * token認證失敗后處理 */ @Autowired private OnFailureToken onFailureToken; /** * 登錄認證邏輯 */ @Autowired private LoginProvider loginProvider; /** * token認證邏輯 */ @Autowired private TokenProvider tokenProvider; /** * 配置請求權限 * @param http * @throws Exception */ @Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() // 允許登錄路徑通過
.antMatchers("/login").permitAll() // 允許業務路徑通過
.antMatchers("/**").permitAll() .and() .authorizeRequests() .anyRequest() .authenticated() .and() .csrf().disable() // 添加攔截器
.addFilterBefore(getLoginFilter(), UsernamePasswordAuthenticationFilter.class) .addFilterBefore(getTokenFilter(), UsernamePasswordAuthenticationFilter.class); } /** * 登錄攔截器 * @return * @throws Exception */
private AbstractAuthenticationProcessingFilter getLoginFilter() throws Exception { BeforeLoginFilter beforeLoginFilter = new BeforeLoginFilter("/login", successHandler, failureHandler); beforeLoginFilter.setAuthenticationManager(super.authenticationManager()); return beforeLoginFilter; } /** * token攔截器 * @return * @throws Exception */
private AbstractAuthenticationProcessingFilter getTokenFilter() throws Exception { TokenFilter tokenFilter = new TokenFilter("/**", onFailureToken); tokenFilter.setAuthenticationManager(super.authenticationManager()); return tokenFilter; } /** * 配置認證邏輯列表,按順序進行認證 * @param auth */ @Override protected void configure(AuthenticationManagerBuilder auth) { auth.authenticationProvider(loginProvider); auth.authenticationProvider(tokenProvider); } }
自定義攔截器,繼承AbstractAuthenticationProcessingFilter
package com.travelsky.auto.login; import lombok.extern.slf4j.Slf4j; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter; import org.springframework.security.web.authentication.AuthenticationFailureHandler; import org.springframework.security.web.authentication.AuthenticationSuccessHandler; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; /** * 登錄自定義攔截器,繼承AbstractAuthenticationProcessingFilter */ @Slf4j public class BeforeLoginFilter extends AbstractAuthenticationProcessingFilter { /** *認證成功的處理對象 */
private AuthenticationSuccessHandler successHandler; /** * 認證失敗的處理對象 */
private AuthenticationFailureHandler failureHandler; public BeforeLoginFilter(String defaultFilterProcessesUrl, AuthenticationSuccessHandler successHandler, AuthenticationFailureHandler failureHandler) { super(defaultFilterProcessesUrl); this.successHandler = successHandler; this.failureHandler = failureHandler; } /** * 過濾器處理邏輯 * @param request * @param response * @return * @throws AuthenticationException * @throws IOException * @throws ServletException */ @Override public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException { log.info("進入登錄過濾器"); String userName = request.getParameter("userName"); String password = request.getParameter("password"); LoginInfo loginInfo = new LoginInfo(null, userName, password); log.info("username = {}", userName); log.info("password = {}", password); // 返回指定的認證對象
LoginAuthenticationToken loginAuthentication = new LoginAuthenticationToken(null, loginInfo, null); // 將來由認證邏輯AuthenticationProvider來處理,這里返回LoginAuthenticationToken對象,將來由AuthenticationProvider的supports方法相匹配的認證邏輯來處理
return this.getAuthenticationManager().authenticate(loginAuthentication); } /** * 認證邏輯認證成功成功后會調用此方法 * @param request * @param response * @param chain * @param authResult * @throws IOException * @throws ServletException */ @Override protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException { successHandler.onAuthenticationSuccess(request, response, authResult); } /** * 認證邏輯認證失敗后調用此方法 * @param request * @param response * @param failed * @throws IOException * @throws ServletException */ @Override protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException { failureHandler.onAuthenticationFailure(request, response, failed); } }
自定義認證邏輯,繼承AuthenticationProvider
package com.travelsky.auto.login; import com.alibaba.fastjson.JSONObject; import com.travelsky.pojo.MenuVO; import com.travelsky.pojo.SysUserInfo; import com.travelsky.service.SysUserInfoService; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.authentication.AuthenticationProvider; import org.springframework.security.authentication.AuthenticationServiceException; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.stereotype.Component; import java.util.List; /** * 認證邏輯 */ @Component @Slf4j public class LoginProvider implements AuthenticationProvider { @Autowired private SysUserInfoService userInfoService; /** * 認證邏輯,認證的目標對象由supports斷定,supports斷定的目標對象是LoginAuthenticationToken自定義認證對象 * @param authentication * @return * @throws AuthenticationException */ @Override public Authentication authenticate(Authentication authentication) throws AuthenticationException { log.info("登錄驗證器,接收參數:{}", JSONObject.toJSONString(authentication)); // Authentication的具體子類,可由supports斷定,supports斷定子類是LoginAuthenticationToken,因此可以強轉
final LoginAuthenticationToken loginAuthentication = (LoginAuthenticationToken) authentication; final LoginInfo loginInfo = loginAuthentication.getLoginInfo(); final String userName = loginInfo.getUserName(); final SysUserInfo userInfo = userInfoService.getByUserId(userName); if (userInfo.getPassword().equals(loginInfo.getPassword())) { final List<MenuVO> userInfoMenus = userInfoService.getUserInfoMenus(userInfo.getId()); return new LoginAuthenticationToken(null, loginInfo, userInfoMenus); } else { log.error("登錄驗證失敗"); throw new AuthenticationServiceException("登錄失敗........."); } } /** * 指定對誰進行認證,這里指定對LoginAuthenticationToken自定義認證對象進行認證 * @param authentication * @return
*/ @Override public boolean supports(Class<?> authentication) { return LoginAuthenticationToken.class.isAssignableFrom(authentication); } }
自定義認證成功的處理邏輯,繼承SimpleUrlAuthenticationSuccessHandler
package com.travelsky.auto.login; import com.alibaba.fastjson.JSONObject; import com.fasterxml.jackson.databind.ObjectMapper; import com.travelsky.auto.token.TokenContent; import com.travelsky.auto.token.TokenFactory; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.MediaType; import org.springframework.security.core.Authentication; import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler; import org.springframework.stereotype.Component; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; /** * 自定義認證成功處理邏輯,繼承SimpleUrlAuthenticationSuccessHandler類 */ @Component @Slf4j public class OnSuccessfulLogin extends SimpleUrlAuthenticationSuccessHandler { /** * 返回頁面的對象,可將對象轉化為文本形式(json格式) */ @Autowired private ObjectMapper objectMapper; /** * token工廠 */ @Autowired private TokenFactory tokenFactory; @Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { log.info("登錄認證成功,傳入參數:{}", JSONObject.toJSONString(authentication)); // 配置響應編碼格式為UTF-8
response.setCharacterEncoding("UTF-8"); // 配置返回格式為json
response.setContentType(MediaType.APPLICATION_JSON_VALUE); // Authentication的具體子類為LoginAuthenticationToken
final LoginAuthenticationToken loginAuthenticationToken = (LoginAuthenticationToken) authentication; final TokenContent tokenContent = tokenFactory.createToken("token"); JSONObject json = new JSONObject(); json.put("code", "000000"); json.put("message", "認證成功"); json.put("claims", tokenContent.getClaims()); json.put("token", tokenContent.getToken()); json.put("menuList", loginAuthenticationToken.getUserInfoMenus()); log.info("生產token:{}",tokenContent.getToken()); // 將數據保存到objectMapper中,將會轉化為文本格式,返回給頁面
objectMapper.writeValue(response.getWriter(), json); } }
自定義認證失敗邏輯,繼承SimpleUrlAuthenticationFailureHandler
package com.travelsky.auto.login; import com.alibaba.fastjson.JSONObject; import com.fasterxml.jackson.databind.ObjectMapper; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.MediaType; 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 public class OnFailureLogin extends SimpleUrlAuthenticationFailureHandler { @Autowired private ObjectMapper objectMapper; @Override public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException { log.info("登錄認證失敗,傳入參數:{}", JSONObject.toJSONString(exception)); response.setCharacterEncoding("UTF-8"); response.setContentType(MediaType.APPLICATION_JSON_VALUE); JSONObject json = new JSONObject(); json.put("code", "999999"); json.put("message", exception.getMessage()); objectMapper.writeValue(response.getWriter(), json); } }
自定義認證對象,繼承AbstractAuthenticationToken
package com.travelsky.auto.login; import com.travelsky.pojo.MenuVO; import org.springframework.security.authentication.AbstractAuthenticationToken; import org.springframework.security.core.GrantedAuthority; import java.util.Collection; import java.util.List; /** * 自定義登錄認證對象 */
public class LoginAuthenticationToken extends AbstractAuthenticationToken { /** * 登錄數據對象 */
private LoginInfo loginInfo; /** * 菜單數據對象 */
private List<MenuVO> userInfoMenus; LoginAuthenticationToken(Collection<? extends GrantedAuthority> authorities, LoginInfo loginInfo, List<MenuVO> userInfoMenus) { super(authorities); this.userInfoMenus = userInfoMenus; this.loginInfo = loginInfo; } @Override public Object getCredentials() { return loginInfo.getPassword(); } @Override public Object getPrincipal() { return loginInfo.getUserName(); } public LoginInfo getLoginInfo() { return loginInfo; } public List<MenuVO> getUserInfoMenus() { return userInfoMenus; } }
自定義攔截器:繼承AbstractAuthenticationProcessingFilter,重寫attemptAuthentication(HttpServletRequest request, HttpServletResponse response) 方法,返回AbstractAuthenticationToken的子類,AbstractAuthenticationToken的子類可自己定義內容(例如:用戶信息,token字符串,Claims),只要繼承AbstractAuthenticationToken即可
自定義驗證規則:實現AuthenticationProvider接口,authenticate方法參數Authentication與攔截器attemptAuthentication方法返回的是同一個對象,重寫 authenticate(Authentication authentication),其中supports(Class<?> authentication)方法用來指定當前檢驗規則是針對哪個AbstractAuthenticationToken子類對象。springboot自帶默認的登錄驗證是返回UsernamePasswordAuthenticationToken對象。