如果你不想让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接口里得到