SpringBoot Security + JWT配置

1、引入依赖项

https://central.sonatype.com/

lombokmybatis-plus请自行根据需求添加

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
	<groupId>org.springframework.security</groupId>
	<artifactId>spring-security-test</artifactId>
	<scope>test</scope>
</dependency>
<dependency>
	<groupId>io.jsonwebtoken</groupId>
	<artifactId>jjwt-api</artifactId>
	<version>0.12.6</version>
</dependency>
<dependency>
	<groupId>io.jsonwebtoken</groupId>
	<artifactId>jjwt-impl</artifactId>
	<version>0.12.6</version>
</dependency>
<dependency>
	<groupId>io.jsonwebtoken</groupId>
	<artifactId>jjwt-jackson</artifactId>
	<version>0.12.6</version>
</dependency>

2、实现User实体类

create table user
(
    id       int      not null
        primary key,
    username tinytext not null,
    password tinytext not null,
    avatar   tinytext null
    type        int          null comment '用户身份,用于权限管理',
);
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
    
    @TableId(type = IdType.AUTO)
    private Integer id;

    private String username;

    private String password;

    private String avatar;
    
    private Integer type;
}

3、实现UserDetailsServiceImpl

继承自UserDetailsService接口,用来接入数据库信息

public class UserDetailsImpl implements UserDetails {

    private User user;

    public UserDetailsImpl(User user) {
        this.user = user;
    }

    public User getUser() {
        return user;
    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        List<GrantedAuthority> list = new ArrayList<>();
        list.add((GrantedAuthority) () -> switch (user.getType()) {
            case USER_TYPE_ADMIN -> "admin"; // 管理员
            case USER_TYPE_MODERATOR -> "moderator"; // 版主
            default -> "USER"; // 普通用户
        });

        return list;
    }

    @Override
    public String getPassword() {
        return user.getPassword();
    }

    @Override
    public String getUsername() {
        return user.getUsername();
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return true;
    }
}

4、实现 JwtUtil

jwt 工具类,用来创建和解析jwt token

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import org.springframework.stereotype.Component;

import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;
import java.util.Date;
import java.util.UUID;

@Component
public class JwtUtil {
    public static final long JWT_TTL = 60 * 60 * 1000L * 24 * 14;  // 有效期14天
    public static final String JWT_KEY = "SDFGjhdsfalshdSAFKSDBJKJBASKJdsasgdkfkjsdhgfakjbfjk14651354565fasdf1353453524324fHFdsjkdsfds12123sd2131afasdfac"; // 需要足够长

    public static String getUUID() {
        return UUID.randomUUID().toString().replaceAll("-", "");
    }

    public static String createJWT(String subject) {
        JwtBuilder builder = getJwtBuilder(subject, null, getUUID());
        return builder.compact();
    }

    private static JwtBuilder getJwtBuilder(String subject, Long ttlMillis, String uuid) {
        SecretKey secretKey = generalKey();
        long nowMillis = System.currentTimeMillis();
        Date now = new Date(nowMillis);
        if (ttlMillis == null) {
            ttlMillis = JwtUtil.JWT_TTL;
        }

        long expMillis = nowMillis + ttlMillis;
        Date expDate = new Date(expMillis);
        return Jwts.builder()
                .id(uuid)
                .subject(subject)
                .issuer("sg")
                .issuedAt(now)
                .signWith(secretKey)
                .expiration(expDate);
    }

    public static SecretKey generalKey() {
        byte[] encodeKey = Base64.getDecoder().decode(JwtUtil.JWT_KEY);
        return new SecretKeySpec(encodeKey, 0, encodeKey.length, "HmacSHA256");
    }

    public static Claims parseJWT(String jwt) throws Exception {
        SecretKey secretKey = generalKey();
        return Jwts.parser()
                .verifyWith(secretKey)
                .build()
                .parseSignedClaims(jwt)
                .getPayload();
    }
}

5、实现JwtAuthenticationTokenFilter

用来验证jwt token,如果验证成功,则将User信息注入上下文中

