Spring Security 6.X + JWT + RBAC 权限管理实战教程(上)

前言

本教程基于 Spring Boot 3.x + Spring Security 6.x 实现,采用 JWT + Redis 的认证方案,结合 RBAC 权限模型,实现了一个完整的权限管理系统。

一、项目依赖配置

关键依赖说明:

	<!-- SpringWeb --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!-- lombok --><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency><!-- mysql驱动 --><dependency><groupId>com.mysql</groupId><artifactId>mysql-connector-j</artifactId><scope>runtime</scope></dependency><!-- Druid 连接池 --><dependency><groupId>com.alibaba</groupId><artifactId>druid-spring-boot-3-starter</artifactId><version>1.2.21</version></dependency><!-- MybatisPlus起步依赖 --><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-spring-boot3-starter</artifactId><version>3.5.5</version></dependency><!-- Knife4j API文档生产工具 --><dependency><groupId>com.github.xiaoymin</groupId><artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId><version>4.4.0</version></dependency><!-- swagger注解支持:Knife4j依赖本依赖 --><dependency><groupId>io.swagger</groupId><artifactId>swagger-annotations</artifactId><version>1.5.22</version></dependency><!-- redis --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><!-- SpringSecurity --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId></dependency><!-- validation参数校验依赖 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-validation</artifactId></dependency><!-- fastjson --><dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>2.0.40</version></dependency><!-- Hutool工具类库:图形验证码生成、加解密、简单http请求、类拷贝等 --><dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>5.8.25</version></dependency><!-- JWT依赖:用于生成和解析JWT --><dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt</artifactId><version>0.9.1</version></dependency><!-- 如果jdk大于1.8,则还需导入以下依赖 --><dependency><groupId>javax.xml.bind</groupId><artifactId>jaxb-api</artifactId><version>2.3.1</version></dependency><!-- SpringTest --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency>

二、创建RBAC权限数据表

RBAC 模型基于角色的访问控制,这是软件设计中最常用的权限管理模型,通过权限关联角色、角色关联用户的方法来间接地赋予用户权限,从而实现用户与权限的解耦。
在这里插入图片描述

💡 RBAC模型说明:

  • 用户表(user):存储用户基本信息
  • 角色表(role):定义系统角色
  • 菜单表(menu):存储系统菜单和权限标识
  • 关联表:通过中间表实现多对多关系

1. 员工表(用户表)

-- auto-generated definition
create table emp
(id          bigint unsigned auto_increment comment 'ID'primary key,dept_id     bigint unsigned              null comment '部门ID',username    varchar(50)                  not null comment '用户名',password    varchar(255)                 null comment '密码',name        varchar(50)                  not null comment '姓名',status      tinyint unsigned default '0' null comment '状态 0-正常 1-禁用',constraint usernameunique (username)
)comment '员工表' row_format = DYNAMIC;

2.菜单表

-- auto-generated definition
create table menu
(id          bigint unsigned auto_increment comment '菜单ID'primary key,parent_id   bigint unsigned            null comment '父菜单ID(支持多级菜单)',menu_name   varchar(50) default 'NULL' not null comment '菜单名称',path        varchar(255)               null comment '路由地址',component   varchar(255)               null comment '组件路径',visible     tinyint     default 0      null comment '菜单状态(0显示 1隐藏)',status      tinyint     default 0      null comment '菜单状态(0正常 1禁用)',perms       varchar(100)               null comment '权限标识( 如user:read )',icon        varchar(100)               null comment '菜单图标',order_num   tinyint                    null comment '显示顺序',type        char                       null comment '菜单类型(''M''-菜单 ''B''-按钮)',create_time datetime                   null comment '创建时间',create_user bigint                     null comment '创建者ID',update_time datetime                   null comment '更新时间',update_user bigint                     null comment '更新者ID'
)comment '菜单表' row_format = DYNAMIC;

3.角色表

-- auto-generated definition
create table role
(id          bigint unsigned auto_increment comment '主键ID'primary key,name        varchar(50)       null comment '角色名称',role_key    varchar(50)       null comment '角色权限标识(如ADMIN)',description varchar(255)      null comment '角色描述',status      tinyint default 0 null comment '角色状态(0正常 1停用)',create_time datetime          null comment '创建时间',create_user bigint            null comment '创建者ID',update_time datetime          null comment '更新时间',update_user bigint            null comment '更新者ID'
)comment '角色表' row_format = DYNAMIC;

4.员工角色关联表

-- auto-generated definition
create table emp_role
(emp_id  bigint unsigned auto_increment comment '员工id',role_id bigint unsigned default '0' not null comment '角色id',primary key (emp_id, role_id)
)row_format = DYNAMIC;

