修复语音对讲功能推流鉴权key不存在问题,修复了JWT判断token过期问题

pull/1587/head
cntianxin@outlook.com 2024-08-21 19:00:00 +08:00
parent f56cf67f52
commit 46a6d7bc38
5 changed files with 181 additions and 78 deletions

View File

@ -1,13 +1,19 @@
package com.genersoft.iot.vmp.conf.security;
import com.genersoft.iot.vmp.VManageBootstrap;
import com.genersoft.iot.vmp.conf.UserSetting;
import com.genersoft.iot.vmp.conf.security.dto.JwtUser;
import com.genersoft.iot.vmp.storager.dao.dto.Role;
import com.genersoft.iot.vmp.storager.dao.dto.User;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
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.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
@ -18,6 +24,8 @@ import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import static com.genersoft.iot.vmp.conf.security.dto.JwtUser.TokenStatus.NORMAL;
/**
* jwt token
*/
@ -25,75 +33,47 @@ import java.util.ArrayList;
@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {
private final static Logger logger = LoggerFactory.getLogger(JwtAuthenticationFilter.class);
@Autowired
private UserSetting userSetting;
@Autowired
private UserDetailsService userDetailsService;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
// 忽略登录请求的token验证
String requestURI = request.getRequestURI();
if ((requestURI.startsWith("/doc.html") || requestURI.startsWith("/swagger-ui") ) && !userSetting.getDocEnable()) {
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
return;
}
if (requestURI.equalsIgnoreCase("/api/user/login")) {
chain.doFilter(request, response);
return;
}
if (!userSetting.isInterfaceAuthentication()) {
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(null, null, new ArrayList<>() );
SecurityContextHolder.getContext().setAuthentication(token);
chain.doFilter(request, response);
return;
}
// String requestURI = request.getRequestURI();
// if ((requestURI.startsWith("/doc.html") || requestURI.startsWith("/swagger-ui") ) && !userSetting.getDocEnable()) {
// response.setStatus(HttpServletResponse.SC_NOT_FOUND);
// return;
// }
// if (requestURI.equalsIgnoreCase("/api/user/login")) {
// chain.doFilter(request, response);
// return;
// }
//
// if (!userSetting.isInterfaceAuthentication()) {
// UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(null, null, new ArrayList<>() );
// SecurityContextHolder.getContext().setAuthentication(token);
// chain.doFilter(request, response);
// return;
// }
String jwt = request.getHeader(JwtUtils.getHeader());
// 这里如果没有jwt继续往后走因为后面还有鉴权管理器等去判断是否拥有身份凭证所以是可以放行的
// 没有jwt相当于匿名访问若有一些接口是需要权限的则不能访问这些接口
if (StringUtils.isBlank(jwt)) {
jwt = request.getParameter(JwtUtils.getHeader());
if (StringUtils.isBlank(jwt)) {
jwt = request.getHeader(JwtUtils.getApiKeyHeader());
if (StringUtils.isBlank(jwt)) {
chain.doFilter(request, response);
return;
}
if(jwt != null) {
JwtUser jwtUser = JwtUtils.verifyToken(jwt);
String username = jwtUser.getUserName();
if(jwtUser.getStatus() == NORMAL) {
UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
}
}
JwtUser jwtUser = JwtUtils.verifyToken(jwt);
String username = jwtUser.getUserName();
// TODO 处理各个状态
switch (jwtUser.getStatus()){
case EXPIRED:
response.setStatus(400);
chain.doFilter(request, response);
// 异常
return;
case EXCEPTION:
// 过期
response.setStatus(400);
chain.doFilter(request, response);
return;
case EXPIRING_SOON:
// 即将过期
// return;
default:
}
// 构建UsernamePasswordAuthenticationToken,这里密码为null是因为提供了正确的JWT,实现自动登录
User user = new User();
user.setId(jwtUser.getUserId());
user.setUsername(jwtUser.getUserName());
user.setPassword(jwtUser.getPassword());
Role role = new Role();
role.setId(jwtUser.getRoleId());
user.setRole(role);
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(user, jwtUser.getPassword(), new ArrayList<>() );
SecurityContextHolder.getContext().setAuthentication(token);
chain.doFilter(request, response);
}

View File

@ -29,6 +29,7 @@ import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.Date;
import java.util.List;
import java.util.Map;
@ -108,6 +109,69 @@ public class JwtUtils implements InitializingBean {
return rsaJsonWebKey;
}
/**
* token
*
*/
public static String getUserNameFromToken(String token){
String username = null;
JwtUser jwtUser = new JwtUser();
try {
JwtConsumer consumer = new JwtConsumerBuilder()
.setAllowedClockSkewInSeconds(30)
.setRequireSubject()
.setExpectedAudience(AUDIENCE)
.setVerificationKey(rsaJsonWebKey.getPublicKey())
.build();
JwtClaims claims = consumer.processToClaims(token);
NumericDate expirationTime = claims.getExpirationTime();
if (expirationTime != null) {
// 判断是否即将过期, 默认剩余时间小于5分钟未即将过期
// 剩余时间 (秒)
long timeRemaining = LocalDateTime.now().toEpochSecond(ZoneOffset.ofHours(8)) - expirationTime.getValue();
if (timeRemaining < 5 * 60) {
jwtUser.setStatus(JwtUser.TokenStatus.EXPIRING_SOON);
} else {
jwtUser.setStatus(JwtUser.TokenStatus.NORMAL);
}
} else {
jwtUser.setStatus(JwtUser.TokenStatus.NORMAL);
}
Long apiKeyId = claims.getClaimValue("apiKeyId", Long.class);
if (apiKeyId != null) {
UserApiKey userApiKey = userApiKeyService.getUserApiKeyById(apiKeyId.intValue());
if (userApiKey == null || !userApiKey.isEnable()) {
jwtUser.setStatus(JwtUser.TokenStatus.EXPIRED);
}
}
username = (String) claims.getClaimValue("userName");
User user = userService.getUserByUsername(username);
jwtUser.setUserName(username);
jwtUser.setPassword(user.getPassword());
jwtUser.setRoleId(user.getRole().getId());
jwtUser.setUserId(user.getId());
return username;
} catch (InvalidJwtException e) {
if (e.hasErrorCode(ErrorCodes.EXPIRED)) {
jwtUser.setStatus(JwtUser.TokenStatus.EXPIRED);
} else {
jwtUser.setStatus(JwtUser.TokenStatus.EXCEPTION);
}
return username;
} catch (Exception e) {
logger.error("[Token解析失败] {}", e.getMessage());
jwtUser.setStatus(JwtUser.TokenStatus.EXPIRED);
return username;
}
}
public static String createToken(String username, Long expirationTime, Map<String, Object> extra) {
try {
/*
@ -176,13 +240,12 @@ public class JwtUtils implements InitializingBean {
.setExpectedAudience(AUDIENCE)
.setVerificationKey(rsaJsonWebKey.getPublicKey())
.build();
JwtClaims claims = consumer.processToClaims(token);
NumericDate expirationTime = claims.getExpirationTime();
if (expirationTime != null) {
// 判断是否即将过期, 默认剩余时间小于5分钟未即将过期
// 剩余时间 (秒)
long timeRemaining = LocalDateTime.now().toEpochSecond(ZoneOffset.ofHours(8)) - expirationTime.getValue();
long timeRemaining = expirationTime.getValue() - LocalDateTime.now().toEpochSecond(ZoneOffset.ofHours(8));
if (timeRemaining < 5 * 60) {
jwtUser.setStatus(JwtUser.TokenStatus.EXPIRING_SOON);
} else {

View File

@ -7,6 +7,8 @@ import org.springframework.security.authentication.UsernamePasswordAuthenticatio
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContext;
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.crypto.bcrypt.BCryptPasswordEncoder;
import javax.security.sasl.AuthenticationException;
@ -32,7 +34,6 @@ public class SecurityUtils {
LoginUser user = (LoginUser) authenticate.getPrincipal();
SecurityContextHolder.getContext().setAuthentication(token);
return user;
}
@ -55,8 +56,8 @@ public class SecurityUtils {
Object principal = authentication.getPrincipal();
if(principal!=null && !"anonymousUser".equals(principal.toString())){
User user = (User) principal;
return new LoginUser(user, LocalDateTime.now());
return (LoginUser) principal;
// return new LoginUser(user, LocalDateTime.now());
}
}
return null;

View File

@ -7,6 +7,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
@ -15,7 +16,9 @@ import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.web.cors.CorsConfiguration;
@ -27,6 +30,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import com.genersoft.iot.vmp.service.IUserService;
/**
* Spring Security
*
@ -42,6 +46,8 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserSetting userSetting;
@Autowired
private IUserService userService;
@Autowired
private DefaultUserDetailsServiceImpl userDetailsService;
@ -101,12 +107,13 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
provider.setUserDetailsService(userDetailsService);
// 设置密码加密算法
provider.setPasswordEncoder(passwordEncoder());
auth.authenticationProvider(provider);
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.headers().contentTypeOptions().disable()
http.headers().contentTypeOptions().disable()
.and().cors().configurationSource(configurationSource())
.and().csrf().disable()
.sessionManagement()
@ -127,6 +134,41 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
.logoutSuccessHandler(logoutHandler)
;
http.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
// ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry registry = http
// .authorizeRequests();
// //允许跨域请求的OPTIONS请求
// registry.antMatchers(HttpMethod.OPTIONS)
// .permitAll();
// registry.and()
// .authorizeRequests()
// .antMatchers(HttpMethod.GET,
// "/",
// "/swagger-ui/",
// "/doc.html")
// .permitAll()
// .antMatchers("/api/user/login", "/api/ptz/**", "/zlm/**", "/api/server/**","/index/hook/**","/index/hook/abl/**", "/swagger-ui/**", "/doc.html#/**")
// .permitAll()
// .anyRequest()
// .authenticated()
// // 关闭跨站请求防护及不使用session
// .and()
// .headers().contentTypeOptions().disable()
// .and()
// .cors().configurationSource(configurationSource())
// .and()
// .csrf()
// .disable()
// .sessionManagement()
// .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
// // 自定义权限拒绝处理类
// .and()
// .exceptionHandling()
// .authenticationEntryPoint(anonymousAuthenticationEntryPoint)
// .and().logout().logoutUrl("/api/user/logout").permitAll()
// .logoutSuccessHandler(logoutHandler)
// // 自定义权限拦截器JWT过滤器
// .and()
// .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
}

View File

@ -18,6 +18,11 @@ import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.util.DigestUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.web.bind.annotation.*;
@ -25,6 +30,7 @@ import org.springframework.web.bind.annotation.*;
import javax.security.sasl.AuthenticationException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.security.Principal;
import java.time.LocalDateTime;
import java.util.List;
@ -42,6 +48,12 @@ public class UserController {
@Autowired
private IRoleService roleService;
private final UserDetailsService userDetailsService;
public UserController(UserDetailsService userDetailsService) {
this.userDetailsService = userDetailsService;
}
@GetMapping("/login")
@PostMapping("/login")
@Operation(summary = "登录", description = "登录成功后返回AccessToken 可以从返回值获取到也可以从响应头中获取到," +
@ -51,18 +63,16 @@ public class UserController {
@Parameter(name = "password", description = "密码32位md5加密", required = true)
public LoginUser login(HttpServletRequest request, HttpServletResponse response, @RequestParam String username, @RequestParam String password){
LoginUser user;
try {
user = SecurityUtils.login(username, password, authenticationManager);
} catch (AuthenticationException e) {
throw new ControllerException(ErrorCode.ERROR100.getCode(), e.getMessage());
}
if (user == null) {
throw new ControllerException(ErrorCode.ERROR100.getCode(), "用户名或密码错误");
}else {
String jwt = JwtUtils.createToken(username);
response.setHeader(JwtUtils.getHeader(), jwt);
user.setAccessToken(jwt);
}
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(userDetails, null,userDetails.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
user = (LoginUser) userDetails;
String jwt = JwtUtils.createToken(username);
response.setHeader(JwtUtils.getHeader(), jwt);
user.setAccessToken(jwt);
return user;
}
@ -210,14 +220,21 @@ public class UserController {
@PostMapping("/userInfo")
@Operation(summary = "管理员修改普通用户密码")
public LoginUser getUserInfo() {
public LoginUser getUserInfo(Principal principal) {
if (principal == null) {
throw new ControllerException(ErrorCode.ERROR100.getCode(), "用户不存在");
}
// 获取当前登录用户id
LoginUser userInfo = SecurityUtils.getUserInfo();
if (userInfo == null) {
throw new ControllerException(ErrorCode.ERROR100);
}
User user = userService.getUser(userInfo.getUsername(), userInfo.getPassword());
return new LoginUser(user, LocalDateTime.now());
// User user = userService.getUser(userInfo.getUsername(), userInfo.getPassword());
// if (user == null) {
// throw new ControllerException(ErrorCode.ERROR100.getCode(), "用户不存在");
// }
return userInfo;
}
}