🔧 修改验证请求逻辑和添加注释

This commit is contained in:
Bunny 2025-07-21 17:17:22 +08:00
parent f8354789a7
commit 1992b00aa2
14 changed files with 160 additions and 162 deletions

View File

@ -1,16 +1,19 @@
package com.auth.common.exception;
import com.auth.common.context.BaseContext;
import com.auth.common.model.common.result.Result;
import com.auth.common.model.common.result.ResultCodeEnum;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.support.DefaultMessageSourceResolvable;
import org.springframework.security.authorization.AuthorizationDeniedException;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import java.sql.SQLIntegrityConstraintViolationException;
import java.nio.file.AccessDeniedException;
import java.sql.SQLException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
@ -69,9 +72,9 @@ public class GlobalExceptionHandler {
// 表单验证字段
@ExceptionHandler(MethodArgumentNotValidException.class)
public Result<String> handleValidationExceptions(MethodArgumentNotValidException ex) {
log.error("表单验证失败用户Id{}", BaseContext.getUserId());
String errorMessage = ex.getBindingResult().getFieldErrors().stream()
.map(DefaultMessageSourceResolvable::getDefaultMessage)
.distinct()
.collect(Collectors.joining(", "));
return Result.error(null, 201, errorMessage);
}
@ -85,10 +88,28 @@ public class GlobalExceptionHandler {
return Result.error(null, 500, exception.getMessage());
}
// 处理SQL异常
@ExceptionHandler(SQLIntegrityConstraintViolationException.class)
// spring security异常
@ExceptionHandler(AccessDeniedException.class)
@ResponseBody
public Result<String> exceptionHandler(SQLIntegrityConstraintViolationException exception) {
public Result<String> error(AccessDeniedException exception) {
log.error("GlobalExceptionHandler===>spring security异常{}", exception.getMessage());
return Result.error(ResultCodeEnum.FAIL_NO_ACCESS_DENIED);
}
// spring security异常
@ExceptionHandler(AuthorizationDeniedException.class)
@ResponseBody
public Result<String> error(AuthorizationDeniedException exception) {
log.warn("AuthorizationDeniedException===>spring security异常{}", exception.getMessage());
return Result.error(ResultCodeEnum.FAIL_NO_ACCESS_DENIED);
}
// 处理SQL异常
@ExceptionHandler(SQLException.class)
@ResponseBody
public Result<String> exceptionHandler(SQLException exception) {
log.error("GlobalExceptionHandler===>处理SQL异常:{}", exception.getMessage());
String message = exception.getMessage();
@ -99,4 +120,5 @@ public class GlobalExceptionHandler {
return Result.error(ResultCodeEnum.UNKNOWN_EXCEPTION);
}
}
}

View File

@ -1,5 +1,6 @@
package com.auth.common.utils;
import com.auth.common.exception.AuthenticSecurityException;
import com.auth.common.model.common.result.ResultCodeEnum;
import io.jsonwebtoken.Claims;

View File

@ -33,20 +33,11 @@ public class UserEntity extends BaseEntity /* implements UserDetails, Credential
@Schema(name = "password", title = "密码")
private String password;
@Schema(name = "avatar", title = "头像")
@Schema(name = "avatar", title = "头像URL")
private String avatar;
@Schema(name = "summary", title = "个人描述")
private String summary;
@Schema(name = "ipAddress", title = "最后登录IP")
private String ipAddress;
@Schema(name = "ipRegion", title = "最后登录ip归属地")
private String ipRegion;
@Schema(name = "status", title = "1:禁用 0:正常")
private Boolean status;
@Schema(name = "status", title = "状态(0=禁用,1=正常,2=锁定)")
private Integer status;
@Schema(name = "salt", title = "密码盐值")
private String salt;
@ -76,7 +67,7 @@ public class UserEntity extends BaseEntity /* implements UserDetails, Credential
private Long postId;
@Schema(name = "isDeleted", title = "是否删除")
private Integer isDeleted;
private Boolean isDeleted;
// @TableField(exist = false)
// private Set<? extends GrantedAuthority> authorities;

View File

@ -16,50 +16,88 @@ import java.util.Date;
public class UserRolePermission {
// 用户角色关联信息
@Schema(name = "userId", title = "用户Id")
private Long userId;
// 角色权限关联信息
@Schema(name = "roleId", title = "角色Id")
private Long roleId;
@Schema(name = "permissionId", title = "权限Id")
private Long permissionId;
// 权限信息
@Schema(name = "permissionCode", title = "权限编码")
private String permissionCode;
@Schema(name = "permissionName", title = "权限名称")
private String permissionName;
@Schema(name = "permissionType", title = "权限类型")
private String permissionType;
@Schema(name = "url", title = "URL地址")
private String url;
@Schema(name = "method", title = "请求方法")
private String method;
@Schema(name = "permissionLevel", title = "权限层级")
private Integer permissionLevel;
@Schema(name = "permissionPath", title = "权限路径")
private String permissionPath;
@Schema(name = "permissionOrderNum", title = "排序号")
private Integer permissionOrderNum;
@Schema(name = "permissionStatus", title = "状态(0禁用 1启用)")
private Integer permissionStatus;
@Schema(name = "permissionRemark", title = "备注")
private String permissionRemark;
// 用户信息
@Schema(name = "username", title = "用户名")
private String username;
@Schema(name = "nickname", title = "昵称")
private String nickname;
@Schema(name = "email", title = "邮箱")
private String email;
@Schema(name = "phone", title = "手机号")
private String phone;
@Schema(name = "salt", title = "加密盐值")
private String salt;
@Schema(name = "password", title = "密码")
private String password;
@Schema(name = "avatar", title = "头像")
private String avatar;
@Schema(name = "gender", title = "性别(0未知 1男 2女)")
private Integer gender;
@Schema(name = "birthday", title = "生日")
private Date birthday;
@Schema(name = "introduction", title = "个人简介")
private String introduction;
@Schema(name = "lastLoginIp", title = "最后登录IP")
private String lastLoginIp;
@Schema(name = "lastLoginTime", title = "最后登录时间")
private Date lastLoginTime;
@Schema(name = "lastLoginRegion", title = "最后登录地区")
private String lastLoginRegion;
@Schema(name = "status", title = "状态(0禁用 1启用)")
private Integer status;
@Schema(name = "deptId", title = "部门ID")
private Long deptId;
@Schema(name = "postId", title = "岗位ID")
private Long postId;
@Schema(name = "isDeleted", title = "是否删除(0否 1是)")
private Boolean isDeleted;
// 角色信息
@Schema(name = "roleCode", title = "角色编码")
private String roleCode;
@Schema(name = "roleName", title = "角色名称")
private String roleName;
@Schema(name = "roleType", title = "角色类型")
private String roleType;
@Schema(name = "roleDataScope", title = "数据范围")
private String roleDataScope;
@Schema(name = "roleOrderNum", title = "排序号")
private Integer roleOrderNum;
@Schema(name = "roleStatus", title = "状态(0禁用 1启用)")
private Integer roleStatus;
@Schema(name = "roleRemark", title = "备注")
private String roleRemark;
}

View File

@ -11,10 +11,6 @@
<id column="phone" property="phone"/>
<id column="password" property="password"/>
<id column="avatar" property="avatar"/>
<id column="sex" property="sex"/>
<id column="summary" property="summary"/>
<id column="ip_address" property="ipAddress"/>
<id column="ip_region" property="ipRegion"/>
<id column="status" property="status"/>
<id column="is_deleted" property="isDeleted"/>
<id column="salt" property="salt"/>
@ -30,14 +26,11 @@
<id column="update_time" property="updateTime"/>
<id column="create_user" property="createUser"/>
<id column="update_user" property="updateUser"/>
<id column="is_deleted" property="isDeleted"/>
</resultMap>
<!-- 通用查询结果列 -->
<sql id="Base_Column_List">
id,
username,nickname,email,phone,password,avatar,sex,summary,ip_address,ip_region,status,is_deleted,salt,gender,birthday,introduction,last_login_ip,last_login_region,last_login_time,dept_id,post_id,
create_time, update_time, create_user, update_user, is_deleted
id, username,nickname,email,phone,password,avatar,status,is_deleted,salt,gender,birthday,introduction,last_login_ip,last_login_region,last_login_time,dept_id,post_id, create_time, update_time, create_user, update_user
</sql>
<!-- 分页查询用户信息内容 -->
@ -69,18 +62,6 @@
<if test="dto.avatar != null and dto.avatar != ''">
and base.avatar like CONCAT('%',#{dto.avatar},'%')
</if>
<if test="dto.sex != null and dto.sex != ''">
and base.sex like CONCAT('%',#{dto.sex},'%')
</if>
<if test="dto.summary != null and dto.summary != ''">
and base.summary like CONCAT('%',#{dto.summary},'%')
</if>
<if test="dto.ipAddress != null and dto.ipAddress != ''">
and base.ip_address like CONCAT('%',#{dto.ipAddress},'%')
</if>
<if test="dto.ipRegion != null and dto.ipRegion != ''">
and base.ip_region like CONCAT('%',#{dto.ipRegion},'%')
</if>
<if test="dto.status != null and dto.status != ''">
and base.status like CONCAT('%',#{dto.status},'%')
</if>
@ -122,15 +103,15 @@
select *
from v_user_role_permission vurp
where vurp.user_id = #{userId}
and vurp.is_deleteds = 0
and vurp.status = 0
and vurp.is_deleted = 0
and vurp.status = 1
</select>
<!-- 根据用户id查找该用户的角色内容 -->
<select id="selectRolesByUserId" resultType="com.auth.dao.base.entity.RoleEntity">
SELECT tr.*
FROM t_user_role tur
JOIN t_role tr ON tur.role_id = tr.id
FROM sys_user_role tur
JOIN sys_role tr ON tur.role_id = tr.id
<where>
<if test="userId != null">
tur.user_id = #{userId}
@ -152,7 +133,7 @@
select
<include refid="Base_Column_List"/>
from sys_user
where username = #{username} and is_deleted = 0 and status = 0
where username = #{username} and is_deleted = 0 and status = 1
</select>
</mapper>

View File

@ -1 +0,0 @@
package com.auth.model.base.vo;

View File

@ -8,7 +8,6 @@ import com.auth.module.security.handler.SecurityAuthenticationEntryPoint;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authorization.method.PrePostTemplateDefaults;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
@ -17,7 +16,6 @@ import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
@Configuration
@EnableWebSecurity
@EnableMethodSecurity(jsr250Enabled = true)
@ -52,13 +50,25 @@ public class SecurityWebConfiguration {
authorizeRequests
// 不认证登录接口
.requestMatchers(pathsProperties.noAuthPaths.toArray(String[]::new)).permitAll()
// 只认证 securedPaths 下的所有接口
// 安全路径配置说明
// =======================================================================
// 也可以在这里写多参数传入"/api/**","/admin/**"
// 但是在 Spring过滤器中如果要放行不需要认证请求但是需要认证的接口必需要携带token
// 做法是在这里定义要认证的接口如果要做成动态可以放到数据库
// 主要功能配置需要认证的接口路径支持多个参数"/api/**","/admin/**"
//
// 注意事项
// 1. 此处配置会覆盖方法级的@PermitAll注解
// 2. 如需"携带token则验证否则放行"的动态效果不可用此方式配置
// - 原因配置为authenticated()则必须登录
// - 若配置为permitAll()则无法自动注入SecurityContext
//
// 最佳实践建议
// 1. 固定需要强制认证的路径可在此配置pathsProperties.securedPaths
// 2. 动态路径建议
// - 方案A存储在数据库+自定义过滤器实现
// - 方案B使用方法级权限控制@PreAuthorize等
//
// 示例配置当前项目
// .requestMatchers(pathsProperties.securedPaths.toArray(String[]::new)).authenticated()
// =======================================================================
.requestMatchers(pathsProperties.securedPaths.toArray(String[]::new)).authenticated()
// 其余请求都放行
.anyRequest().permitAll()
)
@ -73,35 +83,4 @@ public class SecurityWebConfiguration {
return http.build();
}
/**
* 注册一个用于Spring Security预授权/后授权的模板元注解默认配置Bean
*
* <p>该Bean提供了基于SpEL表达式的权限校验模板可用于自定义组合注解</p>
*
* <h3>典型用法</h3>
* <p>通过此配置可以简化自定义权限注解的定义例如</p>
* <pre>{@code
* &#064;Target({ElementType.METHOD, ElementType.TYPE})
* &#064;Retention(RetentionPolicy.RUNTIME)
* &#064;PreAuthorize("hasAnyAuthority( // 使用模板提供的表达式语法
* public @interface HasAnyAuthority {
* String[] auth(); // 接收权限列表参数
* }
* }</pre>
*
* <h3>注意事项</h3>
* <ul>
* <li>需要确保Spring Security的预授权功能已启用</li>
* <li>模板表达式应符合SpEL语法规范</li>
* </ul>
*
* @return PrePostTemplateDefaults 实例用于预/后授权注解的默认配置
* @see org.springframework.security.access.prepost.PreAuthorize
* @see org.springframework.security.access.prepost.PostAuthorize
*/
@Bean
PrePostTemplateDefaults prePostTemplateDefaults() {
return new PrePostTemplateDefaults();
}
}

