feat(security): implement JWT-based authentication and authorization

- Configured JWT token validation filter in security chain
- Added user role mapping with new t_user_role table and UserRole entity
- Implemented custom authentication entry point and access denied handler
- Updated UserDetailService to load user roles from database
- Added @PreAuthorize annotation support for method-level security
- Refactored build scripts to use java-library plugin and proper dependency scope
- Enhanced SQL schema with user role table and improved table comments
- Added global exception handler for AccessDeniedException
- Introduced ResponseCodeEnum constants for unauthorized and forbidden access
- Integrated TokenAuthenticationFilter into Spring Security filter chain
This commit is contained in:
2025-11-29 15:19:35 +08:00
parent de52e2816c
commit 0a126eb520
17 changed files with 339 additions and 28 deletions

6
.idea/data_source_mapping.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DataSourcePerFileMappings">
<file url="file://$PROJECT_DIR$/sql/createTable.sql" value="bb8330e4-9a89-4978-ad63-ad6402096c16" />
</component>
</project>

2
.idea/modules.xml generated
View File

@@ -5,6 +5,8 @@
<module fileurl="file://$PROJECT_DIR$/weblog-springboot.iml" filepath="$PROJECT_DIR$/weblog-springboot.iml" /> <module fileurl="file://$PROJECT_DIR$/weblog-springboot.iml" filepath="$PROJECT_DIR$/weblog-springboot.iml" />
<module fileurl="file://$PROJECT_DIR$/.idea/modules/weblog-module-admin/weblog-springboot.weblog-module-admin.main.iml" filepath="$PROJECT_DIR$/.idea/modules/weblog-module-admin/weblog-springboot.weblog-module-admin.main.iml" /> <module fileurl="file://$PROJECT_DIR$/.idea/modules/weblog-module-admin/weblog-springboot.weblog-module-admin.main.iml" filepath="$PROJECT_DIR$/.idea/modules/weblog-module-admin/weblog-springboot.weblog-module-admin.main.iml" />
<module fileurl="file://$PROJECT_DIR$/.idea/modules/weblog-module-common/weblog-springboot.weblog-module-common.main.iml" filepath="$PROJECT_DIR$/.idea/modules/weblog-module-common/weblog-springboot.weblog-module-common.main.iml" /> <module fileurl="file://$PROJECT_DIR$/.idea/modules/weblog-module-common/weblog-springboot.weblog-module-common.main.iml" filepath="$PROJECT_DIR$/.idea/modules/weblog-module-common/weblog-springboot.weblog-module-common.main.iml" />
<module fileurl="file://$PROJECT_DIR$/.idea/modules/weblog-module-jwt/weblog-springboot.weblog-module-jwt.main.iml" filepath="$PROJECT_DIR$/.idea/modules/weblog-module-jwt/weblog-springboot.weblog-module-jwt.main.iml" />
<module fileurl="file://$PROJECT_DIR$/.idea/modules/weblog-web/weblog-springboot.weblog-web.main.iml" filepath="$PROJECT_DIR$/.idea/modules/weblog-web/weblog-springboot.weblog-web.main.iml" />
</modules> </modules>
</component> </component>
</project> </project>

View File