5.角色菜单关联表

-- auto-generated definition
create table role_menu
(role_id bigint unsigned auto_increment comment '角色ID',menu_id bigint unsigned default '0' not null comment '菜单id',primary key (role_id, menu_id)
)row_format = DYNAMIC;

三、核心配置类

1. 安全配置类

创建 SecurityConfig.java

/*** @description SpringSecurity配置类*/
@Configuration
@RequiredArgsConstructor
@EnableWebSecurity // 开启SpringSecurity的自定义配置(在SpringBoot项目中可以省略)
@EnableMethodSecurity // 开启全局函数权限
public class SecurityConfig {// 自定义的用于认证的过滤器,进行jwt的校验操作private final JwtTokenOncePerRequestFilter jwtTokenFilter;// 认证用户无权限访问资源的处理器private final CustomerAccessDeniedHandler customerAccessDeniedHandler;// 客户端进行认证数据的提交时出现异常,或者是匿名用户访问受限资源的处理器private final AnonymousAuthenticationHandler anonymousAuthentication;// 用户认证校验失败处理器private final LoginFailureHandler loginFailureHandler;/*** 创建BCryptPasswordEncoder注入容器,用于密码加密*/@Beanpublic PasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder();}/*** 登录时调用AuthenticationManager.authenticate执行一次校验*/@Beanpublic AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception {return config.getAuthenticationManager();}@Beanpublic SecurityFilterChain filterChain(HttpSecurity http) throws Exception{// 添加自定义异常处理类http.exceptionHandling(configurer -> {configurer.accessDeniedHandler(customerAccessDeniedHandler) // 配置认证用户无权限访问资源的处理器.authenticationEntryPoint(anonymousAuthentication); // 配置匿名用户未认证的处理器});// 配置关闭csrf机制http.csrf(AbstractHttpConfigurer::disable);// 用户认证校验失败处理器http.formLogin(conf -> conf.failureHandler(loginFailureHandler));// STATELESS(无状态):表示应用程序是无状态的,不创建会话http.sessionManagement(conf -> conf.sessionCreationPolicy(SessionCreationPolicy.STATELESS));// 配置放行路径http.authorizeHttpRequests(auth -> auth.requestMatchers("/swagger-ui/**", // 放行Swagger相关路径"/swagger-ui.html","/swagger-resources/**","/v3/api-docs/**","/webjars/**","/doc.html","/admin/emp/login"  // 放行登录接口路径).permitAll().anyRequest().authenticated());// 配置过滤器的执行顺序http.addFilterBefore(jwtTokenFilter, UsernamePasswordAuthenticationFilter.class);return http.build();}}

什么是 BCrypt 加密?
BCrypt 是一个基于 Blowfish 密码算法的密码哈希函数,专门为密码加密而设计。它具有以下特点:

