Spring Security源碼解析

秒懂SpringBoot之全網最易懂的Spring Security教程
SpringBoot整合Spring-Security 認證篇(保姆級教程)
SpringBoot整合Spring Security【超詳細教程】
spring security 超詳細使用教程(接入springboot、前后端分離)

Security 自定義 UsernamePasswordAuthenticationFilter 替換原攔截器
SpringSecurity自定義UsernamePasswordAuthenticationFilter
自定義過濾器替換 UsernamePasswordAuthenticationFilter

Spring Security 實戰干貨:必須掌握的一些內置 Filter
Spring Security權限控制框架使用指南

你真的了解 Cookie 和 Session 嗎?
Session 、Cookie和Token三者的關系和區別

簡單使用 Spring Security

pom.xml

<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!--springSecurity--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId></dependency>
</dependencies>

啟動類

@SpringBootApplication
public class SpringBootSecurityApplication {public static void main(String[] args) {SpringApplication.run(SpringBootSecurityApplication.class, args);}
}

配置文件

server:port: 8001spring:security:user:name: adminpassword: 123456

controller

@RestController
@RequestMapping("/auth")
public class TestController {@GetMapping("/hello")public String sayHello(){return "hello security";}
}

啟動項目
訪問 http://localhost:8001/auth/hello 會出現登錄頁面
在這里插入圖片描述

輸入賬號密碼 正常輸出

如果沒有配置賬號密碼
username:user
password:隨機生成,會打印在你的控制臺日志上。

Spring Security 代碼執行流程

一個請求過來Spring Security會按照下圖的步驟處理:
在這里插入圖片描述

進入到Filter中 UsernamePasswordAuthenticationFilter

在這里插入圖片描述
進入 UsernamePasswordAuthenticationFilter

public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)throws IOException, ServletException {HttpServletRequest request = (HttpServletRequest) req;HttpServletResponse response = (HttpServletResponse) res;if (!requiresAuthentication(request, response)) {chain.doFilter(request, response);return;}if (logger.isDebugEnabled()) {logger.debug("Request is to process authentication");}Authentication authResult;try {authResult = attemptAuthentication(request, response);if (authResult == null) {return;}//此處底層會設置  JSESSIONID 并存儲 JSESSIONIDsessionStrategy.onAuthentication(authResult, request, response);}catch (InternalAuthenticationServiceException failed) {//失敗處理unsuccessfulAuthentication(request, response, failed);return;}catch (AuthenticationException failed) {//失敗處理unsuccessfulAuthentication(request, response, failed);return;}// Authentication successif (continueChainBeforeSuccessfulAuthentication) {chain.doFilter(request, response);}//成功后處理successfulAuthentication(request, response, chain, authResult);
}
//UsernamePasswordAuthenticationFilter.java
public Authentication attemptAuthentication(HttpServletRequest request,HttpServletResponse response) throws AuthenticationException {...String username = obtainUsername(request);String password = obtainPassword(request);...UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);...//this.getAuthenticationManager()  默認為 ProviderManagerreturn this.getAuthenticationManager().authenticate(authRequest);
}

進入 ProviderManager
循環執行 AuthenticationProvider 的 authenticate 方法

