Spring Security 安全校验前后端分离
Spring Security 是一个专注于向 Java 应用程序提供身份验证和授权的安全框架,在Web情况下,它是借助Filter来实现对哀求的校验; 因为是一个框架,开发出来的目标是为了适配各个不同的场景,各种扩展,再加上框架本身默认的功能是在以template 画html, 以Session做回话管理这种开发模式; 不过我们现在都是前后端分离,以是原有的一些功能就不怎么实用了,导致我们刚接手时会觉得有点困难,接下来我们简朴讲解一下框架的流程,以及后续更改为前后端使用Token交互的方式;官方的中文翻译
DelegatingFilterProxy 负责FilterBean的延迟加载(忽略不用管);
↓
FilterChainProxy 内部委托给 List filterChains进行处理惩罚
https://i-blog.csdnimg.cn/direct/83933a60a2dd4a7a8512bbf37518e666.png
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>
@Bean
protected SecurityFilterChain filterChain(HttpSecurity httpSecurity, JWTParseFilter jwtParseFilter,
AccountTokenAuthenticationFilter accountTokenAuthenticationFilter,
List<Customizer<AuthorizeHttpRequestsConfigurer<HttpSecurity>.AuthorizationManagerRequestMatcherRegistry>> customizer,
DBAuthorizationManager dbAuthorizationManager) throws Exception {
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(new AuthorizationFilter(dbAuthorizationManager), AuthorizationFilter.class);
return httpSecurity.build();
}
JWTParseFilter 主要是检查token 是否存在, 存在的话则进行校验检测,判定是不是我们的自己生成的, 检测通事后,将token 转换为UsernamePasswordAuthenticationToken 对象传入上下文
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
String token = getToken(request);
log.debug("JWTParseFilter.doFilterInternaltoken {}", token);
if (StringUtils.isNotEmpty(token)) {
validToken(request, token);
}
// 继续过滤链
filterChain.doFilter(request, response);
}
private void validToken(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 = new ArrayList<>();
if (StringUtils.isNotBlank(roles)) {
String[] split = roles.split(",");
for (String role : split) {
grantedAuthorities.add(new SimpleGrantedAuthority(role));
}
}
User user = new User(userName, "", grantedAuthorities);
// 创建 Authentication,并设置到上下文
UsernamePasswordAuthenticationToken authenticationToken =
UsernamePasswordAuthenticationToken.authenticated(
user, null, Collections.emptyList());
authenticationToken.setDetails(new WebAuthenticationDetails(request));
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
}
生成token
@Slf4j
@Component
public class AccountTokenAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
public static final String SPRING_SECURITY_FORM_USERNAME_KEY = "username";
public static final String SPRING_SECURITY_FORM_PASSWORD_KEY = "password";
private static final AntPathRequestMatcher DEFAULT_ANT_PATH_REQUEST_MATCHER = new AntPathRequestMatcher("/user/login",
"POST");
public AccountTokenAuthenticationFilter(AuthenticationManager authenticationManager) {
super(DEFAULT_ANT_PATH_REQUEST_MATCHER, authenticationManager);
}
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
throws AuthenticationException, 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));
return this.getAuthenticationManager().authenticate(authRequest);
}
}
DBAuthorizationManager 主要负责对Url 进行权限认证, 实际使用的时间使用数据库设置的数据作为数据源, 这样在使用的时间,直接改数据库就可以了, 不要使用注解,后期该权限就得发布, 走审批走CD ,流程很繁琐;
@Component
public class DBAuthorizationManager implements AuthorizationManager<HttpServletRequest> {
Map<String, List<String>> urlAuth = Map.of("/security/test1/success", List.of("admin"));
@Override
public AuthorizationDecision check(Supplier<Authentication> authentication, HttpServletRequest object) {
String uri = object.getRequestURI();
List<String> strings = urlAuth.get(uri);
if (strings == null) {
return new AuthorizationDecision(true);
}
if (strings.contains(uri)) {
return new AuthorizationDecision(true);
} else {
return new AuthorizationDecision(false);
}
}
}
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页:
[1]