  1. 自适应性:可以通过增加迭代次数来增加计算强度
  2. 加盐处理:自动生成随机盐值并混入密码
  3. 防彩虹表:每次加密同一个密码得到的结果都不同
  4. 单向加密:无法通过加密后的密文反推原始密码

2. JWT工具类

创建 JwtUtil.java

/*** JWT令牌工具类*/
public class JwtUtil {/*** 生成jwt* 使用Hs256算法, 私匙使用固定秘钥** @param secretKey jwt秘钥* @param ttlMillis jwt过期时间(毫秒)* @param claims    设置的信息* @return*/public static String createJWT(String secretKey, long ttlMillis, Map<String, Object> claims) {// 指定签名的时候使用的签名算法,也就是header那部分SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;// 生成JWT的时间long expMillis = System.currentTimeMillis() + ttlMillis;Date exp = new Date(expMillis);// 设置jwt的bodyJwtBuilder builder = Jwts.builder()// 如果有私有声明,一定要先设置这个自己创建的私有的声明,这个是给builder的claim赋值,一旦写在标准的声明赋值之后,就是覆盖了那些标准的声明的.setClaims(claims)// 设置签名使用的签名算法和签名使用的秘钥.signWith(signatureAlgorithm, secretKey.getBytes(StandardCharsets.UTF_8))// 设置过期时间.setExpiration(exp);return builder.compact();}/*** Token解密** @param secretKey jwt秘钥 此秘钥一定要保留好在服务端, 不能暴露出去, 否则sign就可以被伪造, 如果对接多个客户端建议改造成多个* @param token     加密后的token* @return*/public static Claims parseJWT(String secretKey, String token) {// 得到DefaultJwtParserClaims claims = Jwts.parser()// 设置签名的秘钥.setSigningKey(secretKey.getBytes(StandardCharsets.UTF_8))// 设置需要解析的jwt.parseClaimsJws(token).getBody();return claims;}}

application.yml中加入JWT相关配置:

jwt:secret-key: your_key # jwt签名加密秘钥ttl: 7200000 # jwt过期时间token-name: Authorization # 前端传递过来的令牌名称

创建 JwtProperties.java

/*** 生成jwt令牌相关配置*/
@Data
@Component
@ConfigurationProperties(prefix = "jwt")
public class JwtProperties {private String secretKey; // jwt签名加密秘钥private long ttl; // jwt过期时间private String tokenName; // jwt签名加密秘钥}

四、用户认证实现

1.创建员工实体类

创建 Emp.java

/*** 管理员实体类*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Tag(name = "Emp", description = "员工实体")
public class Emp implements Serializable {@Serialprivate static final long serialVersionUID = 4317337818874663187L;@Schema(description = "员工ID")private Long id;@Schema(description = "部门ID")private Long deptId;@Schema(description = "用户名")private String username;@JSONField(serialize = false)@Schema(description = "密码")private String password;@Schema(description = "姓名")private String name;@Schema(description = "状态: 0-正常, 1-禁用")private Integer status;
}

2. 自定义用户详情类

创建 EmpLogin.java实现UserDetails接口:用于封装用户的详细信息权限列表

/*** @description UserDetails的实现类*/
@Data
@NoArgsConstructor
public class EmpLogin implements UserDetails {@Serialprivate static final long serialVersionUID = 7330836274775504268L;public EmpLogin(Emp emp, List<String> list) {this.emp = emp;this.list = list;}// 权限列表private List<String> list;private Emp emp;//自定义一个权限列表的集合,中转操作@JSONField(serialize = false) //在序列化对象时忽略该字段private List<SimpleGrantedAuthority> authorities;// 用于返回权限信息@Overridepublic Collection<? extends GrantedAuthority> getAuthorities() {if (authorities != null) {return authorities;}authorities = new ArrayList<>();for (String item : list) {if (item != null && !item.trim().isEmpty()) {SimpleGrantedAuthority authority = new SimpleGrantedAuthority(item);authorities.add(authority);}}return authorities;}// 获取密码@Overridepublic String getPassword() {return emp.getPassword();}// 获取用户名@Overridepublic String getUsername() {return emp.getUsername();}// 账号是否未过期@Overridepublic boolean isAccountNonExpired() {return UserDetails.super.isAccountNonExpired();}// 判断账号是否没有锁定@Overridepublic boolean isAccountNonLocked() {return UserDetails.super.isAccountNonLocked();}// 判断账户是否没有超时@Overridepublic boolean isCredentialsNonExpired() {return UserDetails.super.isCredentialsNonExpired();}// 判断账号是否可用@Overridepublic boolean isEnabled() {return UserDetails.super.isEnabled();}
}

说明:

  • getAuthorities 方法构建用户的权限集合。
  • 重写 getPasswordgetUsername 方法,用于提供用户的凭证

3. 实现UserDetailsService

创建 UserDetailsServiceImpl.java实现UserDetailsService接口:完成自定义用户查询逻辑

@Service
@RequiredArgsConstructor
public class UserDetailsServiceImpl implements UserDetailsService {private final EmpMapper empMapper;private final MenuMapper menuMapper;/*** 根据用户名查询用户信息*/@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {if (username.isEmpty()){throw new InternalAuthenticationServiceException("");}//  根据用户名查询用户信息QueryWrapper<Emp> wrapper = new QueryWrapper<>();wrapper.eq("username", username);Emp emp = empMapper.selectOne(wrapper);// 判断是否查到用户 如果没查到抛出异常if (ObjectUtil.isNull(emp)){throw new UsernameNotFoundException("");}// 2.赋权操作 查询数据库List<String> list = menuMapper.getMenuByUserId(emp.getId());for (String s : list) {System.out.println(s);}return new EmpLogin(emp, list);}
}

说明:

