Spring Security 是一个专注于向 Java 应用程序提供身份验证和授权的安全框架,在Web环境下,它是借助Filter来实现对请求的校验; 因为是一个框架,开发出来的目的是为了适配各个不同的场景,各种扩展,再加上框架本身默认的功能是在以template 画html, 以Session做回话管理这种开发模式; 不过我们现在都是前后端分离,所以原有的一些功能就不怎么适用了,导致我们刚接手时会觉得有点困难,接下来我们简单讲解一下框架的流程,以及后续更改为前后端使用Token交互的方式;
官方的中文翻译
DelegatingFilterProxy 负责FilterBean的延迟加载(忽略不用管);
↓
FilterChainProxy 内部委托给 List filterChains进行处理
SecurityFilterChain 对Url 进行匹配,匹配通过, 使用内部的Filter进行处理
org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter@50f097b5,
将 Security 上下文与 Spring Web 中用于处理异步请求映射的 WebAsyncManager 进行集成。
org.springframework.security.web.context.SecurityContextHolderFilter@6fc6deb7,
用于生成 SecurityContextHolder
org.springframework.security.web.header.HeaderWriterFilter@6f76c2cc,
获取定义的HeaderWriter 对象,对请求头进行write
org.springframework.security.web.csrf.CsrfFilter@c29fe36,
处理Csrf攻击
org.springframework.security.web.authentication.logout.LogoutFilter@ef60710,
登出操作
org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter@7c2dfa2,
定义登录使用的Url 各种参数如何获取,生成UsernamePasswordAuthenticationToken 委托给AuthenticationManager 进行鉴权;
org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter@4397a639,
提供一个登录一个页面, 忽略
org.springframework.security.web.authentication.ui.DefaultLogoutPageGeneratingFilter@7add838c,
登出页面, 忽略
org.springframework.security.web.authentication.www.BasicAuthenticationFilter@5cc9d3d0,
提供基于Basic 的登录校验
org.springframework.security.web.savedrequest.RequestCacheAwareFilter@7da39774,
请求缓存, 用于访问非登录接口后重定向到登录接口 ,并在登录成功跳回原接口;
org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter@32b0876c,
用来创建 Servlet3SecurityContextHolderAwareRequestWrapper ,主要是是Servlet 的鉴权体系与Spring整合到一起, 忽略
org.springframework.security.web.authentication.AnonymousAuthenticationFilter@3662bdff,
创建匿名用户, 项目上除非有特殊需求, 这个也可以忽略
org.springframework.security.web.access.ExceptionTranslationFilter@77681ce4,
对鉴权失败的异常处理, 也可忽略, 只要知道在鉴权认证那步会抛出哪些异常就可以
org.springframework.security.web.access.intercept.AuthorizationFilter@169268a7]
权限判断, 哪些接口有权限访问,哪些接口没有权限访问之类的
对于安全方面,还涉及到一些Http 请求头的安全参数, 具体的可以看代码;
改为Token 登录;
<groupId>com.auth0</groupId><artifactId>java-jwt</artifactId></dependency><dependency><groupId>com.auth0</groupId><artifactId>jwks-rsa</artifactId></dependency><dependency><groupId>commons-collections</groupId><artifactId>commons-collections</artifactId><version>3.2</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId></dependency>
@BeanprotectedSecurityFilterChainfilterChain(HttpSecurity httpSecurity,JWTParseFilter jwtParseFilter,AccountTokenAuthenticationFilter accountTokenAuthenticationFilter,List<Customizer<AuthorizeHttpRequestsConfigurer<HttpSecurity>.AuthorizationManagerRequestMatcherRegistry>> customizer,DBAuthorizationManager dbAuthorizationManager)throwsException{
httpSecurity
// 跨域.cors(Customizer.withDefaults())// CSRF 禁用.csrf(AbstractHttpConfigurer::disable).sessionManagement(AbstractHttpConfigurer::disable).requestCache(RequestCacheConfigurer::disable)// .anonymous(AbstractHttpConfigurer::disable).headers(c -> c.frameOptions(HeadersConfigurer.FrameOptionsConfig::disable)).exceptionHandling(c -> c.authenticationEntryPoint(authenticationEntryPoint).accessDeniedHandler(accessDeniedHandler));// 设置每个请求的权限
httpSecurity.authorizeHttpRequests(
registry ->{
customizer.forEach(e -> e.customize(registry));
registry.anyRequest().authenticated();});// 添加 Token Filter
httpSecurity.addFilterAfter(jwtParseFilter,LogoutFilter.class);
accountTokenAuthenticationFilter.setAuthenticationSuccessHandler(successHandler);
accountTokenAuthenticationFilter.setAuthenticationFailureHandler(failureHandler);
httpSecurity.addFilterAfter(accountTokenAuthenticationFilter,JWTParseFilter.class);
httpSecurity.addFilterAt(newAuthorizationFilter(dbAuthorizationManager),AuthorizationFilter.class);return httpSecurity.build();}
JWTParseFilter 主要是检查token 是否存在, 存在的话则进行校验检测,判断是不是我们的自己生成的, 检测通过后,将token 转换为UsernamePasswordAuthenticationToken 对象传入上下文
protectedvoiddoFilterInternal(HttpServletRequest request,HttpServletResponse response,FilterChain filterChain)throwsServletException,IOException{String token =getToken(request);
log.debug("JWTParseFilter.doFilterInternal token {}", token);if(StringUtils.isNotEmpty(token)){validToken(request, token);}// 继续过滤链
filterChain.doFilter(request, response);}privatevoidvalidToken(HttpServletRequest request,String token){DecodedJWT jwt =JWTHandler.parseToken(token);
log.debug("JWTParseFilter.doFilterInternal jwt {}", jwt);String userName = jwt.getClaim("user_name").asString();String roles = jwt.getClaim("roles").asString();List<GrantedAuthority> grantedAuthorities =newArrayList<>();if(StringUtils.isNotBlank(roles)){String[] split = roles.split(",");for(String role : split){
grantedAuthorities.add(newSimpleGrantedAuthority(role));}}User user =newUser(userName,"", grantedAuthorities);// 创建 Authentication,并设置到上下文UsernamePasswordAuthenticationToken authenticationToken =UsernamePasswordAuthenticationToken.authenticated(
user,null,Collections.emptyList());
authenticationToken.setDetails(newWebAuthenticationDetails(request));SecurityContextHolder.getContext().setAuthentication(authenticationToken);}
生成token
@Slf4j@ComponentpublicclassAccountTokenAuthenticationFilterextendsAbstractAuthenticationProcessingFilter{publicstaticfinalStringSPRING_SECURITY_FORM_USERNAME_KEY="username";publicstaticfinalStringSPRING_SECURITY_FORM_PASSWORD_KEY="password";privatestaticfinalAntPathRequestMatcherDEFAULT_ANT_PATH_REQUEST_MATCHER=newAntPathRequestMatcher("/user/login","POST");publicAccountTokenAuthenticationFilter(AuthenticationManager authenticationManager){super(DEFAULT_ANT_PATH_REQUEST_MATCHER, authenticationManager);}@OverridepublicAuthenticationattemptAuthentication(HttpServletRequest request,HttpServletResponse response)throwsAuthenticationException,IOException{ServletInputStream inputStream = request.getInputStream();String jsonStr =IOUtils.toString(inputStream,StandardCharsets.UTF_8);JSONObject obj =JSON.parseObject(jsonStr);Object username = obj.get(SPRING_SECURITY_FORM_USERNAME_KEY);
username =(username !=null)? username.toString().trim():"";Object password = obj.get(SPRING_SECURITY_FORM_PASSWORD_KEY);
password =(password !=null)? password.toString().trim():"";UsernamePasswordAuthenticationToken authRequest =UsernamePasswordAuthenticationToken.unauthenticated(username,
password);
authRequest.setDetails(this.authenticationDetailsSource.buildDetails(request));returnthis.getAuthenticationManager().authenticate(authRequest);}}
DBAuthorizationManager 主要负责对Url 进行权限认证, 实际使用的时候使用数据库配置的数据作为数据源, 这样在使用的时候,直接改数据库就可以了, 不要使用注解,后期改权限就得发布, 走审批走CD ,流程很繁琐;
@ComponentpublicclassDBAuthorizationManagerimplementsAuthorizationManager<HttpServletRequest>{Map<String,List<String>> urlAuth =Map.of("/security/test1/success",List.of("admin"));@OverridepublicAuthorizationDecisioncheck(Supplier<Authentication> authentication,HttpServletRequest object){String uri = object.getRequestURI();List<String> strings = urlAuth.get(uri);if(strings ==null){returnnewAuthorizationDecision(true);}if(strings.contains(uri)){returnnewAuthorizationDecision(true);}else{returnnewAuthorizationDecision(false);}}}
版权归原作者 jrosy 所有, 如有侵权,请联系我们删除。