@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
    
    @Autowired
    private UserMapper userMapper;
 
    @Override
    protected void doFilterInternal(HttpServletRequest request, @NotNull HttpServletResponse response, @NotNull FilterChain filterChain) throws ServletException, IOException {
        String token = request.getHeader("Authorization");
 
        if (!StringUtils.hasText(token) || !token.startsWith("Bearer ")) {
            filterChain.doFilter(request, response);
            return;
        }
 
        token = token.substring(7);
 
        String userid;
        try {
            Claims claims = JwtUtil.parseJWT(token);
            userid = claims.getSubject();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
 
        User user = userMapper.selectById(Integer.parseInt(userid));
 
        if (user == null) {
            throw new RuntimeException("用户未登录");
        }

        UserDetailsImpl loginUser = new UserDetailsImpl(user);

        UsernamePasswordAuthenticationToken authenticationToken =
                new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities());
 
        SecurityContextHolder.getContext().setAuthentication(authenticationToken);
 
        filterChain.doFilter(request, response);
    }
}

6、添加配置类 SecurityConfig

实现用户密码的加密存储,放行登录、注册等接口

@Configuration
@EnableWebSecurity
public class SecurityConfig {
 
    private final JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;
 
    public SecurityConfig(JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter) {
        this.jwtAuthenticationTokenFilter = jwtAuthenticationTokenFilter;
    }
 
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
 
    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
                .csrf(csrf -> csrf.disable())
                .sessionManagement(session -> session
                        .sessionCreationPolicy(SessionCreationPolicy.STATELESS))
                .authorizeHttpRequests(auth -> auth
                        .requestMatchers("/user/login", "/user/register").permitAll()
                        .requestMatchers(HttpMethod.OPTIONS).permitAll()
                        .requestMatchers(HttpMethod.POST, "/discussPost/top").hasRole("MODERATOR")
                        .requestMatchers(HttpMethod.POST, "/discussPost/wonderful").hasRole("MODERATOR")
                        .requestMatchers(HttpMethod.POST, "/discussPost/delete").hasRole("ADMIN")
                        .anyRequest().authenticated());
 
        http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
 
        return http.build();
    }
 
    @Bean
    public AuthenticationManager authManager(HttpSecurity http) throws Exception {
        AuthenticationManagerBuilder authenticationManagerBuilder =
                http.getSharedObject(AuthenticationManagerBuilder.class);
        return authenticationManagerBuilder.build();
    }
}

7、实现UserDetailsService接口,根据用户名查找用户

@Service
public class UserDetailsServiceImpl implements UserDetailsService {

    @Autowired
    private UserMapper userMapper;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("username", username);
        User user = userMapper.selectOne(queryWrapper);
        if (user == null) {
            throw new RuntimeException("用户不存在");
        }

        return new UserDetailsImpl(user);
    }

}

注1:附上前端通过axios请求受保护API数据的参考格式

const fetchData = async (page: any, size: any) => {
    const response = await axios({
      method: "get",
      url: "http://localhost:8080/api/record/getList",
      headers: {
        Authorization: `Bearer ${useUserStore.getState().user.token}`,
      },
      params: {
        page: page, // 当前页码
        page_size: size, // 每页大小
      },
    });

	console.log(response.data);
  };

注2:配置仅允许本地127.0.0.1访问的API的方法

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    private static final IpAddressMatcher ALLOWED_IP = new IpAddressMatcher("127.0.0.1");

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
                .csrf(csrf -> csrf.disable())
                .sessionManagement(session -> session
                        .sessionCreationPolicy(SessionCreationPolicy.STATELESS))
                .authorizeHttpRequests(auth -> auth
                        .requestMatchers("/bot/add")
                        .access(((authentication, context) -> new AuthorizationDecision(ALLOWED_IP.matches(context.getRequest()))))
                        .requestMatchers(HttpMethod.OPTIONS).permitAll()
                        .anyRequest().authenticated());

        return http.build();
    }

}
posted @ 2025-01-27 13:03  fhyxzmkh  阅读(69)  评论(0)    收藏  举报