
Spring Security 整合 jwt 源码地址
Spring Security 快速入门新建springBoot工程导入如下依赖
包括了后续需要用到的数据库操作相关的组件
org.springframework.boot
spring-boot-starter-security
com.alibaba
druid-spring-boot-starter
1.1.10
mysql
mysql-connector-java
com.alibaba
druid-spring-boot-starter
${druid.version}
com.baomidou
mybatis-plus-boot-starter
org.springframework.boot
spring-boot-starter-web
增加一个接口
@RestController
@RequestMapping("/user")
@RequiredArgsConstructor
public class UserController {
private final UserService userService;
@GetMapping("/{id}")
public R 增加配置文件和数据库表结构
spring:
datasource:
druid:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/zcct-user?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
username: root
password: zclvct
# 初始连接数
initial-size: 5
# 最小连接数
min-idle: 10
# 最大连接数
max-active: 20
# 获取连接超时时间
max-wait: 5000
# 连接有效性检测时间
time-between-eviction-runs-millis: 60000
# 连接在池中最小生存的时间
min-evictable-idle-time-millis: 300000
# 连接在池中最大生存的时间
max-evictable-idle-time-millis: 900000
test-while-idle: true
test-on-borrow: false
test-on-return: false
# 检测连接是否有效
validation-query: select 1
# 配置监控统计
webStatFilter:
enabled: true
stat-view-servlet:
enabled: true
url-pattern: /druid
@PostConstruct
public void init() {
Key key = generalKey();
jwtParser = Jwts.parserBuilder()
.setSigningKey(key)
.build();
jwtBuilder = Jwts.builder()
.signWith(key, SignatureAlgorithm.HS512);
}
public static Key generalKey() {
byte[] encodedKey = Base64.decodeBase64(BASE64_SECRET);
Key key = Keys.hmacShaKeyFor(encodedKey);
return key;
}
public String createToken(String userId, JwtUser jwtUser) {
String token = jwtBuilder
// 加入ID确保生成的 Token 都不一致
.setId(IdUtil.simpleUUID())
.claim(AUTHORITIES_KEY, userId)
.setSubject(userId)
.compact();
redisUtil.set("login:"+userId,jwtUser,1L, TimeUnit.HOURS);
return token;
}
public void parseJwt(String token) {
if (StringUtils.hasText(token) && token.startsWith("Bearer ")) {
// 去掉令牌前缀
token = token.replace("Bearer ", "");
} else {
log.debug("非法Token:{}", token);
}
Claims claims = jwtParser.parseClaimsJws(token).getBody();
String userId = claims.getSubject();
JwtUser jwtUser = (JwtUser)redisUtil.get("login:" + userId);
if(ObjectUtil.isEmpty(jwtUser)) {
throw new RuntimeException("请求未认证");
}
Object authoritiesStr = claims.get(AUTHORITIES_KEY);
Collection extends GrantedAuthority> authorities =
ObjectUtil.isNotEmpty(authoritiesStr) ?
Arrays.stream(authoritiesStr.toString().split(","))
.map(SimpleGrantedAuthority::new)
.collect(Collectors.toList()) : Collections.emptyList();
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(jwtUser,"",authorities);
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
}
}
登录流程实现
用户登录流程图
public Maplogin(LoginUser loginUser) { String username = loginUser.getUsername(); String password = loginUser.getPassword(); // 生成一个 AuthenticationToken UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(username,password); // 使用 authenticationManager 进行认证 Authentication authentication = authenticationManagerBuilder.getObject().authenticate(token); JwtUser jwtUser = (JwtUser)authentication.getPrincipal(); Long userId = jwtUser.getUser().getUserId(); String jwtToken = jwtTokenProvider.createToken(String.valueOf(userId),jwtUser); Map authInfo = new HashMap (2) {{ put("token", "Brear " + jwtToken); put("user", userId); }}; return authInfo; }
在调用 authenticationManagerBuilder.getObject().authenticate(token); 时, 会使用UserDetailsService加载用户信息进行验证,在这里需要增加自己的查询用户信息逻辑,并生成返回前端的token
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userService.findByUserName(username);
if (user == null) {
throw new BadRequestException("账号不存在");
}
if(user.getStatus() == 1) {
throw new BadRequestException("账号已停用");
}
List authorities = roleService.getGrantedAuthorities(user);
Set roleKeys = roleService.getRoleKeys(user);
return new JwtUser(user,roleKeys,authorities);
}
授权流程
创建相关注解
编写通知
@Aspect
@Component
public class AuthorizeAspect {
@Pointcut("@annotation(com.zcct.security.demo.security.annotation.RequireRules) " +
"|| @annotation(com.zcct.security.demo.security.annotation.RequiresLogin) " +
"|| @annotation(com.zcct.security.demo.security.annotation.RequiresPermissions) " +
"|| @annotation(com.zcct.security.demo.security.annotation.RequireAnonymous)")
public void pointcut() {
// 该方法无方法体,主要为了让同类中其他方法使用此切入点
}
@Around("pointcut()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
authorize(method);
Object obj = joinPoint.proceed();
return obj;
}
private void authorize(Method method) {
RequireRules requireRules = method.getAnnotation(RequireRules.class);
if(ObjectUtil.isNotEmpty(requireRules)) {
SecurityUtils.checkRules(requireRules.value());
}
RequiresLogin requiresLogin = method.getAnnotation(RequiresLogin.class);
if(ObjectUtil.isNotEmpty(requiresLogin)) {
SecurityUtils.checkLogin();
}
RequiresPermissions requiresPermissions = method.getAnnotation(RequiresPermissions.class);
if(ObjectUtil.isNotEmpty(requiresPermissions)) {
SecurityUtils.checkPermissions(requiresPermissions.value());
}
RequireAnonymous requireAnonymous = method.getAnnotation(RequireAnonymous.class);
if(ObjectUtil.isNotEmpty(requireAnonymous)) {
SecurityUtils.checkAnonymous();
}
}
}
增加工具类
public class SecurityUtils {
public static JwtUser getCurrentUser() {
UserDetailsService userDetailsService = SpringUtil.getBean(UserDetailsService.class);
return (JwtUser)userDetailsService.loadUserByUsername(getCurrentUsername());
}
public static String getCurrentUsername() {
final Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication == null) {
throw new BadRequestException(HttpStatus.FORBIDDEN,"当前登录状态过期");
}
if (authentication.getPrincipal() instanceof UserDetails) {
UserDetails userDetails = (UserDetails) authentication.getPrincipal();
return userDetails.getUsername();
}
throw new BadRequestException(HttpStatus.FORBIDDEN,"找不到当前登录的信息");
}
public static void checkRules(String[] rules) {
Set ruleKeys = SecurityUtils.getCurrentUser().getRuleKeys();
boolean hasAuthority = ruleKeys.contains("admin") || Arrays.stream(rules).anyMatch(ruleKeys::contains);
if(!hasAuthority) {
throw new BadRequestException(HttpStatus.UNAUTHORIZED,"没有权限");
}
}
public static void checkLogin() {
getCurrentUsername();
}
public static void checkPermissions(String[] permissions) {
// 获取所有权限
List allPermissions = SecurityUtils.getCurrentUser().getAuthorities().
stream().map(GrantedAuthority::getAuthority).
collect(Collectors.toList());
boolean hasAuthority = allPermissions.contains("admin") || Arrays.stream(permissions).anyMatch(allPermissions::contains);
if(!hasAuthority) {
throw new BadRequestException(HttpStatus.UNAUTHORIZED,"没有权限");
}
}
public static void checkAnonymous() {
UserDetails userDetails = null;
try{
userDetails = getCurrentUser();
}catch (Exception e){
}
if(ObjectUtil.isNotEmpty(userDetails)) {
throw new BadRequestException(HttpStatus.UNAUTHORIZED,"不允许访问");
}
}
}