@@ -1,3 +1,5 @@
-- ====================================================================================================================
-- ====================================================================================================================
-- 1. 创建一个函数,用于在数据更新时自动修改 update_time 字段 -- 1. 创建一个函数,用于在数据更新时自动修改 update_time 字段
CREATE OR REPLACE FUNCTION set_update_time() CREATE OR REPLACE FUNCTION set_update_time()
RETURNS TRIGGER AS RETURNS TRIGGER AS
@@ -7,7 +9,6 @@ BEGIN
RETURN NEW; RETURN NEW;
END; END;
$$ LANGUAGE plpgsql; $$ LANGUAGE plpgsql;
-- 2. 创建表(使用 BOOLEAN 替代 SMALLINT for is_deleted -- 2. 创建表(使用 BOOLEAN 替代 SMALLINT for is_deleted
CREATE TABLE t_user CREATE TABLE t_user
( (
@@ -20,14 +21,30 @@ CREATE TABLE t_user
-- 使用 BOOLEAN 逻辑删除DEFAULT FALSE 对应 '0未删除' -- 使用 BOOLEAN 逻辑删除DEFAULT FALSE 对应 '0未删除'
is_deleted BOOLEAN NOT NULL DEFAULT FALSE is_deleted BOOLEAN NOT NULL DEFAULT FALSE
); );
-- 3. 创建触发器,在每次 UPDATE 操作前调用函数 -- 3. 创建触发器,在每次 UPDATE 操作前调用函数
CREATE TRIGGER set_t_user_update_time CREATE TRIGGER set_t_user_update_time
BEFORE UPDATE BEFORE UPDATE
ON t_user ON t_user
FOR EACH ROW FOR EACH ROW
EXECUTE FUNCTION set_update_time(); EXECUTE FUNCTION set_update_time();
-- 添加注释 -- 添加注释
COMMENT ON TABLE t_user IS '用户表(优化版)'; COMMENT ON TABLE t_user IS '用户表(优化版)';
COMMENT ON COLUMN t_user.is_deleted IS '逻辑删除FALSE未删除 TRUE已删除'; COMMENT ON COLUMN t_user.is_deleted IS '逻辑删除FALSE未删除 TRUE已删除';
-- ====================================================================================================================
-- ====================================================================================================================
-- ====================================================================================================================
-- ====================================================================================================================
CREATE TABLE t_user_role
(
id BIGSERIAL PRIMARY KEY,
username VARCHAR(60) NOT NULL,
role_name VARCHAR(60) NOT NULL, -- 重命名为 role_name 避免关键字冲突
create_time TIMESTAMP WITHOUT TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX idx_username ON t_user_role (username);
COMMENT ON COLUMN t_user_role.role_name IS '角色名称';
-- ====================================================================================================================
-- ====================================================================================================================

View File

@@ -1,15 +1,14 @@
plugins { plugins {
java `java-library`
} }
dependencies { dependencies {
// Spring Security api("org.springframework.boot:spring-boot-starter-security")
implementation("org.springframework.boot:spring-boot-starter-security")
// Test
testImplementation("org.springframework.boot:spring-boot-starter-test")
implementation(project(":weblog-module-common")) implementation(project(":weblog-module-common"))
implementation(project(":weblog-module-jwt")) api(project(":weblog-module-jwt"))
testImplementation("org.springframework.boot:spring-boot-starter-test")
testImplementation("org.springframework.security:spring-security-test") testImplementation("org.springframework.security:spring-security-test")
} }

View File

@@ -1,12 +1,15 @@
package com.hanserwei.admin.config; package com.hanserwei.admin.config;
import com.hanserwei.jwt.config.JwtAuthenticationSecurityConfig; import com.hanserwei.jwt.config.JwtAuthenticationSecurityConfig;
import com.hanserwei.jwt.handler.RestAccessDeniedHandler;
import com.hanserwei.jwt.handler.RestAuthenticationEntryPoint;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationProvider; import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider; import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.Customizer; import org.springframework.security.config.Customizer;
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.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
@@ -17,11 +20,18 @@ import org.springframework.security.web.SecurityFilterChain;
@Configuration @Configuration
@EnableWebSecurity @EnableWebSecurity
@EnableMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class WebSecurityConfig { public class WebSecurityConfig {
@Resource @Resource
private JwtAuthenticationSecurityConfig jwtAuthenticationSecurityConfig; private JwtAuthenticationSecurityConfig jwtAuthenticationSecurityConfig;
@Resource
private RestAccessDeniedHandler restAccessDeniedHandler;
@Resource
private RestAuthenticationEntryPoint restAuthenticationEntryPoint;
@Bean @Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http http
@@ -38,7 +48,12 @@ public class WebSecurityConfig {
.sessionManagement(session -> session .sessionManagement(session -> session
.sessionCreationPolicy(SessionCreationPolicy.STATELESS) .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
) )
// 5. 应用自定义配置 (核心变化) // 5. 自定义未登录/权限不足的响应
.exceptionHandling(exception -> exception
.authenticationEntryPoint(restAuthenticationEntryPoint)
.accessDeniedHandler(restAccessDeniedHandler)
)
// 6. 应用自定义配置 (核心变化)
.with(jwtAuthenticationSecurityConfig, Customizer.withDefaults()); .with(jwtAuthenticationSecurityConfig, Customizer.withDefaults());
return http.build(); return http.build();
} }

View File

@@ -4,21 +4,18 @@ plugins {
dependencies { dependencies {
// guava
implementation("com.google.guava:guava:33.5.0-jre") implementation("com.google.guava:guava:33.5.0-jre")
// commons-lang3
implementation("org.apache.commons:commons-lang3:3.20.0") implementation("org.apache.commons:commons-lang3:3.20.0")
// jpa
api("org.springframework.boot:spring-boot-starter-data-jpa")
// test
testImplementation("org.springframework.boot:spring-boot-starter-test")
// jackson
implementation("com.fasterxml.jackson.core:jackson-databind") implementation("com.fasterxml.jackson.core:jackson-databind")
implementation("com.fasterxml.jackson.core:jackson-core") implementation("com.fasterxml.jackson.core:jackson-core")
// aop
implementation("org.springframework.boot:spring-boot-starter-aop") api("org.springframework.boot:spring-boot-starter-data-jpa")
// web
api("org.springframework.boot:spring-boot-starter-web") api("org.springframework.boot:spring-boot-starter-web")
// postgresql api("org.springframework.boot:spring-boot-starter-security")
api("org.springframework.boot:spring-boot-starter-aop")
runtimeOnly("org.postgresql:postgresql") runtimeOnly("org.postgresql:postgresql")
testImplementation("org.springframework.boot:spring-boot-starter-test")
testImplementation("org.springframework.security:spring-security-test")
} }

View File

@@ -0,0 +1,64 @@
package com.hanserwei.common.domain.dataobject;
import jakarta.persistence.*; // 使用 Jakarta Persistence API (JPA 3.0+)
import lombok.Getter;
import lombok.Setter;
import org.hibernate.annotations.CreationTimestamp;
import java.io.Serial;
import java.io.Serializable;
import java.time.Instant;
/**
* 用户角色表t_user_role 对应实体)
*/
@Setter
@Getter
@Entity
@Table(name = "t_user_role",
indexes = {
@Index(name = "idx_username", columnList = "username") // 映射数据库中的 idx_username 索引
})
public class UserRole implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* ID (BIG SERIAL PRIMARY KEY)
*/
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
private Long id;
/**
* 用户名 (VARCHAR(60) NOT NULL)
*/
@Column(name = "username", length = 60, nullable = false)
private String username;
/**
* 角色名称 (VARCHAR(60) NOT NULL)
*/
@Column(name = "role_name", length = 60, nullable = false)
private String roleName;
/**
* 创建时间 (TIMESTAMP WITHOUT TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP)
*/
@CreationTimestamp
@Column(name = "create_time", nullable = false, updatable = false)
private Instant createTime;
// --- 构造函数 (Constructor) ---
public UserRole() {
}
public UserRole(String username, String roleName) {
this.username = username;
this.roleName = roleName;
}
}

View File

@@ -0,0 +1,11 @@
package com.hanserwei.common.domain.repository;
import com.hanserwei.common.domain.dataobject.UserRole;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
public interface UserRoleRepository extends JpaRepository<UserRole, Long> {
List<UserRole> queryAllByUsername(String username);
}

View File

@@ -15,6 +15,8 @@ public enum ResponseCodeEnum implements BaseExceptionInterface {
// ----------- 业务异常状态码 ----------- // ----------- 业务异常状态码 -----------
LOGIN_FAIL("20000", "登录失败"), LOGIN_FAIL("20000", "登录失败"),
USERNAME_OR_PWD_ERROR("20001", "用户名或密码错误"), USERNAME_OR_PWD_ERROR("20001", "用户名或密码错误"),
UNAUTHORIZED("20002", "无访问权限,请先登录!"),
FORBIDDEN("20004", "演示账号仅支持查询操作!")
; ;
// 异常码 // 异常码

View File

@@ -10,6 +10,7 @@ import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.security.access.AccessDeniedException;
import java.util.Optional; import java.util.Optional;
@ControllerAdvice @ControllerAdvice
@@ -76,4 +77,11 @@ public class GlobalExceptionHandler {
return Response.fail(errorCode, errorMessage); return Response.fail(errorCode, errorMessage);
} }
@ExceptionHandler({ AccessDeniedException.class })
public void throwAccessDeniedException(AccessDeniedException e) throws AccessDeniedException {
// 捕获到鉴权失败异常,主动抛出,交给 RestAccessDeniedHandler 去处理
log.info("============= 捕获到 AccessDeniedException");
throw e;
}
} }

View File

@@ -2,12 +2,10 @@ plugins {
`java-library` `java-library`
} }
group = "com.hanserwei.jwt"
version = project.parent?.version ?: "0.0.1-SNAPSHOT"
dependencies { dependencies {
implementation("org.springframework.boot:spring-boot-starter-security") api("org.springframework.boot:spring-boot-starter-security")
implementation(project(":weblog-module-common")) implementation(project(":weblog-module-common"))
implementation("org.apache.commons:commons-lang3:3.20.0")
// jwt // jwt
api(libs.jjwt.api) api(libs.jjwt.api)

View File

@@ -1,6 +1,7 @@
package com.hanserwei.jwt.config; package com.hanserwei.jwt.config;
import com.hanserwei.jwt.filter.JwtAuthenticationFilter; import com.hanserwei.jwt.filter.JwtAuthenticationFilter;
import com.hanserwei.jwt.filter.TokenAuthenticationFilter;
import com.hanserwei.jwt.handler.RestAuthenticationFailureHandler; import com.hanserwei.jwt.handler.RestAuthenticationFailureHandler;
import com.hanserwei.jwt.handler.RestAuthenticationSuccessHandler; import com.hanserwei.jwt.handler.RestAuthenticationSuccessHandler;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
@@ -20,6 +21,9 @@ public class JwtAuthenticationSecurityConfig extends SecurityConfigurerAdapter<D
@Resource @Resource
private RestAuthenticationFailureHandler restAuthenticationFailureHandler; private RestAuthenticationFailureHandler restAuthenticationFailureHandler;
@Resource
private TokenAuthenticationFilter tokenAuthenticationFilter;
@Override @Override
public void configure(HttpSecurity httpSecurity) { public void configure(HttpSecurity httpSecurity) {
// 1. 实例化自定义过滤器 // 1. 实例化自定义过滤器
@@ -35,5 +39,8 @@ public class JwtAuthenticationSecurityConfig extends SecurityConfigurerAdapter<D
// 4. 将过滤器添加到 UsernamePasswordAuthenticationFilter 之前 // 4. 将过滤器添加到 UsernamePasswordAuthenticationFilter 之前
// (JWT 校验通常在用户名密码校验之前或者用来替换它addFilterBefore 是最稳妥的) // (JWT 校验通常在用户名密码校验之前或者用来替换它addFilterBefore 是最稳妥的)
httpSecurity.addFilterBefore(filter, UsernamePasswordAuthenticationFilter.class); httpSecurity.addFilterBefore(filter, UsernamePasswordAuthenticationFilter.class);
// 5. 在链路中加入 Token 校验过滤器,确保携带 Token 的请求被识别为已登录
httpSecurity.addFilterBefore(tokenAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
} }
} }

View File

@@ -0,0 +1,101 @@
package com.hanserwei.jwt.filter;
import com.hanserwei.jwt.utils.JwtTokenHelper;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jws;
import jakarta.annotation.Nonnull;
import jakarta.annotation.Resource;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import java.io.IOException;
import java.util.Objects;
@Slf4j
@Component
public class TokenAuthenticationFilter extends OncePerRequestFilter {
@Resource
private JwtTokenHelper jwtTokenHelper;
@Resource
private UserDetailsService userDetailsService;
@Resource
private AuthenticationEntryPoint authenticationEntryPoint;
@Override
protected void doFilterInternal(HttpServletRequest request,
@Nonnull HttpServletResponse response,
@Nonnull FilterChain filterChain) throws ServletException, IOException {
// 1. 从请求头中获取 Authorization
String header = request.getHeader("Authorization");
// 2. 校验头格式 (必须以 Bearer 开头)
if (StringUtils.startsWith(header, "Bearer ")) {
String token = StringUtils.substring(header, 7);
log.info("JWT Token: {}", token);
if (StringUtils.isNotBlank(token)) {
try {
// 3. 解析 Token (核心步骤)
// 注意JwtTokenHelper.parseToken 方法内部已经处理了 JWT 格式校验和过期校验,
// 并将 JJWT 异常转换为了 Spring Security 的 AuthenticationException 抛出。
Jws<Claims> claimsJws = jwtTokenHelper.parseToken(token);
// 4. 获取用户名
// JJWT 0.12+ 建议使用 getPayload() 替代 getBody()
// 在 Helper 中生成 Token 时使用的是 .subject(username),所以这里取 Subject
String username = claimsJws.getPayload().getSubject();
// 5. 组装认证信息 (如果当前上下文没有认证信息)
if (StringUtils.isNotBlank(username) && Objects.isNull(SecurityContextHolder.getContext().getAuthentication())) {
// 查询数据库获取用户完整信息 (包含权限)
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
// 构建 Spring Security 的认证 Token
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
userDetails,
null,
userDetails.getAuthorities()
);
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
// 6. 将认证信息存入 SecurityContext
SecurityContextHolder.getContext().setAuthentication(authentication);
}
} catch (AuthenticationException e) {
// 7. 异常处理
// 捕获 JwtTokenHelper 抛出的 BadCredentialsException 或 CredentialsExpiredException
// 如果 Token 存在但是无效/过期,直接交给 EntryPoint 处理响应 (通常返回 401)
// 并 return 结束当前过滤器,不再往下执行
authenticationEntryPoint.commence(request, response, e);
return;
} catch (Exception e) {
// 处理其他未预料到的异常
log.error("JWT处理过程中发生未知错误", e);
authenticationEntryPoint.commence(request, response, new AuthenticationException("系统内部认证错误") {
});
return;
}
}
}
// 8. 放行请求 (如果没有 Token 或者 Token 校验通过,继续执行下一个过滤器)
filterChain.doFilter(request, response);
}
}

View File

@@ -0,0 +1,26 @@
package com.hanserwei.jwt.handler;
import com.hanserwei.common.enums.ResponseCodeEnum;
import com.hanserwei.common.utils.Response;
import com.hanserwei.jwt.utils.ResultUtil;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Component;
import java.io.IOException;
@Slf4j
@Component
public class RestAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException {
log.warn("登录成功访问收保护的资源,但是权限不够: ", accessDeniedException);
// 预留,后面引入多角色时会用到
ResultUtil.fail(response, Response.fail(ResponseCodeEnum.FORBIDDEN));
}
}