//ProviderManager.java
public Authentication authenticate(Authentication authentication)throws AuthenticationException {...for (AuthenticationProvider provider : getProviders()) {...result = provider.authenticate(authentication);if (result != null) {if (eraseCredentialsAfterAuthentication&& (result instanceof CredentialsContainer)) {...//此處會將密碼設置為空((CredentialsContainer) result).eraseCredentials();}...return result;}...}...
}

默認執行 DaoAuthenticationProvider 的 authenticate

//AbstractUserDetailsAuthenticationProvider.java
public Authentication authenticate(Authentication authentication)throws AuthenticationException {...// Determine usernameString username = (authentication.getPrincipal() == null) ? "NONE_PROVIDED": authentication.getName();...//獲取用戶信息user = retrieveUser(username,(UsernamePasswordAuthenticationToken) authentication);...try {//校驗用戶密碼additionalAuthenticationChecks(user,(UsernamePasswordAuthenticationToken) authentication);}...return createSuccessAuthentication(principalToReturn, authentication, user);
}

SessionId 處理

sessionStrategy.onAuthentication 最終回執行到
//設置 Set-Cookie

// sessionStrategy.onAuthentication 最終回執行到 
//org.apache.catalina.connector.Request
public String changeSessionId() {Session session = this.getSessionInternal(false);if (session == null) {throw new IllegalStateException(sm.getString("coyoteRequest.changeSessionId"));}// StandardManagerManager manager = this.getContext().getManager();//獲取新的 SessionIdString newSessionId = manager.rotateSessionId(session);//將 SessionId 設置到 response 中this.changeSessionId(newSessionId);return newSessionId;
}
  • 獲取新 SessionId 并將 SessionId 存儲
    manager.rotateSessionId 會生成新的 SessionId 并將 SessionId 存儲到 StandardManager(父級 ManagerBase) 的
    protected Map<String, Session> sessions = new ConcurrentHashMap<>()
@Override
public String rotateSessionId(Session session) {String newId = generateSessionId();changeSessionId(session, newId, true, true);return newId;
}protected void changeSessionId(Session session, String newId,boolean notifySessionListeners, boolean notifyContainerListeners) {String oldId = session.getIdInternal();session.setId(newId, false);session.tellChangedSessionId(newId, oldId,notifySessionListeners, notifyContainerListeners);
}@Override
public void setId(String id, boolean notify) {if ((this.id != null) && (manager != null))manager.remove(this);this.id = id;if (manager != null)manager.add(this);if (notify) {tellNew();}
}@Override
public void add(Session session) {sessions.put(session.getIdInternal(), session);int size = getActiveSessions();if( size > maxActive ) {synchronized(maxActiveUpdateLock) {if( size > maxActive ) {maxActive = size;}}}
}
  • 重設 需要返回的 SessionId
public void changeSessionId(String newSessionId) {...if (response != null) {Cookie newCookie = ApplicationSessionCookieConfig.createSessionCookie(context,newSessionId, isSecure());response.addSessionCookieInternal(newCookie);}
}public void addSessionCookieInternal(final Cookie cookie) {if (isCommitted()) {return;}String name = cookie.getName();final String headername = "Set-Cookie";...if (!set) {addHeader(headername, header);}
}

登錄時session 校驗

通過斷點可發現 最終回執行到 StandardManager(父級 ManagerBase) 的 findSession 中
剛好使用了 上面生成的 sessionId

//ManagerBase.java
@Override
public Session findSession(String id) throws IOException {if (id == null) {return null;}return sessions.get(id);
}

獲取用戶信息

//DaoAuthenticationProvider.java
protected final UserDetails retrieveUser(String username,UsernamePasswordAuthenticationToken authentication)throws AuthenticationException {...UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);if (loadedUser == null) {throw new InternalAuthenticationServiceException("UserDetailsService returned null, which is an interface contract violation");}return loadedUser;...
}

執行 getUserDetailsService(UserDetailsService) 獲取 用戶信息
默認執行到 InMemoryUserDetailsManager 中的 loadUserByUsername方法

// InMemoryUserDetailsManager.java
public UserDetails loadUserByUsername(String username)throws UsernameNotFoundException {UserDetails user = users.get(username.toLowerCase());if (user == null) {throw new UsernameNotFoundException(username);}return new User(user.getUsername(), user.getPassword(), user.isEnabled(),user.isAccountNonExpired(), user.isCredentialsNonExpired(),user.isAccountNonLocked(), user.getAuthorities());
}

校驗用戶密碼是否正確

//DaoAuthenticationProvider.java
protected void additionalAuthenticationChecks(UserDetails userDetails,UsernamePasswordAuthenticationToken authentication)throws AuthenticationException {if (authentication.getCredentials() == null) {logger.debug("Authentication failed: no credentials provided");throw new BadCredentialsException(messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials","Bad credentials"));}String presentedPassword = authentication.getCredentials().toString();//判斷密碼是否正確if (!passwordEncoder.matches(presentedPassword, userDetails.getPassword())) {logger.debug("Authentication failed: password does not match stored value");throw new BadCredentialsException(messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials","Bad credentials"));}
}

根據以上執行流程 可針對相應步驟自定義內容

自定義配置相關步驟

自定義 UserDetailsService

默認是 InMemoryUserDetailsManager

@Service
public class UserDetailService implements UserDetailsService {@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();SysUser sysUser = new SysUser();sysUser.setUsername("admin");sysUser.setPassword(bCryptPasswordEncoder.encode("13579"));Map<String, SysUser> map = new HashMap<>();map.put(sysUser.getUsername(), sysUser);return map.get(username);}
}

用自定義的 UserDetailsService 時 需設置加密方式

@Bean
public BCryptPasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder();
}

也可通過在 config 中配置對應的實現類

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {//配置對應的權限相關對象}
}

自定義 AuthenticationProvider

默認是 DaoAuthenticationProvider

