Compare commits
6 Commits
fae4d505de
...
f803241c78
Author | SHA1 | Date |
---|---|---|
|
f803241c78 | |
|
4a43bf26b8 | |
|
41cd4c9976 | |
|
b11ce877be | |
|
24caac4b00 | |
|
459739b212 |
File diff suppressed because it is too large
Load Diff
|
@ -19,9 +19,15 @@
|
|||
<maven.compiler.source>17</maven.compiler.source>
|
||||
<maven.compiler.target>17</maven.compiler.target>
|
||||
<java.version>17</java.version>
|
||||
<jwt.version>0.12.6</jwt.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
|
||||
<!--jjwt-->
|
||||
<dependency>
|
||||
<groupId>io.jsonwebtoken</groupId>
|
||||
<artifactId>jjwt</artifactId>
|
||||
<version>${jwt.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
|
|
|
@ -47,13 +47,4 @@ public class Knife4jConfig {
|
|||
return GroupedOpenApi.builder().group("security接口").pathsToMatch("/api/security/**").build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public GroupedOpenApi test() {
|
||||
return GroupedOpenApi.builder().group("测试接口").pathsToMatch("/api/test/**").build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public GroupedOpenApi testAdmin() {
|
||||
return GroupedOpenApi.builder().group("测试包含管理员接口").pathsToMatch("/api/test-admin/**").build();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
package com.spring.step2.controller;
|
||||
package com.spring.step2.controller.test;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
|
@ -0,0 +1,41 @@
|
|||
package com.spring.step2.controller.test;
|
||||
|
||||
import com.spring.step2.domain.vo.result.Result;
|
||||
import com.spring.step2.security.annotation.IsAdmin;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@Tag(name = "ADMIN接口", description = "只要包含 Admin 角色都可以访问")
|
||||
@Slf4j
|
||||
@RestController
|
||||
@RequestMapping("/api/security/admin")
|
||||
public class SecurityAdminController {
|
||||
|
||||
@IsAdmin
|
||||
@Operation(summary = "拥有 IsAdmin 的角色可以访问", description = "当前用户拥有 IsAdmin 角色可以访问这个接口")
|
||||
@GetMapping("role-user")
|
||||
public Result<String> roleUser() {
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
@IsAdmin
|
||||
@Operation(summary = "拥有 IsAdmin 的角色可以访问", description = "当前用户拥有 IsAdmin 角色可以访问这个接口")
|
||||
@GetMapping("upper-user")
|
||||
public Result<String> upperUser() {
|
||||
String data = "是区分大小写的";
|
||||
return Result.success(data);
|
||||
}
|
||||
|
||||
@IsAdmin
|
||||
@Operation(summary = "拥有 IsAdmin 的角色可以访问", description = "当前用户拥有 IsAdmin 角色可以访问这个接口")
|
||||
@GetMapping("lower-user")
|
||||
public Result<String> lowerUser() {
|
||||
String data = "如果是大写,但是在这里是小写无法访问";
|
||||
return Result.success(data);
|
||||
}
|
||||
|
||||
}
|
|
@ -10,11 +10,11 @@ import org.springframework.web.bind.annotation.GetMapping;
|
|||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@Tag(name = "测试接口", description = "测试用的接口")
|
||||
@Tag(name = "NORMAl接口", description = "测试用的NORMAl接口")
|
||||
@Slf4j
|
||||
@RestController
|
||||
@RequestMapping("/api/test")
|
||||
public class TestController {
|
||||
@RequestMapping("/api/security/normal")
|
||||
public class SecurityController {
|
||||
|
||||
@PreAuthorize("hasAuthority('role::read')")
|
||||
@Operation(summary = "拥有 role:read 的角色可以访问", description = "当前用户拥有 role:read 角色可以访问这个接口")
|
|
@ -0,0 +1,40 @@
|
|||
package com.spring.step2.controller.test;
|
||||
|
||||
import com.spring.step2.domain.vo.result.Result;
|
||||
import com.spring.step2.security.annotation.HasAnyAuthority;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@Tag(name = "ANY ROLES", description = "只要包含 ANY 角色都可以访问")
|
||||
@Slf4j
|
||||
@RestController
|
||||
@RequestMapping("/api/security/any")
|
||||
public class SecurityHasAnyAuthorityController {
|
||||
|
||||
@HasAnyAuthority(auth = {"'USER'", "'ADMIN'"})
|
||||
@Operation(summary = "拥有 HasAnyXXX 的角色可以访问", description = "当前用户拥有 HasAnyXXX 角色可以访问这个接口")
|
||||
@GetMapping("role-user")
|
||||
public Result<String> roleUser() {
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
@HasAnyAuthority(auth = {"'USER'", "'ADMIN'"})
|
||||
@Operation(summary = "拥有 HasAnyXXX 的角色可以访问", description = "当前用户拥有 HasAnyXXX 角色可以访问这个接口")
|
||||
@GetMapping("upper-user")
|
||||
public Result<String> upperUser() {
|
||||
String data = "是区分大小写的";
|
||||
return Result.success(data);
|
||||
}
|
||||
|
||||
@HasAnyAuthority(auth = {"'USER'", "'ADMIN'"})
|
||||
@Operation(summary = "拥有 HasAnyXXX 的角色可以访问", description = "当前用户拥有 HasAnyXXX 角色可以访问这个接口")
|
||||
@GetMapping("lower-user")
|
||||
public Result<String> lowerUser() {
|
||||
String data = "如果是大写,但是在这里是小写无法访问";
|
||||
return Result.success(data);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
package com.spring.step2.controller.test;
|
||||
|
||||
import com.spring.step2.domain.vo.result.Result;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@Tag(name = "Programmatically", description = "只要包含 Programmatically 角色都可以访问")
|
||||
@Slf4j
|
||||
@RestController
|
||||
@RequestMapping("/api/security/programmatically")
|
||||
public class SecurityProgrammaticallyController {
|
||||
|
||||
@Operation(summary = "拥有 USER 的角色可以访问", description = "当前用户拥有 USER 角色可以访问这个接口")
|
||||
@GetMapping("upper-user")
|
||||
public Result<String> upperUser() {
|
||||
String data = "是区分大小写的";
|
||||
return Result.success(data);
|
||||
}
|
||||
|
||||
@PreAuthorize("@auth.decide(#name)")
|
||||
@Operation(summary = "拥有 USER 的角色可以访问", description = "当前用户拥有 USER 角色可以访问这个接口")
|
||||
@GetMapping("lower-user")
|
||||
public Result<String> lowerUser(String name) {
|
||||
return Result.success(name);
|
||||
}
|
||||
|
||||
}
|
|
@ -9,11 +9,11 @@ import org.springframework.web.bind.annotation.GetMapping;
|
|||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@Tag(name = "测试包含 USER 接口", description = "只要包含 USER 角色都可以访问")
|
||||
@Tag(name = "USER测试", description = "只要包含 USER 角色都可以访问")
|
||||
@Slf4j
|
||||
@RestController
|
||||
@RequestMapping("/api/test-admin")
|
||||
public class TestAdminController {
|
||||
@RequestMapping("/api/security/user")
|
||||
public class SecurityUserController {
|
||||
|
||||
@HasUSERAuthorize("role:read")
|
||||
@Operation(summary = "拥有 role:read 的角色可以访问", description = "当前用户拥有 role:read 角色可以访问这个接口")
|
|
@ -0,0 +1,15 @@
|
|||
package com.spring.step2.security.annotation;
|
||||
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
@Target({ElementType.METHOD, ElementType.TYPE})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@PreAuthorize("hasAnyAuthority({auth})")
|
||||
public @interface HasAnyAuthority {
|
||||
String[] auth();
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
package com.spring.step2.security.annotation;
|
||||
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* 判断当前是否是Admin用户
|
||||
*/
|
||||
@Target({ElementType.METHOD, ElementType.TYPE})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@PreAuthorize("hasAuthority('ADMIN')")
|
||||
public @interface IsAdmin {
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
package com.spring.step2.security.annotation.programmatically;
|
||||
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component("auth")
|
||||
public class AuthorizationLogic {
|
||||
|
||||
public boolean decide(String name) {
|
||||
System.out.println(name);
|
||||
// 直接使用name的实现
|
||||
return name.equalsIgnoreCase("user");
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
package com.spring.step2.security.config;
|
||||
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@Configuration
|
||||
// @EnableMethodSecurity(prePostEnabled = false)
|
||||
public class AuthorizationManagerConfiguration {
|
||||
|
||||
// @Bean
|
||||
// @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
|
||||
// Advisor preAuthorize(PreAuthorizationManager manager) {
|
||||
// return AuthorizationManagerBeforeMethodInterceptor.preAuthorize(manager);
|
||||
// }
|
||||
//
|
||||
// @Bean
|
||||
// @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
|
||||
// Advisor postAuthorize(PostAuthorizationManager manager) {
|
||||
// return AuthorizationManagerAfterMethodInterceptor.postAuthorize(manager);
|
||||
// }
|
||||
|
||||
}
|
|
@ -1,37 +1,43 @@
|
|||
package com.spring.step2.security.config;
|
||||
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.core.userdetails.User;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||
import org.springframework.security.authorization.method.PrePostTemplateDefaults;
|
||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
|
||||
|
||||
@Configuration
|
||||
public class SecurityConfiguration {
|
||||
|
||||
/**
|
||||
* 添加内存用户
|
||||
* 注册一个用于Spring Security预授权/后授权的模板元注解默认配置Bean。
|
||||
*
|
||||
* @return {@link ConditionalOnMissingBean}
|
||||
* <p>该Bean提供了基于SpEL表达式的权限校验模板,可用于自定义组合注解。</p>
|
||||
*
|
||||
* <h3>典型用法</h3>
|
||||
* <p>通过此配置可以简化自定义权限注解的定义,例如:</p>
|
||||
* <pre>{@code
|
||||
* @Target({ElementType.METHOD, ElementType.TYPE})
|
||||
* @Retention(RetentionPolicy.RUNTIME)
|
||||
* @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
|
||||
@ConditionalOnMissingBean(UserDetailsService.class)
|
||||
InMemoryUserDetailsManager inMemoryUserDetailsManager(PasswordEncoder passwordEncoder) {
|
||||
// 使用注入的密码加密器进行密码加密
|
||||
String generatedPassword = passwordEncoder.encode("123456");
|
||||
|
||||
// 创建用户 权限为只读
|
||||
UserDetails bunny = User.withUsername("bunny").password(generatedPassword).roles("USER").authorities("read").build();
|
||||
|
||||
// 管理员可以查看全部
|
||||
UserDetails admin = User.withUsername("admin").password(generatedPassword).roles("ADMIN").authorities("all", "read").build();
|
||||
|
||||
// 返回内存中的用户
|
||||
return new InMemoryUserDetailsManager(bunny, admin);
|
||||
PrePostTemplateDefaults prePostTemplateDefaults() {
|
||||
return new PrePostTemplateDefaults();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -0,0 +1,60 @@
|
|||
package com.spring.step2.security.event;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.aopalliance.intercept.MethodInvocation;
|
||||
import org.springframework.context.event.EventListener;
|
||||
import org.springframework.security.authorization.AuthorizationDecision;
|
||||
import org.springframework.security.authorization.event.AuthorizationDeniedEvent;
|
||||
import org.springframework.security.authorization.event.AuthorizationGrantedEvent;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Arrays;
|
||||
|
||||
@Slf4j
|
||||
@Component
|
||||
public class AuthenticationEvents {
|
||||
|
||||
/**
|
||||
* 监听拒绝授权内容
|
||||
*
|
||||
* @param failure 授权失败
|
||||
*/
|
||||
@EventListener
|
||||
public void onFailure(AuthorizationDeniedEvent<MethodInvocation> failure) {
|
||||
// getSource 和 getObject意思一样,一种是传入泛型自动转换一种是要手动转换
|
||||
Object source = failure.getSource();
|
||||
|
||||
// 直接获取泛型对象
|
||||
MethodInvocation methodInvocation = failure.getObject();
|
||||
Method method = methodInvocation.getMethod();
|
||||
Object[] args = methodInvocation.getArguments();
|
||||
|
||||
log.warn("方法调用被拒绝: {}.{}, 参数: {}",
|
||||
method.getDeclaringClass().getSimpleName(),
|
||||
method.getName(),
|
||||
Arrays.toString(args));
|
||||
|
||||
// 这里面的信息,和接口 /api/security/current-user 内容一样
|
||||
Authentication authentication = failure.getAuthentication().get();
|
||||
|
||||
AuthorizationDecision authorizationDecision = failure.getAuthorizationDecision();
|
||||
// ExpressionAuthorizationDecision [granted=false, expressionAttribute=hasAuthority('ADMIN')]
|
||||
System.out.println(authorizationDecision);
|
||||
|
||||
log.warn("授权失败 - 用户: {}, 权限: {}", authentication.getName(), authorizationDecision);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 监听授权的内容
|
||||
* 如果要监听授权成功的内容,这个内容可能相当的多,毕竟正常情况授权成功的内容还是比较多的。
|
||||
* 既然内容很多又要监听,如果真的需要,一定要处理好业务逻辑,不要被成功的消息淹没。
|
||||
*
|
||||
* @param success 授权成功
|
||||
*/
|
||||
@EventListener
|
||||
public void onSuccess(AuthorizationGrantedEvent<MethodInvocation> success) {
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
package com.spring.step2.security.event;
|
||||
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.security.authorization.AuthorizationEventPublisher;
|
||||
import org.springframework.security.authorization.SpringAuthorizationEventPublisher;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 如果要监听授权和拒绝的授权需要发布一个像下面这样的事件
|
||||
* 之后使用 Spring 的 @EventListener
|
||||
*/
|
||||
@Component
|
||||
public class SecurityAuthorizationPublisher {
|
||||
|
||||
@Bean
|
||||
public AuthorizationEventPublisher authorizationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
|
||||
return new SpringAuthorizationEventPublisher(applicationEventPublisher);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
package com.spring.step2.security.manger;
|
||||
|
||||
/**
|
||||
* 处理方法调用后的授权检查
|
||||
* check()方法接收的是MethodInvocationResult对象,包含已执行方法的结果
|
||||
* 用于决定是否允许返回某个方法的结果(后置过滤)
|
||||
* 这是Spring Security较新的"后置授权"功能
|
||||
*/
|
||||
// @Component
|
||||
// public class PostAuthorizationManager implements AuthorizationManager<MethodInvocationResult> {
|
||||
//
|
||||
// /**
|
||||
// * 这里两个实现方法按照Security官方要求进行实现
|
||||
// * <h4>类说明:</h4>
|
||||
// * 下面的实现是对方法执行前进行权限校验的判断
|
||||
// * <pre>
|
||||
// * <code>AuthorizationManager <MethodInvocation></code>
|
||||
// * </pre>
|
||||
// * 下面的这个是对方法执行后对权限的判断
|
||||
// * <pre>
|
||||
// * <code>AuthorizationManager <MethodInvocationResult></code>
|
||||
// * </pre>
|
||||
// *
|
||||
// * <h4>注意事项:</h4>
|
||||
// * 将上述两个方法按照自定义的方式进行实现后,还需要禁用默认的。
|
||||
// * <pre>
|
||||
// * @Configuration
|
||||
// * @EnableMethodSecurity(prePostEnabled = false)
|
||||
// * class MethodSecurityConfig {
|
||||
// * @Bean
|
||||
// * @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
|
||||
// * Advisor preAuthorize(MyAuthorizationManager manager) {
|
||||
// * return AuthorizationManagerBeforeMethodInterceptor.preAuthorize(manager);
|
||||
// * }
|
||||
// *
|
||||
// * @Bean
|
||||
// * @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
|
||||
// * Advisor postAuthorize(MyAuthorizationManager manager) {
|
||||
// * return AuthorizationManagerAfterMethodInterceptor.postAuthorize(manager);
|
||||
// * }
|
||||
// * }
|
||||
// * </pre>
|
||||
// */
|
||||
// @Override
|
||||
// public AuthorizationDecision check(Supplier<Authentication> authentication, MethodInvocationResult invocation) {
|
||||
// return new AuthorizationDecision(true);
|
||||
// }
|
||||
// }
|
|
@ -0,0 +1,49 @@
|
|||
package com.spring.step2.security.manger;
|
||||
|
||||
/**
|
||||
* 处理方法调用前的授权检查
|
||||
* check()方法接收的是MethodInvocation对象,包含即将执行的方法调用信息
|
||||
* 用于决定是否允许执行某个方法
|
||||
* 这是传统的"前置授权"模式
|
||||
*/
|
||||
// @Component
|
||||
// public class PreAuthorizationManager implements AuthorizationManager<MethodInvocation> {
|
||||
//
|
||||
// /**
|
||||
// * 这里两个实现方法按照Security官方要求进行实现
|
||||
// * <h4>类说明:</h4>
|
||||
// * 下面的实现是对方法执行前进行权限校验的判断
|
||||
// * <pre>
|
||||
// * <code>AuthorizationManager <MethodInvocation></code>
|
||||
// * </pre>
|
||||
// * 下面的这个是对方法执行后对权限的判断
|
||||
// * <pre>
|
||||
// * <code>AuthorizationManager <MethodInvocationResult></code>
|
||||
// * </pre>
|
||||
// *
|
||||
// * <h4>注意事项:</h4>
|
||||
// * 将上述两个方法按照自定义的方式进行实现后,还需要禁用默认的。
|
||||
// * <pre>
|
||||
// * @Configuration
|
||||
// * @EnableMethodSecurity(prePostEnabled = false)
|
||||
// * class MethodSecurityConfig {
|
||||
// * @Bean
|
||||
// * @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
|
||||
// * Advisor preAuthorize(MyAuthorizationManager manager) {
|
||||
// * return AuthorizationManagerBeforeMethodInterceptor.preAuthorize(manager);
|
||||
// * }
|
||||
// *
|
||||
// * @Bean
|
||||
// * @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
|
||||
// * Advisor postAuthorize(MyAuthorizationManager manager) {
|
||||
// * return AuthorizationManagerAfterMethodInterceptor.postAuthorize(manager);
|
||||
// * }
|
||||
// * }
|
||||
// * </pre>
|
||||
// */
|
||||
// @Override
|
||||
// public AuthorizationDecision check(Supplier<Authentication> authentication, MethodInvocation invocation) {
|
||||
// return new AuthorizationDecision(true);
|
||||
// }
|
||||
//
|
||||
// }
|
|
@ -0,0 +1 @@
|
|||
如果需要重写验证逻辑(自定义)使用这里面的类,并在配置类`AuthorizationManagerConfiguration`解开注释,
|
|
@ -37,7 +37,7 @@ public class DbUserDetailService implements UserDetailsService {
|
|||
Long userId = userEntity.getId();
|
||||
|
||||
// 设置用户角色
|
||||
String[] roles = findUserRolesByUserId(userId);
|
||||
String[] roles = findUserRolesByUserId(userId).toArray(String[]::new);
|
||||
|
||||
// 设置用户权限
|
||||
List<String> permissionsByUserId = findPermissionByUserId(userId);
|
||||
|
@ -56,6 +56,7 @@ public class DbUserDetailService implements UserDetailsService {
|
|||
.password(userEntity.getPassword())
|
||||
// 设置用户 authorities
|
||||
.authorities(authorities)
|
||||
.roles(roles)
|
||||
.build();
|
||||
}
|
||||
|
||||
|
@ -65,9 +66,9 @@ public class DbUserDetailService implements UserDetailsService {
|
|||
* @param userId 用户id
|
||||
* @return 当前用户的角色信息
|
||||
*/
|
||||
public String[] findUserRolesByUserId(Long userId) {
|
||||
public List<String> findUserRolesByUserId(Long userId) {
|
||||
List<RoleEntity> roleList = userMapper.selectRolesByUserId(userId);
|
||||
return roleList.stream().map(RoleEntity::getRoleCode).toArray(String[]::new);
|
||||
return roleList.stream().map(RoleEntity::getRoleCode).toList();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -0,0 +1,86 @@
|
|||
package com.spring.step2.security.service;
|
||||
|
||||
import com.spring.step2.utils.JwtTokenUtil;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import javax.crypto.SecretKey;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Configuration
|
||||
@ConfigurationProperties(prefix = "jwt-token")
|
||||
public class JwtBearTokenService {
|
||||
|
||||
@Value("${jwtToken.secret}")
|
||||
public String secret;
|
||||
|
||||
@Value("${jwtToken.subject}")
|
||||
public String subject;
|
||||
|
||||
// private final SecretKey securityKey = Keys.hmacShaKeyFor("Bunny-Auth-Server-Private-SecretKey".getBytes(StandardCharsets.UTF_8));
|
||||
// JWT 的 秘钥
|
||||
@Value("${jwtToken.expired}")
|
||||
public Long expired;
|
||||
|
||||
/**
|
||||
* 创建Token
|
||||
*
|
||||
* @param userId 用户Id
|
||||
* @param username 用户名
|
||||
* @return 令牌Token
|
||||
*/
|
||||
public String createToken(Long userId, String username,
|
||||
List<String> roles, List<String> permissions) {
|
||||
SecretKey key = getSecretKey();
|
||||
// return JwtTokenUtil.createToken(userId, username, subject, key, expired);
|
||||
return JwtTokenUtil.createToken(userId, username, roles, permissions, subject, key, expired);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取安全密钥
|
||||
*
|
||||
* @return {@link SecretKey}
|
||||
*/
|
||||
private SecretKey getSecretKey() {
|
||||
byte[] secretBytes = secret.getBytes(StandardCharsets.UTF_8);
|
||||
return new SecretKeySpec(secretBytes, "HmacSHA256");
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据Token获取用户名
|
||||
*
|
||||
* @param token 令牌
|
||||
* @return 用户名
|
||||
*/
|
||||
public String getUsernameFromToken(String token) {
|
||||
SecretKey secretKey = getSecretKey();
|
||||
return JwtTokenUtil.getUsername(token, secretKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据Token获取用户Id
|
||||
*
|
||||
* @param token 令牌
|
||||
* @return 用户Id
|
||||
*/
|
||||
public Long getUserIdFromToken(String token) {
|
||||
SecretKey secretKey = getSecretKey();
|
||||
return JwtTokenUtil.getUserId(token, secretKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据Token获取用户Id
|
||||
*
|
||||
* @param token 令牌
|
||||
* @return 用户Id
|
||||
*/
|
||||
public Map<String, Object> getMapByToken(String token) {
|
||||
SecretKey secretKey = getSecretKey();
|
||||
return JwtTokenUtil.getMapByToken(token, secretKey);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,223 @@
|
|||
package com.spring.step2.utils;
|
||||
|
||||
|
||||
import com.spring.step2.domain.vo.result.ResultCodeEnum;
|
||||
import com.spring.step2.exception.SecurityException;
|
||||
import io.jsonwebtoken.Claims;
|
||||
import io.jsonwebtoken.Jws;
|
||||
import io.jsonwebtoken.Jwts;
|
||||
import io.micrometer.common.lang.Nullable;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import javax.crypto.SecretKey;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
@Slf4j
|
||||
public class JwtTokenUtil {
|
||||
|
||||
/**
|
||||
* 使用默认主题,默认秘钥,自定义时间,创建集合形式token
|
||||
*
|
||||
* @param map 集合
|
||||
* @param day 过期时间
|
||||
* @return token
|
||||
*/
|
||||
public static String createTokenWithMap(Map<String, Object> map, String subject, SecretKey key, Long day) {
|
||||
return Jwts.builder()
|
||||
.subject(subject)
|
||||
.signWith(key)
|
||||
.expiration(new Date(System.currentTimeMillis() + day))
|
||||
.claims(map)
|
||||
.id(UUID.randomUUID().toString())
|
||||
.compressWith(Jwts.ZIP.GZIP).compact();
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用自定义主题,自定义时间,创建集合形式token
|
||||
*
|
||||
* @param map 集合
|
||||
* @param subject 主题
|
||||
* @param time 过期时间
|
||||
* @return token
|
||||
*/
|
||||
public static String createTokenWithMap(Map<String, Object> map, String subject, SecretKey key, Date time) {
|
||||
return Jwts.builder()
|
||||
.subject(subject)
|
||||
.expiration(time)
|
||||
.claims(map)
|
||||
.id(UUID.randomUUID().toString())
|
||||
.signWith(key)
|
||||
.compressWith(Jwts.ZIP.GZIP).compact();
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建集合形式token
|
||||
*
|
||||
* @param map 集合
|
||||
* @param subject 主题
|
||||
* @param day 过期时间
|
||||
* @return token
|
||||
*/
|
||||
public static String createTokenWithMap(Map<String, Object> map, String subject, SecretKey key, Integer day) {
|
||||
return Jwts.builder()
|
||||
.subject(subject)
|
||||
.expiration(new Date(System.currentTimeMillis() + day))
|
||||
.claims(map)
|
||||
.id(UUID.randomUUID().toString())
|
||||
.signWith(key)
|
||||
.compressWith(Jwts.ZIP.GZIP).compact();
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据用户名和ID创建token
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @param username 用户名
|
||||
* @param day 过期时间
|
||||
* @return token值
|
||||
*/
|
||||
public static String createToken(Long userId, String username, String subject, SecretKey key, Long day) {
|
||||
return Jwts.builder()
|
||||
.subject(subject)
|
||||
.expiration(new Date(System.currentTimeMillis() + day))
|
||||
.claim("userId", userId)
|
||||
.claim("username", username)
|
||||
.id(UUID.randomUUID().toString())
|
||||
.signWith(key)
|
||||
.compressWith(Jwts.ZIP.GZIP).compact();
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据用户名和ID创建token
|
||||
* 在载体中添加角色和权限
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @param username 用户名
|
||||
* @param day 过期时间
|
||||
* @return token值
|
||||
*/
|
||||
public static String createToken(Long userId, String username,
|
||||
List<String> roles, List<String> permissions,
|
||||
String subject, SecretKey key, Long day) {
|
||||
return Jwts.builder()
|
||||
.subject(subject)
|
||||
.expiration(new Date(System.currentTimeMillis() + day))
|
||||
.claim("userId", userId)
|
||||
.claim("username", username)
|
||||
.claim("roles", roles)
|
||||
.claim("permissions", permissions)
|
||||
.id(UUID.randomUUID().toString())
|
||||
.signWith(key)
|
||||
.compressWith(Jwts.ZIP.GZIP).compact();
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用token获取map集合,使用默认秘钥
|
||||
*
|
||||
* @param token token
|
||||
* @return map集合
|
||||
*/
|
||||
public static Map<String, Object> getMapByToken(String token, SecretKey key) {
|
||||
try {
|
||||
if (!StringUtils.hasText(token)) throw new SecurityException(ResultCodeEnum.TOKEN_PARSING_FAILED);
|
||||
// 将 body 值转为map
|
||||
return Jwts.parser().verifyWith(key).build().parseSignedClaims(token).getPayload();
|
||||
|
||||
} catch (Exception exception) {
|
||||
throw new SecurityException(ResultCodeEnum.TOKEN_PARSING_FAILED);
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static String getSubjectByTokenHandler(String token, SecretKey key) {
|
||||
try {
|
||||
if (!StringUtils.hasText(token)) throw new SecurityException(ResultCodeEnum.TOKEN_PARSING_FAILED);
|
||||
Jws<Claims> claimsJws = Jwts.parser().verifyWith(key).build().parseSignedClaims(token);
|
||||
Claims body = claimsJws.getPayload();
|
||||
|
||||
return body.getSubject();
|
||||
|
||||
} catch (Exception exception) {
|
||||
throw new SecurityException(ResultCodeEnum.TOKEN_PARSING_FAILED);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据token获取主题
|
||||
*
|
||||
* @param token token
|
||||
* @return 主题
|
||||
*/
|
||||
public static String getSubjectByToken(String token, SecretKey key) {
|
||||
return getSubjectByTokenHandler(token, key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据token获取用户ID
|
||||
*
|
||||
* @param token token
|
||||
* @return 用户ID
|
||||
*/
|
||||
public static Long getUserId(String token, SecretKey key) {
|
||||
try {
|
||||
if (!StringUtils.hasText(token)) throw new SecurityException(ResultCodeEnum.TOKEN_PARSING_FAILED);
|
||||
|
||||
Jws<Claims> claimsJws = Jwts.parser().verifyWith(key).build().parseSignedClaims(token);
|
||||
Claims claims = claimsJws.getPayload();
|
||||
|
||||
return Long.valueOf(String.valueOf(claims.get("userId")));
|
||||
} catch (Exception exception) {
|
||||
throw new SecurityException(ResultCodeEnum.TOKEN_PARSING_FAILED);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据token获取用户名
|
||||
*
|
||||
* @param token token
|
||||
* @return 用户名
|
||||
*/
|
||||
public static String getUsername(String token, SecretKey key) {
|
||||
try {
|
||||
if (!StringUtils.hasText(token)) return "";
|
||||
|
||||
Jws<Claims> claimsJws = Jwts.parser().verifyWith(key).build().parseSignedClaims(token);
|
||||
Claims claims = claimsJws.getPayload();
|
||||
return (String) claims.get("username");
|
||||
} catch (Exception exception) {
|
||||
throw new SecurityException(ResultCodeEnum.TOKEN_PARSING_FAILED);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断token是否过期
|
||||
*
|
||||
* @param token token
|
||||
* @return 是否过期
|
||||
*/
|
||||
public static boolean isExpired(String token, SecretKey key) {
|
||||
return isExpiredUtil(token, key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否过期
|
||||
*
|
||||
* @param token token
|
||||
* @return 是否过期
|
||||
*/
|
||||
private static boolean isExpiredUtil(String token, SecretKey key) {
|
||||
try {
|
||||
Jws<Claims> claimsJws = Jwts.parser().verifyWith(key).build().parseSignedClaims(token);
|
||||
Date expiration = claimsJws.getPayload().getExpiration();
|
||||
|
||||
return expiration != null && expiration.before(new Date());
|
||||
} catch (Exception exception) {
|
||||
log.error(exception.getMessage(), exception);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -48,3 +48,10 @@ logging:
|
|||
com.spring: debug
|
||||
org.springframework.security: debug
|
||||
|
||||
jwtToken:
|
||||
# 密钥
|
||||
secret: aVeryLongAndSecureRandomStringWithAtLeast32Characters
|
||||
# 主题
|
||||
subject: SecurityBunny
|
||||
# 过期事件 7天
|
||||
expired: 604800
|
||||
|
|
|
@ -379,7 +379,7 @@
|
|||
</div>
|
||||
<ul class="nav-links">
|
||||
<li><a href="#features">特性</a></li>
|
||||
<li><a href="#docs" target="_blank">文档</a></li>
|
||||
<li><a href="#docs">文档</a></li>
|
||||
<li><a href="/user" target="_blank">用户管理</a></li>
|
||||
<li><a href="/login" target="_blank">登录</a></li>
|
||||
</ul>
|
||||
|
|
|
@ -0,0 +1,56 @@
|
|||
package com.spring.step2.security.config;
|
||||
|
||||
import com.spring.step2.security.service.DbUserDetailService;
|
||||
import com.spring.step2.security.service.JwtBearTokenService;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@SpringBootTest
|
||||
class JwtBearTokenServiceTest {
|
||||
|
||||
private String token;
|
||||
|
||||
@Autowired
|
||||
private JwtBearTokenService jwtBearTokenService;
|
||||
|
||||
@Autowired
|
||||
private DbUserDetailService dbUserDetailService;
|
||||
|
||||
@Test
|
||||
void createToken() {
|
||||
long userId = 1944384432521744386L;
|
||||
List<String> roles = dbUserDetailService.findUserRolesByUserId(userId);
|
||||
List<String> permissions = dbUserDetailService.findPermissionByUserId(userId);
|
||||
|
||||
String token = jwtBearTokenService.createToken(userId,
|
||||
"Bunny",
|
||||
roles,
|
||||
permissions);
|
||||
this.token = token;
|
||||
System.out.println(token);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getUsernameFromToken() {
|
||||
createToken();
|
||||
|
||||
String username = jwtBearTokenService.getUsernameFromToken(token);
|
||||
System.out.println(username);
|
||||
|
||||
Long userId = jwtBearTokenService.getUserIdFromToken(token);
|
||||
System.out.println(userId);
|
||||
|
||||
Map<String, Object> map = jwtBearTokenService.getMapByToken(token);
|
||||
|
||||
List<?> roles = (List<?>) map.get("roles");
|
||||
System.out.println(roles);
|
||||
|
||||
// 安全转换 permissions
|
||||
List<?> permissions = (List<?>) map.get("permissions");
|
||||
System.out.println(permissions);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue