diff --git a/README.md b/README.md index f43d1dd..315f9c9 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,10 @@ -# TeamModel extension -> SpringBoot base version of TeamModel extension -> -> **注意**: 所有复盘输出均已脱敏,不包含任何业务,密码等关键信息 - -## 迁移目录: -- Azure OIDC(SSO) 迁移 -- id-token(jwt) 验证迁移 (出现语言框架之间的签名算法规范问题,解决见: [输出复盘](https://juejin.cn/post/7300036605099163702)) \ No newline at end of file +# TeamModel extension +> SpringBoot base version of TeamModel extension +> +> **注意**: 所有复盘输出均已脱敏,不包含任何业务,密码等关键信息 + +## 迁移目录: +- Azure OIDC(SSO) 迁移 +- id-token(jwt) 验证迁移 (出现语言框架之间的签名算法规范问题,解决见: [输出复盘](https://juejin.cn/post/7300036605099163702)) +- 钉钉告警: 异常通知 +- 异常文件记录 \ No newline at end of file diff --git a/src/main/java/cn/teammodel/common/ErrorCode.java b/src/main/java/cn/teammodel/common/ErrorCode.java new file mode 100644 index 0000000..744e048 --- /dev/null +++ b/src/main/java/cn/teammodel/common/ErrorCode.java @@ -0,0 +1,37 @@ +package cn.teammodel.common; + +public enum ErrorCode { + + SUCCESS(0, "ok"), + PARAMS_ERROR(40000, "请求参数错误"), + NOT_LOGIN_ERROR(40100, "未登录"), + NO_AUTH_ERROR(40101, "无权限"), + NOT_FOUND_ERROR(40400, "请求数据不存在"), + FORBIDDEN_ERROR(40300, "禁止访问"), + SYSTEM_ERROR(50000, "系统内部异常"), + OPERATION_ERROR(50001, "操作失败"); + + /** + * 状态码 + */ + private final int code; + + /** + * 信息 + */ + private final String message; + + ErrorCode(int code, String message) { + this.code = code; + this.message = message; + } + + public int getCode() { + return code; + } + + public String getMessage() { + return message; + } + +} \ No newline at end of file diff --git a/src/main/java/cn/teammodel/common/R.java b/src/main/java/cn/teammodel/common/R.java index 4ae130a..3fb1faf 100644 --- a/src/main/java/cn/teammodel/common/R.java +++ b/src/main/java/cn/teammodel/common/R.java @@ -1,37 +1,48 @@ -package cn.teammodel.common; - -public class R { - private Integer code; - private String message; - private String data; - - public R(Integer code, String message, String data) { - this.code = code; - this.message = message; - this.data = data; - } - - public Integer getCode() { - return code; - } - - public void setCode(Integer code) { - this.code = code; - } - - public String getMessage() { - return message; - } - - public void setMessage(String message) { - this.message = message; - } - - public String getData() { - return data; - } - - public void setData(String data) { - this.data = data; - } -} +package cn.teammodel.common; + +import lombok.Data; + +import java.io.Serializable; + +@Data +public class R implements Serializable { + + private int code; + + private T data; + + private String message; + + public R(int code, T data, String message) { + this.code = code; + this.data = data; + this.message = message; + } + + public R(int code, T data) { + this(code, data, ""); + } + + public R(ErrorCode errorCode) { + this(errorCode.getCode(), null, errorCode.getMessage()); + } + public R(ErrorCode errorCode, T data) { + this(errorCode.getCode(), data, errorCode.getMessage()); + } + + public static R success(T data) { + return new R<>(ErrorCode.SUCCESS, data); + } + + public static R error(String msg) { + return new R<>(ErrorCode.SYSTEM_ERROR); + } + + public static R error(Integer code, String msg) { + return new R<>(code, null, msg); + } + + public static R error(ErrorCode errorCode) { + return new R<>(errorCode); + } +} \ No newline at end of file diff --git a/src/main/java/cn/teammodel/config/exception/GlobalExceptionHandler.java b/src/main/java/cn/teammodel/config/exception/GlobalExceptionHandler.java new file mode 100644 index 0000000..a3ca68d --- /dev/null +++ b/src/main/java/cn/teammodel/config/exception/GlobalExceptionHandler.java @@ -0,0 +1,110 @@ +package cn.teammodel.config.exception; + +import cn.teammodel.common.R; +import cn.teammodel.manager.NotificationService; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.ObjectUtils; +import org.springframework.validation.BindException; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.MissingPathVariableException; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; +import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException; + +import javax.annotation.Resource; +import javax.servlet.http.HttpServletRequest; +import java.util.Objects; + + +/** + * 全局异常处理器 + * + * @author ruoyi + */ +@RestControllerAdvice +@Slf4j +public class GlobalExceptionHandler { + + @Resource + private NotificationService notificationService; + /** + * 业务异常 + */ + @ExceptionHandler(ServiceException.class) + public R handleServiceException(ServiceException e, HttpServletRequest request) + { + log.error(e.getMessage(), e); + Integer code = e.getCode(); + return ObjectUtils.isNotEmpty(code) ? R.error(code, e.getMessage()) : R.error(e.getMessage()); + } + + /** + * 请求路径中缺少必需的路径变量 + */ + @ExceptionHandler(MissingPathVariableException.class) + public R handleMissingPathVariableException(MissingPathVariableException e, HttpServletRequest request) + { + String requestURI = request.getRequestURI(); + log.error("请求路径中缺少必需的路径变量'{}',发生系统异常.", requestURI, e); + return R.error(String.format("请求路径中缺少必需的路径变量[%s]", e.getVariableName())); + } + + /** + * 请求参数类型不匹配 + */ + @ExceptionHandler(MethodArgumentTypeMismatchException.class) + public R handleMethodArgumentTypeMismatchException(MethodArgumentTypeMismatchException e, HttpServletRequest request) + { + String requestURI = request.getRequestURI(); + log.error("请求参数类型不匹配'{}',发生系统异常.", requestURI, e); + return R.error(String.format("请求参数类型不匹配,参数[%s]要求类型为:'%s',但输入值为:'%s'", e.getName(), e.getRequiredType().getName(), e.getValue())); + } + + /** + * 拦截未知的运行时异常 + */ + @ExceptionHandler(RuntimeException.class) + public R handleRuntimeException(RuntimeException e, HttpServletRequest request) + { + String requestURI = request.getRequestURI(); + log.error("请求地址'{}',发生未知异常.", requestURI, e); + notificationService.send("RuntimeException 告警: " + e.getMessage()); + return R.error(e.getMessage()); + } + + /** + * 系统异常 + */ + @ExceptionHandler(Exception.class) + public R handleException(Exception e, HttpServletRequest request) + { + String requestURI = request.getRequestURI(); + log.error("请求地址'{}',发生系统异常.", requestURI, e); + + notificationService.send("Exception 告警: " + e.getMessage()); + return R.error(e.getMessage()); + } + + /** + * 自定义验证异常 + */ + @ExceptionHandler(BindException.class) + public R handleBindException(BindException e) + { + log.error(e.getMessage(), e); + String message = e.getAllErrors().get(0).getDefaultMessage(); + return R.error(message); + } + + /** + * 自定义验证异常 + */ + @ExceptionHandler(MethodArgumentNotValidException.class) + public Object handleMethodArgumentNotValidException(MethodArgumentNotValidException e) + { + log.error(e.getMessage(), e); + String message = Objects.requireNonNull(e.getBindingResult().getFieldError()).getDefaultMessage(); + return R.error(message); + } + +} diff --git a/src/main/java/cn/teammodel/config/exception/ServiceException.java b/src/main/java/cn/teammodel/config/exception/ServiceException.java index ee5a2e1..1ac597c 100644 --- a/src/main/java/cn/teammodel/config/exception/ServiceException.java +++ b/src/main/java/cn/teammodel/config/exception/ServiceException.java @@ -1,72 +1,72 @@ -package cn.teammodel.config.exception; - -/** - * 业务异常 - * - * @author winter - */ -public final class ServiceException extends RuntimeException -{ - private static final long serialVersionUID = 1L; - - /** - * 错误码 - */ - private Integer code; - - /** - * 错误提示 - */ - private String message; - - /** - * 错误明细,内部调试错误 - */ - private String detailMessage; - - /** - * 空构造方法,避免反序列化问题 - */ - public ServiceException() - { - } - - public ServiceException(String message) - { - this.message = message; - } - - public ServiceException(String message, Integer code) - { - this.message = message; - this.code = code; - } - - public String getDetailMessage() - { - return detailMessage; - } - - @Override - public String getMessage() - { - return message; - } - - public Integer getCode() - { - return code; - } - - public ServiceException setMessage(String message) - { - this.message = message; - return this; - } - - public ServiceException setDetailMessage(String detailMessage) - { - this.detailMessage = detailMessage; - return this; - } +package cn.teammodel.config.exception; + +/** + * 业务异常 + * + * @author winter + */ +public final class ServiceException extends RuntimeException +{ + private static final long serialVersionUID = 1L; + + /** + * 错误码 + */ + private Integer code; + + /** + * 错误提示 + */ + private String message; + + /** + * 错误明细,内部调试错误 + */ + private String detailMessage; + + /** + * 空构造方法,避免反序列化问题 + */ + public ServiceException() + { + } + + public ServiceException(String message) + { + this.message = message; + } + + public ServiceException(String message, Integer code) + { + this.message = message; + this.code = code; + } + + public String getDetailMessage() + { + return detailMessage; + } + + @Override + public String getMessage() + { + return message; + } + + public Integer getCode() + { + return code; + } + + public ServiceException setMessage(String message) + { + this.message = message; + return this; + } + + public ServiceException setDetailMessage(String detailMessage) + { + this.detailMessage = detailMessage; + return this; + } } \ No newline at end of file diff --git a/src/main/java/cn/teammodel/controller/HelloController.java b/src/main/java/cn/teammodel/controller/HelloController.java index 1a6ca8b..bb3ec76 100644 --- a/src/main/java/cn/teammodel/controller/HelloController.java +++ b/src/main/java/cn/teammodel/controller/HelloController.java @@ -1,20 +1,20 @@ -package cn.teammodel.controller; - -import cn.teammodel.common.R; -import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -@RestController -@RequestMapping("/") -public class HelloController { - - @GetMapping("hello") - @PreAuthorize("@ss.hasRole('admin')") - public R hello() { - System.out.println(SecurityContextHolder.getContext().getAuthentication()); - return new R(200, "success","hello world"); - } +package cn.teammodel.controller; + +import cn.teammodel.common.R; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/") +public class HelloController { + + @GetMapping("hello") + @PreAuthorize("@ss.hasRole('admin')") + public R hello() { + System.out.println(SecurityContextHolder.getContext().getAuthentication()); + return new R(200, "success","hello world"); + } } \ No newline at end of file diff --git a/src/main/java/cn/teammodel/manager/DingAlertNotifier.java b/src/main/java/cn/teammodel/manager/DingAlertNotifier.java new file mode 100644 index 0000000..cdf5f22 --- /dev/null +++ b/src/main/java/cn/teammodel/manager/DingAlertNotifier.java @@ -0,0 +1,44 @@ +package cn.teammodel.manager; + +import com.dingtalk.api.DefaultDingTalkClient; +import com.dingtalk.api.DingTalkClient; +import com.dingtalk.api.request.OapiRobotSendRequest; +import com.taobao.api.ApiException; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +/** + * 钉钉告警服务 + * @author winter + * @create 2023-11-14 10:11 + */ +@Component +@Slf4j +public class DingAlertNotifier implements NotificationService{ + private final DingTalkClient client; + @Autowired + public DingAlertNotifier(@Value("${ding.server-url}") String dingServerUrl) { + this.client = new DefaultDingTalkClient(dingServerUrl); + } + + @Override + public void send(String message) { + OapiRobotSendRequest request = new OapiRobotSendRequest(); + // 文本消息 + request.setMsgtype("text"); + OapiRobotSendRequest.Text text = new OapiRobotSendRequest.Text(); + text.setContent(message); + request.setText(text); + // at 管理员提醒异常 + OapiRobotSendRequest.At atAll = new OapiRobotSendRequest.At(); + atAll.setIsAtAll(true); + request.setAt(atAll); + try { + client.execute(request); + } catch (ApiException e) { + log.error("钉钉 robot 推送消息渠道异常: {}", e.getMessage()); + } + } +} diff --git a/src/main/java/cn/teammodel/manager/NotificationService.java b/src/main/java/cn/teammodel/manager/NotificationService.java new file mode 100644 index 0000000..f002ea4 --- /dev/null +++ b/src/main/java/cn/teammodel/manager/NotificationService.java @@ -0,0 +1,16 @@ +package cn.teammodel.manager; + +/** + * 消息通知接口 + * @author winter + * @create 2023-11-14 10:08 + */ +public interface NotificationService { + + /** + * 发送消息 + * @author: winter + * @date: 2023/11/14 10:09 + */ + void send(String message); +} diff --git a/src/main/java/cn/teammodel/model/entity/TmdUserDetail.java b/src/main/java/cn/teammodel/model/entity/TmdUserDetail.java index bebb961..9dc5584 100644 --- a/src/main/java/cn/teammodel/model/entity/TmdUserDetail.java +++ b/src/main/java/cn/teammodel/model/entity/TmdUserDetail.java @@ -1,50 +1,50 @@ -package cn.teammodel.model.entity; - -import lombok.Data; -import org.springframework.security.core.GrantedAuthority; -import org.springframework.security.core.userdetails.UserDetails; - -import java.util.Collection; - -/** - * @author winter - * @create 2023-11-09 15:28 - */ -@Data -public class TmdUserDetail implements UserDetails { - private User user; - @Override - public Collection getAuthorities() { - return null; - } - - @Override - public String getPassword() { - return null; - } - - @Override - public String getUsername() { - return null; - } - - @Override - public boolean isAccountNonExpired() { - return false; - } - - @Override - public boolean isAccountNonLocked() { - return false; - } - - @Override - public boolean isCredentialsNonExpired() { - return false; - } - - @Override - public boolean isEnabled() { - return true; - } -} +package cn.teammodel.model.entity; + +import lombok.Data; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; + +import java.util.Collection; + +/** + * @author winter + * @create 2023-11-09 15:28 + */ +@Data +public class TmdUserDetail implements UserDetails { + private User user; + @Override + public Collection getAuthorities() { + return null; + } + + @Override + public String getPassword() { + return null; + } + + @Override + public String getUsername() { + return null; + } + + @Override + public boolean isAccountNonExpired() { + return false; + } + + @Override + public boolean isAccountNonLocked() { + return false; + } + + @Override + public boolean isCredentialsNonExpired() { + return false; + } + + @Override + public boolean isEnabled() { + return true; + } +} diff --git a/src/main/java/cn/teammodel/model/entity/User.java b/src/main/java/cn/teammodel/model/entity/User.java index a5c4ed5..7aee302 100644 --- a/src/main/java/cn/teammodel/model/entity/User.java +++ b/src/main/java/cn/teammodel/model/entity/User.java @@ -1,24 +1,24 @@ -package cn.teammodel.model.entity; - -import lombok.Data; -import lombok.ToString; - -import java.util.Set; - -/** - * @author winter - * @create 2023-11-09 15:43 - */ -@Data -@ToString -public class User { - private String id; - private String name; - private String picture; - private String standard; - private String scope; - private String website; - private String area; - private Set roles; - private Set permissions; -} +package cn.teammodel.model.entity; + +import lombok.Data; +import lombok.ToString; + +import java.util.Set; + +/** + * @author winter + * @create 2023-11-09 15:43 + */ +@Data +@ToString +public class User { + private String id; + private String name; + private String picture; + private String standard; + private String scope; + private String website; + private String area; + private Set roles; + private Set permissions; +} diff --git a/src/main/java/cn/teammodel/security/SecurityConfiguration.java b/src/main/java/cn/teammodel/security/SecurityConfiguration.java index a5b3b60..5e9a411 100644 --- a/src/main/java/cn/teammodel/security/SecurityConfiguration.java +++ b/src/main/java/cn/teammodel/security/SecurityConfiguration.java @@ -1,67 +1,63 @@ -package cn.teammodel.security; - -import cn.teammodel.security.filter.AuthInnerTokenFilter; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; -import org.springframework.security.config.annotation.web.configurers.oauth2.server.resource.OAuth2ResourceServerConfigurer; -import org.springframework.security.config.http.SessionCreationPolicy; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter; -import org.springframework.security.oauth2.server.resource.authentication.JwtGrantedAuthoritiesConverter; -import org.springframework.security.oauth2.server.resource.web.BearerTokenAuthenticationFilter; -import org.springframework.security.web.SecurityFilterChain; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RestController; - -import javax.annotation.Resource; - -@Configuration -@EnableWebSecurity -@EnableMethodSecurity -public class SecurityConfiguration { - @Resource - private AuthInnerTokenFilter authInnerTokenFilter; - - @Bean - public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - http - // CSRF禁用,因为不使用session - .csrf().disable() - // 禁用HTTP响应标头 - .headers().cacheControl().disable() - .and() - .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) - .and() - .authorizeRequests(authorizeRequests -> - authorizeRequests - .antMatchers("/public/**").permitAll() - .anyRequest().authenticated() - ) - .oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt) // 启用 OIDC jwt filter - .addFilterAfter(authInnerTokenFilter, BearerTokenAuthenticationFilter.class); // 添加 x-auth-authToken filter - // todo: 失败处理器 - - return http.build(); - } - - /** - * OIDC: 将 jwt 中的 claim 中的 roles 放进 authorities 中 - * - * @author: winter - * @date: 2023/11/8 17:13 - */ - @Bean - public JwtAuthenticationConverter jwtAuthenticationConverter() { - JwtGrantedAuthoritiesConverter grantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter(); - grantedAuthoritiesConverter.setAuthoritiesClaimName("roles"); - grantedAuthoritiesConverter.setAuthorityPrefix(""); - - JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter(); - jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(grantedAuthoritiesConverter); - return jwtAuthenticationConverter; - } -} +package cn.teammodel.security; + +import cn.teammodel.security.filter.AuthInnerTokenFilter; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configurers.oauth2.server.resource.OAuth2ResourceServerConfigurer; +import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter; +import org.springframework.security.oauth2.server.resource.authentication.JwtGrantedAuthoritiesConverter; +import org.springframework.security.oauth2.server.resource.web.BearerTokenAuthenticationFilter; +import org.springframework.security.web.SecurityFilterChain; + +import javax.annotation.Resource; + +@Configuration +@EnableWebSecurity +@EnableMethodSecurity +public class SecurityConfiguration { + @Resource + private AuthInnerTokenFilter authInnerTokenFilter; + + @Bean + public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { + http + // CSRF禁用,因为不使用session + .csrf().disable() + // 禁用HTTP响应标头 + .headers().cacheControl().disable() + .and() + .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) + .and() + .authorizeRequests(authorizeRequests -> + authorizeRequests + .antMatchers("/public/**").permitAll() + .anyRequest().authenticated() + ) + .oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt) // 启用 OIDC jwt filter + .addFilterAfter(authInnerTokenFilter, BearerTokenAuthenticationFilter.class); // 添加 x-auth-authToken filter + // todo: 失败处理器 + + return http.build(); + } + + /** + * OIDC: 将 jwt 中的 claim 中的 roles 放进 authorities 中 + * + * @author: winter + * @date: 2023/11/8 17:13 + */ + @Bean + public JwtAuthenticationConverter jwtAuthenticationConverter() { + JwtGrantedAuthoritiesConverter grantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter(); + grantedAuthoritiesConverter.setAuthoritiesClaimName("roles"); + grantedAuthoritiesConverter.setAuthorityPrefix(""); + + JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter(); + jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(grantedAuthoritiesConverter); + return jwtAuthenticationConverter; + } +} diff --git a/src/main/java/cn/teammodel/security/filter/AuthInnerTokenFilter.java b/src/main/java/cn/teammodel/security/filter/AuthInnerTokenFilter.java index 6a61b1d..ed0bae8 100644 --- a/src/main/java/cn/teammodel/security/filter/AuthInnerTokenFilter.java +++ b/src/main/java/cn/teammodel/security/filter/AuthInnerTokenFilter.java @@ -1,55 +1,53 @@ -package cn.teammodel.security.filter; - -import cn.teammodel.config.exception.ServiceException; -import cn.teammodel.model.entity.TmdUserDetail; -import cn.teammodel.security.utils.JwtTokenUtil; -import cn.teammodel.security.utils.SecurityUtils; -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.AuthenticationException; -import org.springframework.security.core.GrantedAuthority; -import org.springframework.security.core.context.SecurityContext; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.stereotype.Component; -import org.springframework.web.filter.OncePerRequestFilter; - -import javax.servlet.FilterChain; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.io.IOException; -import java.util.Collection; - -/** - * x-auth-authToken filter - * @author winter - * @create 2023-11-09 10:43 - */ -@Component -@Slf4j -public class AuthInnerTokenFilter extends OncePerRequestFilter { - - @Autowired - JwtTokenUtil jwtTokenUtil; - - @Override - protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { - SecurityContext context = SecurityContextHolder.getContext(); - Authentication authentication = context.getAuthentication(); - // 进入此过滤器说明 OIDC 认证成功,则验证 authToken - // 验证 authToken 合法 - TmdUserDetail tmdUserDetail = jwtTokenUtil.getValidUserDetail(request); - if (tmdUserDetail == null) { - log.error("authToken authentication failed"); - throw new ServiceException("无权限"); - } - System.out.println(tmdUserDetail.getUser()); - // 组装 authToken 的 jwt 进 authentication - Collection authorities = authentication.getAuthorities(); - UsernamePasswordAuthenticationToken finalAuthentication = new UsernamePasswordAuthenticationToken(tmdUserDetail, null, authorities); - context.setAuthentication(finalAuthentication); - filterChain.doFilter(request, response); - } -} +package cn.teammodel.security.filter; + +import cn.teammodel.model.entity.TmdUserDetail; +import cn.teammodel.security.utils.JwtTokenUtil; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.context.SecurityContext; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.stereotype.Component; +import org.springframework.web.filter.OncePerRequestFilter; + +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.nio.file.AccessDeniedException; +import java.util.Collection; + +/** + * x-auth-authToken filter + * @author winter + * @create 2023-11-09 10:43 + */ +@Component +@Slf4j +public class AuthInnerTokenFilter extends OncePerRequestFilter { + + @Autowired + JwtTokenUtil jwtTokenUtil; + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { + SecurityContext context = SecurityContextHolder.getContext(); + Authentication authentication = context.getAuthentication(); + // 进入此过滤器说明 OIDC 认证成功,则验证 authToken + // 验证 authToken 合法 + TmdUserDetail tmdUserDetail = jwtTokenUtil.getValidUserDetail(request); + if (tmdUserDetail == null) { + log.error("authToken authentication failed"); + throw new AccessDeniedException("无权限"); + } + System.out.println(tmdUserDetail.getUser()); + // 组装 authToken 的 jwt 进 authentication + Collection authorities = authentication.getAuthorities(); + UsernamePasswordAuthenticationToken finalAuthentication = new UsernamePasswordAuthenticationToken(tmdUserDetail, null, authorities); + context.setAuthentication(finalAuthentication); + filterChain.doFilter(request, response); + } +} diff --git a/src/main/java/cn/teammodel/security/service/PermissionService.java b/src/main/java/cn/teammodel/security/service/PermissionService.java index ddaad19..72c432e 100644 --- a/src/main/java/cn/teammodel/security/service/PermissionService.java +++ b/src/main/java/cn/teammodel/security/service/PermissionService.java @@ -1,174 +1,174 @@ -package cn.teammodel.security.service; - -import java.util.Set; - -import cn.teammodel.model.entity.User; -import cn.teammodel.security.utils.SecurityUtils; -import org.apache.commons.lang3.StringUtils; -import org.springframework.security.core.Authentication; -import org.springframework.stereotype.Service; -import org.springframework.util.CollectionUtils; - -/** - * 自定义权限实现,ss取自SpringSecurity首字母
- *

