首页
在线工具
搜索
1
Kuboard与KubeSphere的区别:Kubernetes管理平台对比
2
ShardingSphere使用中的重点问题剖析
3
Flowable工作流引擎源码深度解析
4
用AI生成的原型设计稿效果还可以
5
如何将Virtualbox和VMware虚拟机相互转换
杂谈与随笔
工具与效率
源码阅读
技术管理
运维
数据库
前端开发
后端开发
Search
标签搜索
Angular
Docker
Phabricator
SpringBoot
Java
Chrome
SpringSecurity
SpringCloud
DDD
Git
Mac
K8S
Kubernetes
ESLint
SSH
高并发
Eclipse
Javascript
Vim
Centos
Jonathan
累计撰写
86
篇文章
累计收到
0
条评论
首页
栏目
杂谈与随笔
工具与效率
源码阅读
技术管理
运维
数据库
前端开发
后端开发
页面
搜索到
3
篇与
的结果
2018-11-15
Spring Security OAuth2与Spring Session Redis整合实现微服务会话共享
Spring Security OAuth2与Spring Session Redis整合实现微服务会话共享 引言 在微服务架构中,会话管理是一个常见的挑战。当用户通过OAuth2认证后,如何在多个微服务之间共享会话信息成为一个亟待解决的问题。本文将详细介绍如何利用Spring Security OAuth2结合Spring Session Redis实现微服务架构下的会话共享方案。 技术栈 Spring Boot Spring Security OAuth2 Spring Session Redis Spring Cloud (可选,用于服务发现等) 为什么选择Redis作为会话存储 在分布式系统中,使用Redis存储会话有以下优势: 高性能:Redis是基于内存的数据库,读写速度极快 数据持久化:支持数据持久化,防止数据丢失 丰富的数据结构:支持多种数据类型,适合存储复杂的会话数据 原生的过期机制:便于管理会话生命周期 实现步骤 1. 添加依赖 首先,我们需要在各个微服务的pom.xml文件中添加必要的依赖: <dependencies> <!-- Spring Boot 依赖 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- Spring Security OAuth2 依赖 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-oauth2-client</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-oauth2-resource-server</artifactId> </dependency> <!-- Spring Session Redis 依赖 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <dependency> <groupId>org.springframework.session</groupId> <artifactId>spring-session-data-redis</artifactId> </dependency> </dependencies> 2. 配置Redis与Spring Session 在每个微服务的application.yml文件中添加Redis和Spring Session的配置: spring: redis: host: localhost port: 6379 password: # 如有密码则填写 database: 0 session: store-type: redis redis: namespace: spring:session # Redis中存储会话的命名空间前缀 timeout: 1800 # 会话超时时间(秒) security: oauth2: client: registration: custom-client: client-id: client-id client-secret: client-secret authorization-grant-type: authorization_code redirect-uri: "{baseUrl}/login/oauth2/code/{registrationId}" scope: read,write provider: custom-provider: authorization-uri: http://auth-server/oauth/authorize token-uri: http://auth-server/oauth/token user-info-uri: http://auth-server/userinfo user-name-attribute: name server: servlet: session: cookie: http-only: true secure: true # 在生产环境中应设置为true 3. 启用Spring Session 在主应用类上添加@EnableRedisHttpSession注解: package com.example.microservice; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession; @SpringBootApplication @EnableRedisHttpSession public class MicroserviceApplication { public static void main(String[] args) { SpringApplication.run(MicroserviceApplication.class, args); } } 4. 配置Spring Security OAuth2 创建Security配置类,配置OAuth2认证: package com.example.microservice.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.web.SecurityFilterChain; @Configuration @EnableWebSecurity public class SecurityConfig { @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http .authorizeHttpRequests(authorize -> authorize .requestMatchers("/public/**").permitAll() .anyRequest().authenticated() ) .oauth2Login(oauth2 -> oauth2 .defaultSuccessUrl("/dashboard") .failureUrl("/login?error=true") ) .logout(logout -> logout .logoutSuccessUrl("/login?logout=true") .invalidateHttpSession(true) .deleteCookies("JSESSIONID") ); return http.build(); } } 5. 创建会话信息存储器 为了确保OAuth2认证信息能够在会话中正确存储和共享,创建自定义的会话信息存储器: package com.example.microservice.session; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; import org.springframework.security.oauth2.core.user.OAuth2User; import org.springframework.session.Session; import org.springframework.session.SessionRepository; import org.springframework.stereotype.Component; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpSession; import java.util.Map; @Component public class OAuth2SessionRepository<S extends Session> { private final SessionRepository<S> sessionRepository; public OAuth2SessionRepository(SessionRepository<S> sessionRepository) { this.sessionRepository = sessionRepository; } public void saveOAuth2Authentication(HttpServletRequest request, Authentication authentication) { HttpSession session = request.getSession(false); if (session != null) { String sessionId = session.getId(); S redisSession = sessionRepository.findById(sessionId); if (redisSession != null) { if (authentication instanceof OAuth2AuthenticationToken) { OAuth2AuthenticationToken oauthToken = (OAuth2AuthenticationToken) authentication; OAuth2User oauth2User = oauthToken.getPrincipal(); // 存储用户信息和授权信息 redisSession.setAttribute("user_name", oauth2User.getAttribute("name")); redisSession.setAttribute("user_email", oauth2User.getAttribute("email")); redisSession.setAttribute("oauth2_provider", oauthToken.getAuthorizedClientRegistrationId()); // 存储OAuth2授权信息(可根据需求扩展) Map<String, Object> attributes = oauth2User.getAttributes(); for (Map.Entry<String, Object> entry : attributes.entrySet()) { if (entry.getValue() instanceof String) { redisSession.setAttribute("oauth2_attr_" + entry.getKey(), entry.getValue()); } } sessionRepository.save(redisSession); } } } } public OAuth2User getOAuth2User(String sessionId) { S redisSession = sessionRepository.findById(sessionId); if (redisSession != null) { // 根据存储的会话信息重建OAuth2User // 这里仅为示例,实际实现需要根据具体情况调整 return null; // 此处需实现具体逻辑 } return null; } } 6. 创建认证成功处理器 实现一个认证成功处理器,在用户成功认证后将OAuth2信息保存到Redis会话中: package com.example.microservice.handler; import com.example.microservice.session.OAuth2SessionRepository; import org.springframework.security.core.Authentication; import org.springframework.security.web.authentication.AuthenticationSuccessHandler; import org.springframework.stereotype.Component; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; @Component public class CustomAuthenticationSuccessHandler implements AuthenticationSuccessHandler { private final OAuth2SessionRepository<?> oAuth2SessionRepository; public CustomAuthenticationSuccessHandler(OAuth2SessionRepository<?> oAuth2SessionRepository) { this.oAuth2SessionRepository = oAuth2SessionRepository; } @Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { // 保存OAuth2认证信息到Redis会话 oAuth2SessionRepository.saveOAuth2Authentication(request, authentication); // 重定向到成功页面 response.sendRedirect("/dashboard"); } } 7. 更新Security配置,使用自定义认证成功处理器 package com.example.microservice.config; import com.example.microservice.handler.CustomAuthenticationSuccessHandler; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.web.SecurityFilterChain; @Configuration @EnableWebSecurity public class SecurityConfig { private final CustomAuthenticationSuccessHandler authenticationSuccessHandler; public SecurityConfig(CustomAuthenticationSuccessHandler authenticationSuccessHandler) { this.authenticationSuccessHandler = authenticationSuccessHandler; } @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http .authorizeHttpRequests(authorize -> authorize .requestMatchers("/public/**").permitAll() .anyRequest().authenticated() ) .oauth2Login(oauth2 -> oauth2 .successHandler(authenticationSuccessHandler) .failureUrl("/login?error=true") ) .logout(logout -> logout .logoutSuccessUrl("/login?logout=true") .invalidateHttpSession(true) .deleteCookies("JSESSIONID") ); return http.build(); } } 8. 创建一个拦截器,用于在请求中恢复认证信息 package com.example.microservice.interceptor; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; import org.springframework.security.oauth2.core.user.OAuth2User; import org.springframework.session.Session; import org.springframework.session.SessionRepository; import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.ModelAndView; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; public class OAuth2AuthenticationInterceptor implements HandlerInterceptor { private final SessionRepository<? extends Session> sessionRepository; public OAuth2AuthenticationInterceptor(SessionRepository<? extends Session> sessionRepository) { this.sessionRepository = sessionRepository; } @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { // 检查是否已经有认证信息 if (SecurityContextHolder.getContext().getAuthentication() == null) { HttpSession httpSession = request.getSession(false); if (httpSession != null) { String sessionId = httpSession.getId(); Session redisSession = sessionRepository.findById(sessionId); if (redisSession != null) { // 从Redis会话中恢复OAuth2认证信息 // 这里需要实现具体的逻辑,根据存储的信息重建OAuth2AuthenticationToken OAuth2User oauth2User = createOAuth2UserFromSession(redisSession); String registrationId = (String) redisSession.getAttribute("oauth2_provider"); if (oauth2User != null && registrationId != null) { OAuth2AuthenticationToken authentication = new OAuth2AuthenticationToken(oauth2User, oauth2User.getAuthorities(), registrationId); SecurityContextHolder.getContext().setAuthentication(authentication); } } } } return true; } private OAuth2User createOAuth2UserFromSession(Session session) { // 根据会话中存储的信息重建OAuth2User对象 // 这里需要实现具体逻辑 return null; // 实际实现中需要返回有效的OAuth2User } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) { // 如果需要,可以在这里添加后处理逻辑 } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { // 如果需要,可以在这里添加完成后的逻辑 } } 9. 注册拦截器 package com.example.microservice.config; import com.example.microservice.interceptor.OAuth2AuthenticationInterceptor; import org.springframework.context.annotation.Configuration; import org.springframework.session.SessionRepository; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @Configuration public class WebConfig implements WebMvcConfigurer { private final SessionRepository<?> sessionRepository; public WebConfig(SessionRepository<?> sessionRepository) { this.sessionRepository = sessionRepository; } @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new OAuth2AuthenticationInterceptor(sessionRepository)); } } 测试会话共享 创建一个控制器来测试会话共享功能: package com.example.microservice.controller; import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.security.oauth2.core.user.OAuth2User; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import javax.servlet.http.HttpSession; import java.util.HashMap; import java.util.Map; @RestController public class TestController { @GetMapping("/api/user-info") public Map<String, Object> getUserInfo(@AuthenticationPrincipal OAuth2User oauth2User, HttpSession session) { Map<String, Object> userInfo = new HashMap<>(); if (oauth2User != null) { userInfo.put("name", oauth2User.getAttribute("name")); userInfo.put("email", oauth2User.getAttribute("email")); userInfo.put("authorities", oauth2User.getAuthorities()); } userInfo.put("sessionId", session.getId()); userInfo.put("creationTime", session.getCreationTime()); userInfo.put("lastAccessedTime", session.getLastAccessedTime()); return userInfo; } @GetMapping("/api/session-test") public Map<String, Object> testSession(HttpSession session) { // 获取或设置会话属性 Object count = session.getAttribute("counter"); int newCount = 1; if (count != null) { newCount = (Integer) count + 1; } session.setAttribute("counter", newCount); Map<String, Object> result = new HashMap<>(); result.put("sessionId", session.getId()); result.put("counter", newCount); result.put("message", "此计数在所有微服务中共享"); return result; } } 部署架构 在实际微服务架构中,通常会通过API网关统一管理认证和会话。这里是一个简化的部署架构图: +----------------+ +----------------+ | API Gateway | | Auth Service | | (Zuul/Spring |---->| (OAuth2 Server)| | Cloud Gateway)| +----------------+ +-------+--------+ | v +-------+--------+ +-------+--------+ | Microservice A |<--->| Redis Session | +----------------+ | Storage | ^ +----------------+ | ^ v | +-------+--------+ | | Microservice B |-------------+ +----------------+ 安全注意事项 在实现会话共享时,需要注意以下安全事项: 确保Redis服务器安全配置,启用密码认证 使用SSL/TLS加密Redis连接 会话ID应使用足够长的随机字符串,防止被猜测 设置合理的会话超时时间 使用secure和httpOnly标志保护会话Cookie 考虑使用JWT等无状态认证方案作为补充 性能优化 为了提高系统性能,可以考虑以下优化措施: 配置Redis连接池 使用Redis集群提高可用性 合理设置会话数据的过期时间 仅存储必要的会话信息,避免存储大量数据 监控Redis性能指标,及时进行扩容 总结 通过Spring Security OAuth2与Spring Session Redis的整合,我们实现了微服务架构下的会话共享方案。这种方案具有以下优势: 统一的认证机制:通过OAuth2提供标准的认证流程 高效的会话存储:利用Redis高性能特性存储会话数据 透明的会话共享:微服务无需感知会话存储细节 良好的扩展性:可以方便地扩展到更多微服务 这种方案适用于大多数微服务架构,特别是那些需要保持用户状态的系统。通过合理配置和优化,可以构建一个安全、高效、可扩展的会话管理系统。
2018年11月15日
2018-11-12
Spring Security 源码阅读笔记
Spring Security 源码阅读笔记 一、核心架构概述 Spring Security的核心是基于过滤器链(Filter Chain)的认证和授权机制。通过分析源码,我们可以看到它的主要组件和执行流程。 1.1 核心组件 SecurityContextHolder: 安全上下文的存储策略 Authentication: 认证信息的抽象 AuthenticationManager: 认证管理器 SecurityFilterChain: 安全过滤器链 UserDetailsService: 用户信息获取服务 PasswordEncoder: 密码编码器 AccessDecisionManager: 访问决策管理器 二、认证流程源码分析 2.1 SecurityContextHolder public class SecurityContextHolder { private static final ThreadLocal<SecurityContext> contextHolder = new ThreadLocal<>(); public static SecurityContext getContext() { SecurityContext ctx = contextHolder.get(); if (ctx == null) { ctx = createEmptyContext(); contextHolder.set(ctx); } return ctx; } // 其它方法... } SecurityContextHolder负责存储当前用户的安全上下文,默认使用ThreadLocal存储,确保每个线程都有独立的安全上下文。 2.2 UsernamePasswordAuthenticationFilter public class UsernamePasswordAuthenticationFilter extends AbstractAuthenticationProcessingFilter { @Override public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException { String username = obtainUsername(request); String password = obtainPassword(request); UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password); return this.getAuthenticationManager().authenticate(authRequest); } // 其它方法... } 这个过滤器负责处理表单登录的认证请求,从请求中获取用户名和密码,然后创建认证令牌交给AuthenticationManager处理。 2.3 ProviderManager public class ProviderManager implements AuthenticationManager { private List<AuthenticationProvider> providers; @Override public Authentication authenticate(Authentication authentication) throws AuthenticationException { Class<? extends Authentication> toTest = authentication.getClass(); AuthenticationException lastException = null; for (AuthenticationProvider provider : getProviders()) { if (!provider.supports(toTest)) { continue; } try { Authentication result = provider.authenticate(authentication); if (result != null) { copyDetails(authentication, result); return result; } } catch (AuthenticationException e) { lastException = e; } } if (lastException != null) { throw lastException; } throw new ProviderNotFoundException("No AuthenticationProvider found for " + toTest.getName()); } // 其它方法... } ProviderManager是AuthenticationManager的实现,它委托一系列AuthenticationProvider来处理认证请求。 2.4 DaoAuthenticationProvider public class DaoAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider { private UserDetailsService userDetailsService; private PasswordEncoder passwordEncoder; @Override protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException { if (authentication.getCredentials() == null) { throw new BadCredentialsException("Bad credentials"); } String presentedPassword = authentication.getCredentials().toString(); if (!passwordEncoder.matches(presentedPassword, userDetails.getPassword())) { throw new BadCredentialsException("Bad credentials"); } } @Override protected UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException { 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 notFound) { throw notFound; } catch (Exception repositoryProblem) { throw new InternalAuthenticationServiceException(repositoryProblem.getMessage(), repositoryProblem); } } // 其它方法... } DaoAuthenticationProvider通过UserDetailsService获取用户信息,然后使用PasswordEncoder验证密码。 三、授权流程源码分析 3.1 FilterSecurityInterceptor public class FilterSecurityInterceptor extends AbstractSecurityInterceptor implements Filter { private static final String FILTER_APPLIED = "__spring_security_filterSecurityInterceptor_filterApplied"; @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { invoke(new FilterInvocation(request, response, chain)); } public void invoke(FilterInvocation fi) throws IOException, ServletException { if ((fi.getRequest() != null) && (fi.getRequest().getAttribute(FILTER_APPLIED) != null) && observeOncePerRequest) { fi.getChain().doFilter(fi.getRequest(), fi.getResponse()); } else { if (fi.getRequest() != null && observeOncePerRequest) { fi.getRequest().setAttribute(FILTER_APPLIED, Boolean.TRUE); } InterceptorStatusToken token = super.beforeInvocation(fi); try { fi.getChain().doFilter(fi.getRequest(), fi.getResponse()); } finally { super.finallyInvocation(token); } super.afterInvocation(token, null); } } // 其它方法... } FilterSecurityInterceptor是过滤器链中最后一个过滤器,负责对请求进行访问控制决策。 3.2 AccessDecisionManager public interface AccessDecisionManager { void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException; boolean supports(ConfigAttribute attribute); boolean supports(Class<?> clazz); } AccessDecisionManager接口定义了授权决策的方法,由具体实现类决定是否允许访问。 3.3 AffirmativeBased public class AffirmativeBased extends AbstractAccessDecisionManager { @Override public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException { int deny = 0; for (AccessDecisionVoter voter : getDecisionVoters()) { int result = voter.vote(authentication, object, configAttributes); switch (result) { case AccessDecisionVoter.ACCESS_GRANTED: return; case AccessDecisionVoter.ACCESS_DENIED: deny++; break; default: break; } } if (deny > 0) { throw new AccessDeniedException(messages.getMessage( "AbstractAccessDecisionManager.accessDenied", "Access is denied")); } // 如果没有voter投赞成票,根据allowIfAllAbstainDecisions决定是否允许访问 checkAllowIfAllAbstainDecisions(); } // 其它方法... } AffirmativeBased是AccessDecisionManager的一个实现,只要有一个投票器投票通过,就允许访问。 四、过滤器链构建过程 4.1 WebSecurity public final class WebSecurity extends AbstractConfiguredSecurityBuilder<Filter, WebSecurity> implements SecurityBuilder<Filter>, ApplicationContextAware { private List<SecurityFilterChain> securityFilterChains = new ArrayList<>(); @Override protected Filter performBuild() throws Exception { // ... int chainSize = this.securityFilterChains.size(); if (chainSize > 0) { return VirtualFilterChain.createChainProxy(this.securityFilterChains); } // ... } // 其它方法... } WebSecurity负责构建过滤器链。 4.2 HttpSecurity public final class HttpSecurity extends AbstractConfiguredSecurityBuilder<DefaultSecurityFilterChain, HttpSecurity> implements SecurityBuilder<DefaultSecurityFilterChain>, HttpSecurityBuilder<HttpSecurity> { @Override protected DefaultSecurityFilterChain performBuild() { filters.sort(comparator); return new DefaultSecurityFilterChain(requestMatcher, filters); } // 其它方法... } HttpSecurity负责配置单个SecurityFilterChain中的过滤器。 五、常见过滤器解析 在Spring Security中,请求会经过一系列过滤器,下面是一些关键过滤器的源码分析: 5.1 SecurityContextPersistenceFilter public class SecurityContextPersistenceFilter extends GenericFilterBean { @Override public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) req; HttpServletResponse response = (HttpServletResponse) res; HttpRequestResponseHolder holder = new HttpRequestResponseHolder(request, response); SecurityContext contextBeforeChainExecution = repo.loadContext(holder); try { SecurityContextHolder.setContext(contextBeforeChainExecution); chain.doFilter(holder.getRequest(), holder.getResponse()); } finally { SecurityContext contextAfterChainExecution = SecurityContextHolder.getContext(); SecurityContextHolder.clearContext(); repo.saveContext(contextAfterChainExecution, holder.getRequest(), holder.getResponse()); } } // 其它方法... } SecurityContextPersistenceFilter负责在请求处理前加载SecurityContext,在请求处理后保存SecurityContext。 5.2 LogoutFilter public class LogoutFilter extends GenericFilterBean { @Override public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) req; HttpServletResponse response = (HttpServletResponse) res; if (requiresLogout(request, response)) { Authentication auth = SecurityContextHolder.getContext().getAuthentication(); if (logger.isDebugEnabled()) { logger.debug("Logging out user '" + auth + "' and transferring to logout destination"); } this.handler.logout(request, response, auth); logoutSuccessHandler.onLogoutSuccess(request, response, auth); return; } chain.doFilter(request, response); } // 其它方法... } LogoutFilter处理用户退出登录的请求。 六、注解式安全配置实现原理 6.1 @EnableWebSecurity @Retention(value = java.lang.annotation.RetentionPolicy.RUNTIME) @Target(value = { java.lang.annotation.ElementType.TYPE }) @Documented @Import({ WebSecurityConfiguration.class, SpringWebMvcImportSelector.class, OAuth2ImportSelector.class, HttpSecurityConfiguration.class }) @EnableGlobalAuthentication @Configuration public @interface EnableWebSecurity { boolean debug() default false; } @EnableWebSecurity导入了WebSecurityConfiguration等配置类。 6.2 WebSecurityConfiguration @Configuration(proxyBeanMethods = false) public class WebSecurityConfiguration { @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); } for (SecurityConfigurer<Filter, WebSecurity> webSecurityConfigurer : webSecurityConfigurers) { webSecurity.apply(webSecurityConfigurer); } return webSecurity.build(); } // 其它方法... } WebSecurityConfiguration负责创建springSecurityFilterChain。 6.3 @PreAuthorize源码 @Target({ ElementType.METHOD, ElementType.TYPE }) @Retention(RetentionPolicy.RUNTIME) @Inherited @Documented @Repeatable(PreAuthorizes.class) public @interface PreAuthorize { String value(); } @PreAuthorize是方法级的安全注解,用于指定访问控制表达式。 6.4 MethodSecurityInterceptor public class MethodSecurityInterceptor extends AbstractSecurityInterceptor implements MethodInterceptor { @Override public Object invoke(MethodInvocation mi) throws Throwable { InterceptorStatusToken token = super.beforeInvocation(mi); Object result; try { result = mi.proceed(); } finally { super.finallyInvocation(token); } return super.afterInvocation(token, result); } // 其它方法... } MethodSecurityInterceptor拦截带有安全注解的方法调用,进行访问控制决策。 七、OAuth2集成源码分析 7.1 OAuth2LoginConfigurer public final class OAuth2LoginConfigurer<B extends HttpSecurityBuilder<B>> extends AbstractAuthenticationFilterConfigurer<B, OAuth2LoginConfigurer<B>, OAuth2LoginAuthenticationFilter> { @Override public void init(B http) throws Exception { OAuth2LoginAuthenticationFilter authenticationFilter = new OAuth2LoginAuthenticationFilter( this.authorizationRequestRepository, this.authorizationRequestRepository); authenticationFilter.setAuthenticationManager( this.authenticationManager(http)); // 设置各种属性... this.setAuthenticationFilter(authenticationFilter); super.init(http); } // 其它方法... } OAuth2LoginConfigurer配置OAuth2登录流程。 7.2 OAuth2AuthorizationRequestRedirectFilter public class OAuth2AuthorizationRequestRedirectFilter extends OncePerRequestFilter { @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { String registrationId = this.resolveRegistrationId(request); if (registrationId == null) { filterChain.doFilter(request, response); return; } ClientRegistration clientRegistration = this.clientRegistrationRepository.findByRegistrationId(registrationId); if (clientRegistration == null) { throw new IllegalArgumentException("Invalid Client Registration with Id: " + registrationId); } OAuth2AuthorizationRequest authorizationRequest = this.authorizationRequestResolver .resolve(request, clientRegistration); if (authorizationRequest == null) { throw new IllegalStateException( "Unable to resolve OAuth2 Authorization Request for Client Registration: " + registrationId); } this.authorizationRequestRepository.saveAuthorizationRequest(authorizationRequest, request, response); URI redirectUri = URI.create(authorizationRequest.getAuthorizationRequestUri()); this.authorizationRedirectStrategy.sendRedirect(request, response, redirectUri.toString()); } // 其它方法... } OAuth2AuthorizationRequestRedirectFilter负责处理OAuth2授权请求的重定向。 八、总结与心得 通过对Spring Security源码的阅读和分析,我对其核心工作流程有了更深入的理解: 安全过滤器链是Spring Security的核心,它通过一系列过滤器来处理HTTP请求的安全性。 认证流程主要通过AuthenticationManager和AuthenticationProvider来实现,支持多种认证方式。 授权决策由AccessDecisionManager负责,通过投票机制来决定是否允许访问。 Spring Security的配置非常灵活,可以通过Java配置、XML配置或注解来实现。 Spring Security与OAuth2的集成是通过专门的过滤器和配置类来实现的。
2018年11月12日
2017-07-02
整合spring security oauth2的时候如果碰到Possible CSRF detected - state parameter was present but no state could be found
解决方案:https://github.com/spring-projects/spring-security-oauth/issues/322 问题所在: The problem is the session then. You have 2 servers running on localhost, on different ports, but cookies don't record the host, only the path, and both are on the root path "/" so they are sharing a cookie. Put one of them in a sub context (e.g. using server.contextPath=/auth for the auth server) and it should work I think. 您有2台服务器在本地主机上运行,不同的端口,但cookie不记录主机,只有路径,并且都在根路径“/”,所以他们共享一个cookie。将其中一个放在子上下文中(例如,使用server.contextPath = / auth进行认证服务器),它应该可以工作。
2017年07月02日