View File

@@ -0,0 +1,32 @@
package com.hanserwei.jwt.handler;
import com.hanserwei.common.enums.ResponseCodeEnum;
import com.hanserwei.common.utils.Response;
import com.hanserwei.jwt.utils.ResultUtil;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.security.authentication.InsufficientAuthenticationException;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;
import java.io.IOException;
@Slf4j
@Component
public class RestAuthenticationEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
log.warn("用户未登录访问受保护的资源: ", authException);
if (authException instanceof InsufficientAuthenticationException) {
ResultUtil.fail(response, HttpStatus.UNAUTHORIZED.value(), Response.fail(ResponseCodeEnum.UNAUTHORIZED));
return;
}
ResultUtil.fail(response, HttpStatus.UNAUTHORIZED.value(), Response.fail(authException.getMessage()));
}
}

View File

@@ -1,14 +1,18 @@
package com.hanserwei.jwt.service; package com.hanserwei.jwt.service;
import com.hanserwei.common.domain.dataobject.User; import com.hanserwei.common.domain.dataobject.User;
import com.hanserwei.common.domain.dataobject.UserRole;
import com.hanserwei.common.domain.repository.UserRepository; import com.hanserwei.common.domain.repository.UserRepository;
import com.hanserwei.common.domain.repository.UserRoleRepository;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import java.util.List;
import java.util.Objects; import java.util.Objects;
@Service @Service
@@ -18,6 +22,9 @@ public class UserDetailServiceImpl implements UserDetailsService {
@Resource @Resource
private UserRepository userRepository; private UserRepository userRepository;
@Resource
private UserRoleRepository userRoleRepository;
@Override @Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// 从数据库查询用户信息 // 从数据库查询用户信息
@@ -27,10 +34,19 @@ public class UserDetailServiceImpl implements UserDetailsService {
throw new UsernameNotFoundException("用户不存在"); throw new UsernameNotFoundException("用户不存在");
} }
List<UserRole> userRoles = userRoleRepository.queryAllByUsername(username);
// 转换为数组
String[] roleArr = new String[0];
if (!CollectionUtils.isEmpty(userRoles)) {
roleArr = userRoles.stream()
.map(UserRole::getRoleName)
.toArray(String[]::new);
}
// authorities 用于指定角色,这里写死为 ADMIN 管理员 // authorities 用于指定角色,这里写死为 ADMIN 管理员
return org.springframework.security.core.userdetails.User.withUsername(user.getUsername()) return org.springframework.security.core.userdetails.User.withUsername(user.getUsername())
.password(user.getPassword()) .password(user.getPassword())
.authorities("ADMIN") .authorities(roleArr)
.build(); .build();
} }
} }

View File

@@ -1,9 +1,11 @@
package com.hanserwei.web.controller; package com.hanserwei.web.controller;
import com.hanserwei.common.aspect.ApiOperationLog; import com.hanserwei.common.aspect.ApiOperationLog;
import com.hanserwei.common.utils.Response;
import com.hanserwei.web.model.User; import com.hanserwei.web.model.User;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.BindingResult; import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError; import org.springframework.validation.FieldError;
import org.springframework.validation.annotation.Validated; import org.springframework.validation.annotation.Validated;
@@ -35,4 +37,12 @@ public class TestController {
return ResponseEntity.ok("参数没有任何问题"); return ResponseEntity.ok("参数没有任何问题");
} }
@PostMapping("/admin/update")
@ApiOperationLog(description = "测试更新接口")
@PreAuthorize("hasRole('ROLE_ADMIN')")
public Response<?> testUpdate() {
log.info("更新成功...");
return Response.success();
}
} }