Compare commits

...

6 Commits

25 changed files with 1538 additions and 460 deletions

File diff suppressed because it is too large Load Diff

View File

@ -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>

View File

@ -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();
}
}

View File

@ -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;

View File

@ -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);
}
}

View File

@ -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 角色可以访问这个接口")

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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 角色可以访问这个接口")

View File

@ -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();
}

View File

@ -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 {
}

View File

@ -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");
}
}

View File

@ -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);
// }
}

View File

@ -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
* &#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
@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();
}
/**

View File

@ -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) {
}
}

View File

@ -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);
}
}

View File

@ -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 &ltMethodInvocation></code>
// * </pre>
// * 下面的这个是对方法执行后对权限的判断
// * <pre>
// * <code>AuthorizationManager &ltMethodInvocationResult></code>
// * </pre>
// *
// * <h4>注意事项</h4>
// * 将上述两个方法按照自定义的方式进行实现后还需要禁用默认的
// * <pre>
// * &#064;Configuration
// * &#064;EnableMethodSecurity(prePostEnabled = false)
// * class MethodSecurityConfig {
// * &#064;Bean
// * &#064;Role(BeanDefinition.ROLE_INFRASTRUCTURE)
// * Advisor preAuthorize(MyAuthorizationManager manager) {
// * return AuthorizationManagerBeforeMethodInterceptor.preAuthorize(manager);
// * }
// *
// * &#064;Bean
// * &#064;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);
// }
// }

View File

@ -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 &ltMethodInvocation></code>
// * </pre>
// * 下面的这个是对方法执行后对权限的判断
// * <pre>
// * <code>AuthorizationManager &ltMethodInvocationResult></code>
// * </pre>
// *
// * <h4>注意事项</h4>
// * 将上述两个方法按照自定义的方式进行实现后还需要禁用默认的
// * <pre>
// * &#064;Configuration
// * &#064;EnableMethodSecurity(prePostEnabled = false)
// * class MethodSecurityConfig {
// * &#064;Bean
// * &#064;Role(BeanDefinition.ROLE_INFRASTRUCTURE)
// * Advisor preAuthorize(MyAuthorizationManager manager) {
// * return AuthorizationManagerBeforeMethodInterceptor.preAuthorize(manager);
// * }
// *
// * &#064;Bean
// * &#064;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);
// }
//
// }

View File

@ -0,0 +1 @@
如果需要重写验证逻辑(自定义)使用这里面的类,并在配置类`AuthorizationManagerConfiguration`解开注释,

View File

@ -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();
}
/**

View File

@ -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);
}
}

View File

@ -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;
}
}
}

View File

@ -48,3 +48,10 @@ logging:
com.spring: debug
org.springframework.security: debug
jwtToken:
# 密钥
secret: aVeryLongAndSecureRandomStringWithAtLeast32Characters
# 主题
subject: SecurityBunny
# 过期事件 7天
expired: 604800

View File

@ -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>

View File

@ -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);
}
}