  • 根据用户名查询用户信息,如果用户不存在,抛出异常。
  • 从数据库中查询用户的权限列表并封装到EmpLogin对象中。

五、JWT认证过滤器

创建 JwtAuthenticationTokenFilter.java:用于拦截请求并校验 JWT 的有效性

/*** @description 每次请求的 Security 过滤类。执行jwt有效性检查,如果失败,不会设置 SecurityContextHolder 信息,会进入 AuthenticationEntryPoint*/
// 每一个servlet请求,只执行一次
@Component
@Slf4j
public class JwtTokenOncePerRequestFilter extends OncePerRequestFilter {@Autowiredprivate JwtProperties jwtProperties; // JWT相关属性配置类@Autowiredprivate RedisUtil redisUtil; // Redis工具类@Autowiredprivate LoginFailureHandler loginFailureHandler;// 添加白名单路径列表private final String[] whitelist = {"/admin/emp/login","/swagger-ui/**","/swagger-ui.html","/swagger-resources/**","/v3/api-docs/**","/webjars/**","/doc.html"};@Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {// 1. 判断当前请求是否在白名单中String uri = request.getRequestURI();if (isWhitelisted(uri)) {filterChain.doFilter(request, response);return;}try {// 2. 校验tokenthis.validateToken(request);} catch (AuthenticationException e) {loginFailureHandler.onAuthenticationFailure(request, response, e); // 处理登录失败的异常return;}filterChain.doFilter(request, response);}// 判断请求路径是否在白名单中private boolean isWhitelisted(String uri) {for (String pattern : whitelist) {if (pattern.endsWith("/**")) {// 处理通配符路径String basePattern = pattern.substring(0, pattern.length() - 3);if (uri.startsWith(basePattern)) {return true;}} else if (pattern.equals(uri)) {// 精确匹配return true;}}return false;}// 校验tokenprivate void validateToken(HttpServletRequest request) {// 说明:登录了,再次请求其他需要认证的资源String token = request.getHeader("Authorization");if (ObjectUtils.isEmpty(token)) { // header没有tokentoken = request.getParameter("Authorization");}if (ObjectUtils.isEmpty(token)) {throw new CustomerAuthenticationException("token为空");}// redis进行校验if (!redisUtil.hasKey("token_" + token)) {throw new CustomerAuthenticationException("token已过期");}// 校验tokenEmpLogin empLogin;try {log.info("jwt校验:{}", token);Claims claims = JwtUtil.parseJWT(jwtProperties.getSecretKey(), token);String loginUserString = claims.get(JwtClaimsConstant.EMP_LOGIN).toString();// 把json字符串转为对象empLogin = JSON.parseObject(loginUserString, EmpLogin.class);log.info("当前员工id:{}", empLogin.getEmp().getId());BaseContext.setCurrentId(empLogin.getEmp().getId());} catch (Exception ex) {throw new CustomerAuthenticationException("token校验失败");}BaseContext.setCurrentId(empLogin.getEmp().getId());// 把校验后的用户信息再次放入到SpringSecurity的上下文中UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(empLogin, null,empLogin.getAuthorities()); // 已认证的 Authentication 对象,包含用户的权限信息SecurityContextHolder.getContext().setAuthentication(authentication);System.out.println(empLogin.getAuthorities());}
}

说明:

  • 过滤器在每次请求时执行 JWT 校验。
  • 通过 Redis 检查 Token 的有效性,解析后将用户信息存入 SecurityContextHolder

六、自定义处理器

帮助我们在认证失败或者授权失败的情况下也能和我们接口一样返回相同结构的json,这样可以让前端能对响应进行统一的处理。

1.自定义验证异常类

创建 CustomerAuthenticationException.java:用于在认证失败的情况下,抛出自定义的认证异常

/*** @description 自定义认证验证异常*/
public class CustomerAuthenticationException extends AuthenticationException {public CustomerAuthenticationException(String msg) {super(msg);}
}

说明:
通过继承 AuthenticationException,可以将自定义异常与 Spring Security 的认证机制结合,支持在认证失败时抛出特定的错误消息。

2.编写认证用户无权限访问处理器

创建CustomerAccessDeniedHandler.java:处理已认证用户尝试访问无权限资源的情况,返回统一格式的 JSON 响应

/*** @description 认证用户无权限访问的处理器*/
@Component
@Slf4j
public class CustomerAccessDeniedHandler implements AccessDeniedHandler{@Overridepublic void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {log.error("权限不足,URI:{},异常:{}", request.getRequestURI(), accessDeniedException.getMessage());// 发生这个行为,做响应处理,给一个响应的结果response.setContentType("application/json;charset=utf-8");// 构建输出流对象ServletOutputStream outputStream = response.getOutputStream();// 调用fastjson工具,进行Result对象序列化String error = JSON.toJSONString(Result.error("权限不足,请联系管理员"));outputStream.write(error.getBytes(StandardCharsets.UTF_8));outputStream.flush();outputStream.close();}
}

说明:

  • 使用 AccessDeniedHandler 接口实现自定义逻辑。
  • 捕获访问拒绝异常并记录日志,同时通过输出流返回统一的 JSON 响应结构,方便前端统一处理。

3.编写匿名用户访问受限资源的处理器

创建AnonymousAuthenticationHandler.java:处理未认证用户(匿名用户)访问受限资源的情况,返回特定的错误信息。

/*** @description 客户端进行认证数据的提交时出现异常,或者是匿名用户访问受限资源的处理器*/
@Component
public class AnonymousAuthenticationHandler implements AuthenticationEntryPoint {@Overridepublic void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {// 发生这个行为,做响应处理,给一个响应的结果response.setContentType("application/json;charset=utf-8");// 构建输出流对象ServletOutputStream outputStream = response.getOutputStream();// 调用fastjson工具,进行Result对象序列化String error = "";if (authException instanceof BadCredentialsException){// 用户名或密码错误  401error = JSON.toJSONString(Result.error(authException.getMessage()));} else if (authException instanceof InternalAuthenticationServiceException) {error = JSON.toJSONString(Result.error("用户名为空"));} else{error = JSON.toJSONString(Result.error("匿名用户无权限访问"));}outputStream.write(error.getBytes(StandardCharsets.UTF_8));outputStream.flush();outputStream.close();}
}

说明:

  • 通过实现 AuthenticationEntryPoint 接口处理未认证用户的访问异常。
  • 根据不同类型的 AuthenticationException 返回不同的错误信息,增强了响应的针对性。

4.编写自定义认证失败的处理器

创建LoginFailureHandler.java:处理用户登录失败时的异常,返回详细的失败原因。

/*** @description 用户校验认证失败的处理器*/
@Component
public class LoginFailureHandler implements AuthenticationFailureHandler {@Overridepublic void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {// 发生这个行为,做响应处理,给一个响应的结果response.setContentType("application/json;charset=utf-8");// 构建输出流对象ServletOutputStream outputStream = response.getOutputStream();String message;if (exception instanceof AccountExpiredException) {message = "用户过期,登录失败";} else if (exception instanceof BadCredentialsException) {message = "用户名或密码错误,请重新输入!";} else if (exception instanceof CredentialsExpiredException) {message = "密码过期,请重新输入!";} else if (exception instanceof DisabledException) {message = "账户被禁用,登录失败!";} else if (exception instanceof LockedException) {message = "账户被锁,登录失败!";} else if (exception instanceof InternalAuthenticationServiceException) {message = "账户不存在,登录失败!";} else if (exception instanceof CustomerAuthenticationException) {message = exception.getMessage();} else {message = "登录失败!";}// 调用fastjson工具,进行Result对象序列化String error = JSON.toJSONString(Result.error(message));outputStream.write(error.getBytes(StandardCharsets.UTF_8));outputStream.flush();outputStream.close();}
}

说明:

