记录一下Springboot项目的Security,配置

如果你不想让Springboot的接口裸奔,就要给接口鉴权,Springboot2提供了一个security,需要在

POM文件里引入,

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-config</artifactId>
</dependency>

注意,一旦设置了spring-boot-starter-security,一定要对应的配置Bean,如下:

@Configuration
public class SecurityConfig {

    private final JwtFilter jwtFilter;

    public SecurityConfig(JwtFilter jwtFilter) {
        this.jwtFilter = jwtFilter;
    }

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http.csrf().disable()
                .authorizeHttpRequests(authorize -> authorize
                        .requestMatchers("/v20241012/query_token/**","/v20241012/stat/**", "/v20241012/monitor/**", "/v20241012/merge").permitAll() // 允许公共端点
                        .anyRequest().authenticated() // 其他所有请求需要认证
                )
                .sessionManagement(session -> session
                        .sessionCreationPolicy(SessionCreationPolicy.STATELESS) // 设置为无状态会话
                )
                .addFilterAfter(jwtFilter, UsernamePasswordAuthenticationFilter.class); // 添加 JWT 过滤器

        return http.build();
    }

} 就像代码里展示的,需要对哪些接口做放开操作,特别说明的是,要先调用接口query_token,对身份进行验证。作为配套,还需要一个Filter,这里是JwtFilter,它是一个server端拦截器,对每个请求做拦截,代码如下:
@Component
public class JwtFilter extends OncePerRequestFilter {

    @Autowired
    private UserDetailsService userDetailsService;


    LoadingCache<String, UserDetails> cache = CacheBuilder.newBuilder()
            .maximumSize(1000)
            .expireAfterWrite(10, TimeUnit.MINUTES) // 写入后过期时间
            .build(
                    new CacheLoader<String, UserDetails>() {
                        @Override
                        public UserDetails load(String key) throws Exception {
                            // 当缓存未命中时,这里的 load() 方法会被调用
                            //TODO 这里应该需要查询自己的数据库表,而不是访问springboot的表
                            return userDetailsService.loadUserByUsername(key);
                        }
                    });
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {
        String authorizationHeader = request.getHeader("Authorization");

        if (authorizationHeader == null || !authorizationHeader.startsWith("Bearer ")) {
            filterChain.doFilter(request, response);
            return;
        }

        String token = authorizationHeader.substring(7);
        try {
            Claims claims = JwtUtil.validateToken(token);
            String username = claims.getSubject();

            if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
                UserDetails userDetails = cache.get(username);
                UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(
                        userDetails, null, userDetails.getAuthorities());
//                authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                SecurityContextHolder.getContext().setAuthentication(authenticationToken);

            }
        }  catch (Exception e) {
            response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
            response.getWriter().write("JWT token validation failed");
            return;
        }

        filterChain.doFilter(request, response);
    }
}

代码也非常简单,从缓存(LocalCache)里查找对比请求里带过来的token,不能直接访问数据库,因为会给数据库带来很高的访问压力。这里用的Guava的Cache,里面除了设置过期时间,还要设置一个回调,如果cache没有命中的时候,从UserDetailsService里调用loadUserByUsername获得值并设置到cache里,请注意,这个UserDetailsService是Springboot Security包下的一个接口,需要用户自己去实现这个接口,我这里的实现如下:

@Service
public class CustomUserDetailsService implements UserDetailsService {
    private final UserInfoMapper userInfoMapper;

    public CustomUserDetailsService(UserInfoMapper userInfoMapper) {
        this.userInfoMapper = userInfoMapper;
    }

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        // 根据用户名加载用户信息
        // 这里可以从数据库或其他数据源获取用户信息
        // 示例代码:
        LambdaQueryWrapper<UserInfo> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        lambdaQueryWrapper.eq(UserInfo::getUsername, username);
        UserInfo one = userInfoMapper.selectOne(lambdaQueryWrapper);
        if (one!=null) {
            return org.springframework.security.core.userdetails.User
                    .withUsername(username)
                    .password(one.getSecret()) // 密码编码器
                    .authorities("ROLE_USER")
                    .build();
        } else {
            throw new UsernameNotFoundException("User not found");
        }
    }
}

如此,你的基于Springboot框架的Web接口就有了安全防护。另外,你需要在你的CRUD接口里得到

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

cjl30804

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值