diff --git a/pom.xml b/pom.xml index 6508184..e2e5f18 100644 --- a/pom.xml +++ b/pom.xml @@ -111,6 +111,22 @@ org.springframework.boot spring-boot-starter-security + + + io.jsonwebtoken + jjwt-api + 0.11.5 + + + io.jsonwebtoken + jjwt-impl + 0.11.5 + + + io.jsonwebtoken + jjwt-jackson + 0.11.5 + diff --git a/src/main/java/asia/yulinling/workflow/config/JwtConfig.java b/src/main/java/asia/yulinling/workflow/config/JwtConfig.java new file mode 100644 index 0000000..1d67657 --- /dev/null +++ b/src/main/java/asia/yulinling/workflow/config/JwtConfig.java @@ -0,0 +1,31 @@ +package asia.yulinling.workflow.config; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + *

+ * JWT配置类 + *

+ * + * @author YLL + * @since 2025/6/11 + */ +@ConfigurationProperties(prefix = "jwt.config") +@Data +public class JwtConfig { + /** + * jwt 加密 key,默认值:kw. + */ + private String key = "kw"; + + /** + * jwt 过期时间,默认值:600000 {@code 10 分钟}. + */ + private Long ttl = 600000L; + + /** + * 开启 记住我 之后 jwt 过期时间,默认值 604800000 {@code 7 天} + */ + private Long remember = 604800000L; +} diff --git a/src/main/java/asia/yulinling/workflow/config/SecurityConfig.java b/src/main/java/asia/yulinling/workflow/config/SecurityConfig.java index b1a2f69..84a6cf9 100644 --- a/src/main/java/asia/yulinling/workflow/config/SecurityConfig.java +++ b/src/main/java/asia/yulinling/workflow/config/SecurityConfig.java @@ -5,6 +5,8 @@ import org.springframework.context.annotation.Configuration; import org.springframework.security.config.Customizer; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.SecurityFilterChain; @Configuration @@ -21,4 +23,12 @@ public class SecurityConfig { .rememberMe(Customizer.withDefaults()); return http.build(); } + + /** + * 使用默认密码加密 + */ + @Bean + public PasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } } diff --git a/src/main/java/asia/yulinling/workflow/model/entity/Permission.java b/src/main/java/asia/yulinling/workflow/model/entity/Permission.java new file mode 100644 index 0000000..e0dc556 --- /dev/null +++ b/src/main/java/asia/yulinling/workflow/model/entity/Permission.java @@ -0,0 +1,60 @@ +package asia.yulinling.workflow.model.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + *

+ * 权限 + *

