diff --git a/src/main/java/com/genersoft/iot/vmp/conf/security/JwtAuthenticationFilter.java b/src/main/java/com/genersoft/iot/vmp/conf/security/JwtAuthenticationFilter.java index ebeea987d..ec4b57db5 100644 --- a/src/main/java/com/genersoft/iot/vmp/conf/security/JwtAuthenticationFilter.java +++ b/src/main/java/com/genersoft/iot/vmp/conf/security/JwtAuthenticationFilter.java @@ -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); } diff --git a/src/main/java/com/genersoft/iot/vmp/conf/security/JwtUtils.java b/src/main/java/com/genersoft/iot/vmp/conf/security/JwtUtils.java index eacff1888..133178353 100644 --- a/src/main/java/com/genersoft/iot/vmp/conf/security/JwtUtils.java +++ b/src/main/java/com/genersoft/iot/vmp/conf/security/JwtUtils.java @@ -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 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 { diff --git a/src/main/java/com/genersoft/iot/vmp/conf/security/SecurityUtils.java b/src/main/java/com/genersoft/iot/vmp/conf/security/SecurityUtils.java index f012f7efb..6bdf3883f 100644 --- a/src/main/java/com/genersoft/iot/vmp/conf/security/SecurityUtils.java +++ b/src/main/java/com/genersoft/iot/vmp/conf/security/SecurityUtils.java @@ -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; diff --git a/src/main/java/com/genersoft/iot/vmp/conf/security/WebSecurityConfig.java b/src/main/java/com/genersoft/iot/vmp/conf/security/WebSecurityConfig.java index ee45e4d18..44fd1c73d 100644 --- a/src/main/java/com/genersoft/iot/vmp/conf/security/WebSecurityConfig.java +++ b/src/main/java/com/genersoft/iot/vmp/conf/security/WebSecurityConfig.java @@ -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.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); } diff --git a/src/main/java/com/genersoft/iot/vmp/vmanager/user/UserController.java b/src/main/java/com/genersoft/iot/vmp/vmanager/user/UserController.java index 86a806fc2..8704f783a 100755 --- a/src/main/java/com/genersoft/iot/vmp/vmanager/user/UserController.java +++ b/src/main/java/com/genersoft/iot/vmp/vmanager/user/UserController.java @@ -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; } }