View File

@ -1,5 +1,6 @@
package com.auth.module.security.filter;
import com.auth.common.context.BaseContext;
import com.auth.common.exception.AuthenticSecurityException;
import com.auth.common.exception.MyAuthenticationException;
@ -42,8 +43,10 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter {
@NotNull HttpServletResponse response,
@NotNull FilterChain filterChain) throws ServletException, IOException {
try {
// 检查白名单路径
// ========================================
// 💡先检查白名单路径
// 有可能会存在需要认证路径中包含不需要认证路径
// ========================================
if (isNoAuthPath(request)) {
filterChain.doFilter(request, response);
return;
@ -64,14 +67,18 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter {
filterChain.doFilter(request, response);
} catch (AuthenticSecurityException e) {
// 直接处理认证异常不再调用filterChain.doFilter()
MyAuthenticationException myAuthenticationException = new MyAuthenticationException(e.getMessage(), e);
securityAuthenticationEntryPoint.commence(request, response, myAuthenticationException);
securityAuthenticationEntryPoint.commence(
request,
response,
new MyAuthenticationException(e.getMessage(), e)
);
} catch (RuntimeException e) {
MyAuthenticationException myAuthenticationException = new MyAuthenticationException("Authentication failed", e);
securityAuthenticationEntryPoint.commence(request, response, myAuthenticationException);
securityAuthenticationEntryPoint.commence(
request,
response,
new MyAuthenticationException("Authentication failed", e)
);
}
}
private boolean validToken(@NotNull HttpServletRequest request) {

View File

@ -1,26 +1,7 @@
package com.auth.module.security.service;
import com.auth.dao.base.view.UserRolePermission;
import org.springframework.security.core.userdetails.UserDetailsService;
import java.util.List;
public interface DbUserDetailService extends UserDetailsService {
/**
* 根据用户id查找该用户的角色内容
*
* @param userRolePermissionList 用户角色权限列表
* @return 当前用户的角色信息
*/
List<String> findUserRolesByUserId(List<UserRolePermission> userRolePermissionList);
/**
* 根据用户id查找该用户的权限内容
*
* @param userId 用户id
* @return 当前用户的权限信息
*/
List<String> findPermissionByUserId(List<UserRolePermission> userRolePermissionList);
}

View File

@ -38,10 +38,10 @@ public class DbUserDetailServiceImpl implements DbUserDetailService {
}
Long userId = userEntity.getId();
List<JaasGrantedAuthority> jaasGrantedAuthorities = new ArrayList<>();
List<UserRolePermission> userRolePermissionList = userMapper.selectUserRolePermissionByUsername(userId);
Map<String, List<UserRolePermission>> roleCodeMap = userRolePermissionList.stream().collect(Collectors.groupingBy(UserRolePermission::getRoleCode));
List<JaasGrantedAuthority> jaasGrantedAuthorities = new ArrayList<>();
roleCodeMap.forEach((s, permissionCode) -> {
List<SimpleGrantedAuthority> permissions = getPermissionByRoleCode(s, roleCodeMap).stream().map(SimpleGrantedAuthority::new).toList();
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(s, null, permissions);
@ -49,32 +49,15 @@ public class DbUserDetailServiceImpl implements DbUserDetailService {
jaasGrantedAuthorities.add(jaasGrantedAuthority);
});
List<String> roles = findUserRolesByUserId(userRolePermissionList);
// List<String> list = new ArrayList<>();
// // 设置用户角色
// List<String> roles = findUserRolesByUserId(userRolePermissionList);
// // 设置用户权限
// List<String> permissions = findPermissionByUserId(userRolePermissionList);
// list.addAll(roles);
// list.addAll(permissions);
// Set<SimpleGrantedAuthority> authorities = list.stream()
// .map(SimpleGrantedAuthority::new)
// .collect(Collectors.toSet());
// 设置用户权限
// userEntity.setAuthorities(authorities);
// // 返回时将用户密码置为空
// userEntity.setPassword(null);
// return userEntity;
List<String> roles = userRolePermissionList.stream().map(UserRolePermission::getRoleCode).toList();
return User.builder()
.username(username)
.password(userEntity.getPassword())
.roles(roles.toArray(String[]::new))
.authorities(jaasGrantedAuthorities)
.disabled(userEntity.getStatus())
// 0=禁用,1=正常,2=锁定
.disabled(!userEntity.getStatus().equals(1))
.build();
}
@ -90,25 +73,4 @@ public class DbUserDetailServiceImpl implements DbUserDetailService {
.toList();
}
/**
* 根据用户id查找该用户的角色内容
*
* @param userRolePermissionList 用户角色权限列表
* @return 当前用户的角色信息
*/
@Override
public List<String> findUserRolesByUserId(List<UserRolePermission> userRolePermissionList) {
return userRolePermissionList.stream().map(UserRolePermission::getRoleCode).toList();
}
/**
* 根据用户id查找该用户的权限内容
*
* @param userRolePermissionList 用户角色权限列表
* @return 当前用户的权限信息
*/
@Override
public List<String> findPermissionByUserId(List<UserRolePermission> userRolePermissionList) {
return userRolePermissionList.stream().map(UserRolePermission::getPermissionCode).toList();
}
}

View File

@ -1,4 +0,0 @@
package com.auth.module.security.service.impl;
// public interface InMemoryUserDetailsService extends UserDetailsService {
// }

View File

@ -8,9 +8,10 @@ jwtToken:
# 认证和鉴权配置
security-path:
secured-paths:
- "/api/**"
no-auth-paths:
- "/*/login"
- "/api/public/**"
admin-authorities:
- "ADMIN"
no-auth-paths:
- "/api/public/**"
secured-paths:
- "/api/v1/**"

View File

@ -6,7 +6,7 @@ import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;
@MapperScan(basePackages = "com.auth.dao.*.mapper")
@ComponentScan(basePackages = {"com.auth.service.base", "com.auth.dao", "com.auth.module"})
@ComponentScan(basePackages = {"com.auth.common", "com.auth.service.base", "com.auth.dao", "com.auth.module.security"})
@SpringBootApplication
public class ServiceBaseMainApplication {
public static void main(String[] args) {

View File

@ -0,0 +1,40 @@
package com.auth.service.base.controller;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@Tag(name = "检查接口", description = "检查当前用户的权限信息")
@RestController
@RequestMapping("/api/security")
public class CheckController {
@Operation(summary = "当前用户的信息", description = "当前用户的信息")
@GetMapping("/current-user")
public Authentication getCurrentUser() {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
System.out.println("Current user: " + auth.getName());
System.out.println("Authorities: " + auth.getAuthorities());
return auth;
}
@Operation(summary = "获取用户详情", description = "获取用户详情")
@GetMapping("user-detail")
public UserDetails getCurrentUserDetail() {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
Object principal = auth.getPrincipal();
if (principal instanceof UserDetails) {
return (UserDetails) principal;
} else {
return User.builder().username("未知").password("未知").build();
}
}
}