- * 1. IES 类似角色判断: hasAuth
- * 2. role 身份判断(authToken 解析出来的用户身份): hasRole
- * 3. permission 权限判断(authToken 解析出来的用户权限): hasPermi
- *

- * @author ruoyi - */ -@Service("ss") -public class PermissionService -{ - /** 所有权限标识 */ - private static final String ALL_PERMISSION = "*:*:*"; - - /** 管理员角色权限标识 */ - private static final String SUPER_ADMIN = "admin"; - - private static final String ROLE_DELIMETER = ","; - - private static final String PERMISSION_DELIMETER = ","; - - - - - /** - * access_token 是否拥有 auth 角色 - * @param auth: - * @return: boolean - * @author: winter - * @date: 2023/11/10 10:05 - * @description: - */ - public boolean hasAuth(String auth) - { - if (StringUtils.isEmpty(auth)) - { - return false; - } - Authentication authentication = SecurityUtils.getAuthentication(); - if (authentication == null || CollectionUtils.isEmpty(authentication.getAuthorities())) - { - return false; - } - return authentication.getAuthorities().contains(auth); - } - - /** - * 验证用户是否具备某权限 - * - * @param permission 权限字符串 - * @return 用户是否具备某权限 - */ - public boolean hasPermi(String permission) - { - if (StringUtils.isEmpty(permission)) - { - return false; - } - User loginUser = SecurityUtils.getLoginUser(); - if (loginUser == null || CollectionUtils.isEmpty(loginUser.getPermissions())) - { - return false; - } - return loginUser.getPermissions().contains(permission); - } - - /** - * 验证用户是否不具备某权限,与 hasPermi逻辑相反 - * - * @param permission 权限字符串 - * @return 用户是否不具备某权限 - */ - public boolean lacksPermi(String permission) - { - return !hasPermi(permission); - } - - /** - * 验证用户是否具有以下任意一个权限 - * - * @param permissions 以 PERMISSION_DELIMETER 为分隔符的权限列表 - * @return 用户是否具有以下任意一个权限 - */ - public boolean hasAnyPermi(String permissions) - { - if (StringUtils.isEmpty(permissions)) - { - return false; - } - User loginUser = SecurityUtils.getLoginUser(); - if (loginUser == null || CollectionUtils.isEmpty(loginUser.getPermissions())) - { - return false; - } - Set authorities = loginUser.getPermissions(); - for (String permission : permissions.split(PERMISSION_DELIMETER)) - { - if (permission != null && authorities.contains(permission)) - { - return true; - } - } - return false; - } - - /** - * 判断用户是否拥有某个角色 - * - * @param role 角色字符串 - * @return 用户是否具备某角色 - */ - public boolean hasRole(String role) - { - if (StringUtils.isEmpty(role)) { - return false; - } - User loginUser = SecurityUtils.getLoginUser(); - if (loginUser == null || CollectionUtils.isEmpty(loginUser.getRoles())) - { - return false; - } - return loginUser.getRoles().contains(role); - } - - /** - * 验证用户是否不具备某角色,与 isRole逻辑相反。 - * - * @param role 角色名称 - * @return 用户是否不具备某角色 - */ - public boolean lacksRole(String role) - { - return !hasRole(role); - } - - /** - * 验证用户是否具有以下任意一个角色 - * - * @param roles 以 ROLE_NAMES_DELIMETER 为分隔符的角色列表 - * @return 用户是否具有以下任意一个角色 - */ - public boolean hasAnyRoles(String roles) - { - if (StringUtils.isEmpty(roles)) - { - return false; - } - User loginUser = SecurityUtils.getLoginUser(); - if (loginUser == null || CollectionUtils.isEmpty(loginUser.getRoles())) - { - return false; - } - for (String role : roles.split(ROLE_DELIMETER)) - { - if (hasRole(role)) - { - return true; - } - } - return false; - } -} +package cn.teammodel.security.service; + +import java.util.Set; + +import cn.teammodel.model.entity.User; +import cn.teammodel.security.utils.SecurityUtils; +import org.apache.commons.lang3.StringUtils; +import org.springframework.security.core.Authentication; +import org.springframework.stereotype.Service; +import org.springframework.util.CollectionUtils; + +/** + * 自定义权限实现,ss取自SpringSecurity首字母
+ *

+ * 1. IES 类似角色判断: hasAuth
+ * 2. role 身份判断(authToken 解析出来的用户身份): hasRole
+ * 3. permission 权限判断(authToken 解析出来的用户权限): hasPermi
+ *

+ * @author ruoyi + */ +@Service("ss") +public class PermissionService +{ + /** 所有权限标识 */ + private static final String ALL_PERMISSION = "*:*:*"; + + /** 管理员角色权限标识 */ + private static final String SUPER_ADMIN = "admin"; + + private static final String ROLE_DELIMETER = ","; + + private static final String PERMISSION_DELIMETER = ","; + + + + + /** + * access_token 是否拥有 auth 角色 + * @param auth: + * @return: boolean + * @author: winter + * @date: 2023/11/10 10:05 + * @description: + */ + public boolean hasAuth(String auth) + { + if (StringUtils.isEmpty(auth)) + { + return false; + } + Authentication authentication = SecurityUtils.getAuthentication(); + if (authentication == null || CollectionUtils.isEmpty(authentication.getAuthorities())) + { + return false; + } + return authentication.getAuthorities().contains(auth); + } + + /** + * 验证用户是否具备某权限 + * + * @param permission 权限字符串 + * @return 用户是否具备某权限 + */ + public boolean hasPermi(String permission) + { + if (StringUtils.isEmpty(permission)) + { + return false; + } + User loginUser = SecurityUtils.getLoginUser(); + if (loginUser == null || CollectionUtils.isEmpty(loginUser.getPermissions())) + { + return false; + } + return loginUser.getPermissions().contains(permission); + } + + /** + * 验证用户是否不具备某权限,与 hasPermi逻辑相反 + * + * @param permission 权限字符串 + * @return 用户是否不具备某权限 + */ + public boolean lacksPermi(String permission) + { + return !hasPermi(permission); + } + + /** + * 验证用户是否具有以下任意一个权限 + * + * @param permissions 以 PERMISSION_DELIMETER 为分隔符的权限列表 + * @return 用户是否具有以下任意一个权限 + */ + public boolean hasAnyPermi(String permissions) + { + if (StringUtils.isEmpty(permissions)) + { + return false; + } + User loginUser = SecurityUtils.getLoginUser(); + if (loginUser == null || CollectionUtils.isEmpty(loginUser.getPermissions())) + { + return false; + } + Set authorities = loginUser.getPermissions(); + for (String permission : permissions.split(PERMISSION_DELIMETER)) + { + if (permission != null && authorities.contains(permission)) + { + return true; + } + } + return false; + } + + /** + * 判断用户是否拥有某个角色 + * + * @param role 角色字符串 + * @return 用户是否具备某角色 + */ + public boolean hasRole(String role) + { + if (StringUtils.isEmpty(role)) { + return false; + } + User loginUser = SecurityUtils.getLoginUser(); + if (loginUser == null || CollectionUtils.isEmpty(loginUser.getRoles())) + { + return false; + } + return loginUser.getRoles().contains(role); + } + + /** + * 验证用户是否不具备某角色,与 isRole逻辑相反。 + * + * @param role 角色名称 + * @return 用户是否不具备某角色 + */ + public boolean lacksRole(String role) + { + return !hasRole(role); + } + + /** + * 验证用户是否具有以下任意一个角色 + * + * @param roles 以 ROLE_NAMES_DELIMETER 为分隔符的角色列表 + * @return 用户是否具有以下任意一个角色 + */ + public boolean hasAnyRoles(String roles) + { + if (StringUtils.isEmpty(roles)) + { + return false; + } + User loginUser = SecurityUtils.getLoginUser(); + if (loginUser == null || CollectionUtils.isEmpty(loginUser.getRoles())) + { + return false; + } + for (String role : roles.split(ROLE_DELIMETER)) + { + if (hasRole(role)) + { + return true; + } + } + return false; + } +} diff --git a/src/main/java/cn/teammodel/security/utils/JwtTokenUtil.java b/src/main/java/cn/teammodel/security/utils/JwtTokenUtil.java index 0e92e17..4b89101 100644 --- a/src/main/java/cn/teammodel/security/utils/JwtTokenUtil.java +++ b/src/main/java/cn/teammodel/security/utils/JwtTokenUtil.java @@ -1,173 +1,173 @@ -package cn.teammodel.security.utils; - -import cn.teammodel.model.entity.User; -import cn.teammodel.model.entity.TmdUserDetail; -import io.jsonwebtoken.Claims; -import io.jsonwebtoken.Jwts; -import io.jsonwebtoken.SignatureAlgorithm; -import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.StringUtils; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.security.core.userdetails.UserDetails; -import org.springframework.stereotype.Component; - -import javax.crypto.spec.SecretKeySpec; -import javax.servlet.http.HttpServletRequest; -import java.nio.charset.StandardCharsets; -import java.util.*; -import java.util.stream.Collectors; - -/** - * @author winter - * @date 2022年11月24日 下午10:50 - * @description 生成jwt令牌的工具类 - */ -@Component -@Slf4j -public class JwtTokenUtil { - private static final long NEVER_EXPIRE = 315360000; // 没有永不过期的api: 让时钟偏移十年 - - @Value("${jwt.secret}") - private String secret; - - /** - * 生成token - * @param userDetails 传入userDetails - * @return token - */ - @Deprecated - public String generateToken(UserDetails userDetails){ - Map claims = new HashMap<>(); - // 添加 payload - //claims.put(CLAIM_KEY_USERNAME,userDetails.getUsername()); - //claims.put(CLAIM_KEY_CREATED,new Date()); - return generateToken(claims); - } - @Deprecated - private String generateToken(Map claims){ - return Jwts.builder() - .setClaims(claims) - .signWith(SignatureAlgorithm.HS512,secret) - .compact(); - } - - /** - * 从token中获取用户名 - * @param token 前端传入的token - * @return 负载中的用户名,获取失败返回null - */ - @Deprecated - public String getUsernameFromToken(String token) { - String username = null; - try { - Claims claims = getClaimsFromToken(token); - username = claims.getSubject(); - }catch (Exception e){ - log.info("jwt验证出错:{}",token); - } - return username; - } - - /** - * 从token中获取Claims,异常返回null - * @param token token - * @return claims或者null - */ - private Claims getClaimsFromToken(String token) { - Claims claims = null; - try { - claims = Jwts.parser() - .setSigningKey(new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256")) - .setAllowedClockSkewSeconds(NEVER_EXPIRE) - .parseClaimsJws(token) - .getBody(); - } catch (Exception e) { - log.warn("token 解析出错:{}",e.getMessage()); - } - return claims; - } - - /** - * 检验token是否还有效 - * @param token 前端传入的token - * @return true表示有效,false表示无效 - */ - public boolean validateToken(String token) { - return !isTokenExpired(token); - } - - public boolean validateToken(HttpServletRequest request) { - String token = request.getHeader("x-auth-AuthToken"); - if (StringUtils.isBlank(token)) { - return false; - } - - return validateToken(token); - } - - private boolean isTokenExpired(String token) { - Date expiredDate = getExpiredDateFromToken(token); - return expiredDate.before(new Date()); - } - - private Date getExpiredDateFromToken(String token) { - Claims claims = getClaimsFromToken(token); - return claims.getExpiration(); - } - - /** - * token 验证成功返回实体,验证失败返回 null - * @param request: - * @return: cn.teammodel.model.entity.TmdUserDetail - * @author: winter - * @date: 2023/11/10 10:24 - * @description: - */ - public TmdUserDetail getValidUserDetail(HttpServletRequest request) { - String token = request.getHeader("x-auth-AuthToken"); - if (StringUtils.isBlank(token)) { - return null; - } - Claims claims = getClaimsFromToken(token); - if (claims == null) { - return null; - } - - // 组装 TmdUserDetail - TmdUserDetail tmdUserDetail = new TmdUserDetail(); - User user = new User(); - String id = claims.getSubject(); - user.setId(id); - user.setName(claims.get("name") == null ? null : claims.get("name", String.class)); - user.setPicture(claims.get("picture") == null ? null : claims.get("picture", String.class)); - user.setStandard(claims.get("standard") == null ? null : claims.get("standard", String.class)); - user.setScope(claims.get("scope") == null ? null : claims.get("scope", String.class)); - user.setWebsite(claims.get("website") == null ? null : claims.get("website", String.class)); - user.setArea(claims.get("area") == null ? null : claims.get("area", String.class)); - - // 取出 roles 和 permissions - Set roleSet = convertToArray(claims.get("roles")); - Set permissionSet = convertToArray(claims.get("permissions")); - user.setRoles(roleSet); - user.setPermissions(permissionSet); - tmdUserDetail.setUser(user); - return tmdUserDetail; - } - - @SuppressWarnings("unchecked") - private Set convertToArray(Object o) { - if (o == null) { - return Collections.emptySet(); - } - if (o instanceof String) { - if (StringUtils.isNotBlank((String)o)) { - return Arrays.stream(((String) o).split(" ")).collect(Collectors.toSet()); - } - return Collections.emptySet(); - } - if (o instanceof Collection) { - return new HashSet<>(((Collection) o)); - } - return Collections.emptySet(); - } +package cn.teammodel.security.utils; + +import cn.teammodel.model.entity.User; +import cn.teammodel.model.entity.TmdUserDetail; +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.SignatureAlgorithm; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.stereotype.Component; + +import javax.crypto.spec.SecretKeySpec; +import javax.servlet.http.HttpServletRequest; +import java.nio.charset.StandardCharsets; +import java.util.*; +import java.util.stream.Collectors; + +/** + * @author winter + * @date 2022年11月24日 下午10:50 + * @description 生成jwt令牌的工具类 + */ +@Component +@Slf4j +public class JwtTokenUtil { + private static final long NEVER_EXPIRE = 315360000; // 没有永不过期的api: 让时钟偏移十年 + + @Value("${jwt.secret}") + private String secret; + + /** + * 生成token + * @param userDetails 传入userDetails + * @return token + */ + @Deprecated + public String generateToken(UserDetails userDetails){ + Map claims = new HashMap<>(); + // 添加 payload + //claims.put(CLAIM_KEY_USERNAME,userDetails.getUsername()); + //claims.put(CLAIM_KEY_CREATED,new Date()); + return generateToken(claims); + } + @Deprecated + private String generateToken(Map claims){ + return Jwts.builder() + .setClaims(claims) + .signWith(SignatureAlgorithm.HS512,secret) + .compact(); + } + + /** + * 从token中获取用户名 + * @param token 前端传入的token + * @return 负载中的用户名,获取失败返回null + */ + @Deprecated + public String getUsernameFromToken(String token) { + String username = null; + try { + Claims claims = getClaimsFromToken(token); + username = claims.getSubject(); + }catch (Exception e){ + log.info("jwt验证出错:{}",token); + } + return username; + } + + /** + * 从token中获取Claims,异常返回null + * @param token token + * @return claims或者null + */ + private Claims getClaimsFromToken(String token) { + Claims claims = null; + try { + claims = Jwts.parser() + .setSigningKey(new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256")) + .setAllowedClockSkewSeconds(NEVER_EXPIRE) + .parseClaimsJws(token) + .getBody(); + } catch (Exception e) { + log.warn("token 解析出错:{}",e.getMessage()); + } + return claims; + } + + /** + * 检验token是否还有效 + * @param token 前端传入的token + * @return true表示有效,false表示无效 + */ + public boolean validateToken(String token) { + return !isTokenExpired(token); + } + + public boolean validateToken(HttpServletRequest request) { + String token = request.getHeader("x-auth-AuthToken"); + if (StringUtils.isBlank(token)) { + return false; + } + + return validateToken(token); + } + + private boolean isTokenExpired(String token) { + Date expiredDate = getExpiredDateFromToken(token); + return expiredDate.before(new Date()); + } + + private Date getExpiredDateFromToken(String token) { + Claims claims = getClaimsFromToken(token); + return claims.getExpiration(); + } + + /** + * token 验证成功返回实体,验证失败返回 null + * @param request: + * @return: cn.teammodel.model.entity.TmdUserDetail + * @author: winter + * @date: 2023/11/10 10:24 + * @description: + */ + public TmdUserDetail getValidUserDetail(HttpServletRequest request) { + String token = request.getHeader("x-auth-AuthToken"); + if (StringUtils.isBlank(token)) { + return null; + } + Claims claims = getClaimsFromToken(token); + if (claims == null) { + return null; + } + + // 组装 TmdUserDetail + TmdUserDetail tmdUserDetail = new TmdUserDetail(); + User user = new User(); + String id = claims.getSubject(); + user.setId(id); + user.setName(claims.get("name") == null ? null : claims.get("name", String.class)); + user.setPicture(claims.get("picture") == null ? null : claims.get("picture", String.class)); + user.setStandard(claims.get("standard") == null ? null : claims.get("standard", String.class)); + user.setScope(claims.get("scope") == null ? null : claims.get("scope", String.class)); + user.setWebsite(claims.get("website") == null ? null : claims.get("website", String.class)); + user.setArea(claims.get("area") == null ? null : claims.get("area", String.class)); + + // 取出 roles 和 permissions + Set roleSet = convertToArray(claims.get("roles")); + Set permissionSet = convertToArray(claims.get("permissions")); + user.setRoles(roleSet); + user.setPermissions(permissionSet); + tmdUserDetail.setUser(user); + return tmdUserDetail; + } + + @SuppressWarnings("unchecked") + private Set convertToArray(Object o) { + if (o == null) { + return Collections.emptySet(); + } + if (o instanceof String) { + if (StringUtils.isNotBlank((String)o)) { + return Arrays.stream(((String) o).split(" ")).collect(Collectors.toSet()); + } + return Collections.emptySet(); + } + if (o instanceof Collection) { + return new HashSet<>(((Collection) o)); + } + return Collections.emptySet(); + } } \ No newline at end of file diff --git a/src/main/java/cn/teammodel/security/utils/SecurityUtils.java b/src/main/java/cn/teammodel/security/utils/SecurityUtils.java index a667415..f41caf5 100644 --- a/src/main/java/cn/teammodel/security/utils/SecurityUtils.java +++ b/src/main/java/cn/teammodel/security/utils/SecurityUtils.java @@ -1,107 +1,107 @@ -package cn.teammodel.security.utils; - -import cn.teammodel.config.exception.ServiceException; -import cn.teammodel.model.entity.User; -import cn.teammodel.model.entity.TmdUserDetail; -import org.springframework.http.HttpStatus; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; - -/** - * 安全服务工具类 - * - * @author winter - */ -public class SecurityUtils -{ - /** - * 用户ID - **/ - public static String getUserId() - { - try - { - return getLoginUser().getId(); - } - catch (Exception e) - { - throw new ServiceException("获取用户ID异常", HttpStatus.UNAUTHORIZED.value()); - } - } - - - /** - * 获取用户账户 - **/ - public static String getUsername() - { - try - { - return getLoginUser().getName(); - } - catch (Exception e) - { - throw new ServiceException("获取用户账户异常", HttpStatus.UNAUTHORIZED.value()); - } - } - - /** - * 获取用户 - **/ - public static User getLoginUser() - { - try - { - return ((TmdUserDetail) getAuthentication().getPrincipal()).getUser(); - } - catch (Exception e) - { - throw new ServiceException("获取用户信息异常", HttpStatus.UNAUTHORIZED.value()); - } - } - - /** - * 获取Authentication - */ - public static Authentication getAuthentication() - { - return SecurityContextHolder.getContext().getAuthentication(); - } - - /** - * 生成BCryptPasswordEncoder密码 - * - * @param password 密码 - * @return 加密字符串 - */ - public static String encryptPassword(String password) - { - BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder(); - return passwordEncoder.encode(password); - } - - /** - * 判断密码是否相同 - * - * @param rawPassword 真实密码 - * @param encodedPassword 加密后字符 - * @return 结果 - */ - public static boolean matchesPassword(String rawPassword, String encodedPassword) - { - BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder(); - return passwordEncoder.matches(rawPassword, encodedPassword); - } - - /** - * 是否为管理员 - * - * @param userId 用户ID - * @return 结果 - */ - public static boolean isAdmin(Long userId) - { - return userId != null && 1L == userId; - } -} +package cn.teammodel.security.utils; + +import cn.teammodel.config.exception.ServiceException; +import cn.teammodel.model.entity.User; +import cn.teammodel.model.entity.TmdUserDetail; +import org.springframework.http.HttpStatus; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; + +/** + * 安全服务工具类 + * + * @author winter + */ +public class SecurityUtils +{ + /** + * 用户ID + **/ + public static String getUserId() + { + try + { + return getLoginUser().getId(); + } + catch (Exception e) + { + throw new ServiceException("获取用户ID异常", HttpStatus.UNAUTHORIZED.value()); + } + } + + + /** + * 获取用户账户 + **/ + public static String getUsername() + { + try + { + return getLoginUser().getName(); + } + catch (Exception e) + { + throw new ServiceException("获取用户账户异常", HttpStatus.UNAUTHORIZED.value()); + } + } + + /** + * 获取用户 + **/ + public static User getLoginUser() + { + try + { + return ((TmdUserDetail) getAuthentication().getPrincipal()).getUser(); + } + catch (Exception e) + { + throw new ServiceException("获取用户信息异常", HttpStatus.UNAUTHORIZED.value()); + } + } + + /** + * 获取Authentication + */ + public static Authentication getAuthentication() + { + return SecurityContextHolder.getContext().getAuthentication(); + } + + /** + * 生成BCryptPasswordEncoder密码 + * + * @param password 密码 + * @return 加密字符串 + */ + public static String encryptPassword(String password) + { + BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder(); + return passwordEncoder.encode(password); + } + + /** + * 判断密码是否相同 + * + * @param rawPassword 真实密码 + * @param encodedPassword 加密后字符 + * @return 结果 + */ + public static boolean matchesPassword(String rawPassword, String encodedPassword) + { + BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder(); + return passwordEncoder.matches(rawPassword, encodedPassword); + } + + /** + * 是否为管理员 + * + * @param userId 用户ID + * @return 结果 + */ + public static boolean isAdmin(Long userId) + { + return userId != null && 1L == userId; + } +} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 74849ac..ec779ad 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -1,20 +1,21 @@ -spring: - cloud: - azure: - cosmos: - endpoint: https://cdhabookdep-free.documents.azure.cn:443 - database: TEAMModelOS - key: v07dMthUjNk8AbwI8698AXHPbXBaaADgtgdYf4sFGXohei6aD2doq6XV45sLMGpDtDDpENAnlGkEUBu9RaAhpg== - - security: - oauth2: - resourceserver: - jwt: - issuer-uri: https://login.partner.microsoftonline.cn/4807e9cf-87b8-4174-aa5b-e76497d7392b/v2.0 - audiences: 72643704-b2e7-4b26-b881-bd5865e7a7a5 - -jwt: - secret: fXO6ko/qyXeYrkecPeKdgXnuLXf9vMEtnBC9OB3s+aA= - -dingding: +spring: + cloud: + azure: + cosmos: + endpoint: https://cdhabookdep-free.documents.azure.cn:443 + database: TEAMModelOS + key: v07dMthUjNk8AbwI8698AXHPbXBaaADgtgdYf4sFGXohei6aD2doq6XV45sLMGpDtDDpENAnlGkEUBu9RaAhpg== + + security: + oauth2: + resourceserver: + jwt: + issuer-uri: https://login.partner.microsoftonline.cn/4807e9cf-87b8-4174-aa5b-e76497d7392b/v2.0 + audiences: 72643704-b2e7-4b26-b881-bd5865e7a7a5 + +jwt: + secret: fXO6ko/qyXeYrkecPeKdgXnuLXf9vMEtnBC9OB3s+aA= + +# 钉钉 webhook +ding: server-url: https://oapi.dingtalk.com/robot/send?access_token=32d9b24f69c2c4fd7c2dab43268b6258a7214d2620e0805d7b6d1429003b64b6 \ No newline at end of file diff --git a/src/test/java/cn/teammodel/TeamModelExtensionApplicationTests.java b/src/test/java/cn/teammodel/TeamModelExtensionApplicationTests.java index a178f89..9064105 100644 --- a/src/test/java/cn/teammodel/TeamModelExtensionApplicationTests.java +++ b/src/test/java/cn/teammodel/TeamModelExtensionApplicationTests.java @@ -1,13 +1,19 @@ package cn.teammodel; +import cn.teammodel.manager.DingAlertNotifier; import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; @SpringBootTest class TeamModelExtensionApplicationTests { + @Autowired + private DingAlertNotifier notifier; + @Test void contextLoads() { + notifier.send("告警: 测试消息推送封装模块"); } } diff --git a/src/test/java/cn/teammodel/TestWithoutSpring.java b/src/test/java/cn/teammodel/TestWithoutSpring.java index a5c6f3c..54605ec 100644 --- a/src/test/java/cn/teammodel/TestWithoutSpring.java +++ b/src/test/java/cn/teammodel/TestWithoutSpring.java @@ -1,66 +1,95 @@ -package cn.teammodel; - -import com.dingtalk.api.DefaultDingTalkClient; -import com.dingtalk.api.DingTalkClient; -import com.dingtalk.api.request.OapiRobotSendRequest; -import com.dingtalk.api.response.OapiRobotSendResponse; -import com.taobao.api.ApiException; -import org.junit.jupiter.api.Test; - -import java.util.Arrays; - -/** - * @author winter - * @create 2023-11-10 10:42 - */ -public class TestWithoutSpring { - @Test - public void testJwtDecode() { -// Claims claims = null; -// try { -// Jwts.parser() -// -// -//// claims = Jwts.parser() -//// .setSigningKey("fXO6ko/qyXeYrkecPeKdgXnuLXf9vMEtnBC9OB3s+aA=") -//// .parseClaimsJws("eyJhbGciOiJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzA0L3htbGRzaWctbW9yZSNobWFjLXNoYTI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJ0ZXN0LnRlYW1tb2RlbC5jbiIsInN1YiI6IjE1OTUzMjEzNTQiLCJhenAiOiJoYmNuIiwiZXhwIjoxNjk5NTg2MzM1LCJuYW1lIjoi572X6ICB5biIIiwicGljdHVyZSI6Imh0dHBzOi8vY29yZXN0b3JhZ2VzZXJ2aWNlLmJsb2IuY29yZS5jaGluYWNsb3VkYXBpLmNuL2FjY291bnQvYXZhdGFyLzE1OTUzMjEzNTQiLCJyb2xlcyI6WyJ0ZWFjaGVyIiwiYWRtaW4iLCJhcmVhIl0sInBlcm1pc3Npb25zIjpbXSwic3RhbmRhcmQiOiJzdGFuZGFyZDEiLCJzY29wZSI6InRlYWNoZXIiLCJhcmVhIjoiNjllM2Q0MTMtNTBhMS00ZjVlLTg0NGEtZTBmN2M5NjIyZWEzIiwid2Vic2l0ZSI6IklFUyJ9.4NdqwDdDQcyberEAirX0srOIb1ADXLJfP-a9qNXb0yw") -//// .getBody(); -// } catch (Exception e) { -// e.printStackTrace(); -// } - } - - @Test - public void testDingDing() throws ApiException { - DingTalkClient client = new DefaultDingTalkClient("https://oapi.dingtalk.com/robot/send?access_token=32d9b24f69c2c4fd7c2dab43268b6258a7214d2620e0805d7b6d1429003b64b6"); - OapiRobotSendRequest request = new OapiRobotSendRequest(); - request.setMsgtype("text"); - OapiRobotSendRequest.Text text = new OapiRobotSendRequest.Text(); - text.setContent("测试告警文本消息"); - request.setText(text); - OapiRobotSendRequest.At at = new OapiRobotSendRequest.At(); - at.setAtMobiles(Arrays.asList("15196506772")); -// isAtAll类型如果不为Boolean,请升级至最新SDK - at.setIsAtAll(true); - at.setAtUserIds(Arrays.asList("109929","32099")); - request.setAt(at); - - request.setMsgtype("link"); - OapiRobotSendRequest.Link link = new OapiRobotSendRequest.Link(); - link.setMessageUrl("https://www.dingtalk.com/"); - link.setPicUrl(""); - link.setTitle("告警时代的火车向前开"); - link.setText("告警这个即将发布的新版本,创始人xx称它为红树林。而在此之前,每当面临重大升级,产品经理们都会取一个应景的代号,这一次,为什么是红树林"); - request.setLink(link); - - request.setMsgtype("markdown"); - OapiRobotSendRequest.Markdown markdown = new OapiRobotSendRequest.Markdown(); - markdown.setTitle("杭州天气"); - markdown.setText("#### 告警杭州天气 @156xxxx8827\n" + - "> 9度,西北风1级,空气良89,相对温度73%\n\n" + - "> ![screenshot](https://gw.alicdn.com/tfs/TB1ut3xxbsrBKNjSZFpXXcXhFXa-846-786.png)\n" + - "> ###### 10点20分发布 [天气](http://www.thinkpage.cn/) \n"); - request.setMarkdown(markdown); - OapiRobotSendResponse response = client.execute(request); - } -} +package cn.teammodel; + +import cn.teammodel.common.R; +import cn.teammodel.model.entity.User; +import com.dingtalk.api.DefaultDingTalkClient; +import com.dingtalk.api.DingTalkClient; +import com.dingtalk.api.request.OapiRobotSendRequest; +import com.dingtalk.api.response.OapiRobotSendResponse; +import com.taobao.api.ApiException; +import lombok.extern.slf4j.Slf4j; +import org.junit.jupiter.api.Test; + +import java.util.Arrays; + +/** + * @author winter + * @create 2023-11-10 10:42 + */ +@Slf4j +public class TestWithoutSpring { + @Test + public void testJwtDecode() { +// Claims claims = null; +// try { +// Jwts.parser() +// +// +//// claims = Jwts.parser() +//// .setSigningKey("fXO6ko/qyXeYrkecPeKdgXnuLXf9vMEtnBC9OB3s+aA=") +//// .parseClaimsJws("eyJhbGciOiJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzA0L3htbGRzaWctbW9yZSNobWFjLXNoYTI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJ0ZXN0LnRlYW1tb2RlbC5jbiIsInN1YiI6IjE1OTUzMjEzNTQiLCJhenAiOiJoYmNuIiwiZXhwIjoxNjk5NTg2MzM1LCJuYW1lIjoi572X6ICB5biIIiwicGljdHVyZSI6Imh0dHBzOi8vY29yZXN0b3JhZ2VzZXJ2aWNlLmJsb2IuY29yZS5jaGluYWNsb3VkYXBpLmNuL2FjY291bnQvYXZhdGFyLzE1OTUzMjEzNTQiLCJyb2xlcyI6WyJ0ZWFjaGVyIiwiYWRtaW4iLCJhcmVhIl0sInBlcm1pc3Npb25zIjpbXSwic3RhbmRhcmQiOiJzdGFuZGFyZDEiLCJzY29wZSI6InRlYWNoZXIiLCJhcmVhIjoiNjllM2Q0MTMtNTBhMS00ZjVlLTg0NGEtZTBmN2M5NjIyZWEzIiwid2Vic2l0ZSI6IklFUyJ9.4NdqwDdDQcyberEAirX0srOIb1ADXLJfP-a9qNXb0yw") +//// .getBody(); +// } catch (Exception e) { +// e.printStackTrace(); +// } + } + + @Test + public void testDingDing() throws ApiException { + DingTalkClient client = new DefaultDingTalkClient("https://oapi.dingtalk.com/robot/send?access_token=32d9b24f69c2c4fd7c2dab43268b6258a7214d2620e0805d7b6d1429003b64b6"); + OapiRobotSendRequest request = new OapiRobotSendRequest(); + request.setMsgtype("text"); + OapiRobotSendRequest.Text text = new OapiRobotSendRequest.Text(); + text.setContent("测试告警文本消息"); + request.setText(text); + OapiRobotSendRequest.At at = new OapiRobotSendRequest.At(); + at.setAtMobiles(Arrays.asList("15196506772")); +// isAtAll类型如果不为Boolean,请升级至最新SDK + at.setIsAtAll(true); + at.setAtUserIds(Arrays.asList("109929","32099")); + request.setAt(at); + + request.setMsgtype("link"); + OapiRobotSendRequest.Link link = new OapiRobotSendRequest.Link(); + link.setMessageUrl("https://www.dingtalk.com/"); + link.setPicUrl(""); + link.setTitle("告警时代的火车向前开"); + link.setText("告警这个即将发布的新版本,创始人xx称它为红树林。而在此之前,每当面临重大升级,产品经理们都会取一个应景的代号,这一次,为什么是红树林"); + request.setLink(link); + + request.setMsgtype("markdown"); + OapiRobotSendRequest.Markdown markdown = new OapiRobotSendRequest.Markdown(); + markdown.setTitle("杭州天气"); + markdown.setText("#### 告警杭州天气 @156xxxx8827\n" + + "> 9度,西北风1级,空气良89,相对温度73%\n\n" + + "> ![screenshot](https://gw.alicdn.com/tfs/TB1ut3xxbsrBKNjSZFpXXcXhFXa-846-786.png)\n" + + "> ###### 10点20分发布 [天气](http://www.thinkpage.cn/) \n"); + request.setMarkdown(markdown); + OapiRobotSendResponse response = client.execute(request); + } + + @Test + public void testException() { + try { + throw new Exception("出现异常"); + } catch (Exception e) { + //e.printStackTrace(); + log.error("error: {}", e); + //System.out.println(e.getMessage()); + //System.out.println(e.getCause()); + //System.out.println(e.getStackTrace()); + } + } + @Test + public void testResult() { + User user = new User(); + user.setId("ds"); + user.setName("ds"); + user.setPicture("s"); + user.setStandard("s"); + user.setScope("ad"); + user.setWebsite("dsa"); + user.setArea(""); + System.out.println(R.success(user)); + } +}