@Component
public class MyAuthenticationProvider implements AuthenticationProvider {@Autowiredprivate UserDetailService userDetailService;@Overridepublic Authentication authenticate(Authentication authentication) throws AuthenticationException {String username = authentication.getName();String presentedPassword = (String) authentication.getCredentials();// 根據用戶名獲取用戶信息UserDetails userDetails = this.userDetailService.loadUserByUsername(username);if (StringUtils.isEmpty(userDetails)) {throw new BadCredentialsException("用戶名不存在");} else {//校驗 將輸入的密碼presentedPassword加密  和 數據庫中的密碼userDetails.getPassword() 校驗是否一致UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken(userDetails, authentication.getCredentials(), userDetails.getAuthorities());result.setDetails(authentication.getDetails());return result;}}@Overridepublic boolean supports(Class<?> authentication) {return true;}
}

自定義 AuthenticationManager

默認是 ProviderManager

@Beanprotected AuthenticationManager authenticationManager() throws Exception {ProviderManager manager = new ProviderManager(Arrays.asList(myAuthenticationProvider));return manager;} 

自定義 Filter, 重寫 UsernamePasswordAuthenticationFilter

public class LoginFilter extends UsernamePasswordAuthenticationFilter {private AuthenticationManager authenticationManager;public LoginFilter(AuthenticationManager authenticationManager) {this.authenticationManager = authenticationManager;}//這個方法是用來去嘗試驗證用戶的@Overridepublic Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {try {String username = request.getParameter("username");String password = request.getParameter("password");return authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password));} catch (Exception e) {try {response.setContentType("application/json;charset=utf-8");response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);PrintWriter out = response.getWriter();Map<String, Object> map = new HashMap<>();map.put("code", HttpServletResponse.SC_UNAUTHORIZED);map.put("message", "賬號或密碼錯誤!");out.write(new ObjectMapper().writeValueAsString(map));out.flush();out.close();} catch (Exception e1) {e1.printStackTrace();}throw new RuntimeException(e);}}//成功之后執行的方法@Overridepublic void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {SysUser sysUser = new SysUser();sysUser.setUsername(authResult.getName());String token = JwtToolUtils.tokenCreate(authResult.getName(), 600);response.addHeader("Authorization", "RobodToken " + token);    //將Token信息返回給用戶try {//登錄成功時,返回json格式進行提示response.setContentType("application/json;charset=utf-8");response.setStatus(HttpServletResponse.SC_OK);PrintWriter out = response.getWriter();Map<String, Object> map = new HashMap<String, Object>(4);map.put("code", HttpServletResponse.SC_OK);map.put("message", "登陸成功!");out.write(new ObjectMapper().writeValueAsString(map));out.flush();out.close();} catch (Exception e1) {e1.printStackTrace();}//執行父級流程//super.successfulAuthentication(request, response, chain, authResult);}
}

自定義 Filter, 添加在 UsernamePasswordAuthenticationFilter 之前

@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {@Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException, ServletException, IOException {//獲取tokenString token = request.getHeader("token");if (!StringUtils.hasText(token)) {//token為空的話, 就不管它, 讓SpringSecurity中的其他過濾器處理請求//請求放行filterChain.doFilter(request, response);return;}//jwt 解析token 后的用戶信息SysUser securityUser = new SysUser();securityUser.setUsername("aa");securityUser.setPassword("123");//將用戶安全信息存入SecurityContextHolder, 在之后SpringSecurity的過濾器就不會攔截UsernamePasswordAuthenticationToken authenticationToken =new UsernamePasswordAuthenticationToken(securityUser, null, null);SecurityContextHolder.getContext().setAuthentication(authenticationToken);//放行filterChain.doFilter(request, response);}
}

配置自定義的過濾器

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {@AutowiredJwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;//配置SpringSecurity  Http 相關信息@Overridepublic void configure(HttpSecurity http) throws Exception {http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);}
}

項目啟動相關初始化

Security 賬號密碼加載

UserDetailsServiceAutoConfiguration 加載時

獲取 SecurityProperties 配置文件中配置的賬號密碼, 如果密碼是默認的隨機生成的,將密碼輸入到控制臺

//UserDetailsServiceAutoConfiguration.java
@Bean
@ConditionalOnMissingBean(type = "org.springframework.security.oauth2.client.registration.ClientRegistrationRepository")
@Lazy
public InMemoryUserDetailsManager inMemoryUserDetailsManager(SecurityProperties properties,ObjectProvider<PasswordEncoder> passwordEncoder) {SecurityProperties.User user = properties.getUser();List<String> roles = user.getRoles();return new InMemoryUserDetailsManager(User.withUsername(user.getName()).password(getOrDeducePassword(user, passwordEncoder.getIfAvailable())).roles(StringUtils.toStringArray(roles)).build());
}private String getOrDeducePassword(SecurityProperties.User user, PasswordEncoder encoder) {String password = user.getPassword();//如果是默認動態生成的,輸出到控制臺if (user.isPasswordGenerated()) {logger.info(String.format("%n%nUsing generated security password: %s%n", user.getPassword()));}if (encoder != null || PASSWORD_ALGORITHM_PATTERN.matcher(password).matches()) {return password;}return NOOP_PASSWORD_PREFIX + password;
}

SecurityProperties.java
默認賬號 user
默認密碼隨機生成
passwordGenerated 設置密碼是否是隨機生成的。 默認為true, 當配置的密碼不為空時,置為false

public class SecurityProperties {...private User user = new User();...public static class User {private String name = "user";private String password = UUID.randomUUID().toString();private boolean passwordGenerated = true;...public void setPassword(String password) {if (!StringUtils.hasLength(password)) {return;}this.passwordGenerated = false;this.password = password;}...}
}

設置默認的用戶賬號信息(users)

public InMemoryUserDetailsManager(UserDetails... users) {for (UserDetails user : users) {createUser(user);}
}
public void createUser(UserDetails user) {Assert.isTrue(!userExists(user.getUsername()), "user should not exist");users.put(user.getUsername().toLowerCase(), new MutableUser(user));
}

在這里插入圖片描述

WebSecurityConfiguration 加載

涉及到的Configuration

ReactiveUserDetailsServiceAutoConfiguration@AutoConfigureAfterRSocketMessagingAutoConfigurationSecurityAutoConfiguration#importSpringBootWebSecurityConfigurationWebSecurityEnablerConfiguration@EnableWebSecurity #importWebSecurityConfigurationSpringWebMvcImportSelectorOAuth2ImportSelector@EnableGlobalAuthenticationAuthenticationConfiguration@ImportObjectPostProcessorConfigurationSecurityDataConfigurationSecurityFilterAutoConfiguration	UserDetailsServiceAutoConfiguration

加載流程

當未配置自定義的 WebSecurityConfigurerAdapter@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(WebSecurityConfigurerAdapter.class)
@ConditionalOnMissingBean(WebSecurityConfigurerAdapter.class)
@ConditionalOnWebApplication(type = Type.SERVLET)
public class SpringBootWebSecurityConfiguration {@Configuration(proxyBeanMethods = false)@Order(SecurityProperties.BASIC_AUTH_ORDER)static class DefaultConfigurerAdapter extends WebSecurityConfigurerAdapter {}
}@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(AuthenticationManager.class)
@ConditionalOnBean(ObjectPostProcessor.class)
@ConditionalOnMissingBean(value = { AuthenticationManager.class, AuthenticationProvider.class, UserDetailsService.class },type = { "org.springframework.security.oauth2.jwt.JwtDecoder","org.springframework.security.oauth2.server.resource.introspection.OpaqueTokenIntrospector" })
public class UserDetailsServiceAutoConfiguration {private static final String NOOP_PASSWORD_PREFIX = "{noop}";private static final Pattern PASSWORD_ALGORITHM_PATTERN = Pattern.compile("^\\{.+}.*$");private static final Log logger = LogFactory.getLog(UserDetailsServiceAutoConfiguration.class);@Bean@ConditionalOnMissingBean(type = "org.springframework.security.oauth2.client.registration.ClientRegistrationRepository")@Lazypublic InMemoryUserDetailsManager inMemoryUserDetailsManager(SecurityProperties properties,ObjectProvider<PasswordEncoder> passwordEncoder) {SecurityProperties.User user = properties.getUser();List<String> roles = user.getRoles();return new InMemoryUserDetailsManager(User.withUsername(user.getName()).password(getOrDeducePassword(user, passwordEncoder.getIfAvailable())).roles(StringUtils.toStringArray(roles)).build());}private String getOrDeducePassword(SecurityProperties.User user, PasswordEncoder encoder) {String password = user.getPassword();if (user.isPasswordGenerated()) {logger.info(String.format("%n%nUsing generated security password: %s%n", user.getPassword()));}if (encoder != null || PASSWORD_ALGORITHM_PATTERN.matcher(password).matches()) {return password;}return NOOP_PASSWORD_PREFIX + password;}}@Configuration(proxyBeanMethods = false)
@Import(ObjectPostProcessorConfiguration.class)
public class AuthenticationConfiguration {@Autowired(required = false)public void setGlobalAuthenticationConfigurers(List<GlobalAuthenticationConfigurerAdapter> configurers) {configurers.sort(AnnotationAwareOrderComparator.INSTANCE);this.globalAuthConfigurers = configurers;}
}WebSecurityConfiguration 中的  setFilterChainProxySecurityConfigurer 加載會 獲取所有 SecurityConfigurer 的實現類 對 獲取到的 SecurityConfigurer 集合排序循環執行 webSecurity.apply(webSecurityConfigurer) 將 webSecurityConfigurer 添加到 webSecurity 的 configurers 中設置 this.webSecurityConfigurers = webSecurityConfigurers;@Configuration(proxyBeanMethods = false)
public class WebSecurityConfiguration implements ImportAware, BeanClassLoaderAware {@Autowired(required = false)public void setFilterChainProxySecurityConfigurer(ObjectPostProcessor<Object> objectPostProcessor,@Value("#{@autowiredWebSecurityConfigurersIgnoreParents.getWebSecurityConfigurers()}") List<SecurityConfigurer<Filter, WebSecurity>> webSecurityConfigurers)throws Exception {webSecurity = objectPostProcessor.postProcess(new WebSecurity(objectPostProcessor));if (debugEnabled != null) {webSecurity.debug(debugEnabled);}webSecurityConfigurers.sort(AnnotationAwareOrderComparator.INSTANCE);Integer previousOrder = null;Object previousConfig = null;for (SecurityConfigurer<Filter, WebSecurity> config : webSecurityConfigurers) {Integer order = AnnotationAwareOrderComparator.lookupOrder(config);if (previousOrder != null && previousOrder.equals(order)) {throw new IllegalStateException("@Order on WebSecurityConfigurers must be unique. Order of "+ order + " was already used on " + previousConfig + ", so it cannot be used on "+ config + " too.");}previousOrder = order;previousConfig = config;}for (SecurityConfigurer<Filter, WebSecurity> webSecurityConfigurer : webSecurityConfigurers) {webSecurity.apply(webSecurityConfigurer);}this.webSecurityConfigurers = webSecurityConfigurers;}@Bean@DependsOn(AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME)public SecurityExpressionHandler<FilterInvocation> webSecurityExpressionHandler() {return webSecurity.getExpressionHandler();}@Bean(name = AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME)public Filter springSecurityFilterChain() throws Exception {boolean hasConfigurers = webSecurityConfigurers != null&& !webSecurityConfigurers.isEmpty();if (!hasConfigurers) {WebSecurityConfigurerAdapter adapter = objectObjectPostProcessor.postProcess(new WebSecurityConfigurerAdapter() {});webSecurity.apply(adapter);}return webSecurity.build();}
}WebSecurityConfiguration 中的 webSecurityExpressionHandler 加載 @DependsOn 依賴 springSecurityFilterChainWebSecurityConfiguration 中的 springSecurityFilterChain 加載1、判斷是否有定義的 webSecurityConfigurers2、執行 webSecurity.build()執行到 AbstractSecurityBuilder 中的 build 方法執行到 AbstractConfiguredSecurityBuilder 中的 doBuild 方法@Overrideprotected final O doBuild() throws Exception {synchronized (configurers) {buildState = BuildState.INITIALIZING;beforeInit();init();buildState = BuildState.CONFIGURING;beforeConfigure();configure();buildState = BuildState.BUILDING;O result = performBuild();buildState = BuildState.BUILT;return result;}}整體執行流程1beforeInit();2init(); 循環執行 configurers 的 init 方法執行到 WebSecurityConfigurerAdapter 中的 init 方法public void init(final WebSecurity web) throws Exception {final HttpSecurity http = getHttp();web.addSecurityFilterChainBuilder(http).postBuildAction(() -> {FilterSecurityInterceptor securityInterceptor = http.getSharedObject(FilterSecurityInterceptor.class);web.securityInterceptor(securityInterceptor);});}protected final HttpSecurity getHttp() throws Exception {if (http != null) {return http;}AuthenticationEventPublisher eventPublisher = getAuthenticationEventPublisher();localConfigureAuthenticationBldr.authenticationEventPublisher(eventPublisher);AuthenticationManager authenticationManager = authenticationManager();authenticationBuilder.parentAuthenticationManager(authenticationManager);Map<Class<?>, Object> sharedObjects = createSharedObjects();http = new HttpSecurity(objectPostProcessor, authenticationBuilder,sharedObjects);...configure(http);return http;}authenticationManager(); 獲取 AuthenticationManager protected AuthenticationManager authenticationManager() throws Exception {...authenticationManager = authenticationConfiguration.getAuthenticationManager();...}public AuthenticationManager getAuthenticationManager() throws Exception {...for (GlobalAuthenticationConfigurerAdapter config : globalAuthConfigurers) {authBuilder.apply(config);}authenticationManager = authBuilder.build();...}globalAuthConfigurers 獲取 GlobalAuthenticationConfigurerAdapter 的實現類 //TODO 循環執行 authBuilder.apply(config)GlobalAuthenticationConfigurerAdapter 添加到 authBuilder 的 configurers 中authBuilder.build();public final O build() throws Exception {if (this.building.compareAndSet(false, true)) {this.object = doBuild();return this.object;}throw new AlreadyBuiltException("This object has already been built");}執行到 AbstractSecurityBuilder 中的 build 方法執行到 AbstractConfiguredSecurityBuilder 中的 doBuild 方法執行流程同上InitializeUserDetailsManagerConfigurerpublic void configure(AuthenticationManagerBuilder auth) throws Exception {if (auth.isConfigured()) {return;}UserDetailsService userDetailsService = getBeanOrNull(UserDetailsService.class);if (userDetailsService == null) {return;}PasswordEncoder passwordEncoder = getBeanOrNull(PasswordEncoder.class);UserDetailsPasswordService passwordManager = getBeanOrNull(UserDetailsPasswordService.class);DaoAuthenticationProvider provider = new DaoAuthenticationProvider();provider.setUserDetailsService(userDetailsService);if (passwordEncoder != null) {provider.setPasswordEncoder(passwordEncoder);}if (passwordManager != null) {provider.setUserDetailsPasswordService(passwordManager);}provider.afterPropertiesSet();auth.authenticationProvider(provider);}1、獲取 UserDetailsService (InMemoryUserDetailsManager)2、獲取 PasswordEncoder 為空3、獲取 UserDetailsPasswordService (InMemoryUserDetailsManager)4DaoAuthenticationProvider provider = new DaoAuthenticationProvider();public DaoAuthenticationProvider() {setPasswordEncoder(PasswordEncoderFactories.createDelegatingPasswordEncoder());}public static PasswordEncoder createDelegatingPasswordEncoder() {String encodingId = "bcrypt";Map<String, PasswordEncoder> encoders = new HashMap<>();encoders.put(encodingId, new BCryptPasswordEncoder());encoders.put("ldap", new org.springframework.security.crypto.password.LdapShaPasswordEncoder());encoders.put("MD4", new org.springframework.security.crypto.password.Md4PasswordEncoder());encoders.put("MD5", new org.springframework.security.crypto.password.MessageDigestPasswordEncoder("MD5"));encoders.put("noop", org.springframework.security.crypto.password.NoOpPasswordEncoder.getInstance());encoders.put("pbkdf2", new Pbkdf2PasswordEncoder());encoders.put("scrypt", new SCryptPasswordEncoder());encoders.put("SHA-1", new org.springframework.security.crypto.password.MessageDigestPasswordEncoder("SHA-1"));encoders.put("SHA-256", new org.springframework.security.crypto.password.MessageDigestPasswordEncoder("SHA-256"));encoders.put("sha256", new org.springframework.security.crypto.password.StandardPasswordEncoder());encoders.put("argon2", new Argon2PasswordEncoder());return new DelegatingPasswordEncoder(encodingId, encoders);}設置加密方式5、設置對應的 UserDetailsServicePasswordEncoderUserDetailsPasswordService6、執行 afterPropertiesSet7、auth.authenticationProvider 往 AuthenticationManagerBuilder 的 authenticationProviders 中添加數據會執行到 AuthenticationManagerBuilder@Overrideprotected ProviderManager performBuild() throws Exception {if (!isConfigured()) {logger.debug("No authenticationProviders and no parentAuthenticationManager defined. Returning null.");return null;}ProviderManager providerManager = new ProviderManager(authenticationProviders,parentAuthenticationManager);if (eraseCredentials != null) {providerManager.setEraseCredentialsAfterAuthentication(eraseCredentials);}if (eventPublisher != null) {providerManager.setAuthenticationEventPublisher(eventPublisher);}providerManager = postProcess(providerManager);return providerManager;}為 http 的 configurers 添加對應的 configurerconfigure(http);protected void configure(HttpSecurity http) throws Exception {logger.debug("Using default configure(HttpSecurity). If subclassed this will potentially override subclass configure(HttpSecurity).");http.authorizeRequests().anyRequest().authenticated().and().formLogin().and().httpBasic();}public FormLoginConfigurer<HttpSecurity> formLogin() throws Exception {return getOrApply(new FormLoginConfigurer<>());}public FormLoginConfigurer() {super(new UsernamePasswordAuthenticationFilter(), null);usernameParameter("username");passwordParameter("password");}// 調用 login 接口才會進入 UsernamePasswordAuthenticationFilter過濾器public UsernamePasswordAuthenticationFilter() {super(new AntPathRequestMatcher("/login", "POST"));}protected AbstractAuthenticationFilterConfigurer(F authenticationFilter,String defaultLoginProcessingUrl) {this();this.authFilter = authenticationFilter;if (defaultLoginProcessingUrl != null) {loginProcessingUrl(defaultLoginProcessingUrl);}}addSecurityFilterChainBuilder(http)//為 WebSecurity 的 securityFilterChainBuilders 中添加數據public WebSecurity addSecurityFilterChainBuilder(SecurityBuilder<? extends SecurityFilterChain> securityFilterChainBuilder) {this.securityFilterChainBuilders.add(securityFilterChainBuilder);return this;}3beforeConfigure();4configure(); 循環執行 configurers 的 configure 方法執行 自定義的 configure 方法5performBuild();@Overrideprotected Filter performBuild() throws Exception {...for (SecurityBuilder<? extends SecurityFilterChain> securityFilterChainBuilder : securityFilterChainBuilders) {securityFilterChains.add(securityFilterChainBuilder.build());}...Filter result = filterChainProxy;postBuildAction.run();return result;}此時的 securityFilterChainBuilders 是 HttpSecuritysecurityFilterChainBuilder.build()循環執行 securityFilterChainBuilder 中的 configurers執行 FormLoginConfigurer 的 configure 方法 // AbstractAuthenticationFilterConfigurer.class@Overridepublic void configure(B http) throws Exception {PortMapper portMapper = http.getSharedObject(PortMapper.class);if (portMapper != null) {authenticationEntryPoint.setPortMapper(portMapper);}RequestCache requestCache = http.getSharedObject(RequestCache.class);if (requestCache != null) {this.defaultSuccessHandler.setRequestCache(requestCache);}authFilter.setAuthenticationManager(http.getSharedObject(AuthenticationManager.class));authFilter.setAuthenticationSuccessHandler(successHandler);authFilter.setAuthenticationFailureHandler(failureHandler);if (authenticationDetailsSource != null) {authFilter.setAuthenticationDetailsSource(authenticationDetailsSource);}SessionAuthenticationStrategy sessionAuthenticationStrategy = http.getSharedObject(SessionAuthenticationStrategy.class);if (sessionAuthenticationStrategy != null) {authFilter.setSessionAuthenticationStrategy(sessionAuthenticationStrategy);}RememberMeServices rememberMeServices = http.getSharedObject(RememberMeServices.class);if (rememberMeServices != null) {authFilter.setRememberMeServices(rememberMeServices);}F filter = postProcess(authFilter);http.addFilter(filter);}//authFilter 為 UsernamePasswordAuthenticationFilter.java將 filter 添加到 HttpSecurity 中, 在過濾器中使用

過濾器鏈

FilterComparator() {Step order = new Step(INITIAL_ORDER, ORDER_STEP);put(ChannelProcessingFilter.class, order.next());put(ConcurrentSessionFilter.class, order.next());put(WebAsyncManagerIntegrationFilter.class, order.next());put(SecurityContextPersistenceFilter.class, order.next());put(HeaderWriterFilter.class, order.next());put(CorsFilter.class, order.next());put(CsrfFilter.class, order.next());put(LogoutFilter.class, order.next());filterToOrder.put("org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestRedirectFilter",order.next());filterToOrder.put("org.springframework.security.saml2.provider.service.servlet.filter.Saml2WebSsoAuthenticationRequestFilter",order.next());put(X509AuthenticationFilter.class, order.next());put(AbstractPreAuthenticatedProcessingFilter.class, order.next());filterToOrder.put("org.springframework.security.cas.web.CasAuthenticationFilter",order.next());filterToOrder.put("org.springframework.security.oauth2.client.web.OAuth2LoginAuthenticationFilter",order.next());filterToOrder.put("org.springframework.security.saml2.provider.service.servlet.filter.Saml2WebSsoAuthenticationFilter",order.next());put(UsernamePasswordAuthenticationFilter.class, order.next());put(ConcurrentSessionFilter.class, order.next());filterToOrder.put("org.springframework.security.openid.OpenIDAuthenticationFilter", order.next());put(DefaultLoginPageGeneratingFilter.class, order.next());put(DefaultLogoutPageGeneratingFilter.class, order.next());put(ConcurrentSessionFilter.class, order.next());put(DigestAuthenticationFilter.class, order.next());filterToOrder.put("org.springframework.security.oauth2.server.resource.web.BearerTokenAuthenticationFilter", order.next());put(BasicAuthenticationFilter.class, order.next());put(RequestCacheAwareFilter.class, order.next());put(SecurityContextHolderAwareRequestFilter.class, order.next());put(JaasApiIntegrationFilter.class, order.next());put(RememberMeAuthenticationFilter.class, order.next());put(AnonymousAuthenticationFilter.class, order.next());filterToOrder.put("org.springframework.security.oauth2.client.web.OAuth2AuthorizationCodeGrantFilter",order.next());put(SessionManagementFilter.class, order.next());put(ExceptionTranslationFilter.class, order.next());put(FilterSecurityInterceptor.class, order.next());put(SwitchUserFilter.class, order.next());}

異常問題處理過程

1、通過 postman 訪問登錄接口異常

未配置自定義的 WebSecurityConfigurerAdapter 時
通過 postman 訪問頁面登錄的接口, 獲取 JSESSIONID 失敗

curl --location 'http://localhost:8001/login' \
--header 'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--header 'Cookie: JSESSIONID=24E4E66AD4D606C8F98022A30ABD49F9' \
--data-urlencode 'username=admin' \
--data-urlencode 'password=2'

是因為 CsrfFilter 中 doFilterInternal 有個 !csrfToken.getToken().equals(actualToken) 導致獲取失敗

protected void doFilterInternal(HttpServletRequest request,HttpServletResponse response, FilterChain filterChain)throws ServletException, IOException {request.setAttribute(HttpServletResponse.class.getName(), response);CsrfToken csrfToken = this.tokenRepository.loadToken(request);...if (!csrfToken.getToken().equals(actualToken)) {...}filterChain.doFilter(request, response);
}

處理方式

  • 1、需要在 headers 中增加 X-CSRF-TOKEN 參數
    在這里插入圖片描述

  • 2、禁用 csrf
    參照 WebSecurityConfigurerAdapter 的配置 增加 禁用 csrf

@Override
protected void configure(HttpSecurity http) throws Exception {http.csrf().disable().authorizeRequests().anyRequest().authenticated().and().formLogin().and().httpBasic();
}

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/81085.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/81085.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/81085.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

LeetCode 3392.統計符合條件長度為 3 的子數組數目:一次遍歷模擬

【LetMeFly】3392.統計符合條件長度為 3 的子數組數目&#xff1a;一次遍歷模擬 力扣題目鏈接&#xff1a;https://leetcode.cn/problems/count-subarrays-of-length-three-with-a-condition/ 給你一個整數數組 nums &#xff0c;請你返回長度為 3 的 子數組&#xff0c;滿足…

讀論文筆記-CoOp:對CLIP的handcrafted改進

讀論文筆記-Learning to Prompt for Vision-Language Models Problems 現有基于prompt engineering的多模態模型在設計合適的prompt時有很大困難&#xff0c;從而設計了一種更簡單的方法來制作prompt。 Motivations prompt engineering雖然促進了視覺表示的學習&#xff0c…

從零構建 MCP Server 與 Client:打造你的第一個 AI 工具集成應用

目錄 &#x1f680; 從零構建 MCP Server 與 Client&#xff1a;打造你的第一個 AI 工具集成應用 &#x1f9f1; 1. 準備工作 &#x1f6e0;? 2. 構建 MCP Server&#xff08;服務端&#xff09; 2.1 初始化服務器 &#x1f9e9; 3. 添加自定義工具&#xff08;Tools&…

Django 自定義celery-beat調度器,查詢自定義表的Cron表達式進行任務調度

學習目標&#xff1a; 通過自定義的CronScheduler調度器在兼容標準的調度器的情況下&#xff0c;查詢自定義任務表去生成調度任務并分配給celery worker進行執行 不了解Celery框架的小伙伴可以先看一下我的上一篇文章&#xff1a;Celery框架組件分析及使用 學習內容&#xff…

藍橋杯 1. 確定字符串是否包含唯一字符

確定字符串是否包含唯一字符 原題目鏈接 題目描述 實現一個算法來識別一個字符串的字符是否是唯一的&#xff08;忽略字母大小寫&#xff09;。 若唯一&#xff0c;則輸出 YES&#xff0c;否則輸出 NO。 輸入描述 輸入一行字符串&#xff0c;長度不超過 100。 輸出描述 輸…

a-upload組件實現文件的上傳——.pdf,.ppt,.pptx,.doc,.docx,.xls,.xlsx,.txt

實現下面的上傳/下載/刪除功能&#xff1a;要求支持&#xff1a;【.pdf,.ppt,.pptx,.doc,.docx,.xls,.xlsx,.txt】 分析上面的效果圖&#xff0c;分為【上傳】按鈕和【文件列表】功能&#xff1a; 解決步驟1&#xff1a;上傳按鈕 直接上代碼&#xff1a; <a-uploadmultip…

.NET Core 數據庫ORM框架用法簡述

.NET Core ORM框架用法簡述 一、主流.NET Core ORM框架概述 在.NET Core生態系統中&#xff0c;主流的ORM(Object-Relational Mapping)框架包括&#xff1a; ??Entity Framework Core (EF Core)?? - 微軟官方推出的ORM框架??Dapper?? - 輕量級微ORM??Npgsql.Entit…

halcon打開圖形窗口

1、dev_open_window 參數如下&#xff1a; 1&#xff09;Row(輸入參數) y方向上&#xff0c;圖形窗口距離左上角頂端的像素個數 2&#xff09;Column(輸入參數) x方向上&#xff0c;距離左上角左邊的像素個數 3&#xff09;Width(輸入參數) 圖形窗口寬度 4&#xff09;He…

2025東三省D題深圳杯D題數學建模挑戰賽數模思路代碼文章教學

完整內容請看文章最下面的推廣群 一、問題一&#xff1a;混合STR圖譜中貢獻者人數判定 問題解析 給定混合STR圖譜&#xff0c;識別其中的真實貢獻者人數是后續基因型分離與個體識別的前提。圖譜中每個位點最多應出現2n個峰&#xff08;n為人數&#xff09;&#xff0c;但由…

iView Table 組件跨頁選擇功能實現文檔

iView Table 組件跨頁選擇功能實現文檔 功能概述 實現基于 iView Table 組件的多選功能&#xff0c;支持以下特性&#xff1a; ? 跨頁數據持久化選擇? 當前頁全選/取消全選? 自動同步選中狀態顯示? 分頁切換狀態保持? 高性能大數據量支持 實現方案 技術棧 iView UI 4…

家庭服務器IPV6搭建無限郵箱系統指南

qq郵箱操作 // 郵箱配置信息 // 注意&#xff1a;使用QQ郵箱需要先開啟IMAP服務并獲取授權碼 // 設置方法&#xff1a;登錄QQ郵箱 -> 設置 -> 賬戶 -> 開啟IMAP/SMTP服務 -> 生成授權碼 服務器操作 fetchmail 同步QQ郵箱 nginx搭建web顯示本地同步過來的郵箱 ssh…

Tauri v1 與 v2 配置對比

本文檔對比 Tauri v1 和 v2 版本的配置結構和內容差異&#xff0c;幫助開發者了解版本變更并進行遷移。 配置結構變化 v1 配置結構 {"package": { ... },"tauri": { "allowlist": { ... },"bundle": { ... },"security":…

對js的Date二次封裝,繼承了原Date的所有方法,增加了自己擴展的方法,可以實現任意時間往前往后推算多少小時、多少天、多少周、多少月;

封裝js時間工具 概述 該方法繼承了 js 中 Date的所有方法&#xff1b;同時擴展了一部分自用方法&#xff1a; 1、任意時間 往前推多少小時&#xff0c;天&#xff0c;月&#xff0c;周&#xff1b;參數1、2必填&#xff0c;參數3可選beforeDate(num,formatter,dateVal); befo…

TimeDistill:通過跨架構蒸餾的MLP高效長期時間序列預測

原文地址&#xff1a;https://arxiv.org/abs/2502.15016 發表會議&#xff1a;暫定&#xff08;但是Star很高&#xff09; 代碼地址&#xff1a;無 作者&#xff1a;Juntong Ni &#xff08;倪浚桐&#xff09;, Zewen Liu &#xff08;劉澤文&#xff09;, Shiyu Wang&…

DeepSeek最新大模型發布-DeepSeek-Prover-V2-671B

2025 年 4 月 30 日&#xff0c;DeepSeek 開源了新模型 DeepSeek-Prover-V2-671B&#xff0c;該模型聚焦數學定理證明任務&#xff0c;基于混合專家架構&#xff0c;使用 Lean 4 框架進行形式化推理訓練&#xff0c;參數規模達 6710 億&#xff0c;結合強化學習與大規模合成數據…

如何用AI生成假期旅行照?

以下是2025年最新AI生成假期旅行照片的實用工具推薦及使用指南&#xff0c;結合工具特點、研發背景和適用場景進行綜合解析&#xff1a; 一、主流AI旅行照片生成工具推薦與對比 1. 搜狐簡單AI&#xff08;國內工具&#xff09; ? 特點&#xff1a; ? 一鍵優化與背景替換&…

ElaticSearch

ElaticSearch: 全文搜索 超級強&#xff0c;比如模糊查詢、關鍵詞高亮等 海量數據 高效查詢&#xff0c;比傳統關系數據庫快得多&#xff08;尤其是搜索&#xff09; 靈活的數據結構&#xff08;Schema靈活&#xff0c;可以動態字段&#xff09; 分布式高可用&#xff0c;天…

Android開發,實現一個簡約又好看的登錄頁

文章目錄 1. 編寫布局文件2.設計要點說明3. 效果圖4. 關于作者其它項目視頻教程介紹 1. 編寫布局文件 編寫activity.login.xml 布局文件 <?xml version"1.0" encoding"utf-8"?> <androidx.appcompat.widget.LinearLayoutCompat xmlns:android…

機器學習:【拋擲硬幣的貝葉斯后驗概率】

首先,拋硬幣的問題通常涉及先驗概率、似然函數和后驗概率。假設用戶可能想通過觀察一系列的正面(H)和反面(T)來更新硬幣的偏差概率。例如,先驗可能假設硬幣是均勻的,但隨著觀察到更多數據,用貝葉斯定理計算后驗分布。 通常,硬幣的偏差可以用Beta分布作為先驗,因為它…

Echarts 問題:自定義的 legend 點擊后消失,格式化 legend 的隱藏文本樣式

文章目錄 問題分析實現步驟代碼解釋問題 如下圖所示,在自定義的 legend 點擊后會消失 分析 我把隱藏的圖例字體顏色設為灰色,可以借助 legend.formatter 和 legend.textStyle 結合 option.series 的 show 屬性來達成。以下是具體的實現步驟和示例代碼: <!DOCTYPE ht…