SpringBoot Security + JWT配置
1、引入依赖项
lombok
与mybatis-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();
}
}