Spring Security 安全校验前后端分离

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 登录;

		<dependency>
            <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.doFilterInternal  token {}", 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);
        }
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值