集各位大佬的精华,弄出来的一套基于jdk17,Springboot3.0,SpringSecurity6.0,jwt的登陆及接口拦截体系。
1.先贴依赖:
org.springframework.boot
spring-boot-starter-parent
3.0.0
org.springframework.boot
spring-boot-starter-jdbc
org.springframework.boot
spring-boot-starter-security
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-test
test
org.springframework.security
spring-security-test
test
com.nimbusds
nimbus-jose-jwt
9.31
org.projectlombok
lombok
true
com.baomidou
mybatis-plus-boot-starter
3.5.3.1
com.baomidou
mybatis-plus-generator
3.5.3.1
mybatis
org.mybatis
log4j
log4j
1.2.17
junit
junit
4.12
test
com.alibaba.fastjson2
fastjson2
2.0.26
2.jwtUtil
import com.alibaba.fastjson2.JSON;
import com.nimbusds.jose.*;
import com.nimbusds.jose.crypto.MACSigner;
import com.nimbusds.jose.crypto.MACVerifier;
import com.zy.common.ResponseEntity;
import com.zy.entity.security.Claims;
import java.text.ParseException;
import java.util.Date;
import java.util.UUID;
/**
* @author : zy
* JWT工具类
*/
public class JWTUtil {
//密钥
private static final String secret = "xpo1xgnl5ksinxkgu1nb6vcx3zaq1wsxvv";
// 1000 * 60 * 60 * 24 * 1 一天
//过期时间12h,单位毫秒
private static final long EXPIRE = 1000 * 60 * 60 * 12;
// 测试时为1min
// private static final long EXPIRE = 1000 * 60 * 1;
/**
* 创建token
*
* @param claims 用户信息
* @return 令牌
*/
public static String createToken(Claims claims) {
try {
//对密钥进行签名
JWSSigner jwsSigner = new MACSigner(secret);
//准备JWS header
JWSHeader jwsHeader = new JWSHeader
.Builder(JWSAlgorithm.HS256)
.type(JOSEObjectType.JWT)
.build();
//准备JWS payload
claims.setJti(UUID.randomUUID().toString());
claims.setIat(new Date().getTime());
claims.setExp(new Date(System.currentTimeMillis() + EXPIRE).getTime());
Payload payload = new Payload(JSON.toJSONString(claims));
//封装JWS对象
JWSObject jwsObject = new JWSObject(jwsHeader, payload);
//签名
jwsObject.sign(jwsSigner);
return jwsObject.serialize();
} catch (KeyLengthException e) {
e.printStackTrace();
} catch (JOSEException e) {
e.printStackTrace();
}
return null;
}
/**
* 验证并获取用户信息
*
* @param token 令牌
* @return 解析后用户信息
*/
public static ResponseEntity verifyToken(String token) {
JWSObject jwsObject;
ResponseEntity response = new ResponseEntity<>();
try {
jwsObject = JWSObject.parse(token);
//HMAC验证器
JWSVerifier jwsVerifier = new MACVerifier(secret);
if (!jwsObject.verify(jwsVerifier)) {
response.setCode(10008).setErrorMsg("token无效");
return response;
}
String payload = jwsObject.getPayload().toString();
Claims claims = JSON.parseObject(payload, Claims.class);
if (claims.getExp() < new Date().getTime()) {
response.setCode(10008).setErrorMsg("token无效");
return response;
}
response.setCode(200).setData(claims).setMessage("解析成功");
return response;
} catch (ParseException | JOSEException e) {
e.printStackTrace();
}
response.setCode(10008).setErrorMsg("token无效");
return response;
}
}
3.jwtAuthenticationFilter
import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.zy.common.ResponseEntity;
import com.zy.config.security.JWTUtil;
import com.zy.entity.security.Claims;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import java.io.IOException;
import java.util.logging.Logger;
/**
* @author : zy
*
* 自定义jwt全局过滤器
* 1.没有携带token放行
* 2.携带token,将用户信息添加至security上下文中
*/
@Component
public class JWTAuthenticationFilter extends OncePerRequestFilter {
private static final Logger logger = Logger.getLogger(JWTAuthenticationFilter.class.toString());
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
//获取当前请求的uri
String uri = request.getRequestURI();
logger.info("请求路径:" + uri);
//判断是否是认证请求路径
//是:直接放行
if (uri.endsWith("/auth/login") || uri.endsWith("/auth/logout") || uri.startsWith("/swagger-ui")
|| uri.endsWith("doc.html") || uri.startsWith("/webjars/css") || uri.startsWith("/webjars/js")
|| uri.startsWith("/v3/api-docs") || uri.startsWith("/favicon.ico")
|| uri.startsWith("**/*.html") || uri.endsWith("/webjars/springfox-swagger-ui")
|| uri.startsWith("/swagger-resources")) {
filterChain.doFilter(request, response);
return;
}
//否:获取请求头中携带的token
String authorization = request.getHeader("Authorization");
logger.info("携带authorization:" + authorization);
//判断是否携带token
//否:抛出异常
if (StringUtils.isBlank(authorization)) {
logger.info("未查询到token");
return;
}
String realToken = authorization.replace("Bearer ", "");
//是:校验jwt有效性
ResponseEntity responseE = JWTUtil.verifyToken(realToken);
Claims data = (Claims) responseE.getData();
if (ObjectUtils.isEmpty(data)) {
logger.info("token失效");
return;
}
// 验证token对象是否存在及验证token是否过期
if (ObjectUtils.isEmpty(data)) {
logger.info("token无效或者已经失效");
return;
}
if (responseE.getCode() != 200) {
logger.info("token无效");
return;
}
filterChain.doFilter(request, response);
}
}
4.SecurityConfig
注:此处.requestMatchers("/**").permitAll()应该是把所有接口都放开,在jwtAuthenticationFilter中实现接口拦截,因为不写/**,其余接口直接访问不到,才疏学浅不知道为啥,大家可以试试,改正。
import com.zy.config.security.filter.JWTAuthenticationFilter;
import com.zy.entity.SysUser;
import com.zy.entity.security.LoginUser;
import com.zy.mapper.SysUserMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.LockedException;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
/**
* @author zy
*/
@Configuration
public class SecurityConfig {
@Autowired
private SysUserMapper sysUserMapper;
@Autowired
private JWTAuthenticationFilter jwtAuthenticationFilter;
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public UserDetailsService userDetailsService() {
return username -> {
SysUser user = sysUserMapper.selectByName(username);
if (user == null) {
throw new UsernameNotFoundException("用户不存在");
}
Integer status = user.getStatus();
if (status != 0){
throw new LockedException("用户已停用");
}
return new LoginUser(user);
};
}
@Bean
public AuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
authenticationProvider.setUserDetailsService(userDetailsService());
authenticationProvider.setPasswordEncoder(passwordEncoder());
authenticationProvider.setHideUserNotFoundExceptions(false);
return authenticationProvider;
}
@Bean
public AuthenticationManager authenticationManager() {
return new ProviderManager(authenticationProvider());
}
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authenticationProvider(authenticationProvider())
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)
.authorizeHttpRequests()
.requestMatchers(HttpMethod.OPTIONS).permitAll()
.requestMatchers("/**").permitAll()
.requestMatchers("/auth/login").permitAll()
.requestMatchers("/auth/logout").permitAll()
.requestMatchers("/swagger-ui.html").permitAll()
.requestMatchers("/doc.html").permitAll()
.requestMatchers("/webjars/springfox-swagger-ui/**").permitAll()
.requestMatchers("/swagger-resources").permitAll()
.requestMatchers("/v3/api-docs/**").permitAll()
.requestMatchers("/favicon.ico").permitAll()
.requestMatchers("/error").permitAll()
.anyRequest()
.authenticated();
return http.build();
}
}
5.返回类型 ResponseEntity
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.util.HashMap;
/**
* @author zy
*/
@Schema(description = "返回响应数据")
@Data
public class ResponseEntity {
@Schema(description = "编码")
private int code = 200;
@Schema(description = "基本信息")
private String message = "成功";
@Schema(description = "错误信息")
private String errorMsg = "";
@Schema(description = "返回对象")
private T data;
/**
* 成功状态码
*/
public static final Integer SUCCESS = 200;
/**
* 失败状态码
*/
public static final Integer ERROR = 500;
private static HashMap ERROR_CODE = new HashMap() {
{
put(100, "暂无数据");
put(200, "成功");
put(300, "失败");
put(500, "失败状态码");
put(10000, "通用错误");
///用户类
put(10001, "用户名或密码错误");
put(10002, "登录状态已过期");
put(10003, "注册用户已存在");
put(10004, "账号已被锁定,请在一小时后重试");
put(10005, "旧密码错误");
put(10006, "用户名已存在");
put(10007, "ip没有权限");
put(10008, "token无效");
put(10009, "token失效");
///操作权限类
put(20001, "无操作权限");
///参数类
put(30001, "非法参数");
put(30002, "缺少必要参数");
数据操作类
put(40001, "添加数据失败");
put(40002, "更新数据失败");
put(40003, "删除数据失败");
put(40004, "添加数据失败,对象已经存在,建议修改或者删除");
put(50001, "不存在的对象");
put(99999, "无任何资源权限");
put(990000, "系统错误");
}
};
public ResponseEntity() {
}
public ResponseEntity(T date) {
this.data = date;
}
public int getCode() {
return code;
}
public ResponseEntity setCode(int code) {
this.code = code;
if (ERROR_CODE.containsKey(code)) {
setMessage(ERROR_CODE.get(code));
}
return this;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public T getData() {
return data;
}
public ResponseEntity setData(T data) {
this.data = data;
return this;
}
public static ResponseEntity def(Class clazz) {
return new ResponseEntity<>();
}
public ResponseEntity ok() {
setCode(200);
return this;
}
public ResponseEntity error(int code) {
setCode(code);
return this;
}
public ResponseEntity message(String message) {
setMessage(message);
return this;
}
public ResponseEntity data(T data) {
setData(data);
return this;
}
public ResponseEntity back(int code, String message, T data) {
setCode(code);
setMessage(message);
setData(data);
return this;
}
public static Boolean isError(ResponseEntity r) {
return !isSuccess(r);
}
public static Boolean isSuccess(ResponseEntity r) {
return ResponseEntity.SUCCESS == r.getCode();
}
}
6.Claims
import lombok.Data;
import java.util.List;
/**
* jwt实体数据
*/
@Data
public class Claims {
/**
* 主题
*/
private String sub;
/**
* 签发时间
*/
private Long iat;
/**
* 过期时间
*/
private Long exp;
/**
* JWT ID
*/
private String jti;
/**
* 用户id
*/
private String userId;
/**
* 用户名
*/
private String username;
/**
* 用户状态(1:正常;0:禁用)
*/
private String status;
/**
* 用户角色
*/
private List roles;
/**
* 权限列表
*/
private List permissions;
public Claims(String sub, Long iat, Long exp, String jti, String userId, String username, String status, List roles, List permissions) {
this.sub = sub;
this.iat = iat;
this.exp = exp;
this.jti = jti;
this.userId = userId;
this.username = username;
this.status = status;
this.roles = roles;
this.permissions = permissions;
}
public String getSub() {
return sub;
}
public void setSub(String sub) {
this.sub = sub;
}
public Long getIat() {
return iat;
}
public void setIat(Long iat) {
this.iat = iat;
}
public Long getExp() {
return exp;
}
public void setExp(Long exp) {
this.exp = exp;
}
public String getJti() {
return jti;
}
public void setJti(String jti) {
this.jti = jti;
}
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public List getRoles() {
return roles;
}
public void setRoles(List roles) {
this.roles = roles;
}
public List getPermissions() {
return permissions;
}
public void setPermissions(List permissions) {
this.permissions = permissions;
}
public static ClaimsBuilder builder() {
return new ClaimsBuilder();
}
public static final class ClaimsBuilder {
//主题
private String sub;
//签发时间
private Long iat;
//过期时间
private Long exp;
//JWT ID
private String jti;
//用户id
private String userId;
//用户名
private String username;
private String status;
//用户角色
private List roles;
private List permissions;
private ClaimsBuilder() {
}
public ClaimsBuilder sub(String sub) {
this.sub = sub;
return this;
}
public ClaimsBuilder iat(Long iat) {
this.iat = iat;
return this;
}
public ClaimsBuilder exp(Long exp) {
this.exp = exp;
return this;
}
public ClaimsBuilder jti(String jti) {
this.jti = jti;
return this;
}
public ClaimsBuilder userId(String userId) {
this.userId = userId;
return this;
}
public ClaimsBuilder username(String username) {
this.username = username;
return this;
}
public ClaimsBuilder status(String status) {
this.status = status;
return this;
}
public ClaimsBuilder roles(List roles) {
this.roles = roles;
return this;
}
public ClaimsBuilder permissions(List permissions) {
this.permissions = permissions;
return this;
}
public Claims build() {
return new Claims(
this.sub,
this.iat,
this.exp,
this.jti,
this.userId,
this.username,
this.status,
this.roles,
this.permissions);
}
}
}
7.LoginUser
import com.zy.entity.SysUser;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.ArrayList;
import java.util.Collection;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class LoginUser implements UserDetails {
private SysUser user;
@Override
public Collection extends GrantedAuthority> getAuthorities() {
return new ArrayList<>();
}
@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;
}
}
8.login登陆
public ResponseEntity login(SysUser sysUser ) {
ResponseEntity
9.sysUser是用户实体类,相关的代码就不贴了,可以根据自己数据库需求进行编写。
有什么疑问或者教导请指正,谢谢。