  • 覆写 onAuthenticationFailure 方法,根据不同的认证异常类型返回详细的失败原因。
  • 提供灵活的错误提示信息,便于用户快速定位登录失败的具体原因。

5.编写全局异常处理器

创建GlobalExceptionHandler.java:捕获并统一处理项目中的异常,包括业务异常和 SQL 异常。

/*** 全局异常处理器,处理项目中抛出的业务异常*/
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {/*** 捕获所有异常*/@ExceptionHandler(Exception.class)public Result ex(Exception ex){// 如果是访问拒绝异常,不处理,让SecurityConfig中配置的处理器处理if(ex instanceof AccessDeniedException) {throw (AccessDeniedException)ex;}log.error("全局异常信息:{}", ex.getMessage());return Result.error(StringUtils.hasLength(ex.getMessage()) ? ex.getMessage() : "操作失败");}/*** 捕获业务异常*/@ExceptionHandlerpublic Result exceptionHandler(BaseException ex){log.error("业务异常信息:{}", ex.getResultEnum().message());return Result.error(ex.getResultEnum());}/*** 处理SQL异常*/@ExceptionHandlerpublic Result exceptionHandler(SQLIntegrityConstraintViolationException ex){// 错误信息:Duplicate entry 'zhaosi' for key 'employee.idx_username' -- > 用户ID重复String message = ex.getMessage();if(message.contains("Duplicate entry")){String[] split = message.split(" ");String username = split[2];String msg = username + ResultEnum.USER_NAME_HAS_EXISTED.message();return Result.error(msg);}else {return Result.error(ResultEnum.UNKNOWN_ERROR);}}
}

在全局异常处理器中为何要这样写?
在上一篇文章中,进行详细的讲解 Spring Security 6.3 权限异常处理实战解析

七、登录接口实现

1.实体类准备

创建EmpLoginVO.java

/*** 用户登录响应对象*/
@Data
@Builder
@Tag(name = "EmpLoginVO", description = "员工登录响应")
public class EmpLoginVO implements Serializable {@Serialprivate static final long serialVersionUID = 4393557997355879737L;@Schema(description = "用户ID")private Long id;@Schema(description = "用户名")private String username;@Schema(description = "姓名")private String name;@Schema(description = "令牌")private String token;
}

创建EmpLoginDTO.java

@Data
public class EmpLoginDTO implements Serializable {@Serialprivate static final long serialVersionUID = 8347822700891152077L;@NotBlank(message = "账号不能为空")@Pattern(regexp = "^\\w{5,20}$", message = "用户名的长度必须为5~16位")private String username; // 账号@NotBlank(message = "密码不能为空")@Pattern(regexp = "^\\w{5,16}$", message = "密码的长度必须为5~16位")private String password; // 密码
}

2.ThreadLocal工具类准备

创建BaseContext.java

public class BaseContext {public static ThreadLocal<Long> threadLocal = new ThreadLocal<>();public static void setCurrentId(Long id) {threadLocal.set(id);}public static Long getCurrentId() {return threadLocal.get();}public static void removeCurrentId() {threadLocal.remove();}}

3.Controller层

创建EmpController.java:

@CrossOrigin // 允许跨域
@RestController
@RequestMapping("admin/emp")
@Tag(name = "员工管理接口")
@Slf4j
@RequiredArgsConstructor
public class EmpController {private final EmpService empService;private final RedisUtil redisUtil;/*** 员工登录* @param empLoginDTO 员工登录信息* @return  统一返回结果*/@PostMapping("/login")@Operation(summary = "员工登录")public Result<EmpLoginVO> login(@Validated @RequestBody EmpLoginDTO empLoginDTO) {log.info("员工:{},登录成功", empLoginDTO.getUsername());EmpLoginVO empLoginVO = empService.empLogin(empLoginDTO);return Result.success(empLoginVO);}/*** 员工退出登录* @return  统一返回结果*/@PostMapping("/logout")@Operation(summary = "员工退出登录")public Result logout(HttpServletRequest request, HttpServletResponse response) {log.info("员工ID:{},退出登录", BaseContext.getCurrentId());String token = request.getHeader("Authorization");if (ObjectUtils.isEmpty(token)) { // header没有tokentoken = request.getParameter("Authorization");}Authentication authentication = SecurityContextHolder.getContext().getAuthentication();if (authentication != null) {// 清除上下文new SecurityContextLogoutHandler().logout(request, response, authentication);// 清理redisredisUtil.del("token_" + token);// 清理ThreadLocalBaseContext.removeCurrentId();}return Result.success();}
}

4.Service层

创建EmpService.java接口:

public interface EmpService extends IService<Emp> {/*** 管理员登录* @param empLoginDTO 管理员登录表单* @return 员工登录VO*/EmpLoginVO empLogin(EmpLoginDTO empLoginDTO);}

创建EmpServiceImpl.java实现类:

@Service
@Slf4j
@RequiredArgsConstructor
public class EmpServiceImpl extends ServiceImpl<EmpMapper, Emp> implements EmpService {private final EmpMapper empMapper;private final AuthenticationManager authenticationManager;private final JwtProperties jwtProperties;private final RedisUtil redisUtil;/*** 管理员登录*/public EmpLoginVO empLogin(EmpLoginDTO empLoginDTO) {String username = empLoginDTO.getUsername();String password = empLoginDTO.getPassword();// 1. 封装用户登录表单,创建未认证Authentication对象UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(username, password);// 2. 进行校验Authentication authenticate = authenticationManager.authenticate(authentication);// 3. 获取用户信息if (Objects.isNull(authenticate)){throw new RuntimeException("用户名或密码错误");}EmpLogin empLogin = (EmpLogin) authenticate.getPrincipal();Emp emp = empLogin.getEmp();if (emp.getStatus() == 1){throw new RuntimeException("账号被禁用");}log.info("员工 {} 登录成功", empLogin.getEmp().getName());// 登录成功,生成jwt令牌Map<String, Object> claims = new HashMap<>();// 使用fastjson的方法,把对象转换成json字符串String loginEmpString = JSON.toJSONString(empLogin);claims.put(JwtClaimsConstant.EMP_LOGIN, loginEmpString);String token = JwtUtil.createJWT(jwtProperties.getSecretKey(),jwtProperties.getTtl(),claims);// 存储redis白名单String tokenKey = "token_" + token;redisUtil.set(tokenKey, token, jwtProperties.getTtl()/1000);BaseContext.setCurrentId(emp.getId());//3、返回实体对象return EmpLoginVO.builder().id(emp.getId()).token(token).username(emp.getUsername()).name(emp.getName()).build();}
}

5.Mapper层

创建EmpMapper.java接口:

@Mapper
public interface EmpMapper extends BaseMapper<Emp> {}

八、权限控制使用

1. 注解方式

在需要权限控制的接口上添加注解


// 在启动类或者SecurityConfig配置类上添加
@EnableMethodSecurity// 需要 "ems:employee:list" 权限才能访问
@PreAuthorize("hasAuthority('ems:employee:list')")
@GetMapping("/page")
public Result<PageResult> getList(EmpPageDTO empPageDTO) {return Result.success(empService.pageQuery(empPageDTO));
}// 需要多个权限中的任意一个
@PreAuthorize("hasAnyAuthority('ems:employee:add','ems:employee:edit')")
@PostMapping
public Result add(@RequestBody EmpAddDTO empAddDTO) {empService.add(empAddDTO);return Result.success();
}

2. 配置方式

SecurityConfig 中配置:

.authorizeRequests().antMatchers("/admin/emp/login").anonymous()  // 允许匿名访问.antMatchers("/admin/emp/info").authenticated()  // 需要认证.antMatchers("/admin/emp/**").hasRole("ADMIN")  // 需要ADMIN角色

总结

本教程介绍了 Spring Security 框架的基础搭建过程,包括认证、授权、异常处理等核心功能的实现。通过这些基础配置,我们已经构建了一个安全、可靠的权限管理框架。在下篇教程中,我们将继续完善角色管理和动态权限控制,敬请期待!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/4412.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

InVideo AI技术浅析(五):生成对抗网络

一、特效生成 1. 工作原理 特效生成是计算机视觉中的高级应用,旨在通过算法生成高质量的视觉特效,如风格迁移、图像到图像的翻译等。InVideo AI 使用生成对抗网络(GAN)来实现这一功能。GAN 通过生成器和判别器两个网络的对抗训练,生成逼真的视觉特效。 2. 关键技术模型…

JWT在线解密/JWT在线解码 - 加菲工具

JWT在线解密/JWT在线解码 首先进入加菲工具 选择 “JWT 在线解密/解码” https://www.orcc.online 或者直接进入JWT 在线解密/解码 https://www.orcc.online/tools/jwt 进入功能页面 使用 输入对应的jwt内容&#xff0c;点击解码按钮即可

Ubuntu 24.04 LTS 安装 tailscale 并访问 SMB共享文件夹

Ubuntu 24.04 LTS 安装 tailscale 安装 Tailscale 官方仓库 首先&#xff0c;确保系统包列表是最新的&#xff1a; sudo apt update接下来&#xff0c;安装 Tailscale 所需的仓库和密钥&#xff1a; curl -fsSL https://tailscale.com/install.sh | sh这会自动下载并安装 …

吴恩达深度学习——神经网络介绍

文章内容来自BV11H4y1F7uH&#xff0c;仅为个人学习所用。 文章目录 什么是神经网络引入神经网络神经元激活函数ReLU隐藏单元 用神经网络进行监督学习监督学习与无监督学习举例 什么是神经网络 引入 已经有六个房子的数据集&#xff0c;横轴为房子大小&#xff0c;纵轴为房子…

xiao esp32 S3播放SD卡wav音频

本文旨在使用xiao esp32 S3 播放SD卡上的音频文件 1 硬件准备 SD卡 2 代码实现 2.1 依赖库 ESP32-audioI2S-master 2.2 代码 #include "Arduino.h" #include "Audio.h" #include "SD.h"// Digital I/O used #define I2S_DOUT 6 #defi…

SAP POC 项目完工进度 - 收入确认方式【工程制造行业】【新准则下工程项目收入确认】

1. SAP POC收入确认基础概念 1.1 定义与原则 SAP POC&#xff08;Percentage of Completion&#xff09;收入确认方式是一种基于项目完工进度来确认收入的方法。其核心原则是根据项目实际完成的工作量或成本投入占预计总工作量或总成本的比例&#xff0c;来确定当期应确认的收…

【25】Word:林涵-科普文章❗

目录 题目​ NO1.2.3 NO4.5.6 NO7.8 NO9.10 NO11.12 不连续选择&#xff1a;按住ctrl按键&#xff0c;不连续选择连续选择&#xff1a;按住shift按键&#xff0c;选择第一个&#xff0c;选择最后一个。中间部分全部被选择 题目 NO1.2.3 布局→纸张方向&#xff1a;横向…

Java基础——概念和常识(语言特点、JVM、JDK、JRE、AOT/JIT等介绍)

我是一个计算机专业研0的学生卡蒙Camel&#x1f42b;&#x1f42b;&#x1f42b;&#xff08;刚保研&#xff09; 记录每天学习过程&#xff08;主要学习Java、python、人工智能&#xff09;&#xff0c;总结知识点&#xff08;内容来自&#xff1a;自我总结网上借鉴&#xff0…

OpenWrt 中使用 LuCI 界面部署 Docker 镜像

本篇博客将介绍如何在 OpenWrt 上使用 LuCI 部署 Docker 镜像&#xff0c;以 "hello-world" 镜像为例。 前提条件 已安装支持 Docker 的 OpenWrt 系统。 Docker 服务已在 OpenWrt 上成功安装并运行。 LuCI Docker 插件&#xff08;luci-app-docker 或类似的管理界…

MySQL 主从复制原理及其工作过程的配置

一、MySQL主从复制原理 MySQL 主从同步是一种数据库复制技术&#xff0c;它通过将主服务器上的数据更改复制到一个或多个从服务器&#xff0c;实现数据的自动同步。 主从同步的核心原理是将主服务器上的二进制日志复制到从服务器&#xff0c;并在从服务器上执行这些日志中的操作…

网络编程-UDP套接字

文章目录 UDP/TCP协议简介两种协议的联系与区别Socket是什么 UDP的SocketAPIDatagramSocketDatagramPacket 使用UDP模拟通信服务器端客户端测试 完整测试代码 UDP/TCP协议简介 两种协议的联系与区别 TCP和UDP其实是传输层的两个协议的内容, 差别非常大, 对于我们的Java来说, …

nginx 配置代理,根据 不同的请求头进行转发至不同的代理

解决场景&#xff1a;下载发票的版式文件&#xff0c;第三方返回的是url链接地址&#xff0c;但是服务是部署在内网环境&#xff0c;无法访问互联网进行下载。此时需要进行走反向代理出去&#xff0c;如果按照已有套路&#xff0c;就是根据不同的访问前缀&#xff0c;跳转不同的…

Unity补充 -- 协程相关

1.协程。 协程并不是线程。线程是主线程之外的另一条 代码按照逻辑执行通道。协程则是在代码在按照逻辑执行的同时&#xff0c;是否需要执行额外的语句块。 2.协程的作用。 在update执行的时候&#xff0c;是按照帧来进行刷新的&#xff0c;也是按照帧执行代码的。但是又不想…

计算机毕业设计Python+卷积神经网络租房推荐系统 租房大屏可视化 租房爬虫 hadoop spark 58同城租房爬虫 房源推荐系统

温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 作者简介&#xff1a;Java领…

【Golang/nacos】nacos配置的增删查改,以及服务注册的golang实例及分析

前言 本文分析的实例来源于nacos在github上的开源仓库 nacos配置的增删查改 先具体来看一段代码&#xff0c;我将逐步分析每一段的作用 package mainimport ("fmt""time""github.com/nacos-group/nacos-sdk-go/clients""github.com/naco…

AIGC视频生成明星——Emu Video模型

大家好&#xff0c;这里是好评笔记&#xff0c;公主号&#xff1a;Goodnote&#xff0c;专栏文章私信限时Free。本文详细介绍Meta的视频生成模型Emu Video&#xff0c;作为Meta发布的第二款视频生成模型&#xff0c;在视频生成领域发挥关键作用。 &#x1f33a;优质专栏回顾&am…

5、docker-compose和docker-harbor

安装部署docker-compose 自动编排工具&#xff0c;可以根据dockerfile自动化的部署docker容器。是yaml文件格式&#xff0c;注意缩进。 1、安装docker-compose 2、配置compose配置文件docker-compose.yml 3、运行docker-compose.yml -f&#xff1a;指定文件&#xff0c;up&…

Vue3 nginx 打包后遇到的问题

前端vite文件配置 export default defineConfig({plugins: [vue(),DefineOptions()],base:./,resolve:{alias:{:/src, //配置指向src目录components:/src/components,views:/src/views}},server:{// host:0.0.0.0,// port:7000,proxy:{/api:{target:xxx, // 目标服务器地址 &am…

云上贵州多彩宝荣获仓颉社区先锋应用奖 | 助力数字政务新突破

在信息技术应用创新的浪潮中&#xff0c;仓颉社区吸引了众多企业和开发者的积极参与&#xff0c;已有多个应用成功落地&#xff0c;展现出蓬勃的创新活力。仓颉编程语言精心遴选了在社区建设、应用创新、开源共建、技术布道等方面做出突出贡献的优秀项目应用&#xff0c;并颁发…

强推未发表!3D图!Transformer-LSTM+NSGAII工艺参数优化、工程设计优化!

目录 效果一览基本介绍程序设计参考资料 效果一览 基本介绍 1.Transformer-LSTMNSGAII多目标优化算法&#xff0c;工艺参数优化、工程设计优化&#xff01;&#xff08;Matlab完整源码和数据&#xff09; Transformer-LSTM模型的架构&#xff1a;输入层&#xff1a;多个变量作…