+ * + * @author YLL + * @since 2025/6/11 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +@TableName("`wk_permission`") +public class Permission { + /** + * 主键id + */ + @TableId(type = IdType.AUTO) + private Long id; + + /** + * 权限名 + */ + private String name; + + /** + * 类型为页面时,代表前端路由地址,类型为按钮时,代表后端接口地址 + */ + private String url; + + /** + * 权限类型,页面-1,按钮-2 + */ + private Integer type; + + /** + * 权限表达式 + */ + private String permission; + + /** + * 排序 + */ + private Integer sort; + + /** + * 父级Id + */ + private Long parentId; +} \ No newline at end of file diff --git a/src/main/java/asia/yulinling/workflow/model/entity/Role.java b/src/main/java/asia/yulinling/workflow/model/entity/Role.java new file mode 100644 index 0000000..d6fddb8 --- /dev/null +++ b/src/main/java/asia/yulinling/workflow/model/entity/Role.java @@ -0,0 +1,54 @@ +package asia.yulinling.workflow.model.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.Date; + +/** + *

+ * 角色 + *

+ * + * @author YLL + * @since 2025/6/11 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@TableName("wk_role") +public class Role { + + /** + * 主键 + */ + @TableId(type = IdType.AUTO) + private Long id; + + /** + * 角色名 + */ + private String name; + + /** + * 描述 + */ + private String description; + + /** + * 创建时间 + */ + @TableField("create_time") + private Date createTime; + + /** + * 更新时间 + */ + @TableField("update_time") + private Date updateTime; +} \ No newline at end of file diff --git a/src/main/java/asia/yulinling/workflow/model/entity/RolePermission.java b/src/main/java/asia/yulinling/workflow/model/entity/RolePermission.java new file mode 100644 index 0000000..12ecb41 --- /dev/null +++ b/src/main/java/asia/yulinling/workflow/model/entity/RolePermission.java @@ -0,0 +1,25 @@ +package asia.yulinling.workflow.model.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +/** + *

+ * 角色权限 + *

+ * + * @author YLL + * @since 2025/6/11 + */ +@Data +@TableName("wk_role_permission") +public class RolePermission { + /** + * 角色id + */ + private Long roleId; + /** + * 权限id + */ + private Long permissionId; +} diff --git a/src/main/java/asia/yulinling/workflow/model/entity/RoleUser.java b/src/main/java/asia/yulinling/workflow/model/entity/RoleUser.java new file mode 100644 index 0000000..981f37d --- /dev/null +++ b/src/main/java/asia/yulinling/workflow/model/entity/RoleUser.java @@ -0,0 +1,25 @@ +package asia.yulinling.workflow.model.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +/** + *

+ * 角色用户 + *

+ * + * @author YLL + * @since 2025/6/11 + */ +@Data +@TableName("wk_role_user") +public class RoleUser { + /** + * 权限id + */ + private Long roleId; + /** + * 用户id + */ + private Long userId; +} \ No newline at end of file diff --git a/src/main/java/asia/yulinling/workflow/model/entity/User.java b/src/main/java/asia/yulinling/workflow/model/entity/User.java index 1f551c7..60e115d 100644 --- a/src/main/java/asia/yulinling/workflow/model/entity/User.java +++ b/src/main/java/asia/yulinling/workflow/model/entity/User.java @@ -46,11 +46,6 @@ public class User { */ private String password; - /** - * 加密使用盐 - */ - private String salt; - /** * 邮箱 */ diff --git a/src/main/java/asia/yulinling/workflow/model/vo/user/UserPrincipal.java b/src/main/java/asia/yulinling/workflow/model/vo/user/UserPrincipal.java new file mode 100644 index 0000000..1b926d5 --- /dev/null +++ b/src/main/java/asia/yulinling/workflow/model/vo/user/UserPrincipal.java @@ -0,0 +1,122 @@ +package asia.yulinling.workflow.model.vo.user; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.fasterxml.jackson.annotation.JsonIgnore; +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.Collection; +import java.util.Date; +import java.util.List; + +/** + *

+ * 自定义User + *

+ * + * @author YLL + * @since 2025/6/11 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class UserPrincipal implements UserDetails { + + /** + * 主键id + */ + @TableId(type = IdType.AUTO) + private Long id; + + /** + * 用户名 + */ + private String username; + + /** + * 昵称 + */ + private String nickname; + + /** + * 加密后的密码 + */ + @JsonIgnore + private String password; + + /** + * 加密使用盐 + */ + @JsonIgnore + private String salt; + + /** + * 邮箱 + */ + private String email; + + /** + * 生日 + */ + private String birthday; + + /** + * 性别,男-1,女-2 + */ + private Integer sex; + + /** + * 手机号 + */ + private String phone; + + /** + * 状态 -1:删除 0:警用 1:启用 + */ + private Integer status; + + /** + * 创建时间 + */ + private Date createTime; + + /** + * 上次更新时间 + */ + private Date updateTime; + + /** + * 上次登录时间 + */ + private Date lastLoginTime; + + /** + * 用户角色列表 + */ + private List roles; + + /** + * 用户权限列表 + */ + private Collection authorities; + + @Override + public Collection getAuthorities() { + return List.of(); + } + + @Override + public String getPassword() { + return ""; + } + + @Override + public String getUsername() { + return ""; + } +} diff --git a/src/main/java/asia/yulinling/workflow/utils/JwtUtil.java b/src/main/java/asia/yulinling/workflow/utils/JwtUtil.java new file mode 100644 index 0000000..ce552a6 --- /dev/null +++ b/src/main/java/asia/yulinling/workflow/utils/JwtUtil.java @@ -0,0 +1,103 @@ +package asia.yulinling.workflow.utils; + +import asia.yulinling.workflow.config.JwtConfig; +import asia.yulinling.workflow.model.vo.user.UserPrincipal; +import io.jsonwebtoken.security.Keys; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.GrantedAuthority; +import io.jsonwebtoken.*; + +import java.security.Key; +import java.util.Collection; +import java.util.Date; +import java.util.List; + +/** + *

+ * JWT工具类 + *

+ * + * @author YLL + * @since 2025/6/11 + */ +@EnableConfigurationProperties(JwtConfig.class) +@Configuration +@RequiredArgsConstructor +@Slf4j +public class JwtUtil { + private final JwtConfig jwtConfig; + + /** + * 创建JWT + * + * @param rememberMe 记住我 + * @param id 用户id + * @param subject 用户名 + * @param roles 用户角色 + * @param authorities 用户权限 + * @return JWT + */ + public String createJWT(Boolean rememberMe, Long id, String subject, List roles, Collection authorities) { + Date now = new Date(); + + Key key = Keys.hmacShaKeyFor(jwtConfig.getKey().getBytes()); + + JwtBuilder builder = Jwts.builder() + .setId(id.toString()) + .setSubject(subject) + .setIssuedAt(now) + .signWith(key) + .claim("roles", roles) + .claim("authorities", authorities); + + // 设置过期时间 + Long ttl = rememberMe ? jwtConfig.getRemember() : jwtConfig.getTtl(); + builder.setExpiration(new Date(now.getTime() + ttl)); + + return builder.compact(); + } + + /** + * 创建JWT + * + * @param authentication 用户认证信息 + * @param rememberMe 记住我 + * @return JWT + */ + public String createJWT(Authentication authentication, Boolean rememberMe) { + UserPrincipal userPrincipal = (UserPrincipal) authentication.getPrincipal(); + return createJWT( + rememberMe, + userPrincipal.getId(), + userPrincipal.getUsername(), + userPrincipal.getRoles(), + userPrincipal.getAuthorities() + ); + } + + public Claims parseJWT(String jwt) { + try { + // 1. 构建密钥 + Key key = Keys.hmacShaKeyFor(jwtConfig.getKey().getBytes()); + + // 2. 使用 parserBuilder 构建解析器 + Jws jws = Jwts.parserBuilder() + .setSigningKey(key) + .build() + .parseClaimsJws(jwt); + + Claims claims = jws.getBody(); + String id = claims.getId(); + String username = claims.getSubject(); + + return claims; + } catch (ExpiredJwtException e) { + log.error("ExpiredJwtException", e); + throw new JwtException("ExpiredJwtException"); + } + } +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index e3e986e..be36363 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -36,4 +36,8 @@ spring.mail.properties.mail.smtp.starttls.required=true spring.mail.properties.mail.smtp.ssl.enable=false spring.mail.properties.mail.display.sendmail=spring-boot-demo # Jasypt配置 -jasypt.encryptor.password=abc \ No newline at end of file +jasypt.encryptor.password=abc +# JWT配置 +jwt.config.key=kw +jwt.config.ttl=600000 +jwt.config.remember=604800000 \ No newline at end of file diff --git a/src/main/resources/db/schema.sql b/src/main/resources/db/schema.sql index 80c9d90..792170a 100644 --- a/src/main/resources/db/schema.sql +++ b/src/main/resources/db/schema.sql @@ -5,7 +5,6 @@ CREATE TABLE `wk_user` `username` VARCHAR(32) NOT NULL UNIQUE COMMENT '用户名', `nickname` VARCHAR(32) NOT NULL UNIQUE COMMENT '昵称', `password` VARCHAR(32) NOT NULL COMMENT '加密后的密码', - `salt` VARCHAR(32) NOT NULL COMMENT '加密使用的盐', `email` VARCHAR(32) NOT NULL UNIQUE COMMENT '邮箱', `birthday` DATETIME DEFAULT NULL COMMENT '生日', `sex` INT(2) DEFAULT NULL COMMENT '性别,男-1,女-2',